summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
Diffstat (limited to 'Userland')
-rw-r--r--Userland/CMakeLists.txt1
-rw-r--r--Userland/DynamicLoader/CMakeLists.txt12
-rw-r--r--Userland/Libraries/CMakeLists.txt36
-rw-r--r--Userland/Libraries/LibAudio/Buffer.cpp134
-rw-r--r--Userland/Libraries/LibAudio/Buffer.h148
-rw-r--r--Userland/Libraries/LibAudio/CMakeLists.txt15
-rw-r--r--Userland/Libraries/LibAudio/ClientConnection.cpp125
-rw-r--r--Userland/Libraries/LibAudio/ClientConnection.h70
-rw-r--r--Userland/Libraries/LibAudio/Loader.cpp47
-rw-r--r--Userland/Libraries/LibAudio/Loader.h94
-rw-r--r--Userland/Libraries/LibAudio/WavLoader.cpp278
-rw-r--r--Userland/Libraries/LibAudio/WavLoader.h82
-rw-r--r--Userland/Libraries/LibAudio/WavWriter.cpp126
-rw-r--r--Userland/Libraries/LibAudio/WavWriter.h70
-rw-r--r--Userland/Libraries/LibC/CMakeLists.txt90
-rw-r--r--Userland/Libraries/LibC/alloca.h29
-rw-r--r--Userland/Libraries/LibC/arpa/inet.cpp92
-rw-r--r--Userland/Libraries/LibC/arpa/inet.h75
-rw-r--r--Userland/Libraries/LibC/assert.cpp54
-rw-r--r--Userland/Libraries/LibC/assert.h56
-rw-r--r--Userland/Libraries/LibC/bits/FILE.h38
-rw-r--r--Userland/Libraries/LibC/bits/posix1_lim.h65
-rw-r--r--Userland/Libraries/LibC/bits/stdint.h144
-rw-r--r--Userland/Libraries/LibC/byteswap.h37
-rw-r--r--Userland/Libraries/LibC/crt0.cpp76
-rw-r--r--Userland/Libraries/LibC/crt0_shared.cpp66
-rw-r--r--Userland/Libraries/LibC/crti.S35
-rw-r--r--Userland/Libraries/LibC/crtn.S33
-rw-r--r--Userland/Libraries/LibC/ctype.cpp63
-rw-r--r--Userland/Libraries/LibC/ctype.h106
-rw-r--r--Userland/Libraries/LibC/cxxabi.cpp89
-rw-r--r--Userland/Libraries/LibC/dirent.cpp200
-rw-r--r--Userland/Libraries/LibC/dirent.h77
-rw-r--r--Userland/Libraries/LibC/dlfcn.cpp131
-rw-r--r--Userland/Libraries/LibC/dlfcn.h44
-rw-r--r--Userland/Libraries/LibC/endian.h109
-rw-r--r--Userland/Libraries/LibC/errno.h55
-rw-r--r--Userland/Libraries/LibC/errno_numbers.h103
-rw-r--r--Userland/Libraries/LibC/fcntl.cpp106
-rw-r--r--Userland/Libraries/LibC/fcntl.h111
-rw-r--r--Userland/Libraries/LibC/fd_set.h39
-rw-r--r--Userland/Libraries/LibC/float.h27
-rw-r--r--Userland/Libraries/LibC/getopt.cpp369
-rw-r--r--Userland/Libraries/LibC/getopt.h46
-rw-r--r--Userland/Libraries/LibC/grp.cpp182
-rw-r--r--Userland/Libraries/LibC/grp.h49
-rw-r--r--Userland/Libraries/LibC/iconv.h40
-rw-r--r--Userland/Libraries/LibC/inttypes.h70
-rw-r--r--Userland/Libraries/LibC/ioctl.cpp44
-rw-r--r--Userland/Libraries/LibC/libcinit.cpp48
-rw-r--r--Userland/Libraries/LibC/libgen.cpp84
-rw-r--r--Userland/Libraries/LibC/libgen.h36
-rw-r--r--Userland/Libraries/LibC/limits.h78
-rw-r--r--Userland/Libraries/LibC/locale.cpp78
-rw-r--r--Userland/Libraries/LibC/locale.h72
-rw-r--r--Userland/Libraries/LibC/malloc.cpp472
-rw-r--r--Userland/Libraries/LibC/mallocdefs.h95
-rw-r--r--Userland/Libraries/LibC/memory.h29
-rw-r--r--Userland/Libraries/LibC/mman.cpp111
-rw-r--r--Userland/Libraries/LibC/mman.h67
-rw-r--r--Userland/Libraries/LibC/mntent.cpp37
-rw-r--r--Userland/Libraries/LibC/mntent.h48
-rw-r--r--Userland/Libraries/LibC/net/if.h71
-rw-r--r--Userland/Libraries/LibC/net/route.h40
-rw-r--r--Userland/Libraries/LibC/netdb.cpp590
-rw-r--r--Userland/Libraries/LibC/netdb.h78
-rw-r--r--Userland/Libraries/LibC/netinet/in.h74
-rw-r--r--Userland/Libraries/LibC/netinet/ip.h29
-rw-r--r--Userland/Libraries/LibC/netinet/ip_icmp.h47
-rw-r--r--Userland/Libraries/LibC/netinet/tcp.h29
-rw-r--r--Userland/Libraries/LibC/paths.h30
-rw-r--r--Userland/Libraries/LibC/poll.cpp51
-rw-r--r--Userland/Libraries/LibC/poll.h53
-rw-r--r--Userland/Libraries/LibC/pwd.cpp189
-rw-r--r--Userland/Libraries/LibC/pwd.h52
-rw-r--r--Userland/Libraries/LibC/qsort.cpp102
-rw-r--r--Userland/Libraries/LibC/regex.h125
-rw-r--r--Userland/Libraries/LibC/scanf.cpp227
-rw-r--r--Userland/Libraries/LibC/sched.cpp60
-rw-r--r--Userland/Libraries/LibC/sched.h50
-rw-r--r--Userland/Libraries/LibC/serenity.cpp139
-rw-r--r--Userland/Libraries/LibC/serenity.h99
-rw-r--r--Userland/Libraries/LibC/setjmp.S57
-rw-r--r--Userland/Libraries/LibC/setjmp.h52
-rw-r--r--Userland/Libraries/LibC/signal.cpp248
-rw-r--r--Userland/Libraries/LibC/signal.h109
-rw-r--r--Userland/Libraries/LibC/signal_numbers.h61
-rw-r--r--Userland/Libraries/LibC/spawn.cpp289
-rw-r--r--Userland/Libraries/LibC/spawn.h99
-rw-r--r--Userland/Libraries/LibC/ssp.cpp55
-rw-r--r--Userland/Libraries/LibC/stat.cpp99
-rw-r--r--Userland/Libraries/LibC/stdarg.h44
-rw-r--r--Userland/Libraries/LibC/stdbool.h42
-rw-r--r--Userland/Libraries/LibC/stddef.h42
-rw-r--r--Userland/Libraries/LibC/stdint.h31
-rw-r--r--Userland/Libraries/LibC/stdio.cpp1219
-rw-r--r--Userland/Libraries/LibC/stdio.h120
-rw-r--r--Userland/Libraries/LibC/stdlib.cpp1083
-rw-r--r--Userland/Libraries/LibC/stdlib.h109
-rw-r--r--Userland/Libraries/LibC/string.cpp486
-rw-r--r--Userland/Libraries/LibC/string.h74
-rw-r--r--Userland/Libraries/LibC/strings.cpp72
-rw-r--r--Userland/Libraries/LibC/strings.h39
-rw-r--r--Userland/Libraries/LibC/sys/arch/i386/regs.h48
-rw-r--r--Userland/Libraries/LibC/sys/cdefs.h54
-rw-r--r--Userland/Libraries/LibC/sys/file.h27
-rw-r--r--Userland/Libraries/LibC/sys/internals.h48
-rw-r--r--Userland/Libraries/LibC/sys/ioctl.h36
-rw-r--r--Userland/Libraries/LibC/sys/ioctl_numbers.h94
-rw-r--r--Userland/Libraries/LibC/sys/mman.h29
-rw-r--r--Userland/Libraries/LibC/sys/param.h30
-rw-r--r--Userland/Libraries/LibC/sys/prctl.cpp40
-rw-r--r--Userland/Libraries/LibC/sys/prctl.h37
-rw-r--r--Userland/Libraries/LibC/sys/prctl_numbers.h30
-rw-r--r--Userland/Libraries/LibC/sys/ptrace.cpp69
-rw-r--r--Userland/Libraries/LibC/sys/ptrace.h48
-rw-r--r--Userland/Libraries/LibC/sys/resource.h58
-rw-r--r--Userland/Libraries/LibC/sys/select.cpp52
-rw-r--r--Userland/Libraries/LibC/sys/select.h40
-rw-r--r--Userland/Libraries/LibC/sys/socket.cpp159
-rw-r--r--Userland/Libraries/LibC/sys/socket.h183
-rw-r--r--Userland/Libraries/LibC/sys/stat.h72
-rw-r--r--Userland/Libraries/LibC/sys/sysmacros.h27
-rw-r--r--Userland/Libraries/LibC/sys/time.h135
-rw-r--r--Userland/Libraries/LibC/sys/times.h43
-rw-r--r--Userland/Libraries/LibC/sys/types.h108
-rw-r--r--Userland/Libraries/LibC/sys/uio.cpp38
-rw-r--r--Userland/Libraries/LibC/sys/uio.h41
-rw-r--r--Userland/Libraries/LibC/sys/un.h39
-rw-r--r--Userland/Libraries/LibC/sys/utsname.h45
-rw-r--r--Userland/Libraries/LibC/sys/wait.cpp102
-rw-r--r--Userland/Libraries/LibC/sys/wait.h59
-rw-r--r--Userland/Libraries/LibC/syslog.cpp159
-rw-r--r--Userland/Libraries/LibC/syslog.h177
-rw-r--r--Userland/Libraries/LibC/termcap.cpp164
-rw-r--r--Userland/Libraries/LibC/termcap.h44
-rw-r--r--Userland/Libraries/LibC/termios.cpp149
-rw-r--r--Userland/Libraries/LibC/termios.h235
-rw-r--r--Userland/Libraries/LibC/time.cpp376
-rw-r--r--Userland/Libraries/LibC/time.h91
-rw-r--r--Userland/Libraries/LibC/times.cpp35
-rw-r--r--Userland/Libraries/LibC/ulimit.cpp45
-rw-r--r--Userland/Libraries/LibC/ulimit.h35
-rw-r--r--Userland/Libraries/LibC/unistd.cpp725
-rw-r--r--Userland/Libraries/LibC/unistd.h210
-rw-r--r--Userland/Libraries/LibC/utime.cpp43
-rw-r--r--Userland/Libraries/LibC/utime.h35
-rw-r--r--Userland/Libraries/LibC/utmp.h78
-rw-r--r--Userland/Libraries/LibC/utsname.cpp38
-rw-r--r--Userland/Libraries/LibC/wchar.cpp150
-rw-r--r--Userland/Libraries/LibC/wchar.h48
-rw-r--r--Userland/Libraries/LibChess/CMakeLists.txt8
-rw-r--r--Userland/Libraries/LibChess/Chess.cpp970
-rw-r--r--Userland/Libraries/LibChess/Chess.h324
-rw-r--r--Userland/Libraries/LibChess/UCICommand.cpp332
-rw-r--r--Userland/Libraries/LibChess/UCICommand.h291
-rw-r--r--Userland/Libraries/LibChess/UCIEndpoint.cpp132
-rw-r--r--Userland/Libraries/LibChess/UCIEndpoint.h80
-rw-r--r--Userland/Libraries/LibCompress/CMakeLists.txt8
-rw-r--r--Userland/Libraries/LibCompress/Deflate.cpp452
-rw-r--r--Userland/Libraries/LibCompress/Deflate.h117
-rw-r--r--Userland/Libraries/LibCompress/Gzip.cpp183
-rw-r--r--Userland/Libraries/LibCompress/Gzip.h94
-rw-r--r--Userland/Libraries/LibCompress/Zlib.cpp79
-rw-r--r--Userland/Libraries/LibCompress/Zlib.h56
-rw-r--r--Userland/Libraries/LibCore/Account.cpp316
-rw-r--r--Userland/Libraries/LibCore/Account.h109
-rw-r--r--Userland/Libraries/LibCore/ArgsParser.cpp392
-rw-r--r--Userland/Libraries/LibCore/ArgsParser.h94
-rw-r--r--Userland/Libraries/LibCore/CMakeLists.txt37
-rw-r--r--Userland/Libraries/LibCore/Command.cpp127
-rw-r--r--Userland/Libraries/LibCore/Command.h40
-rw-r--r--Userland/Libraries/LibCore/ConfigFile.cpp242
-rw-r--r--Userland/Libraries/LibCore/ConfigFile.h82
-rw-r--r--Userland/Libraries/LibCore/DateTime.cpp263
-rw-r--r--Userland/Libraries/LibCore/DateTime.h74
-rw-r--r--Userland/Libraries/LibCore/DirIterator.cpp121
-rw-r--r--Userland/Libraries/LibCore/DirIterator.h65
-rw-r--r--Userland/Libraries/LibCore/DirectoryWatcher.cpp98
-rw-r--r--Userland/Libraries/LibCore/DirectoryWatcher.h61
-rw-r--r--Userland/Libraries/LibCore/ElapsedTimer.cpp57
-rw-r--r--Userland/Libraries/LibCore/ElapsedTimer.h54
-rw-r--r--Userland/Libraries/LibCore/Event.cpp72
-rw-r--r--Userland/Libraries/LibCore/Event.h159
-rw-r--r--Userland/Libraries/LibCore/EventLoop.cpp835
-rw-r--r--Userland/Libraries/LibCore/EventLoop.h122
-rw-r--r--Userland/Libraries/LibCore/File.cpp270
-rw-r--r--Userland/Libraries/LibCore/File.h78
-rw-r--r--Userland/Libraries/LibCore/FileStream.h171
-rw-r--r--Userland/Libraries/LibCore/Forward.h61
-rw-r--r--Userland/Libraries/LibCore/GetPassword.cpp70
-rw-r--r--Userland/Libraries/LibCore/GetPassword.h37
-rw-r--r--Userland/Libraries/LibCore/Gzip.cpp165
-rw-r--r--Userland/Libraries/LibCore/Gzip.h41
-rw-r--r--Userland/Libraries/LibCore/IODevice.cpp336
-rw-r--r--Userland/Libraries/LibCore/IODevice.h133
-rw-r--r--Userland/Libraries/LibCore/IODeviceStreamReader.h61
-rw-r--r--Userland/Libraries/LibCore/LocalServer.cpp161
-rw-r--r--Userland/Libraries/LibCore/LocalServer.h57
-rw-r--r--Userland/Libraries/LibCore/LocalSocket.cpp107
-rw-r--r--Userland/Libraries/LibCore/LocalSocket.h45
-rw-r--r--Userland/Libraries/LibCore/MimeData.cpp102
-rw-r--r--Userland/Libraries/LibCore/MimeData.h72
-rw-r--r--Userland/Libraries/LibCore/NetworkJob.cpp108
-rw-r--r--Userland/Libraries/LibCore/NetworkJob.h83
-rw-r--r--Userland/Libraries/LibCore/NetworkResponse.cpp39
-rw-r--r--Userland/Libraries/LibCore/NetworkResponse.h46
-rw-r--r--Userland/Libraries/LibCore/Notifier.cpp76
-rw-r--r--Userland/Libraries/LibCore/Notifier.h66
-rw-r--r--Userland/Libraries/LibCore/Object.cpp270
-rw-r--r--Userland/Libraries/LibCore/Object.h346
-rw-r--r--Userland/Libraries/LibCore/ProcessStatisticsReader.cpp142
-rw-r--r--Userland/Libraries/LibCore/ProcessStatisticsReader.h98
-rw-r--r--Userland/Libraries/LibCore/Property.cpp42
-rw-r--r--Userland/Libraries/LibCore/Property.h59
-rw-r--r--Userland/Libraries/LibCore/Socket.cpp236
-rw-r--r--Userland/Libraries/LibCore/Socket.h89
-rw-r--r--Userland/Libraries/LibCore/SocketAddress.cpp36
-rw-r--r--Userland/Libraries/LibCore/SocketAddress.h115
-rw-r--r--Userland/Libraries/LibCore/StandardPaths.cpp77
-rw-r--r--Userland/Libraries/LibCore/StandardPaths.h42
-rw-r--r--Userland/Libraries/LibCore/SyscallUtils.h56
-rw-r--r--Userland/Libraries/LibCore/TCPServer.cpp125
-rw-r--r--Userland/Libraries/LibCore/TCPServer.h58
-rw-r--r--Userland/Libraries/LibCore/TCPSocket.cpp70
-rw-r--r--Userland/Libraries/LibCore/TCPSocket.h44
-rw-r--r--Userland/Libraries/LibCore/Timer.cpp96
-rw-r--r--Userland/Libraries/LibCore/Timer.h81
-rw-r--r--Userland/Libraries/LibCore/UDPServer.cpp123
-rw-r--r--Userland/Libraries/LibCore/UDPServer.h66
-rw-r--r--Userland/Libraries/LibCore/UDPSocket.cpp61
-rw-r--r--Userland/Libraries/LibCore/UDPSocket.h42
-rw-r--r--Userland/Libraries/LibCore/puff.cpp832
-rw-r--r--Userland/Libraries/LibCore/puff.h44
-rw-r--r--Userland/Libraries/LibCoreDump/Backtrace.cpp128
-rw-r--r--Userland/Libraries/LibCoreDump/Backtrace.h68
-rw-r--r--Userland/Libraries/LibCoreDump/CMakeLists.txt7
-rw-r--r--Userland/Libraries/LibCoreDump/Forward.h34
-rw-r--r--Userland/Libraries/LibCoreDump/Reader.cpp215
-rw-r--r--Userland/Libraries/LibCoreDump/Reader.h120
-rw-r--r--Userland/Libraries/LibCpp/CMakeLists.txt6
-rw-r--r--Userland/Libraries/LibCpp/Lexer.cpp789
-rw-r--r--Userland/Libraries/LibCpp/Lexer.h146
-rw-r--r--Userland/Libraries/LibCrypt/CMakeLists.txt6
-rw-r--r--Userland/Libraries/LibCrypt/crypt.cpp86
-rw-r--r--Userland/Libraries/LibCrypto/ASN1/ASN1.h112
-rw-r--r--Userland/Libraries/LibCrypto/ASN1/DER.h474
-rw-r--r--Userland/Libraries/LibCrypto/ASN1/PEM.h75
-rw-r--r--Userland/Libraries/LibCrypto/Authentication/GHash.cpp148
-rw-r--r--Userland/Libraries/LibCrypto/Authentication/GHash.h76
-rw-r--r--Userland/Libraries/LibCrypto/Authentication/HMAC.h138
-rw-r--r--Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp272
-rw-r--r--Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.h162
-rw-r--r--Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp745
-rw-r--r--Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.h156
-rw-r--r--Userland/Libraries/LibCrypto/CMakeLists.txt16
-rw-r--r--Userland/Libraries/LibCrypto/Checksum/Adler32.cpp46
-rw-r--r--Userland/Libraries/LibCrypto/Checksum/Adler32.h58
-rw-r--r--Userland/Libraries/LibCrypto/Checksum/CRC32.cpp45
-rw-r--r--Userland/Libraries/LibCrypto/Checksum/CRC32.h86
-rw-r--r--Userland/Libraries/LibCrypto/Checksum/ChecksumFunction.h40
-rw-r--r--Userland/Libraries/LibCrypto/Cipher/AES.cpp429
-rw-r--r--Userland/Libraries/LibCrypto/Cipher/AES.h2481
-rw-r--r--Userland/Libraries/LibCrypto/Cipher/Cipher.h139
-rw-r--r--Userland/Libraries/LibCrypto/Cipher/Mode/CBC.h142
-rw-r--r--Userland/Libraries/LibCrypto/Cipher/Mode/CTR.h209
-rw-r--r--Userland/Libraries/LibCrypto/Cipher/Mode/GCM.h154
-rw-r--r--Userland/Libraries/LibCrypto/Cipher/Mode/Mode.h118
-rw-r--r--Userland/Libraries/LibCrypto/Hash/HashFunction.h62
-rw-r--r--Userland/Libraries/LibCrypto/Hash/HashManager.h317
-rw-r--r--Userland/Libraries/LibCrypto/Hash/MD5.cpp225
-rw-r--r--Userland/Libraries/LibCrypto/Hash/MD5.h128
-rw-r--r--Userland/Libraries/LibCrypto/Hash/SHA1.cpp169
-rw-r--r--Userland/Libraries/LibCrypto/Hash/SHA1.h107
-rw-r--r--Userland/Libraries/LibCrypto/Hash/SHA2.cpp282
-rw-r--r--Userland/Libraries/LibCrypto/Hash/SHA2.h202
-rw-r--r--Userland/Libraries/LibCrypto/NumberTheory/ModularFunctions.cpp359
-rw-r--r--Userland/Libraries/LibCrypto/NumberTheory/ModularFunctions.h72
-rw-r--r--Userland/Libraries/LibCrypto/PK/Code/Code.h55
-rw-r--r--Userland/Libraries/LibCrypto/PK/Code/EMSA_PSS.h180
-rw-r--r--Userland/Libraries/LibCrypto/PK/PK.h68
-rw-r--r--Userland/Libraries/LibCrypto/PK/RSA.cpp329
-rw-r--r--Userland/Libraries/LibCrypto/PK/RSA.h235
-rw-r--r--Userland/Libraries/LibCrypto/Verification.h36
-rw-r--r--Userland/Libraries/LibDebug/CMakeLists.txt14
-rw-r--r--Userland/Libraries/LibDebug/DebugInfo.cpp371
-rw-r--r--Userland/Libraries/LibDebug/DebugInfo.h154
-rw-r--r--Userland/Libraries/LibDebug/DebugSession.cpp432
-rw-r--r--Userland/Libraries/LibDebug/DebugSession.h310
-rw-r--r--Userland/Libraries/LibDebug/Dwarf/AbbreviationsMap.cpp88
-rw-r--r--Userland/Libraries/LibDebug/Dwarf/AbbreviationsMap.h60
-rw-r--r--Userland/Libraries/LibDebug/Dwarf/CompilationUnit.cpp44
-rw-r--r--Userland/Libraries/LibDebug/Dwarf/CompilationUnit.h56
-rw-r--r--Userland/Libraries/LibDebug/Dwarf/DIE.cpp244
-rw-r--r--Userland/Libraries/LibDebug/Dwarf/DIE.h95
-rw-r--r--Userland/Libraries/LibDebug/Dwarf/DwarfInfo.cpp71
-rw-r--r--Userland/Libraries/LibDebug/Dwarf/DwarfInfo.h71
-rw-r--r--Userland/Libraries/LibDebug/Dwarf/DwarfTypes.h294
-rw-r--r--Userland/Libraries/LibDebug/Dwarf/Expression.cpp64
-rw-r--r--Userland/Libraries/LibDebug/Dwarf/Expression.h56
-rw-r--r--Userland/Libraries/LibDebug/Dwarf/LineProgram.cpp269
-rw-r--r--Userland/Libraries/LibDebug/Dwarf/LineProgram.h118
-rw-r--r--Userland/Libraries/LibDebug/StackFrameUtils.cpp42
-rw-r--r--Userland/Libraries/LibDebug/StackFrameUtils.h43
-rw-r--r--Userland/Libraries/LibDesktop/AppFile.cpp123
-rw-r--r--Userland/Libraries/LibDesktop/AppFile.h63
-rw-r--r--Userland/Libraries/LibDesktop/CMakeLists.txt12
-rw-r--r--Userland/Libraries/LibDesktop/Launcher.cpp147
-rw-r--r--Userland/Libraries/LibDesktop/Launcher.h64
-rw-r--r--Userland/Libraries/LibDiff/CMakeLists.txt8
-rw-r--r--Userland/Libraries/LibDiff/Format.cpp43
-rw-r--r--Userland/Libraries/LibDiff/Format.h33
-rw-r--r--Userland/Libraries/LibDiff/Hunks.cpp151
-rw-r--r--Userland/Libraries/LibDiff/Hunks.h57
-rw-r--r--Userland/Libraries/LibELF/Arch/i386/plt_trampoline.S58
-rw-r--r--Userland/Libraries/LibELF/AuxiliaryVector.h123
-rw-r--r--Userland/Libraries/LibELF/CMakeLists.txt1
-rw-r--r--Userland/Libraries/LibELF/CoreDump.h87
-rw-r--r--Userland/Libraries/LibELF/DynamicLinker.cpp287
-rw-r--r--Userland/Libraries/LibELF/DynamicLinker.h45
-rw-r--r--Userland/Libraries/LibELF/DynamicLoader.cpp530
-rw-r--r--Userland/Libraries/LibELF/DynamicLoader.h164
-rw-r--r--Userland/Libraries/LibELF/DynamicObject.cpp527
-rw-r--r--Userland/Libraries/LibELF/DynamicObject.h414
-rw-r--r--Userland/Libraries/LibELF/Image.cpp419
-rw-r--r--Userland/Libraries/LibELF/Image.h299
-rw-r--r--Userland/Libraries/LibELF/Validation.cpp267
-rw-r--r--Userland/Libraries/LibELF/Validation.h36
-rw-r--r--Userland/Libraries/LibELF/exec_elf.h792
-rw-r--r--Userland/Libraries/LibGUI/AboutDialog.cpp133
-rw-r--r--Userland/Libraries/LibGUI/AboutDialog.h53
-rw-r--r--Userland/Libraries/LibGUI/AbstractButton.cpp204
-rw-r--r--Userland/Libraries/LibGUI/AbstractButton.h87
-rw-r--r--Userland/Libraries/LibGUI/AbstractSlider.cpp89
-rw-r--r--Userland/Libraries/LibGUI/AbstractSlider.h72
-rw-r--r--Userland/Libraries/LibGUI/AbstractTableView.cpp403
-rw-r--r--Userland/Libraries/LibGUI/AbstractTableView.h127
-rw-r--r--Userland/Libraries/LibGUI/AbstractView.cpp765
-rw-r--r--Userland/Libraries/LibGUI/AbstractView.h220
-rw-r--r--Userland/Libraries/LibGUI/Action.cpp300
-rw-r--r--Userland/Libraries/LibGUI/Action.h174
-rw-r--r--Userland/Libraries/LibGUI/ActionGroup.cpp44
-rw-r--r--Userland/Libraries/LibGUI/ActionGroup.h64
-rw-r--r--Userland/Libraries/LibGUI/Application.cpp291
-rw-r--r--Userland/Libraries/LibGUI/Application.h128
-rw-r--r--Userland/Libraries/LibGUI/AutocompleteProvider.cpp203
-rw-r--r--Userland/Libraries/LibGUI/AutocompleteProvider.h93
-rw-r--r--Userland/Libraries/LibGUI/BoxLayout.cpp239
-rw-r--r--Userland/Libraries/LibGUI/BoxLayout.h78
-rw-r--r--Userland/Libraries/LibGUI/BreadcrumbBar.cpp148
-rw-r--r--Userland/Libraries/LibGUI/BreadcrumbBar.h69
-rw-r--r--Userland/Libraries/LibGUI/Button.cpp170
-rw-r--r--Userland/Libraries/LibGUI/Button.h74
-rw-r--r--Userland/Libraries/LibGUI/CMakeLists.txt114
-rw-r--r--Userland/Libraries/LibGUI/Calendar.cpp371
-rw-r--r--Userland/Libraries/LibGUI/Calendar.h142
-rw-r--r--Userland/Libraries/LibGUI/CheckBox.cpp89
-rw-r--r--Userland/Libraries/LibGUI/CheckBox.h51
-rw-r--r--Userland/Libraries/LibGUI/Clipboard.cpp167
-rw-r--r--Userland/Libraries/LibGUI/Clipboard.h70
-rw-r--r--Userland/Libraries/LibGUI/ColorInput.cpp130
-rw-r--r--Userland/Libraries/LibGUI/ColorInput.h69
-rw-r--r--Userland/Libraries/LibGUI/ColorPicker.cpp724
-rw-r--r--Userland/Libraries/LibGUI/ColorPicker.h70
-rw-r--r--Userland/Libraries/LibGUI/ColumnsView.cpp347
-rw-r--r--Userland/Libraries/LibGUI/ColumnsView.h71
-rw-r--r--Userland/Libraries/LibGUI/ComboBox.cpp296
-rw-r--r--Userland/Libraries/LibGUI/ComboBox.h85
-rw-r--r--Userland/Libraries/LibGUI/Command.cpp35
-rw-r--r--Userland/Libraries/LibGUI/Command.h53
-rw-r--r--Userland/Libraries/LibGUI/ControlBoxButton.cpp93
-rw-r--r--Userland/Libraries/LibGUI/ControlBoxButton.h52
-rw-r--r--Userland/Libraries/LibGUI/CppSyntaxHighlighter.cpp127
-rw-r--r--Userland/Libraries/LibGUI/CppSyntaxHighlighter.h49
-rw-r--r--Userland/Libraries/LibGUI/Desktop.cpp87
-rw-r--r--Userland/Libraries/LibGUI/Desktop.h61
-rw-r--r--Userland/Libraries/LibGUI/Dialog.cpp95
-rw-r--r--Userland/Libraries/LibGUI/Dialog.h67
-rw-r--r--Userland/Libraries/LibGUI/DisplayLink.cpp92
-rw-r--r--Userland/Libraries/LibGUI/DisplayLink.h42
-rw-r--r--Userland/Libraries/LibGUI/DragOperation.cpp125
-rw-r--r--Userland/Libraries/LibGUI/DragOperation.h69
-rw-r--r--Userland/Libraries/LibGUI/EditingEngine.cpp453
-rw-r--r--Userland/Libraries/LibGUI/EditingEngine.h85
-rw-r--r--Userland/Libraries/LibGUI/EmojiInputDialog.cpp112
-rw-r--r--Userland/Libraries/LibGUI/EmojiInputDialog.h46
-rw-r--r--Userland/Libraries/LibGUI/Event.cpp72
-rw-r--r--Userland/Libraries/LibGUI/Event.h403
-rw-r--r--Userland/Libraries/LibGUI/FileIconProvider.cpp276
-rw-r--r--Userland/Libraries/LibGUI/FileIconProvider.h48
-rw-r--r--Userland/Libraries/LibGUI/FilePicker.cpp334
-rw-r--r--Userland/Libraries/LibGUI/FilePicker.h106
-rw-r--r--Userland/Libraries/LibGUI/FileSystemModel.cpp647
-rw-r--r--Userland/Libraries/LibGUI/FileSystemModel.h187
-rw-r--r--Userland/Libraries/LibGUI/FilteringProxyModel.cpp135
-rw-r--r--Userland/Libraries/LibGUI/FilteringProxyModel.h73
-rw-r--r--Userland/Libraries/LibGUI/FocusSource.h37
-rw-r--r--Userland/Libraries/LibGUI/FontPicker.cpp226
-rw-r--r--Userland/Libraries/LibGUI/FontPicker.h66
-rw-r--r--Userland/Libraries/LibGUI/FontPickerDialog.gml95
-rw-r--r--Userland/Libraries/LibGUI/Forward.h104
-rw-r--r--Userland/Libraries/LibGUI/Frame.cpp83
-rw-r--r--Userland/Libraries/LibGUI/Frame.h63
-rw-r--r--Userland/Libraries/LibGUI/GMLFormatter.cpp120
-rw-r--r--Userland/Libraries/LibGUI/GMLFormatter.h35
-rw-r--r--Userland/Libraries/LibGUI/GMLLexer.cpp175
-rw-r--r--Userland/Libraries/LibGUI/GMLLexer.h90
-rw-r--r--Userland/Libraries/LibGUI/GMLParser.cpp161
-rw-r--r--Userland/Libraries/LibGUI/GMLParser.h35
-rw-r--r--Userland/Libraries/LibGUI/GMLSyntaxHighlighter.cpp107
-rw-r--r--Userland/Libraries/LibGUI/GMLSyntaxHighlighter.h48
-rw-r--r--Userland/Libraries/LibGUI/GroupBox.cpp73
-rw-r--r--Userland/Libraries/LibGUI/GroupBox.h50
-rw-r--r--Userland/Libraries/LibGUI/HeaderView.cpp387
-rw-r--r--Userland/Libraries/LibGUI/HeaderView.h101
-rw-r--r--Userland/Libraries/LibGUI/INILexer.cpp160
-rw-r--r--Userland/Libraries/LibGUI/INILexer.h89
-rw-r--r--Userland/Libraries/LibGUI/INISyntaxHighlighter.cpp106
-rw-r--r--Userland/Libraries/LibGUI/INISyntaxHighlighter.h48
-rw-r--r--Userland/Libraries/LibGUI/Icon.cpp102
-rw-r--r--Userland/Libraries/LibGUI/Icon.h86
-rw-r--r--Userland/Libraries/LibGUI/IconView.cpp821
-rw-r--r--Userland/Libraries/LibGUI/IconView.h177
-rw-r--r--Userland/Libraries/LibGUI/ImageWidget.cpp139
-rw-r--r--Userland/Libraries/LibGUI/ImageWidget.h70
-rw-r--r--Userland/Libraries/LibGUI/InputBox.cpp121
-rw-r--r--Userland/Libraries/LibGUI/InputBox.h54
-rw-r--r--Userland/Libraries/LibGUI/ItemListModel.h81
-rw-r--r--Userland/Libraries/LibGUI/JSSyntaxHighlighter.cpp154
-rw-r--r--Userland/Libraries/LibGUI/JSSyntaxHighlighter.h49
-rw-r--r--Userland/Libraries/LibGUI/JsonArrayModel.cpp138
-rw-r--r--Userland/Libraries/LibGUI/JsonArrayModel.h94
-rw-r--r--Userland/Libraries/LibGUI/Label.cpp127
-rw-r--r--Userland/Libraries/LibGUI/Label.h74
-rw-r--r--Userland/Libraries/LibGUI/Layout.cpp163
-rw-r--r--Userland/Libraries/LibGUI/Layout.h88
-rw-r--r--Userland/Libraries/LibGUI/LazyWidget.cpp49
-rw-r--r--Userland/Libraries/LibGUI/LazyWidget.h48
-rw-r--r--Userland/Libraries/LibGUI/LinkLabel.cpp110
-rw-r--r--Userland/Libraries/LibGUI/LinkLabel.h56
-rw-r--r--Userland/Libraries/LibGUI/ListView.cpp288
-rw-r--r--Userland/Libraries/LibGUI/ListView.h86
-rw-r--r--Userland/Libraries/LibGUI/Margins.h69
-rw-r--r--Userland/Libraries/LibGUI/Menu.cpp191
-rw-r--r--Userland/Libraries/LibGUI/Menu.h76
-rw-r--r--Userland/Libraries/LibGUI/MenuBar.cpp82
-rw-r--r--Userland/Libraries/LibGUI/MenuBar.h57
-rw-r--r--Userland/Libraries/LibGUI/MenuItem.cpp110
-rw-r--r--Userland/Libraries/LibGUI/MenuItem.h87
-rw-r--r--Userland/Libraries/LibGUI/MessageBox.cpp166
-rw-r--r--Userland/Libraries/LibGUI/MessageBox.h71
-rw-r--r--Userland/Libraries/LibGUI/Model.cpp125
-rw-r--r--Userland/Libraries/LibGUI/Model.h132
-rw-r--r--Userland/Libraries/LibGUI/ModelEditingDelegate.h107
-rw-r--r--Userland/Libraries/LibGUI/ModelIndex.cpp49
-rw-r--r--Userland/Libraries/LibGUI/ModelIndex.h100
-rw-r--r--Userland/Libraries/LibGUI/ModelRole.h44
-rw-r--r--Userland/Libraries/LibGUI/ModelSelection.cpp117
-rw-r--r--Userland/Libraries/LibGUI/ModelSelection.h122
-rw-r--r--Userland/Libraries/LibGUI/MultiView.cpp165
-rw-r--r--Userland/Libraries/LibGUI/MultiView.h124
-rw-r--r--Userland/Libraries/LibGUI/Notification.cpp66
-rw-r--r--Userland/Libraries/LibGUI/Notification.h59
-rw-r--r--Userland/Libraries/LibGUI/OpacitySlider.cpp165
-rw-r--r--Userland/Libraries/LibGUI/OpacitySlider.h56
-rw-r--r--Userland/Libraries/LibGUI/Painter.cpp49
-rw-r--r--Userland/Libraries/LibGUI/Painter.h40
-rw-r--r--Userland/Libraries/LibGUI/ProcessChooser.cpp129
-rw-r--r--Userland/Libraries/LibGUI/ProcessChooser.h59
-rw-r--r--Userland/Libraries/LibGUI/ProgressBar.cpp96
-rw-r--r--Userland/Libraries/LibGUI/ProgressBar.h71
-rw-r--r--Userland/Libraries/LibGUI/RadioButton.cpp86
-rw-r--r--Userland/Libraries/LibGUI/RadioButton.h53
-rw-r--r--Userland/Libraries/LibGUI/RegularEditingEngine.cpp91
-rw-r--r--Userland/Libraries/LibGUI/RegularEditingEngine.h44
-rw-r--r--Userland/Libraries/LibGUI/ResizeCorner.cpp113
-rw-r--r--Userland/Libraries/LibGUI/ResizeCorner.h45
-rw-r--r--Userland/Libraries/LibGUI/RunningProcessesModel.cpp118
-rw-r--r--Userland/Libraries/LibGUI/RunningProcessesModel.h65
-rw-r--r--Userland/Libraries/LibGUI/ScrollBar.cpp400
-rw-r--r--Userland/Libraries/LibGUI/ScrollBar.h93
-rw-r--r--Userland/Libraries/LibGUI/ScrollableWidget.cpp242
-rw-r--r--Userland/Libraries/LibGUI/ScrollableWidget.h104
-rw-r--r--Userland/Libraries/LibGUI/SeparatorWidget.cpp62
-rw-r--r--Userland/Libraries/LibGUI/SeparatorWidget.h46
-rw-r--r--Userland/Libraries/LibGUI/ShellSyntaxHighlighter.cpp549
-rw-r--r--Userland/Libraries/LibGUI/ShellSyntaxHighlighter.h49
-rw-r--r--Userland/Libraries/LibGUI/Shortcut.cpp57
-rw-r--r--Userland/Libraries/LibGUI/Shortcut.h71
-rw-r--r--Userland/Libraries/LibGUI/Slider.cpp184
-rw-r--r--Userland/Libraries/LibGUI/Slider.h108
-rw-r--r--Userland/Libraries/LibGUI/SortingProxyModel.cpp316
-rw-r--r--Userland/Libraries/LibGUI/SortingProxyModel.h97
-rw-r--r--Userland/Libraries/LibGUI/SpinBox.cpp119
-rw-r--r--Userland/Libraries/LibGUI/SpinBox.h67
-rw-r--r--Userland/Libraries/LibGUI/Splitter.cpp218
-rw-r--r--Userland/Libraries/LibGUI/Splitter.h91
-rw-r--r--Userland/Libraries/LibGUI/StackWidget.cpp94
-rw-r--r--Userland/Libraries/LibGUI/StackWidget.h53
-rw-r--r--Userland/Libraries/LibGUI/StatusBar.cpp107
-rw-r--r--Userland/Libraries/LibGUI/StatusBar.h54
-rw-r--r--Userland/Libraries/LibGUI/SyntaxHighlighter.cpp155
-rw-r--r--Userland/Libraries/LibGUI/SyntaxHighlighter.h89
-rw-r--r--Userland/Libraries/LibGUI/TabWidget.cpp469
-rw-r--r--Userland/Libraries/LibGUI/TabWidget.h128
-rw-r--r--Userland/Libraries/LibGUI/TableView.cpp253
-rw-r--r--Userland/Libraries/LibGUI/TableView.h66
-rw-r--r--Userland/Libraries/LibGUI/TextBox.cpp95
-rw-r--r--Userland/Libraries/LibGUI/TextBox.h59
-rw-r--r--Userland/Libraries/LibGUI/TextDocument.cpp848
-rw-r--r--Userland/Libraries/LibGUI/TextDocument.h247
-rw-r--r--Userland/Libraries/LibGUI/TextEditor.cpp1668
-rw-r--r--Userland/Libraries/LibGUI/TextEditor.h354
-rw-r--r--Userland/Libraries/LibGUI/TextPosition.h78
-rw-r--r--Userland/Libraries/LibGUI/TextRange.h106
-rw-r--r--Userland/Libraries/LibGUI/ToolBar.cpp121
-rw-r--r--Userland/Libraries/LibGUI/ToolBar.h66
-rw-r--r--Userland/Libraries/LibGUI/ToolBarContainer.cpp70
-rw-r--r--Userland/Libraries/LibGUI/ToolBarContainer.h46
-rw-r--r--Userland/Libraries/LibGUI/TreeView.cpp633
-rw-r--r--Userland/Libraries/LibGUI/TreeView.h94
-rw-r--r--Userland/Libraries/LibGUI/UndoStack.cpp103
-rw-r--r--Userland/Libraries/LibGUI/UndoStack.h58
-rw-r--r--Userland/Libraries/LibGUI/Variant.cpp462
-rw-r--r--Userland/Libraries/LibGUI/Variant.h292
-rw-r--r--Userland/Libraries/LibGUI/VimEditingEngine.cpp213
-rw-r--r--Userland/Libraries/LibGUI/VimEditingEngine.h58
-rw-r--r--Userland/Libraries/LibGUI/Widget.cpp1056
-rw-r--r--Userland/Libraries/LibGUI/Widget.h406
-rw-r--r--Userland/Libraries/LibGUI/Window.cpp965
-rw-r--r--Userland/Libraries/LibGUI/Window.h266
-rw-r--r--Userland/Libraries/LibGUI/WindowServerConnection.cpp354
-rw-r--r--Userland/Libraries/LibGUI/WindowServerConnection.h84
-rw-r--r--Userland/Libraries/LibGUI/WindowType.h45
-rw-r--r--Userland/Libraries/LibGemini/CMakeLists.txt11
-rw-r--r--Userland/Libraries/LibGemini/Document.cpp113
-rw-r--r--Userland/Libraries/LibGemini/Document.h149
-rw-r--r--Userland/Libraries/LibGemini/Forward.h37
-rw-r--r--Userland/Libraries/LibGemini/GeminiJob.cpp150
-rw-r--r--Userland/Libraries/LibGemini/GeminiJob.h74
-rw-r--r--Userland/Libraries/LibGemini/GeminiRequest.cpp60
-rw-r--r--Userland/Libraries/LibGemini/GeminiRequest.h52
-rw-r--r--Userland/Libraries/LibGemini/GeminiResponse.cpp41
-rw-r--r--Userland/Libraries/LibGemini/GeminiResponse.h52
-rw-r--r--Userland/Libraries/LibGemini/Job.cpp183
-rw-r--r--Userland/Libraries/LibGemini/Job.h80
-rw-r--r--Userland/Libraries/LibGemini/Line.cpp143
-rw-r--r--Userland/Libraries/LibGfx/AffineTransform.cpp175
-rw-r--r--Userland/Libraries/LibGfx/AffineTransform.h81
-rw-r--r--Userland/Libraries/LibGfx/BMPLoader.cpp1400
-rw-r--r--Userland/Libraries/LibGfx/BMPLoader.h58
-rw-r--r--Userland/Libraries/LibGfx/BMPWriter.cpp135
-rw-r--r--Userland/Libraries/LibGfx/BMPWriter.h51
-rw-r--r--Userland/Libraries/LibGfx/Bitmap.cpp495
-rw-r--r--Userland/Libraries/LibGfx/Bitmap.h336
-rw-r--r--Userland/Libraries/LibGfx/BitmapFont.cpp324
-rw-r--r--Userland/Libraries/LibGfx/BitmapFont.h150
-rw-r--r--Userland/Libraries/LibGfx/CMakeLists.txt37
-rw-r--r--Userland/Libraries/LibGfx/CharacterBitmap.cpp46
-rw-r--r--Userland/Libraries/LibGfx/CharacterBitmap.h54
-rw-r--r--Userland/Libraries/LibGfx/ClassicStylePainter.cpp405
-rw-r--r--Userland/Libraries/LibGfx/ClassicStylePainter.h49
-rw-r--r--Userland/Libraries/LibGfx/ClassicWindowTheme.cpp215
-rw-r--r--Userland/Libraries/LibGfx/ClassicWindowTheme.h63
-rw-r--r--Userland/Libraries/LibGfx/Color.cpp440
-rw-r--r--Userland/Libraries/LibGfx/Color.h333
-rw-r--r--Userland/Libraries/LibGfx/DisjointRectSet.cpp188
-rw-r--r--Userland/Libraries/LibGfx/DisjointRectSet.h159
-rw-r--r--Userland/Libraries/LibGfx/Emoji.cpp54
-rw-r--r--Userland/Libraries/LibGfx/Emoji.h40
-rw-r--r--Userland/Libraries/LibGfx/Filters/BoxBlurFilter.h42
-rw-r--r--Userland/Libraries/LibGfx/Filters/Filter.h52
-rw-r--r--Userland/Libraries/LibGfx/Filters/GenericConvolutionFilter.h173
-rw-r--r--Userland/Libraries/LibGfx/Filters/LaplacianFilter.h41
-rw-r--r--Userland/Libraries/LibGfx/Filters/SharpenFilter.h41
-rw-r--r--Userland/Libraries/LibGfx/Filters/SpatialGaussianBlurFilter.h43
-rw-r--r--Userland/Libraries/LibGfx/Font.cpp40
-rw-r--r--Userland/Libraries/LibGfx/Font.h114
-rw-r--r--Userland/Libraries/LibGfx/FontDatabase.cpp159
-rw-r--r--Userland/Libraries/LibGfx/FontDatabase.h59
-rw-r--r--Userland/Libraries/LibGfx/Forward.h72
-rw-r--r--Userland/Libraries/LibGfx/GIFLoader.cpp765
-rw-r--r--Userland/Libraries/LibGfx/GIFLoader.h58
-rw-r--r--Userland/Libraries/LibGfx/Gamma.h147
-rw-r--r--Userland/Libraries/LibGfx/ICOLoader.cpp438
-rw-r--r--Userland/Libraries/LibGfx/ICOLoader.h58
-rw-r--r--Userland/Libraries/LibGfx/ImageDecoder.cpp87
-rw-r--r--Userland/Libraries/LibGfx/ImageDecoder.h98
-rw-r--r--Userland/Libraries/LibGfx/JPGLoader.cpp1420
-rw-r--r--Userland/Libraries/LibGfx/JPGLoader.h58
-rw-r--r--Userland/Libraries/LibGfx/Matrix.h104
-rw-r--r--Userland/Libraries/LibGfx/Matrix4x4.h122
-rw-r--r--Userland/Libraries/LibGfx/Orientation.h38
-rw-r--r--Userland/Libraries/LibGfx/PBMLoader.cpp225
-rw-r--r--Userland/Libraries/LibGfx/PBMLoader.h61
-rw-r--r--Userland/Libraries/LibGfx/PGMLoader.cpp228
-rw-r--r--Userland/Libraries/LibGfx/PGMLoader.h61
-rw-r--r--Userland/Libraries/LibGfx/PNGLoader.cpp1078
-rw-r--r--Userland/Libraries/LibGfx/PNGLoader.h58
-rw-r--r--Userland/Libraries/LibGfx/PPMLoader.cpp230
-rw-r--r--Userland/Libraries/LibGfx/PPMLoader.h61
-rw-r--r--Userland/Libraries/LibGfx/Painter.cpp1629
-rw-r--r--Userland/Libraries/LibGfx/Painter.h167
-rw-r--r--Userland/Libraries/LibGfx/Palette.cpp117
-rw-r--r--Userland/Libraries/LibGfx/Palette.h158
-rw-r--r--Userland/Libraries/LibGfx/Path.cpp234
-rw-r--r--Userland/Libraries/LibGfx/Path.h220
-rw-r--r--Userland/Libraries/LibGfx/Point.cpp88
-rw-r--r--Userland/Libraries/LibGfx/Point.h257
-rw-r--r--Userland/Libraries/LibGfx/PortableImageLoaderCommon.h309
-rw-r--r--Userland/Libraries/LibGfx/Rect.cpp181
-rw-r--r--Userland/Libraries/LibGfx/Rect.h471
-rw-r--r--Userland/Libraries/LibGfx/ShareableBitmap.cpp76
-rw-r--r--Userland/Libraries/LibGfx/ShareableBitmap.h64
-rw-r--r--Userland/Libraries/LibGfx/Size.cpp71
-rw-r--r--Userland/Libraries/LibGfx/Size.h179
-rw-r--r--Userland/Libraries/LibGfx/StandardCursor.h51
-rw-r--r--Userland/Libraries/LibGfx/Streamer.h78
-rw-r--r--Userland/Libraries/LibGfx/StylePainter.cpp86
-rw-r--r--Userland/Libraries/LibGfx/StylePainter.h87
-rw-r--r--Userland/Libraries/LibGfx/SystemTheme.cpp130
-rw-r--r--Userland/Libraries/LibGfx/SystemTheme.h162
-rw-r--r--Userland/Libraries/LibGfx/TextAlignment.h92
-rw-r--r--Userland/Libraries/LibGfx/TextAttributes.h41
-rw-r--r--Userland/Libraries/LibGfx/TextElision.h36
-rw-r--r--Userland/Libraries/LibGfx/Triangle.cpp42
-rw-r--r--Userland/Libraries/LibGfx/Triangle.h81
-rw-r--r--Userland/Libraries/LibGfx/Vector3.h133
-rw-r--r--Userland/Libraries/LibGfx/WindowTheme.cpp42
-rw-r--r--Userland/Libraries/LibGfx/WindowTheme.h69
-rw-r--r--Userland/Libraries/LibHTTP/CMakeLists.txt10
-rw-r--r--Userland/Libraries/LibHTTP/Forward.h37
-rw-r--r--Userland/Libraries/LibHTTP/HttpJob.cpp106
-rw-r--r--Userland/Libraries/LibHTTP/HttpJob.h69
-rw-r--r--Userland/Libraries/LibHTTP/HttpRequest.cpp195
-rw-r--r--Userland/Libraries/LibHTTP/HttpRequest.h83
-rw-r--r--Userland/Libraries/LibHTTP/HttpResponse.cpp41
-rw-r--r--Userland/Libraries/LibHTTP/HttpResponse.h53
-rw-r--r--Userland/Libraries/LibHTTP/HttpsJob.cpp151
-rw-r--r--Userland/Libraries/LibHTTP/HttpsJob.h75
-rw-r--r--Userland/Libraries/LibHTTP/Job.cpp405
-rw-r--r--Userland/Libraries/LibHTTP/Job.h87
-rw-r--r--Userland/Libraries/LibIPC/CMakeLists.txt9
-rw-r--r--Userland/Libraries/LibIPC/ClientConnection.h78
-rw-r--r--Userland/Libraries/LibIPC/Connection.h281
-rw-r--r--Userland/Libraries/LibIPC/Decoder.cpp186
-rw-r--r--Userland/Libraries/LibIPC/Decoder.h132
-rw-r--r--Userland/Libraries/LibIPC/Dictionary.h65
-rw-r--r--Userland/Libraries/LibIPC/Encoder.cpp173
-rw-r--r--Userland/Libraries/LibIPC/Encoder.h111
-rw-r--r--Userland/Libraries/LibIPC/Endpoint.cpp39
-rw-r--r--Userland/Libraries/LibIPC/Endpoint.h55
-rw-r--r--Userland/Libraries/LibIPC/File.h49
-rw-r--r--Userland/Libraries/LibIPC/Forward.h37
-rw-r--r--Userland/Libraries/LibIPC/Message.cpp41
-rw-r--r--Userland/Libraries/LibIPC/Message.h54
-rw-r--r--Userland/Libraries/LibIPC/ServerConnection.h70
-rw-r--r--Userland/Libraries/LibImageDecoderClient/CMakeLists.txt11
-rw-r--r--Userland/Libraries/LibImageDecoderClient/Client.cpp88
-rw-r--r--Userland/Libraries/LibImageDecoderClient/Client.h52
-rw-r--r--Userland/Libraries/LibJS/AST.cpp2247
-rw-r--r--Userland/Libraries/LibJS/AST.h1385
-rw-r--r--Userland/Libraries/LibJS/CMakeLists.txt88
-rw-r--r--Userland/Libraries/LibJS/Console.cpp138
-rw-r--r--Userland/Libraries/LibJS/Console.h104
-rw-r--r--Userland/Libraries/LibJS/Forward.h172
-rw-r--r--Userland/Libraries/LibJS/Heap/Allocator.cpp69
-rw-r--r--Userland/Libraries/LibJS/Heap/Allocator.h71
-rw-r--r--Userland/Libraries/LibJS/Heap/DeferGC.h50
-rw-r--r--Userland/Libraries/LibJS/Heap/Handle.cpp44
-rw-r--r--Userland/Libraries/LibJS/Heap/Handle.h85
-rw-r--r--Userland/Libraries/LibJS/Heap/Heap.cpp331
-rw-r--r--Userland/Libraries/LibJS/Heap/Heap.h132
-rw-r--r--Userland/Libraries/LibJS/Heap/HeapBlock.cpp87
-rw-r--r--Userland/Libraries/LibJS/Heap/HeapBlock.h110
-rw-r--r--Userland/Libraries/LibJS/Interpreter.cpp197
-rw-r--r--Userland/Libraries/LibJS/Interpreter.h97
-rw-r--r--Userland/Libraries/LibJS/Lexer.cpp655
-rw-r--r--Userland/Libraries/LibJS/Lexer.h85
-rw-r--r--Userland/Libraries/LibJS/MarkupGenerator.cpp233
-rw-r--r--Userland/Libraries/LibJS/MarkupGenerator.h66
-rw-r--r--Userland/Libraries/LibJS/Parser.cpp2049
-rw-r--r--Userland/Libraries/LibJS/Parser.h227
-rw-r--r--Userland/Libraries/LibJS/Runtime/Accessor.h82
-rw-r--r--Userland/Libraries/LibJS/Runtime/Array.cpp86
-rw-r--r--Userland/Libraries/LibJS/Runtime/Array.h51
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp47
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayBuffer.h51
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.cpp85
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.h50
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp87
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.h46
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp141
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayConstructor.h52
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayIterator.cpp54
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayIterator.h56
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp95
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.h45
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp1037
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayPrototype.h71
-rw-r--r--Userland/Libraries/LibJS/Runtime/BigInt.cpp48
-rw-r--r--Userland/Libraries/LibJS/Runtime/BigInt.h50
-rw-r--r--Userland/Libraries/LibJS/Runtime/BigIntConstructor.cpp92
-rw-r--r--Userland/Libraries/LibJS/Runtime/BigIntConstructor.h51
-rw-r--r--Userland/Libraries/LibJS/Runtime/BigIntObject.cpp54
-rw-r--r--Userland/Libraries/LibJS/Runtime/BigIntObject.h55
-rw-r--r--Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp89
-rw-r--r--Userland/Libraries/LibJS/Runtime/BigIntPrototype.h47
-rw-r--r--Userland/Libraries/LibJS/Runtime/BooleanConstructor.cpp62
-rw-r--r--Userland/Libraries/LibJS/Runtime/BooleanConstructor.h48
-rw-r--r--Userland/Libraries/LibJS/Runtime/BooleanObject.cpp47
-rw-r--r--Userland/Libraries/LibJS/Runtime/BooleanObject.h49
-rw-r--r--Userland/Libraries/LibJS/Runtime/BooleanPrototype.cpp77
-rw-r--r--Userland/Libraries/LibJS/Runtime/BooleanPrototype.h46
-rw-r--r--Userland/Libraries/LibJS/Runtime/BoundFunction.cpp79
-rw-r--r--Userland/Libraries/LibJS/Runtime/BoundFunction.h68
-rw-r--r--Userland/Libraries/LibJS/Runtime/Cell.cpp58
-rw-r--r--Userland/Libraries/LibJS/Runtime/Cell.h87
-rw-r--r--Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h255
-rw-r--r--Userland/Libraries/LibJS/Runtime/ConsoleObject.cpp106
-rw-r--r--Userland/Libraries/LibJS/Runtime/ConsoleObject.h53
-rw-r--r--Userland/Libraries/LibJS/Runtime/Date.cpp124
-rw-r--r--Userland/Libraries/LibJS/Runtime/Date.h93
-rw-r--r--Userland/Libraries/LibJS/Runtime/DateConstructor.cpp252
-rw-r--r--Userland/Libraries/LibJS/Runtime/DateConstructor.h52
-rw-r--r--Userland/Libraries/LibJS/Runtime/DatePrototype.cpp299
-rw-r--r--Userland/Libraries/LibJS/Runtime/DatePrototype.h68
-rw-r--r--Userland/Libraries/LibJS/Runtime/Error.cpp62
-rw-r--r--Userland/Libraries/LibJS/Runtime/Error.h68
-rw-r--r--Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp98
-rw-r--r--Userland/Libraries/LibJS/Runtime/ErrorConstructor.h69
-rw-r--r--Userland/Libraries/LibJS/Runtime/ErrorPrototype.cpp140
-rw-r--r--Userland/Libraries/LibJS/Runtime/ErrorPrototype.h65
-rw-r--r--Userland/Libraries/LibJS/Runtime/ErrorTypes.cpp36
-rw-r--r--Userland/Libraries/LibJS/Runtime/ErrorTypes.h193
-rw-r--r--Userland/Libraries/LibJS/Runtime/Exception.cpp63
-rw-r--r--Userland/Libraries/LibJS/Runtime/Exception.h54
-rw-r--r--Userland/Libraries/LibJS/Runtime/Function.cpp99
-rw-r--r--Userland/Libraries/LibJS/Runtime/Function.h79
-rw-r--r--Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp105
-rw-r--r--Userland/Libraries/LibJS/Runtime/FunctionConstructor.h48
-rw-r--r--Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp189
-rw-r--r--Userland/Libraries/LibJS/Runtime/FunctionPrototype.h49
-rw-r--r--Userland/Libraries/LibJS/Runtime/GlobalObject.cpp310
-rw-r--r--Userland/Libraries/LibJS/Runtime/GlobalObject.h133
-rw-r--r--Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp396
-rw-r--r--Userland/Libraries/LibJS/Runtime/IndexedProperties.h190
-rw-r--r--Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp135
-rw-r--r--Userland/Libraries/LibJS/Runtime/IteratorOperations.h46
-rw-r--r--Userland/Libraries/LibJS/Runtime/IteratorPrototype.cpp56
-rw-r--r--Userland/Libraries/LibJS/Runtime/IteratorPrototype.h45
-rw-r--r--Userland/Libraries/LibJS/Runtime/JSONObject.cpp507
-rw-r--r--Userland/Libraries/LibJS/Runtime/JSONObject.h70
-rw-r--r--Userland/Libraries/LibJS/Runtime/LexicalEnvironment.cpp134
-rw-r--r--Userland/Libraries/LibJS/Runtime/LexicalEnvironment.h100
-rw-r--r--Userland/Libraries/LibJS/Runtime/MarkedValueList.cpp50
-rw-r--r--Userland/Libraries/LibJS/Runtime/MarkedValueList.h58
-rw-r--r--Userland/Libraries/LibJS/Runtime/MathObject.cpp504
-rw-r--r--Userland/Libraries/LibJS/Runtime/MathObject.h78
-rw-r--r--Userland/Libraries/LibJS/Runtime/NativeFunction.cpp81
-rw-r--r--Userland/Libraries/LibJS/Runtime/NativeFunction.h63
-rw-r--r--Userland/Libraries/LibJS/Runtime/NativeProperty.cpp56
-rw-r--r--Userland/Libraries/LibJS/Runtime/NativeProperty.h49
-rw-r--r--Userland/Libraries/LibJS/Runtime/NumberConstructor.cpp115
-rw-r--r--Userland/Libraries/LibJS/Runtime/NumberConstructor.h53
-rw-r--r--Userland/Libraries/LibJS/Runtime/NumberObject.cpp50
-rw-r--r--Userland/Libraries/LibJS/Runtime/NumberObject.h50
-rw-r--r--Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp146
-rw-r--r--Userland/Libraries/LibJS/Runtime/NumberPrototype.h44
-rw-r--r--Userland/Libraries/LibJS/Runtime/Object.cpp927
-rw-r--r--Userland/Libraries/LibJS/Runtime/Object.h168
-rw-r--r--Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp242
-rw-r--r--Userland/Libraries/LibJS/Runtime/ObjectConstructor.h60
-rw-r--r--Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp166
-rw-r--r--Userland/Libraries/LibJS/Runtime/ObjectPrototype.h52
-rw-r--r--Userland/Libraries/LibJS/Runtime/PrimitiveString.cpp57
-rw-r--r--Userland/Libraries/LibJS/Runtime/PrimitiveString.h50
-rw-r--r--Userland/Libraries/LibJS/Runtime/PropertyAttributes.h104
-rw-r--r--Userland/Libraries/LibJS/Runtime/PropertyName.h163
-rw-r--r--Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp80
-rw-r--r--Userland/Libraries/LibJS/Runtime/ProxyConstructor.h48
-rw-r--r--Userland/Libraries/LibJS/Runtime/ProxyObject.cpp559
-rw-r--r--Userland/Libraries/LibJS/Runtime/ProxyObject.h75
-rw-r--r--Userland/Libraries/LibJS/Runtime/Reference.cpp106
-rw-r--r--Userland/Libraries/LibJS/Runtime/Reference.h101
-rw-r--r--Userland/Libraries/LibJS/Runtime/ReflectObject.cpp280
-rw-r--r--Userland/Libraries/LibJS/Runtime/ReflectObject.h57
-rw-r--r--Userland/Libraries/LibJS/Runtime/RegExpConstructor.cpp74
-rw-r--r--Userland/Libraries/LibJS/Runtime/RegExpConstructor.h48
-rw-r--r--Userland/Libraries/LibJS/Runtime/RegExpObject.cpp170
-rw-r--r--Userland/Libraries/LibJS/Runtime/RegExpObject.h66
-rw-r--r--Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp254
-rw-r--r--Userland/Libraries/LibJS/Runtime/RegExpPrototype.h57
-rw-r--r--Userland/Libraries/LibJS/Runtime/ScopeObject.cpp49
-rw-r--r--Userland/Libraries/LibJS/Runtime/ScopeObject.h60
-rw-r--r--Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp187
-rw-r--r--Userland/Libraries/LibJS/Runtime/ScriptFunction.h77
-rw-r--r--Userland/Libraries/LibJS/Runtime/Shape.cpp227
-rw-r--r--Userland/Libraries/LibJS/Runtime/Shape.h132
-rw-r--r--Userland/Libraries/LibJS/Runtime/StringConstructor.cpp135
-rw-r--r--Userland/Libraries/LibJS/Runtime/StringConstructor.h51
-rw-r--r--Userland/Libraries/LibJS/Runtime/StringIterator.cpp49
-rw-r--r--Userland/Libraries/LibJS/Runtime/StringIterator.h54
-rw-r--r--Userland/Libraries/LibJS/Runtime/StringIteratorPrototype.cpp80
-rw-r--r--Userland/Libraries/LibJS/Runtime/StringIteratorPrototype.h45
-rw-r--r--Userland/Libraries/LibJS/Runtime/StringObject.cpp57
-rw-r--r--Userland/Libraries/LibJS/Runtime/StringObject.h54
-rw-r--r--Userland/Libraries/LibJS/Runtime/StringOrSymbol.h196
-rw-r--r--Userland/Libraries/LibJS/Runtime/StringPrototype.cpp629
-rw-r--r--Userland/Libraries/LibJS/Runtime/StringPrototype.h70
-rw-r--r--Userland/Libraries/LibJS/Runtime/Symbol.cpp53
-rw-r--r--Userland/Libraries/LibJS/Runtime/Symbol.h56
-rw-r--r--Userland/Libraries/LibJS/Runtime/SymbolConstructor.cpp99
-rw-r--r--Userland/Libraries/LibJS/Runtime/SymbolConstructor.h51
-rw-r--r--Userland/Libraries/LibJS/Runtime/SymbolObject.cpp57
-rw-r--r--Userland/Libraries/LibJS/Runtime/SymbolObject.h60
-rw-r--r--Userland/Libraries/LibJS/Runtime/SymbolPrototype.cpp97
-rw-r--r--Userland/Libraries/LibJS/Runtime/SymbolPrototype.h48
-rw-r--r--Userland/Libraries/LibJS/Runtime/TypedArray.cpp163
-rw-r--r--Userland/Libraries/LibJS/Runtime/TypedArray.h180
-rw-r--r--Userland/Libraries/LibJS/Runtime/TypedArrayConstructor.cpp65
-rw-r--r--Userland/Libraries/LibJS/Runtime/TypedArrayConstructor.h49
-rw-r--r--Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp70
-rw-r--r--Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h45
-rw-r--r--Userland/Libraries/LibJS/Runtime/Uint8ClampedArray.cpp86
-rw-r--r--Userland/Libraries/LibJS/Runtime/Uint8ClampedArray.h57
-rw-r--r--Userland/Libraries/LibJS/Runtime/VM.cpp365
-rw-r--r--Userland/Libraries/LibJS/Runtime/VM.h290
-rw-r--r--Userland/Libraries/LibJS/Runtime/Value.cpp1221
-rw-r--r--Userland/Libraries/LibJS/Runtime/Value.h357
-rw-r--r--Userland/Libraries/LibJS/Runtime/WithScope.cpp67
-rw-r--r--Userland/Libraries/LibJS/Runtime/WithScope.h50
-rw-r--r--Userland/Libraries/LibJS/SourceRange.h43
-rw-r--r--Userland/Libraries/LibJS/Tests/add-values-to-primitive.js6
-rw-r--r--Userland/Libraries/LibJS/Tests/arguments-object.js15
-rw-r--r--Userland/Libraries/LibJS/Tests/automatic-semicolon-insertion.js65
-rw-r--r--Userland/Libraries/LibJS/Tests/break-continue-syntax-errors.js17
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.from.js69
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.isArray.js26
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.js53
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.of.js59
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype-generic-functions.js150
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.concat.js43
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.every.js55
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.fill.js20
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.filter.js68
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.find.js65
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.findIndex.js68
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.forEach.js70
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.includes.js18
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.indexOf.js25
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.join.js24
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.lastIndexOf.js22
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.map.js57
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.pop.js29
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.push.js23
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.reduce.js113
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.reduceRight.js118
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.reverse.js9
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.shift.js23
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.slice.js39
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.some.js37
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.sort.js204
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.splice.js49
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.toLocaleString.js62
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.toString.js66
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.unshift.js23
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.values.js44
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/array-basic.js57
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/array-length-setter.js37
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/array-shrink-during-find-crash.js19
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/array-simple-and-generic-storage-initialization.js15
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/array-spread.js25
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.isView.js27
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.js13
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.prototype.byteLength.js6
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.js68
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.@@toStringTag.js3
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.toLocaleString.js10
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.toString.js10
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.valueOf.js12
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/BigInt/bigint-basic.js80
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/BigInt/bigint-minus.js8
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/BigInt/bigint-number-mix-errors.js31
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.js32
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.prototype.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.prototype.toString.js17
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.prototype.valueOf.js16
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.UTC.js51
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.js77
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.now.js10
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.parse.js32
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getDate.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getDay.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getFullYear.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getHours.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getMilliseconds.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getMinutes.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getMonth.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getSeconds.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getTime.js6
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCDate.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCDay.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCFullYear.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCHours.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCMilliseconds.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCMinutes.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCMonth.js26
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCSeconds.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toISOString.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Error/Error.js18
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Error/Error.prototype.name.js10
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Error/Error.prototype.toString.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Function/Function.js53
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.@@hasInstance.js8
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.apply.js51
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.bind.js149
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.call.js53
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.toString.js17
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Infinity/Infinity.js22
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.@@toStringTag.js4
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.parse-reviver.js11
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.parse.js37
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-exception-in-property-getter.js10
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-order.js30
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-proxy.js11
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-replacer.js38
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-space.js47
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify.js71
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math-constants.js10
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.@@toStringTag.js4
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.abs.js14
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.acosh.js9
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.asin.js15
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.asinh.js6
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.atan.js12
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.atan2.js28
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.atanh.js10
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.cbrt.js12
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.ceil.js13
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.clz32.js44
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.cos.js14
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.cosh.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.exp.js13
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.expm1.js13
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.floor.js13
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.fround.js9
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.hypot.js9
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.log.js8
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.log10.js9
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.log1p.js8
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.log2.js10
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.max.js10
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.min.js9
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.pow.js25
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.sign.js36
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.sin.js15
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.sinh.js6
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.sqrt.js4
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.tan.js14
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.tanh.js8
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Math/Math.trunc.js12
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/NaN/NaN.js13
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Number/Number-constants.js11
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Number/Number.isFinite.js24
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Number/Number.isInteger.js32
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Number/Number.isNaN.js25
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Number/Number.isSafeInteger.js24
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Number/Number.js33
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Number/Number.parseFloat.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.js4
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toString.js83
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperty.js242
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.entries.js64
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.getOwnPropertyDescriptor.js61
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.getOwnPropertyNames.js14
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.getPrototypeOf.js10
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.is.js54
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.isExtensible.js18
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.js13
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.keys.js51
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.preventExtensions.js67
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.constructor.js18
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.hasOwnProperty.js13
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.isPrototypeOf.js18
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.propertyIsEnumerable.js15
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.toLocaleString.js35
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.toString.js20
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.setPrototypeOf.js43
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.values.js51
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-apply.js36
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-construct.js72
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-defineProperty.js133
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-deleteProperty.js58
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-get.js96
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-getOwnPropertyDescriptor.js220
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-getPrototypeOf.js95
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-has.js74
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-isExtensible.js39
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-preventExtensions.js59
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-set.js99
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-setPrototypeOf.js96
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.js37
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.apply.js44
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.construct.js75
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.defineProperty.js82
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.deleteProperty.js66
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.get.js67
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getOwnPropertyDescriptor.js41
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getPrototypeOf.js37
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.has.js45
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.isExtensible.js33
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.ownKeys.js51
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.preventExtensions.js42
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.set.js102
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.setPrototypeOf.js57
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.js8
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.exec.js58
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.flags.js11
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.source.js8
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.test.js58
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.toString.js6
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.fromCharCode.js17
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.js9
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype-generic-functions.js39
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.charAt.js20
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.charCodeAt.js21
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.concat.js17
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.endsWith.js42
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.includes.js12
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.indexOf.js8
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.js9
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.lastIndexOf.js22
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.padEnd.js18
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.padStart.js18
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.repeat.js25
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.slice.js17
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.split.js35
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.startsWith.js36
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.substr.js16
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.substring.js14
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toLowerCase.js9
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toString.js6
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toUpperCase.js9
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.trim.js58
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.valueOf.js12
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/String/String.raw.js31
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.for.js23
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.js19
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.keyFor.js24
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.prototype.@@toStringTag.js3
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.prototype.toString.js25
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.prototype.valueOf.js15
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Symbol/well-known-symbol-existence.js15
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.BYTES_PER_ELEMENT.js10
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.js133
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.length.js19
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/TypedArray/typed-array-basic.js86
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/functions/isFinite.js23
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/functions/isNaN.js26
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/functions/parseFloat.js59
-rw-r--r--Userland/Libraries/LibJS/Tests/classes/class-advanced-extends.js35
-rw-r--r--Userland/Libraries/LibJS/Tests/classes/class-basic.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/classes/class-constructor.js67
-rw-r--r--Userland/Libraries/LibJS/Tests/classes/class-errors.js100
-rw-r--r--Userland/Libraries/LibJS/Tests/classes/class-expressions.js68
-rw-r--r--Userland/Libraries/LibJS/Tests/classes/class-getters.js75
-rw-r--r--Userland/Libraries/LibJS/Tests/classes/class-inheritance.js133
-rw-r--r--Userland/Libraries/LibJS/Tests/classes/class-methods.js51
-rw-r--r--Userland/Libraries/LibJS/Tests/classes/class-setters.js108
-rw-r--r--Userland/Libraries/LibJS/Tests/classes/class-static-getters.js87
-rw-r--r--Userland/Libraries/LibJS/Tests/classes/class-static-setters.js112
-rw-r--r--Userland/Libraries/LibJS/Tests/classes/class-static.js72
-rw-r--r--Userland/Libraries/LibJS/Tests/classes/class-strict-mode.js19
-rw-r--r--Userland/Libraries/LibJS/Tests/comments-basic.js35
-rw-r--r--Userland/Libraries/LibJS/Tests/computed-property-throws.js19
-rw-r--r--Userland/Libraries/LibJS/Tests/const-declaration-missing-initializer.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/const-reassignment.js17
-rw-r--r--Userland/Libraries/LibJS/Tests/custom-@@hasInstance.js9
-rw-r--r--Userland/Libraries/LibJS/Tests/custom-@@toStringTag.js26
-rw-r--r--Userland/Libraries/LibJS/Tests/debugger-statement.js3
-rw-r--r--Userland/Libraries/LibJS/Tests/empty-statements.js19
-rw-r--r--Userland/Libraries/LibJS/Tests/exception-ReferenceError.js3
-rw-r--r--Userland/Libraries/LibJS/Tests/exception-in-catch-block.js25
-rw-r--r--Userland/Libraries/LibJS/Tests/exponentiation-basic.js37
-rw-r--r--Userland/Libraries/LibJS/Tests/functions/arrow-functions.js164
-rw-r--r--Userland/Libraries/LibJS/Tests/functions/constructor-basic.js11
-rw-r--r--Userland/Libraries/LibJS/Tests/functions/function-TypeError.js47
-rw-r--r--Userland/Libraries/LibJS/Tests/functions/function-default-parameters.js143
-rw-r--r--Userland/Libraries/LibJS/Tests/functions/function-duplicate-parameters.js45
-rw-r--r--Userland/Libraries/LibJS/Tests/functions/function-hoisting.js39
-rw-r--r--Userland/Libraries/LibJS/Tests/functions/function-length.js23
-rw-r--r--Userland/Libraries/LibJS/Tests/functions/function-missing-arg.js10
-rw-r--r--Userland/Libraries/LibJS/Tests/functions/function-name.js57
-rw-r--r--Userland/Libraries/LibJS/Tests/functions/function-new-target.js25
-rw-r--r--Userland/Libraries/LibJS/Tests/functions/function-prototype-writable.js10
-rw-r--r--Userland/Libraries/LibJS/Tests/functions/function-rest-params.js47
-rw-r--r--Userland/Libraries/LibJS/Tests/functions/function-spread.js38
-rw-r--r--Userland/Libraries/LibJS/Tests/functions/function-strict-mode.js60
-rw-r--r--Userland/Libraries/LibJS/Tests/functions/function-this-in-arguments.js16
-rw-r--r--Userland/Libraries/LibJS/Tests/if-statement-function-declaration.js41
-rw-r--r--Userland/Libraries/LibJS/Tests/indexed-access-string-object.js15
-rw-r--r--Userland/Libraries/LibJS/Tests/invalid-lhs-in-assignment.js32
-rw-r--r--Userland/Libraries/LibJS/Tests/iterators/%IteratorPrototype%.js12
-rw-r--r--Userland/Libraries/LibJS/Tests/iterators/array-iterator.js53
-rw-r--r--Userland/Libraries/LibJS/Tests/iterators/string-iterator.js48
-rw-r--r--Userland/Libraries/LibJS/Tests/labels.js47
-rw-r--r--Userland/Libraries/LibJS/Tests/let-scoping.js14
-rw-r--r--Userland/Libraries/LibJS/Tests/loops/break-basic.js30
-rw-r--r--Userland/Libraries/LibJS/Tests/loops/continue-basic.js10
-rw-r--r--Userland/Libraries/LibJS/Tests/loops/do-while-basic.js24
-rw-r--r--Userland/Libraries/LibJS/Tests/loops/for-basic.js15
-rw-r--r--Userland/Libraries/LibJS/Tests/loops/for-head-errors.js23
-rw-r--r--Userland/Libraries/LibJS/Tests/loops/for-in-basic.js45
-rw-r--r--Userland/Libraries/LibJS/Tests/loops/for-no-curlies.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/loops/for-of-basic.js100
-rw-r--r--Userland/Libraries/LibJS/Tests/loops/for-scopes.js18
-rw-r--r--Userland/Libraries/LibJS/Tests/loops/while-basic.js25
-rw-r--r--Userland/Libraries/LibJS/Tests/new-expression.js53
-rw-r--r--Userland/Libraries/LibJS/Tests/numeric-literals-basic.js52
-rw-r--r--Userland/Libraries/LibJS/Tests/object-basic.js169
-rw-r--r--Userland/Libraries/LibJS/Tests/object-expression-computed-property.js12
-rw-r--r--Userland/Libraries/LibJS/Tests/object-getter-setter-shorthand.js62
-rw-r--r--Userland/Libraries/LibJS/Tests/object-method-shorthand.js50
-rw-r--r--Userland/Libraries/LibJS/Tests/object-spread.js117
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/assignment-operators.js135
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/binary-bitwise-and.js60
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/binary-bitwise-left-shift.js60
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/binary-bitwise-or.js60
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/binary-bitwise-right-shift.js62
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/binary-bitwise-unsigned-right-shift.js62
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/binary-bitwise-xor.js60
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/binary-relational.js729
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/bitwise-not.js23
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/comma-operator.js24
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/delete-basic.js61
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/delete-global-variable.js9
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/delete-globalThis-property-crash.js8
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/in-operator-basic.js32
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/instanceof-basic.js36
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/logical-and.js50
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/logical-expressions-short-circuit.js13
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/logical-nullish-coalescing.js50
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/logical-or.js50
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/modulo-basic.js16
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/ternary-basic.js20
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/typeof-basic.js27
-rw-r--r--Userland/Libraries/LibJS/Tests/operators/void-basic.js14
-rw-r--r--Userland/Libraries/LibJS/Tests/ordinary-to-primitive.js23
-rw-r--r--Userland/Libraries/LibJS/Tests/parseInt.js59
-rw-r--r--Userland/Libraries/LibJS/Tests/parser-declaration-in-single-statement-context.js6
-rw-r--r--Userland/Libraries/LibJS/Tests/parser-line-terminators.js66
-rw-r--r--Userland/Libraries/LibJS/Tests/parser-unary-associativity.js14
-rw-r--r--Userland/Libraries/LibJS/Tests/program-strict-mode.js18
-rw-r--r--Userland/Libraries/LibJS/Tests/return.js53
-rw-r--r--Userland/Libraries/LibJS/Tests/runtime-error-call-stack-size.js21
-rw-r--r--Userland/Libraries/LibJS/Tests/strict-mode-blocks.js25
-rw-r--r--Userland/Libraries/LibJS/Tests/strict-mode-errors.js15
-rw-r--r--Userland/Libraries/LibJS/Tests/string-escapes.js63
-rw-r--r--Userland/Libraries/LibJS/Tests/string-spread.js25
-rw-r--r--Userland/Libraries/LibJS/Tests/switch-basic.js78
-rw-r--r--Userland/Libraries/LibJS/Tests/switch-break.js20
-rw-r--r--Userland/Libraries/LibJS/Tests/tagged-template-literals.js108
-rw-r--r--Userland/Libraries/LibJS/Tests/template-literals.js65
-rw-r--r--Userland/Libraries/LibJS/Tests/test-common-tests.js417
-rw-r--r--Userland/Libraries/LibJS/Tests/test-common.js462
-rw-r--r--Userland/Libraries/LibJS/Tests/throw-basic.js34
-rw-r--r--Userland/Libraries/LibJS/Tests/to-number-basic.js75
-rw-r--r--Userland/Libraries/LibJS/Tests/to-number-exception.js25
-rw-r--r--Userland/Libraries/LibJS/Tests/try-catch-finally-nested.js55
-rw-r--r--Userland/Libraries/LibJS/Tests/try-catch-finally.js172
-rw-r--r--Userland/Libraries/LibJS/Tests/update-expression-on-member-expression.js8
-rw-r--r--Userland/Libraries/LibJS/Tests/update-expressions-basic.js53
-rw-r--r--Userland/Libraries/LibJS/Tests/use-strict-directive.js61
-rw-r--r--Userland/Libraries/LibJS/Tests/var-multiple-declarator.js8
-rw-r--r--Userland/Libraries/LibJS/Tests/var-scoping.js17
-rw-r--r--Userland/Libraries/LibJS/Tests/variable-undefined.js14
-rw-r--r--Userland/Libraries/LibJS/Tests/with-basic.js24
-rw-r--r--Userland/Libraries/LibJS/Token.cpp272
-rw-r--r--Userland/Libraries/LibJS/Token.h229
-rw-r--r--Userland/Libraries/LibKeyboard/CMakeLists.txt7
-rw-r--r--Userland/Libraries/LibKeyboard/CharacterMap.cpp110
-rw-r--r--Userland/Libraries/LibKeyboard/CharacterMap.h55
-rw-r--r--Userland/Libraries/LibKeyboard/CharacterMapData.h102
-rw-r--r--Userland/Libraries/LibKeyboard/CharacterMapFile.cpp110
-rw-r--r--Userland/Libraries/LibKeyboard/CharacterMapFile.h43
-rw-r--r--Userland/Libraries/LibLine/CMakeLists.txt10
-rw-r--r--Userland/Libraries/LibLine/Editor.cpp1769
-rw-r--r--Userland/Libraries/LibLine/Editor.h489
-rw-r--r--Userland/Libraries/LibLine/InternalFunctions.cpp496
-rw-r--r--Userland/Libraries/LibLine/KeyCallbackMachine.cpp113
-rw-r--r--Userland/Libraries/LibLine/KeyCallbackMachine.h112
-rw-r--r--Userland/Libraries/LibLine/Span.h55
-rw-r--r--Userland/Libraries/LibLine/StringMetrics.h72
-rw-r--r--Userland/Libraries/LibLine/Style.h184
-rw-r--r--Userland/Libraries/LibLine/SuggestionDisplay.h104
-rw-r--r--Userland/Libraries/LibLine/SuggestionManager.cpp196
-rw-r--r--Userland/Libraries/LibLine/SuggestionManager.h161
-rw-r--r--Userland/Libraries/LibLine/VT.h44
-rw-r--r--Userland/Libraries/LibLine/XtermSuggestionDisplay.cpp205
-rw-r--r--Userland/Libraries/LibM/CMakeLists.txt10
-rw-r--r--Userland/Libraries/LibM/TestMath.cpp114
-rw-r--r--Userland/Libraries/LibM/math.cpp566
-rw-r--r--Userland/Libraries/LibM/math.h137
-rw-r--r--Userland/Libraries/LibMarkdown/Block.h42
-rw-r--r--Userland/Libraries/LibMarkdown/CMakeLists.txt13
-rw-r--r--Userland/Libraries/LibMarkdown/CodeBlock.cpp160
-rw-r--r--Userland/Libraries/LibMarkdown/CodeBlock.h56
-rw-r--r--Userland/Libraries/LibMarkdown/Document.cpp133
-rw-r--r--Userland/Libraries/LibMarkdown/Document.h46
-rw-r--r--Userland/Libraries/LibMarkdown/Heading.cpp89
-rw-r--r--Userland/Libraries/LibMarkdown/Heading.h56
-rw-r--r--Userland/Libraries/LibMarkdown/HorizontalRule.cpp69
-rw-r--r--Userland/Libraries/LibMarkdown/HorizontalRule.h48
-rw-r--r--Userland/Libraries/LibMarkdown/List.cpp156
-rw-r--r--Userland/Libraries/LibMarkdown/List.h56
-rw-r--r--Userland/Libraries/LibMarkdown/Paragraph.cpp72
-rw-r--r--Userland/Libraries/LibMarkdown/Paragraph.h68
-rw-r--r--Userland/Libraries/LibMarkdown/Table.cpp238
-rw-r--r--Userland/Libraries/LibMarkdown/Table.h64
-rw-r--r--Userland/Libraries/LibMarkdown/Text.cpp298
-rw-r--r--Userland/Libraries/LibMarkdown/Text.h74
-rw-r--r--Userland/Libraries/LibPCIDB/CMakeLists.txt6
-rw-r--r--Userland/Libraries/LibPCIDB/Database.cpp248
-rw-r--r--Userland/Libraries/LibPCIDB/Database.h106
-rw-r--r--Userland/Libraries/LibProtocol/CMakeLists.txt12
-rw-r--r--Userland/Libraries/LibProtocol/Client.cpp120
-rw-r--r--Userland/Libraries/LibProtocol/Client.h64
-rw-r--r--Userland/Libraries/LibProtocol/Download.cpp142
-rw-r--r--Userland/Libraries/LibProtocol/Download.h118
-rw-r--r--Userland/Libraries/LibPthread/CMakeLists.txt8
-rw-r--r--Userland/Libraries/LibPthread/pthread.cpp659
-rw-r--r--Userland/Libraries/LibPthread/pthread.h123
-rw-r--r--Userland/Libraries/LibPthread/pthread_once.cpp107
-rw-r--r--Userland/Libraries/LibRegex/C/Regex.cpp256
-rw-r--r--Userland/Libraries/LibRegex/CMakeLists.txt10
-rw-r--r--Userland/Libraries/LibRegex/Forward.h58
-rw-r--r--Userland/Libraries/LibRegex/Regex.h31
-rw-r--r--Userland/Libraries/LibRegex/RegexByteCode.cpp749
-rw-r--r--Userland/Libraries/LibRegex/RegexByteCode.h837
-rw-r--r--Userland/Libraries/LibRegex/RegexDebug.h154
-rw-r--r--Userland/Libraries/LibRegex/RegexError.h102
-rw-r--r--Userland/Libraries/LibRegex/RegexLexer.cpp235
-rw-r--r--Userland/Libraries/LibRegex/RegexLexer.h110
-rw-r--r--Userland/Libraries/LibRegex/RegexMatch.h291
-rw-r--r--Userland/Libraries/LibRegex/RegexMatcher.cpp396
-rw-r--r--Userland/Libraries/LibRegex/RegexMatcher.h296
-rw-r--r--Userland/Libraries/LibRegex/RegexOptions.h161
-rw-r--r--Userland/Libraries/LibRegex/RegexParser.cpp1493
-rw-r--r--Userland/Libraries/LibRegex/RegexParser.h208
-rw-r--r--Userland/Libraries/LibRegex/Tests/Benchmark.cpp991
-rw-r--r--Userland/Libraries/LibRegex/Tests/CMakeLists.txt20
-rw-r--r--Userland/Libraries/LibRegex/Tests/Regex.cpp600
-rw-r--r--Userland/Libraries/LibRegex/Tests/RegexLibC.cpp1140
-rw-r--r--Userland/Libraries/LibTLS/CMakeLists.txt11
-rw-r--r--Userland/Libraries/LibTLS/Certificate.h97
-rw-r--r--Userland/Libraries/LibTLS/ClientHandshake.cpp667
-rw-r--r--Userland/Libraries/LibTLS/Exchange.cpp280
-rw-r--r--Userland/Libraries/LibTLS/Handshake.cpp177
-rw-r--r--Userland/Libraries/LibTLS/Record.cpp472
-rw-r--r--Userland/Libraries/LibTLS/Socket.cpp260
-rw-r--r--Userland/Libraries/LibTLS/TLSPacketBuilder.h120
-rw-r--r--Userland/Libraries/LibTLS/TLSv12.cpp887
-rw-r--r--Userland/Libraries/LibTLS/TLSv12.h509
-rw-r--r--Userland/Libraries/LibTTF/CMakeLists.txt8
-rw-r--r--Userland/Libraries/LibTTF/Cmap.cpp174
-rw-r--r--Userland/Libraries/LibTTF/Cmap.h130
-rw-r--r--Userland/Libraries/LibTTF/Font.cpp469
-rw-r--r--Userland/Libraries/LibTTF/Font.h140
-rw-r--r--Userland/Libraries/LibTTF/Glyf.cpp519
-rw-r--r--Userland/Libraries/LibTTF/Glyf.h176
-rw-r--r--Userland/Libraries/LibTTF/Tables.h149
-rw-r--r--Userland/Libraries/LibTar/CMakeLists.txt6
-rw-r--r--Userland/Libraries/LibTar/Tar.h107
-rw-r--r--Userland/Libraries/LibTar/TarStream.cpp133
-rw-r--r--Userland/Libraries/LibTar/TarStream.h72
-rw-r--r--Userland/Libraries/LibTextCodec/CMakeLists.txt6
-rw-r--r--Userland/Libraries/LibTextCodec/Decoder.cpp274
-rw-r--r--Userland/Libraries/LibTextCodec/Decoder.h57
-rw-r--r--Userland/Libraries/LibThread/BackgroundAction.cpp74
-rw-r--r--Userland/Libraries/LibThread/BackgroundAction.h97
-rw-r--r--Userland/Libraries/LibThread/CMakeLists.txt7
-rw-r--r--Userland/Libraries/LibThread/Lock.h140
-rw-r--r--Userland/Libraries/LibThread/Thread.cpp68
-rw-r--r--Userland/Libraries/LibThread/Thread.h77
-rw-r--r--Userland/Libraries/LibUnwind/UnwindBase.h253
-rw-r--r--Userland/Libraries/LibVT/CMakeLists.txt8
-rw-r--r--Userland/Libraries/LibVT/Line.cpp127
-rw-r--r--Userland/Libraries/LibVT/Line.h135
-rw-r--r--Userland/Libraries/LibVT/Position.h74
-rw-r--r--Userland/Libraries/LibVT/Range.h87
-rw-r--r--Userland/Libraries/LibVT/Terminal.cpp1235
-rw-r--r--Userland/Libraries/LibVT/Terminal.h244
-rw-r--r--Userland/Libraries/LibVT/TerminalWidget.cpp1138
-rw-r--r--Userland/Libraries/LibVT/TerminalWidget.h226
-rw-r--r--Userland/Libraries/LibVT/XtermColors.h286
-rw-r--r--Userland/Libraries/LibWeb/Bindings/EventListenerWrapper.cpp46
-rw-r--r--Userland/Libraries/LibWeb/Bindings/EventListenerWrapper.h49
-rw-r--r--Userland/Libraries/LibWeb/Bindings/EventTargetWrapperFactory.cpp37
-rw-r--r--Userland/Libraries/LibWeb/Bindings/EventTargetWrapperFactory.h36
-rw-r--r--Userland/Libraries/LibWeb/Bindings/EventWrapperFactory.cpp42
-rw-r--r--Userland/Libraries/LibWeb/Bindings/EventWrapperFactory.h36
-rw-r--r--Userland/Libraries/LibWeb/Bindings/LocationObject.cpp146
-rw-r--r--Userland/Libraries/LibWeb/Bindings/LocationObject.h58
-rw-r--r--Userland/Libraries/LibWeb/Bindings/NavigatorObject.cpp69
-rw-r--r--Userland/Libraries/LibWeb/Bindings/NavigatorObject.h48
-rw-r--r--Userland/Libraries/LibWeb/Bindings/NodeWrapperFactory.cpp347
-rw-r--r--Userland/Libraries/LibWeb/Bindings/NodeWrapperFactory.h38
-rw-r--r--Userland/Libraries/LibWeb/Bindings/RangeConstructor.cpp62
-rw-r--r--Userland/Libraries/LibWeb/Bindings/RangeConstructor.h47
-rw-r--r--Userland/Libraries/LibWeb/Bindings/RangePrototype.cpp161
-rw-r--r--Userland/Libraries/LibWeb/Bindings/RangePrototype.h53
-rw-r--r--Userland/Libraries/LibWeb/Bindings/RangeWrapper.cpp47
-rw-r--r--Userland/Libraries/LibWeb/Bindings/RangeWrapper.h48
-rw-r--r--Userland/Libraries/LibWeb/Bindings/ScriptExecutionContext.cpp35
-rw-r--r--Userland/Libraries/LibWeb/Bindings/ScriptExecutionContext.h43
-rw-r--r--Userland/Libraries/LibWeb/Bindings/WindowObject.cpp361
-rw-r--r--Userland/Libraries/LibWeb/Bindings/WindowObject.h88
-rw-r--r--Userland/Libraries/LibWeb/Bindings/Wrappable.cpp44
-rw-r--r--Userland/Libraries/LibWeb/Bindings/Wrappable.h57
-rw-r--r--Userland/Libraries/LibWeb/Bindings/Wrapper.h49
-rw-r--r--Userland/Libraries/LibWeb/Bindings/XMLHttpRequestConstructor.cpp73
-rw-r--r--Userland/Libraries/LibWeb/Bindings/XMLHttpRequestConstructor.h47
-rw-r--r--Userland/Libraries/LibWeb/Bindings/XMLHttpRequestPrototype.cpp112
-rw-r--r--Userland/Libraries/LibWeb/Bindings/XMLHttpRequestPrototype.h49
-rw-r--r--Userland/Libraries/LibWeb/Bindings/XMLHttpRequestWrapper.cpp62
-rw-r--r--Userland/Libraries/LibWeb/Bindings/XMLHttpRequestWrapper.h47
-rw-r--r--Userland/Libraries/LibWeb/CMakeLists.txt399
-rw-r--r--Userland/Libraries/LibWeb/CSS/.gitignore3
-rw-r--r--Userland/Libraries/LibWeb/CSS/ComputedValues.h161
-rw-r--r--Userland/Libraries/LibWeb/CSS/Default.css196
-rw-r--r--Userland/Libraries/LibWeb/CSS/Identifiers.json122
-rw-r--r--Userland/Libraries/LibWeb/CSS/Length.cpp103
-rw-r--r--Userland/Libraries/LibWeb/CSS/Length.h181
-rw-r--r--Userland/Libraries/LibWeb/CSS/LengthBox.h40
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/CSSParser.cpp902
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/CSSParser.h59
-rw-r--r--Userland/Libraries/LibWeb/CSS/Properties.json368
-rw-r--r--Userland/Libraries/LibWeb/CSS/QuirksMode.css3
-rw-r--r--Userland/Libraries/LibWeb/CSS/Selector.cpp67
-rw-r--r--Userland/Libraries/LibWeb/CSS/Selector.h106
-rw-r--r--Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp172
-rw-r--r--Userland/Libraries/LibWeb/CSS/SelectorEngine.h36
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleDeclaration.cpp40
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleDeclaration.h58
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleInvalidator.cpp74
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleInvalidator.h46
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleProperties.cpp484
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleProperties.h96
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleResolver.cpp598
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleResolver.h65
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleRule.cpp41
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleRule.h57
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleSheet.cpp40
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleSheet.h52
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleSheetList.cpp41
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleSheetList.h52
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleValue.cpp194
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleValue.h357
-rw-r--r--Userland/Libraries/LibWeb/CodeGenerators/CMakeLists.txt11
-rw-r--r--Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_cpp.cpp119
-rw-r--r--Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_h.cpp106
-rw-r--r--Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_ValueID_cpp.cpp115
-rw-r--r--Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_ValueID_h.cpp98
-rw-r--r--Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp951
-rw-r--r--Userland/Libraries/LibWeb/DOM/Attribute.h51
-rw-r--r--Userland/Libraries/LibWeb/DOM/CharacterData.cpp41
-rw-r--r--Userland/Libraries/LibWeb/DOM/CharacterData.h57
-rw-r--r--Userland/Libraries/LibWeb/DOM/CharacterData.idl9
-rw-r--r--Userland/Libraries/LibWeb/DOM/Comment.cpp41
-rw-r--r--Userland/Libraries/LibWeb/DOM/Comment.h44
-rw-r--r--Userland/Libraries/LibWeb/DOM/Comment.idl3
-rw-r--r--Userland/Libraries/LibWeb/DOM/DOMImplementation.cpp75
-rw-r--r--Userland/Libraries/LibWeb/DOM/DOMImplementation.h60
-rw-r--r--Userland/Libraries/LibWeb/DOM/DOMImplementation.idl7
-rw-r--r--Userland/Libraries/LibWeb/DOM/Document.cpp690
-rw-r--r--Userland/Libraries/LibWeb/DOM/Document.h295
-rw-r--r--Userland/Libraries/LibWeb/DOM/Document.idl37
-rw-r--r--Userland/Libraries/LibWeb/DOM/DocumentFragment.cpp40
-rw-r--r--Userland/Libraries/LibWeb/DOM/DocumentFragment.h56
-rw-r--r--Userland/Libraries/LibWeb/DOM/DocumentFragment.idl11
-rw-r--r--Userland/Libraries/LibWeb/DOM/DocumentType.cpp40
-rw-r--r--Userland/Libraries/LibWeb/DOM/DocumentType.h58
-rw-r--r--Userland/Libraries/LibWeb/DOM/DocumentType.idl7
-rw-r--r--Userland/Libraries/LibWeb/DOM/Element.cpp335
-rw-r--r--Userland/Libraries/LibWeb/DOM/Element.h127
-rw-r--r--Userland/Libraries/LibWeb/DOM/Element.idl23
-rw-r--r--Userland/Libraries/LibWeb/DOM/ElementFactory.cpp264
-rw-r--r--Userland/Libraries/LibWeb/DOM/ElementFactory.h35
-rw-r--r--Userland/Libraries/LibWeb/DOM/Event.cpp59
-rw-r--r--Userland/Libraries/LibWeb/DOM/Event.h186
-rw-r--r--Userland/Libraries/LibWeb/DOM/Event.idl23
-rw-r--r--Userland/Libraries/LibWeb/DOM/EventDispatcher.cpp332
-rw-r--r--Userland/Libraries/LibWeb/DOM/EventDispatcher.h43
-rw-r--r--Userland/Libraries/LibWeb/DOM/EventListener.cpp38
-rw-r--r--Userland/Libraries/LibWeb/DOM/EventListener.h72
-rw-r--r--Userland/Libraries/LibWeb/DOM/EventTarget.cpp70
-rw-r--r--Userland/Libraries/LibWeb/DOM/EventTarget.h85
-rw-r--r--Userland/Libraries/LibWeb/DOM/EventTarget.idl6
-rw-r--r--Userland/Libraries/LibWeb/DOM/Node.cpp263
-rw-r--r--Userland/Libraries/LibWeb/DOM/Node.h156
-rw-r--r--Userland/Libraries/LibWeb/DOM/Node.idl16
-rw-r--r--Userland/Libraries/LibWeb/DOM/NonDocumentTypeChildNode.h73
-rw-r--r--Userland/Libraries/LibWeb/DOM/NonElementParentNode.h60
-rw-r--r--Userland/Libraries/LibWeb/DOM/ParentNode.cpp83
-rw-r--r--Userland/Libraries/LibWeb/DOM/ParentNode.h68
-rw-r--r--Userland/Libraries/LibWeb/DOM/Position.cpp49
-rw-r--r--Userland/Libraries/LibWeb/DOM/Position.h78
-rw-r--r--Userland/Libraries/LibWeb/DOM/Range.cpp75
-rw-r--r--Userland/Libraries/LibWeb/DOM/Range.h90
-rw-r--r--Userland/Libraries/LibWeb/DOM/ShadowRoot.cpp49
-rw-r--r--Userland/Libraries/LibWeb/DOM/ShadowRoot.h58
-rw-r--r--Userland/Libraries/LibWeb/DOM/ShadowRoot.idl6
-rw-r--r--Userland/Libraries/LibWeb/DOM/Text.cpp46
-rw-r--r--Userland/Libraries/LibWeb/DOM/Text.h48
-rw-r--r--Userland/Libraries/LibWeb/DOM/Text.idl3
-rw-r--r--Userland/Libraries/LibWeb/DOM/Timer.cpp60
-rw-r--r--Userland/Libraries/LibWeb/DOM/Timer.h63
-rw-r--r--Userland/Libraries/LibWeb/DOM/Window.cpp177
-rw-r--r--Userland/Libraries/LibWeb/DOM/Window.h98
-rw-r--r--Userland/Libraries/LibWeb/DOM/XMLHttpRequest.cpp121
-rw-r--r--Userland/Libraries/LibWeb/DOM/XMLHttpRequest.h85
-rw-r--r--Userland/Libraries/LibWeb/DOMTreeModel.cpp159
-rw-r--r--Userland/Libraries/LibWeb/DOMTreeModel.h60
-rw-r--r--Userland/Libraries/LibWeb/Dump.cpp403
-rw-r--r--Userland/Libraries/LibWeb/Dump.h41
-rw-r--r--Userland/Libraries/LibWeb/DumpLayoutTree/CMakeLists.txt6
-rw-r--r--Userland/Libraries/LibWeb/DumpLayoutTree/main.cpp60
-rw-r--r--Userland/Libraries/LibWeb/FontCache.cpp47
-rw-r--r--Userland/Libraries/LibWeb/FontCache.h61
-rw-r--r--Userland/Libraries/LibWeb/Forward.h297
-rw-r--r--Userland/Libraries/LibWeb/HTML/AttributeNames.cpp63
-rw-r--r--Userland/Libraries/LibWeb/HTML/AttributeNames.h163
-rw-r--r--Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp227
-rw-r--r--Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h103
-rw-r--r--Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl29
-rw-r--r--Userland/Libraries/LibWeb/HTML/EventNames.cpp49
-rw-r--r--Userland/Libraries/LibWeb/HTML/EventNames.h85
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLAnchorElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLAnchorElement.h46
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLAnchorElement.idl16
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLAreaElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLAreaElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLAreaElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLAudioElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLAudioElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLAudioElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLBRElement.cpp46
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLBRElement.h43
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLBRElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLBaseElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLBaseElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLBaseElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLBlinkElement.cpp57
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLBlinkElement.h45
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLBodyElement.cpp81
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLBodyElement.h47
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLBodyElement.idl10
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLButtonElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLButtonElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLButtonElement.idl8
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp105
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.h58
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.idl7
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDListElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDListElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDListElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDataElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDataElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDataElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDataListElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDataListElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDataListElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDialogElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDialogElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDialogElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDirectoryElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDirectoryElement.h42
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDirectoryElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDivElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDivElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLDivElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLElement.cpp141
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLElement.h60
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLElement.idl11
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLEmbedElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLEmbedElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLEmbedElement.idl11
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.h47
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLFontElement.cpp53
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLFontElement.h43
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLFontElement.idl7
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLFormElement.cpp151
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLFormElement.h53
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLFormElement.idl10
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLFrameElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLFrameElement.h42
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLFrameElement.idl9
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLFrameSetElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLFrameSetElement.h42
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLFrameSetElement.idl6
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLHRElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLHRElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLHRElement.idl9
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLHeadElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLHeadElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLHeadElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLHeadingElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLHeadingElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLHeadingElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLHtmlElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLHtmlElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLHtmlElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp108
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.h60
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.idl19
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp100
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLImageElement.h61
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLImageElement.idl14
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp117
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLInputElement.h57
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl27
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLLIElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLLIElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLLIElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLLabelElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLLabelElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLLabelElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLLegendElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLLegendElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLLegendElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp104
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLLinkElement.h69
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLLinkElement.idl17
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMapElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMapElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMapElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMarqueeElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMarqueeElement.h42
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMarqueeElement.idl9
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl10
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMenuElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMenuElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMenuElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMetaElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMetaElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMetaElement.idl9
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMeterElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMeterElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMeterElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLModElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLModElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLModElement.idl6
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLOListElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLOListElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLOListElement.idl8
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLObjectElement.cpp77
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLObjectElement.h55
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLObjectElement.idl19
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLOptGroupElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLOptGroupElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLOptGroupElement.idl6
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLOptionElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLOptionElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLOptionElement.idl6
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLOutputElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLOutputElement.h47
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLOutputElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLParagraphElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLParagraphElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLParagraphElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLParamElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLParamElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLParamElement.idl9
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLPictureElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLPictureElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLPictureElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLPreElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLPreElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLPreElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLProgressElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLProgressElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLProgressElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLQuoteElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLQuoteElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLQuoteElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp193
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLScriptElement.h70
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLScriptElement.idl13
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLSelectElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLSelectElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLSelectElement.idl7
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLSlotElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLSlotElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLSlotElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLSourceElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLSourceElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLSourceElement.idl9
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLSpanElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLSpanElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLSpanElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLStyleElement.cpp67
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLStyleElement.h47
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLStyleElement.idl7
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableCaptionElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableCaptionElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableCaptionElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp65
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableCellElement.h44
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableCellElement.idl18
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableColElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableColElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableColElement.idl9
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableElement.cpp63
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableElement.h44
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableElement.idl14
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableRowElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableRowElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableRowElement.idl10
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableSectionElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableSectionElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTableSectionElement.idl8
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.cpp61
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.h50
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h47
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl12
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTimeElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTimeElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTimeElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTitleElement.cpp49
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTitleElement.h44
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTitleElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTrackElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTrackElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLTrackElement.idl8
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLUListElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLUListElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLUListElement.idl6
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLUnknownElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLUnknownElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLUnknownElement.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLVideoElement.cpp40
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLVideoElement.h41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLVideoElement.idl6
-rw-r--r--Userland/Libraries/LibWeb/HTML/ImageData.cpp85
-rw-r--r--Userland/Libraries/LibWeb/HTML/ImageData.h61
-rw-r--r--Userland/Libraries/LibWeb/HTML/ImageData.idl7
-rw-r--r--Userland/Libraries/LibWeb/HTML/Parser/Entities.cpp2302
-rw-r--r--Userland/Libraries/LibWeb/HTML/Parser/Entities.h43
-rw-r--r--Userland/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp3027
-rw-r--r--Userland/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.h193
-rw-r--r--Userland/Libraries/LibWeb/HTML/Parser/HTMLToken.cpp83
-rw-r--r--Userland/Libraries/LibWeb/HTML/Parser/HTMLToken.h226
-rw-r--r--Userland/Libraries/LibWeb/HTML/Parser/HTMLTokenizer.cpp2663
-rw-r--r--Userland/Libraries/LibWeb/HTML/Parser/HTMLTokenizer.h190
-rw-r--r--Userland/Libraries/LibWeb/HTML/Parser/ListOfActiveFormattingElements.cpp84
-rw-r--r--Userland/Libraries/LibWeb/HTML/Parser/ListOfActiveFormattingElements.h65
-rw-r--r--Userland/Libraries/LibWeb/HTML/Parser/StackOfOpenElements.cpp159
-rw-r--r--Userland/Libraries/LibWeb/HTML/Parser/StackOfOpenElements.h82
-rw-r--r--Userland/Libraries/LibWeb/HTML/SubmitEvent.h54
-rw-r--r--Userland/Libraries/LibWeb/HTML/SubmitEvent.idl5
-rw-r--r--Userland/Libraries/LibWeb/HTML/TagNames.cpp51
-rw-r--r--Userland/Libraries/LibWeb/HTML/TagNames.h178
-rw-r--r--Userland/Libraries/LibWeb/HighResolutionTime/Performance.cpp73
-rw-r--r--Userland/Libraries/LibWeb/HighResolutionTime/Performance.h60
-rw-r--r--Userland/Libraries/LibWeb/HighResolutionTime/Performance.idl4
-rw-r--r--Userland/Libraries/LibWeb/InProcessWebView.cpp435
-rw-r--r--Userland/Libraries/LibWeb/InProcessWebView.h119
-rw-r--r--Userland/Libraries/LibWeb/Layout/BlockBox.cpp133
-rw-r--r--Userland/Libraries/LibWeb/Layout/BlockBox.h79
-rw-r--r--Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp566
-rw-r--r--Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h73
-rw-r--r--Userland/Libraries/LibWeb/Layout/Box.cpp212
-rw-r--r--Userland/Libraries/LibWeb/Layout/Box.h146
-rw-r--r--Userland/Libraries/LibWeb/Layout/BoxModelMetrics.cpp61
-rw-r--r--Userland/Libraries/LibWeb/Layout/BoxModelMetrics.h52
-rw-r--r--Userland/Libraries/LibWeb/Layout/BreakNode.cpp48
-rw-r--r--Userland/Libraries/LibWeb/Layout/BreakNode.h45
-rw-r--r--Userland/Libraries/LibWeb/Layout/ButtonBox.cpp117
-rw-r--r--Userland/Libraries/LibWeb/Layout/ButtonBox.h55
-rw-r--r--Userland/Libraries/LibWeb/Layout/CanvasBox.cpp68
-rw-r--r--Userland/Libraries/LibWeb/Layout/CanvasBox.h45
-rw-r--r--Userland/Libraries/LibWeb/Layout/CheckBox.cpp103
-rw-r--r--Userland/Libraries/LibWeb/Layout/CheckBox.h54
-rw-r--r--Userland/Libraries/LibWeb/Layout/FormattingContext.cpp548
-rw-r--r--Userland/Libraries/LibWeb/Layout/FormattingContext.h78
-rw-r--r--Userland/Libraries/LibWeb/Layout/FrameBox.cpp102
-rw-r--r--Userland/Libraries/LibWeb/Layout/FrameBox.h49
-rw-r--r--Userland/Libraries/LibWeb/Layout/ImageBox.cpp135
-rw-r--r--Userland/Libraries/LibWeb/Layout/ImageBox.h55
-rw-r--r--Userland/Libraries/LibWeb/Layout/InitialContainingBlockBox.cpp133
-rw-r--r--Userland/Libraries/LibWeb/Layout/InitialContainingBlockBox.h60
-rw-r--r--Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp254
-rw-r--r--Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h50
-rw-r--r--Userland/Libraries/LibWeb/Layout/InlineNode.cpp71
-rw-r--r--Userland/Libraries/LibWeb/Layout/InlineNode.h43
-rw-r--r--Userland/Libraries/LibWeb/Layout/LayoutPosition.cpp67
-rw-r--r--Userland/Libraries/LibWeb/Layout/LayoutPosition.h78
-rw-r--r--Userland/Libraries/LibWeb/Layout/LineBox.cpp83
-rw-r--r--Userland/Libraries/LibWeb/Layout/LineBox.h57
-rw-r--r--Userland/Libraries/LibWeb/Layout/LineBoxFragment.cpp174
-rw-r--r--Userland/Libraries/LibWeb/Layout/LineBoxFragment.h92
-rw-r--r--Userland/Libraries/LibWeb/Layout/ListItemBox.cpp62
-rw-r--r--Userland/Libraries/LibWeb/Layout/ListItemBox.h47
-rw-r--r--Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.cpp52
-rw-r--r--Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.h41
-rw-r--r--Userland/Libraries/LibWeb/Layout/Node.cpp339
-rw-r--r--Userland/Libraries/LibWeb/Layout/Node.h278
-rw-r--r--Userland/Libraries/LibWeb/Layout/ReplacedBox.cpp58
-rw-r--r--Userland/Libraries/LibWeb/Layout/ReplacedBox.h74
-rw-r--r--Userland/Libraries/LibWeb/Layout/SVGBox.cpp55
-rw-r--r--Userland/Libraries/LibWeb/Layout/SVGBox.h44
-rw-r--r--Userland/Libraries/LibWeb/Layout/SVGGraphicsBox.cpp52
-rw-r--r--Userland/Libraries/LibWeb/Layout/SVGGraphicsBox.h43
-rw-r--r--Userland/Libraries/LibWeb/Layout/SVGPathBox.cpp89
-rw-r--r--Userland/Libraries/LibWeb/Layout/SVGPathBox.h44
-rw-r--r--Userland/Libraries/LibWeb/Layout/SVGSVGBox.cpp62
-rw-r--r--Userland/Libraries/LibWeb/Layout/SVGSVGBox.h49
-rw-r--r--Userland/Libraries/LibWeb/Layout/TableBox.cpp46
-rw-r--r--Userland/Libraries/LibWeb/Layout/TableBox.h42
-rw-r--r--Userland/Libraries/LibWeb/Layout/TableCellBox.cpp61
-rw-r--r--Userland/Libraries/LibWeb/Layout/TableCellBox.h50
-rw-r--r--Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp133
-rw-r--r--Userland/Libraries/LibWeb/Layout/TableFormattingContext.h46
-rw-r--r--Userland/Libraries/LibWeb/Layout/TableRowBox.cpp46
-rw-r--r--Userland/Libraries/LibWeb/Layout/TableRowBox.h42
-rw-r--r--Userland/Libraries/LibWeb/Layout/TableRowGroupBox.cpp56
-rw-r--r--Userland/Libraries/LibWeb/Layout/TableRowGroupBox.h41
-rw-r--r--Userland/Libraries/LibWeb/Layout/TextNode.cpp311
-rw-r--r--Userland/Libraries/LibWeb/Layout/TextNode.h59
-rw-r--r--Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp302
-rw-r--r--Userland/Libraries/LibWeb/Layout/TreeBuilder.h59
-rw-r--r--Userland/Libraries/LibWeb/Layout/WidgetBox.cpp68
-rw-r--r--Userland/Libraries/LibWeb/Layout/WidgetBox.h49
-rw-r--r--Userland/Libraries/LibWeb/LayoutTreeModel.cpp163
-rw-r--r--Userland/Libraries/LibWeb/LayoutTreeModel.h60
-rw-r--r--Userland/Libraries/LibWeb/Loader/ContentFilter.cpp68
-rw-r--r--Userland/Libraries/LibWeb/Loader/ContentFilter.h51
-rw-r--r--Userland/Libraries/LibWeb/Loader/FrameLoader.cpp296
-rw-r--r--Userland/Libraries/LibWeb/Loader/FrameLoader.h66
-rw-r--r--Userland/Libraries/LibWeb/Loader/ImageLoader.cpp164
-rw-r--r--Userland/Libraries/LibWeb/Loader/ImageLoader.h79
-rw-r--r--Userland/Libraries/LibWeb/Loader/ImageResource.cpp103
-rw-r--r--Userland/Libraries/LibWeb/Loader/ImageResource.h66
-rw-r--r--Userland/Libraries/LibWeb/Loader/LoadRequest.h94
-rw-r--r--Userland/Libraries/LibWeb/Loader/Resource.cpp168
-rw-r--r--Userland/Libraries/LibWeb/Loader/Resource.h117
-rw-r--r--Userland/Libraries/LibWeb/Loader/ResourceLoader.cpp235
-rw-r--r--Userland/Libraries/LibWeb/Loader/ResourceLoader.h69
-rw-r--r--Userland/Libraries/LibWeb/Namespace.cpp49
-rw-r--r--Userland/Libraries/LibWeb/Namespace.h45
-rw-r--r--Userland/Libraries/LibWeb/Origin.h62
-rw-r--r--Userland/Libraries/LibWeb/OutOfProcessWebView.cpp257
-rw-r--r--Userland/Libraries/LibWeb/OutOfProcessWebView.h96
-rw-r--r--Userland/Libraries/LibWeb/Page/EditEventHandler.cpp124
-rw-r--r--Userland/Libraries/LibWeb/Page/EditEventHandler.h49
-rw-r--r--Userland/Libraries/LibWeb/Page/EventHandler.cpp414
-rw-r--r--Userland/Libraries/LibWeb/Page/EventHandler.h72
-rw-r--r--Userland/Libraries/LibWeb/Page/Frame.cpp274
-rw-r--r--Userland/Libraries/LibWeb/Page/Frame.h121
-rw-r--r--Userland/Libraries/LibWeb/Page/Page.cpp95
-rw-r--r--Userland/Libraries/LibWeb/Page/Page.h107
-rw-r--r--Userland/Libraries/LibWeb/Painting/BorderPainting.cpp162
-rw-r--r--Userland/Libraries/LibWeb/Painting/BorderPainting.h42
-rw-r--r--Userland/Libraries/LibWeb/Painting/PaintContext.h73
-rw-r--r--Userland/Libraries/LibWeb/Painting/StackingContext.cpp95
-rw-r--r--Userland/Libraries/LibWeb/Painting/StackingContext.h52
-rw-r--r--Userland/Libraries/LibWeb/QualifiedName.h52
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGContext.h62
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGElement.cpp36
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGElement.h41
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGElement.idl3
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGGeometryElement.cpp36
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGGeometryElement.h41
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGGeometryElement.idl3
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp51
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h54
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.idl3
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGPathElement.cpp659
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGPathElement.h124
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGPathElement.idl3
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGSVGElement.cpp61
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGSVGElement.h49
-rw-r--r--Userland/Libraries/LibWeb/SVG/SVGSVGElement.idl3
-rw-r--r--Userland/Libraries/LibWeb/SVG/TagNames.cpp48
-rw-r--r--Userland/Libraries/LibWeb/SVG/TagNames.h48
-rwxr-xr-xUserland/Libraries/LibWeb/Scripts/GenerateStyleSheetSource.sh10
-rw-r--r--Userland/Libraries/LibWeb/StylePropertiesModel.cpp81
-rw-r--r--Userland/Libraries/LibWeb/StylePropertiesModel.h65
-rw-r--r--Userland/Libraries/LibWeb/Tests/DOM/Comment.js15
-rw-r--r--Userland/Libraries/LibWeb/Tests/DOM/Element.js39
-rw-r--r--Userland/Libraries/LibWeb/Tests/DOM/Node.js28
-rw-r--r--Userland/Libraries/LibWeb/Tests/DOM/Text.js15
-rw-r--r--Userland/Libraries/LibWeb/Tests/DOM/document.createComment.js13
-rw-r--r--Userland/Libraries/LibWeb/Tests/DOM/document.createDocumentFragment.js11
-rw-r--r--Userland/Libraries/LibWeb/Tests/DOM/document.createTextNode.js13
-rw-r--r--Userland/Libraries/LibWeb/Tests/DOM/document.doctype.js18
-rw-r--r--Userland/Libraries/LibWeb/Tests/DOM/document.documentElement.js16
-rw-r--r--Userland/Libraries/LibWeb/Tests/DOM/mixins/NonElementParentNode.js19
-rw-r--r--Userland/Libraries/LibWeb/Tests/DOM/mixins/ParentNode.js25
-rw-r--r--Userland/Libraries/LibWeb/Tests/HTML/HTMLElement.js9
-rw-r--r--Userland/Libraries/LibWeb/Tests/HTML/HTMLTemplateElement.js27
-rw-r--r--Userland/Libraries/LibWeb/Tests/HTML/document.body.js55
-rw-r--r--Userland/Libraries/LibWeb/Tests/HTML/document.head.js16
-rw-r--r--Userland/Libraries/LibWeb/Tests/HTML/document.readyState.js31
-rw-r--r--Userland/Libraries/LibWeb/Tests/Pages/Comment.html8
-rw-r--r--Userland/Libraries/LibWeb/Tests/Pages/ParentNode.html12
-rw-r--r--Userland/Libraries/LibWeb/Tests/Pages/Template.html14
-rw-r--r--Userland/Libraries/LibWeb/Tests/Window/Base64.js19
-rw-r--r--Userland/Libraries/LibWeb/Tests/Window/window.window_frames_self.js8
-rw-r--r--Userland/Libraries/LibWeb/Tests/libweb_tester.d.ts16
-rw-r--r--Userland/Libraries/LibWeb/Tests/test-common.js31
-rw-r--r--Userland/Libraries/LibWeb/TreeNode.h443
-rw-r--r--Userland/Libraries/LibWeb/UIEvents/EventNames.cpp49
-rw-r--r--Userland/Libraries/LibWeb/UIEvents/EventNames.h49
-rw-r--r--Userland/Libraries/LibWeb/UIEvents/MouseEvent.cpp54
-rw-r--r--Userland/Libraries/LibWeb/UIEvents/MouseEvent.h58
-rw-r--r--Userland/Libraries/LibWeb/UIEvents/MouseEvent.idl6
-rw-r--r--Userland/Libraries/LibWeb/UIEvents/UIEvent.h46
-rw-r--r--Userland/Libraries/LibWeb/UIEvents/UIEvent.idl3
-rw-r--r--Userland/Libraries/LibWeb/URLEncoder.cpp46
-rw-r--r--Userland/Libraries/LibWeb/URLEncoder.h41
-rw-r--r--Userland/Libraries/LibWeb/WebContentClient.cpp149
-rw-r--r--Userland/Libraries/LibWeb/WebContentClient.h68
-rw-r--r--Userland/Libraries/LibWeb/WebViewHooks.h51
-rw-r--r--Userland/Libraries/LibX86/CMakeLists.txt6
-rw-r--r--Userland/Libraries/LibX86/Disassembler.h52
-rw-r--r--Userland/Libraries/LibX86/ELFSymbolProvider.h48
-rw-r--r--Userland/Libraries/LibX86/Instruction.cpp1868
-rw-r--r--Userland/Libraries/LibX86/Instruction.h999
-rw-r--r--Userland/Libraries/LibX86/Interpreter.h629
-rw-r--r--Userland/Utilities/test-js.cpp2
-rw-r--r--Userland/Utilities/test-web.cpp2
1843 files changed, 222560 insertions, 8 deletions
diff --git a/Userland/CMakeLists.txt b/Userland/CMakeLists.txt
index c54156e883..8d83e2237e 100644
--- a/Userland/CMakeLists.txt
+++ b/Userland/CMakeLists.txt
@@ -2,6 +2,7 @@ add_subdirectory(Applications)
add_subdirectory(Demos)
add_subdirectory(DynamicLoader)
add_subdirectory(Games)
+add_subdirectory(Libraries)
add_subdirectory(MenuApplets)
add_subdirectory(Shell)
add_subdirectory(Tests)
diff --git a/Userland/DynamicLoader/CMakeLists.txt b/Userland/DynamicLoader/CMakeLists.txt
index f870efe9ca..e96b2b4ef4 100644
--- a/Userland/DynamicLoader/CMakeLists.txt
+++ b/Userland/DynamicLoader/CMakeLists.txt
@@ -5,11 +5,11 @@ set(LOADER_SOURCES
)
file(GLOB AK_SOURCES "../../AK/*.cpp")
-file(GLOB ELF_SOURCES "../../Libraries/LibELF/*.cpp")
-set(ELF_SOURCES ${ELF_SOURCES} ../../Libraries/LibELF/Arch/i386/plt_trampoline.S)
-file(GLOB LIBC_SOURCES1 "../../Libraries/LibC/*.cpp")
-file(GLOB LIBC_SOURCES2 "../../Libraries/LibC/*/*.cpp")
-file(GLOB LIBC_SOURCES3 "../../Libraries/LibC/*.S")
+file(GLOB ELF_SOURCES "../Libraries/LibELF/*.cpp")
+set(ELF_SOURCES ${ELF_SOURCES} ../Libraries/LibELF/Arch/i386/plt_trampoline.S)
+file(GLOB LIBC_SOURCES1 "../Libraries/LibC/*.cpp")
+file(GLOB LIBC_SOURCES2 "../Libraries/LibC/*/*.cpp")
+file(GLOB LIBC_SOURCES3 "../Libraries/LibC/*.S")
list(FILTER LIBC_SOURCES1 EXCLUDE REGEX ".+crt0.cpp")
list(FILTER LIBC_SOURCES1 EXCLUDE REGEX ".+crt0.+.cpp")
@@ -18,7 +18,7 @@ set(SOURCES ${LOADER_SOURCES} ${AK_SOURCES} ${ELF_SOURCES} ${LIBC_SOURCES1} ${LI
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -nostdlib -pie -fpic -DNO_TLS")
-set_source_files_properties (../../Libraries/LibC/ssp.cpp PROPERTIES COMPILE_FLAGS
+set_source_files_properties (../Libraries/LibC/ssp.cpp PROPERTIES COMPILE_FLAGS
"-fno-stack-protector")
add_executable(Loader.so ${SOURCES})
diff --git a/Userland/Libraries/CMakeLists.txt b/Userland/Libraries/CMakeLists.txt
new file mode 100644
index 0000000000..cb123ae1c6
--- /dev/null
+++ b/Userland/Libraries/CMakeLists.txt
@@ -0,0 +1,36 @@
+add_subdirectory(LibAudio)
+add_subdirectory(LibC)
+add_subdirectory(LibChess)
+add_subdirectory(LibCompress)
+add_subdirectory(LibCore)
+add_subdirectory(LibCoreDump)
+add_subdirectory(LibCpp)
+add_subdirectory(LibCrypt)
+add_subdirectory(LibCrypto)
+add_subdirectory(LibDebug)
+add_subdirectory(LibDesktop)
+add_subdirectory(LibDiff)
+add_subdirectory(LibELF)
+add_subdirectory(LibGemini)
+add_subdirectory(LibGfx)
+add_subdirectory(LibGUI)
+add_subdirectory(LibHTTP)
+add_subdirectory(LibImageDecoderClient)
+add_subdirectory(LibIPC)
+add_subdirectory(LibJS)
+add_subdirectory(LibKeyboard)
+add_subdirectory(LibLine)
+add_subdirectory(LibM)
+add_subdirectory(LibMarkdown)
+add_subdirectory(LibPCIDB)
+add_subdirectory(LibProtocol)
+add_subdirectory(LibPthread)
+add_subdirectory(LibRegex)
+add_subdirectory(LibTar)
+add_subdirectory(LibTextCodec)
+add_subdirectory(LibThread)
+add_subdirectory(LibTLS)
+add_subdirectory(LibTTF)
+add_subdirectory(LibVT)
+add_subdirectory(LibWeb)
+add_subdirectory(LibX86)
diff --git a/Userland/Libraries/LibAudio/Buffer.cpp b/Userland/Libraries/LibAudio/Buffer.cpp
new file mode 100644
index 0000000000..c23576482b
--- /dev/null
+++ b/Userland/Libraries/LibAudio/Buffer.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibAudio/Buffer.h>
+
+namespace Audio {
+
+template<typename SampleReader>
+static void read_samples_from_stream(InputMemoryStream& stream, SampleReader read_sample, Vector<Sample>& samples, ResampleHelper& resampler, int num_channels)
+{
+ double norm_l = 0;
+ double norm_r = 0;
+
+ switch (num_channels) {
+ case 1:
+ for (;;) {
+ while (resampler.read_sample(norm_l, norm_r)) {
+ samples.append(Sample(norm_l));
+ }
+ norm_l = read_sample(stream);
+
+ if (stream.handle_any_error()) {
+ break;
+ }
+ resampler.process_sample(norm_l, norm_r);
+ }
+ break;
+ case 2:
+ for (;;) {
+ while (resampler.read_sample(norm_l, norm_r)) {
+ samples.append(Sample(norm_l, norm_r));
+ }
+ norm_l = read_sample(stream);
+ norm_r = read_sample(stream);
+
+ if (stream.handle_any_error()) {
+ break;
+ }
+ resampler.process_sample(norm_l, norm_r);
+ }
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+static double read_norm_sample_24(InputMemoryStream& stream)
+{
+ u8 byte = 0;
+ stream >> byte;
+ u32 sample1 = byte;
+ stream >> byte;
+ u32 sample2 = byte;
+ stream >> byte;
+ u32 sample3 = byte;
+
+ i32 value = 0;
+ value = sample1 << 8;
+ value |= (sample2 << 16);
+ value |= (sample3 << 24);
+ return double(value) / NumericLimits<i32>::max();
+}
+
+static double read_norm_sample_16(InputMemoryStream& stream)
+{
+ LittleEndian<i16> sample;
+ stream >> sample;
+ return double(sample) / NumericLimits<i16>::max();
+}
+
+static double read_norm_sample_8(InputMemoryStream& stream)
+{
+ u8 sample = 0;
+ stream >> sample;
+ return double(sample) / NumericLimits<u8>::max();
+}
+
+RefPtr<Buffer> Buffer::from_pcm_data(ReadonlyBytes data, ResampleHelper& resampler, int num_channels, int bits_per_sample)
+{
+ InputMemoryStream stream { data };
+ return from_pcm_stream(stream, resampler, num_channels, bits_per_sample, data.size() / (bits_per_sample / 8));
+}
+
+RefPtr<Buffer> Buffer::from_pcm_stream(InputMemoryStream& stream, ResampleHelper& resampler, int num_channels, int bits_per_sample, int num_samples)
+{
+ Vector<Sample> fdata;
+ fdata.ensure_capacity(num_samples);
+
+ switch (bits_per_sample) {
+ case 8:
+ read_samples_from_stream(stream, read_norm_sample_8, fdata, resampler, num_channels);
+ break;
+ case 16:
+ read_samples_from_stream(stream, read_norm_sample_16, fdata, resampler, num_channels);
+ break;
+ case 24:
+ read_samples_from_stream(stream, read_norm_sample_24, fdata, resampler, num_channels);
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ // We should handle this in a better way above, but for now --
+ // just make sure we're good. Worst case we just write some 0s where they
+ // don't belong.
+ ASSERT(!stream.handle_any_error());
+
+ return Buffer::create_with_samples(move(fdata));
+}
+
+}
diff --git a/Userland/Libraries/LibAudio/Buffer.h b/Userland/Libraries/LibAudio/Buffer.h
new file mode 100644
index 0000000000..5be0c2443a
--- /dev/null
+++ b/Userland/Libraries/LibAudio/Buffer.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/MemoryStream.h>
+#include <AK/SharedBuffer.h>
+#include <AK/Types.h>
+#include <AK/Vector.h>
+#include <string.h>
+
+namespace Audio {
+
+// A single sample in an audio buffer.
+// Values are floating point, and should range from -1.0 to +1.0
+struct Sample {
+ Sample()
+ : left(0)
+ , right(0)
+ {
+ }
+
+ // For mono
+ Sample(double left)
+ : left(left)
+ , right(left)
+ {
+ }
+
+ // For stereo
+ Sample(double left, double right)
+ : left(left)
+ , right(right)
+ {
+ }
+
+ void clip()
+ {
+ if (left > 1)
+ left = 1;
+ else if (left < -1)
+ left = -1;
+
+ if (right > 1)
+ right = 1;
+ else if (right < -1)
+ right = -1;
+ }
+
+ void scale(int percent)
+ {
+ double pct = (double)percent / 100.0;
+ left *= pct;
+ right *= pct;
+ }
+
+ Sample& operator+=(const Sample& other)
+ {
+ left += other.left;
+ right += other.right;
+ return *this;
+ }
+
+ double left;
+ double right;
+};
+
+// Small helper to resample from one playback rate to another
+// This isn't really "smart", in that we just insert (or drop) samples.
+// Should do better...
+class ResampleHelper {
+public:
+ ResampleHelper(double source, double target);
+
+ void process_sample(double sample_l, double sample_r);
+ bool read_sample(double& next_l, double& next_r);
+
+private:
+ const double m_ratio;
+ double m_current_ratio { 0 };
+ double m_last_sample_l { 0 };
+ double m_last_sample_r { 0 };
+};
+
+// A buffer of audio samples, normalized to 44100hz.
+class Buffer : public RefCounted<Buffer> {
+public:
+ static RefPtr<Buffer> from_pcm_data(ReadonlyBytes data, ResampleHelper& resampler, int num_channels, int bits_per_sample);
+ static RefPtr<Buffer> from_pcm_stream(InputMemoryStream& stream, ResampleHelper& resampler, int num_channels, int bits_per_sample, int num_samples);
+ static NonnullRefPtr<Buffer> create_with_samples(Vector<Sample>&& samples)
+ {
+ return adopt(*new Buffer(move(samples)));
+ }
+ static NonnullRefPtr<Buffer> create_with_shared_buffer(NonnullRefPtr<SharedBuffer>&& buffer, int sample_count)
+ {
+ return adopt(*new Buffer(move(buffer), sample_count));
+ }
+
+ const Sample* samples() const { return (const Sample*)data(); }
+ int sample_count() const { return m_sample_count; }
+ const void* data() const { return m_buffer->data<void>(); }
+ int size_in_bytes() const { return m_sample_count * (int)sizeof(Sample); }
+ int shbuf_id() const { return m_buffer->shbuf_id(); }
+ SharedBuffer& shared_buffer() { return *m_buffer; }
+
+private:
+ explicit Buffer(Vector<Sample>&& samples)
+ : m_buffer(*SharedBuffer::create_with_size(samples.size() * sizeof(Sample)))
+ , m_sample_count(samples.size())
+ {
+ memcpy(m_buffer->data<void>(), samples.data(), samples.size() * sizeof(Sample));
+ }
+
+ explicit Buffer(NonnullRefPtr<SharedBuffer>&& buffer, int sample_count)
+ : m_buffer(move(buffer))
+ , m_sample_count(sample_count)
+ {
+ }
+
+ NonnullRefPtr<SharedBuffer> m_buffer;
+ const int m_sample_count;
+};
+
+}
diff --git a/Userland/Libraries/LibAudio/CMakeLists.txt b/Userland/Libraries/LibAudio/CMakeLists.txt
new file mode 100644
index 0000000000..bde29e3aa7
--- /dev/null
+++ b/Userland/Libraries/LibAudio/CMakeLists.txt
@@ -0,0 +1,15 @@
+set(SOURCES
+ Buffer.cpp
+ ClientConnection.cpp
+ Loader.cpp
+ WavLoader.cpp
+ WavWriter.cpp
+)
+
+set(GENERATED_SOURCES
+ ../../Services/AudioServer/AudioClientEndpoint.h
+ ../../Services/AudioServer/AudioServerEndpoint.h
+)
+
+serenity_lib(LibAudio audio)
+target_link_libraries(LibAudio LibCore LibIPC)
diff --git a/Userland/Libraries/LibAudio/ClientConnection.cpp b/Userland/Libraries/LibAudio/ClientConnection.cpp
new file mode 100644
index 0000000000..c446cf291f
--- /dev/null
+++ b/Userland/Libraries/LibAudio/ClientConnection.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/SharedBuffer.h>
+#include <LibAudio/Buffer.h>
+#include <LibAudio/ClientConnection.h>
+
+namespace Audio {
+
+ClientConnection::ClientConnection()
+ : IPC::ServerConnection<AudioClientEndpoint, AudioServerEndpoint>(*this, "/tmp/portal/audio")
+{
+}
+
+void ClientConnection::handshake()
+{
+ auto response = send_sync<Messages::AudioServer::Greet>();
+ set_my_client_id(response->client_id());
+}
+
+void ClientConnection::enqueue(const Buffer& buffer)
+{
+ for (;;) {
+ const_cast<Buffer&>(buffer).shared_buffer().share_with(server_pid());
+ auto response = send_sync<Messages::AudioServer::EnqueueBuffer>(buffer.shbuf_id(), buffer.sample_count());
+ if (response->success())
+ break;
+ sleep(1);
+ }
+}
+
+bool ClientConnection::try_enqueue(const Buffer& buffer)
+{
+ const_cast<Buffer&>(buffer).shared_buffer().share_with(server_pid());
+ auto response = send_sync<Messages::AudioServer::EnqueueBuffer>(buffer.shbuf_id(), buffer.sample_count());
+ return response->success();
+}
+
+bool ClientConnection::get_muted()
+{
+ return send_sync<Messages::AudioServer::GetMuted>()->muted();
+}
+
+void ClientConnection::set_muted(bool muted)
+{
+ send_sync<Messages::AudioServer::SetMuted>(muted);
+}
+
+int ClientConnection::get_main_mix_volume()
+{
+ return send_sync<Messages::AudioServer::GetMainMixVolume>()->volume();
+}
+
+void ClientConnection::set_main_mix_volume(int volume)
+{
+ send_sync<Messages::AudioServer::SetMainMixVolume>(volume);
+}
+
+int ClientConnection::get_remaining_samples()
+{
+ return send_sync<Messages::AudioServer::GetRemainingSamples>()->remaining_samples();
+}
+
+int ClientConnection::get_played_samples()
+{
+ return send_sync<Messages::AudioServer::GetPlayedSamples>()->played_samples();
+}
+
+void ClientConnection::set_paused(bool paused)
+{
+ send_sync<Messages::AudioServer::SetPaused>(paused);
+}
+
+void ClientConnection::clear_buffer(bool paused)
+{
+ send_sync<Messages::AudioServer::ClearBuffer>(paused);
+}
+
+int ClientConnection::get_playing_buffer()
+{
+ return send_sync<Messages::AudioServer::GetPlayingBuffer>()->buffer_id();
+}
+
+void ClientConnection::handle(const Messages::AudioClient::FinishedPlayingBuffer& message)
+{
+ if (on_finish_playing_buffer)
+ on_finish_playing_buffer(message.buffer_id());
+}
+
+void ClientConnection::handle(const Messages::AudioClient::MutedStateChanged& message)
+{
+ if (on_muted_state_change)
+ on_muted_state_change(message.muted());
+}
+
+void ClientConnection::handle(const Messages::AudioClient::MainMixVolumeChanged& message)
+{
+ if (on_main_mix_volume_change)
+ on_main_mix_volume_change(message.volume());
+}
+
+}
diff --git a/Userland/Libraries/LibAudio/ClientConnection.h b/Userland/Libraries/LibAudio/ClientConnection.h
new file mode 100644
index 0000000000..10d76f03ee
--- /dev/null
+++ b/Userland/Libraries/LibAudio/ClientConnection.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AudioServer/AudioClientEndpoint.h>
+#include <AudioServer/AudioServerEndpoint.h>
+#include <LibIPC/ServerConnection.h>
+
+namespace Audio {
+
+class Buffer;
+
+class ClientConnection : public IPC::ServerConnection<AudioClientEndpoint, AudioServerEndpoint>
+ , public AudioClientEndpoint {
+ C_OBJECT(ClientConnection)
+public:
+ ClientConnection();
+
+ virtual void handshake() override;
+ void enqueue(const Buffer&);
+ bool try_enqueue(const Buffer&);
+
+ bool get_muted();
+ void set_muted(bool);
+
+ int get_main_mix_volume();
+ void set_main_mix_volume(int);
+
+ int get_remaining_samples();
+ int get_played_samples();
+ int get_playing_buffer();
+
+ void set_paused(bool paused);
+ void clear_buffer(bool paused = false);
+
+ Function<void(i32 buffer_id)> on_finish_playing_buffer;
+ Function<void(bool muted)> on_muted_state_change;
+ Function<void(int volume)> on_main_mix_volume_change;
+
+private:
+ virtual void handle(const Messages::AudioClient::FinishedPlayingBuffer&) override;
+ virtual void handle(const Messages::AudioClient::MutedStateChanged&) override;
+ virtual void handle(const Messages::AudioClient::MainMixVolumeChanged&) override;
+};
+
+}
diff --git a/Userland/Libraries/LibAudio/Loader.cpp b/Userland/Libraries/LibAudio/Loader.cpp
new file mode 100644
index 0000000000..f1444318b5
--- /dev/null
+++ b/Userland/Libraries/LibAudio/Loader.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibAudio/WavLoader.h>
+
+namespace Audio {
+
+Loader::Loader(const StringView& path)
+{
+ m_plugin = make<WavLoaderPlugin>(path);
+ if (m_plugin->sniff())
+ return;
+ m_plugin = nullptr;
+}
+
+Loader::Loader(const ByteBuffer& buffer)
+{
+ m_plugin = make<WavLoaderPlugin>(buffer);
+ if (m_plugin->sniff())
+ return;
+ m_plugin = nullptr;
+}
+
+}
diff --git a/Userland/Libraries/LibAudio/Loader.h b/Userland/Libraries/LibAudio/Loader.h
new file mode 100644
index 0000000000..bb2e2f342f
--- /dev/null
+++ b/Userland/Libraries/LibAudio/Loader.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2018-2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <AK/StringView.h>
+#include <LibCore/File.h>
+
+namespace Audio {
+
+class LoaderPlugin {
+public:
+ virtual ~LoaderPlugin() { }
+
+ virtual bool sniff() = 0;
+
+ virtual bool has_error() { return false; }
+ virtual const char* error_string() { return ""; }
+
+ virtual RefPtr<Buffer> get_more_samples(size_t max_bytes_to_read_from_input = 128 * KiB) = 0;
+
+ virtual void reset() = 0;
+ virtual void seek(const int position) = 0;
+
+ virtual int loaded_samples() = 0;
+ virtual int total_samples() = 0;
+ virtual u32 sample_rate() = 0;
+ virtual u16 num_channels() = 0;
+ virtual u16 bits_per_sample() = 0;
+ virtual RefPtr<Core::File> file() = 0;
+};
+
+class Loader : public RefCounted<Loader> {
+public:
+ static NonnullRefPtr<Loader> create(const StringView& path) { return adopt(*new Loader(path)); }
+ static NonnullRefPtr<Loader> create(const ByteBuffer& buffer) { return adopt(*new Loader(buffer)); }
+
+ bool has_error() const { return m_plugin ? m_plugin->has_error() : true; }
+ const char* error_string() const { return m_plugin ? m_plugin->error_string() : "No loader plugin available"; }
+
+ RefPtr<Buffer> get_more_samples(size_t max_bytes_to_read_from_input = 128 * KiB) const { return m_plugin ? m_plugin->get_more_samples(max_bytes_to_read_from_input) : nullptr; }
+
+ void reset() const
+ {
+ if (m_plugin)
+ m_plugin->reset();
+ }
+ void seek(const int position) const
+ {
+ if (m_plugin)
+ m_plugin->seek(position);
+ }
+
+ int loaded_samples() const { return m_plugin ? m_plugin->loaded_samples() : 0; }
+ int total_samples() const { return m_plugin ? m_plugin->total_samples() : 0; }
+ u32 sample_rate() const { return m_plugin ? m_plugin->sample_rate() : 0; }
+ u16 num_channels() const { return m_plugin ? m_plugin->num_channels() : 0; }
+ u16 bits_per_sample() const { return m_plugin ? m_plugin->bits_per_sample() : 0; }
+ RefPtr<Core::File> file() const { return m_plugin ? m_plugin->file() : nullptr; }
+
+private:
+ Loader(const StringView& path);
+ Loader(const ByteBuffer& buffer);
+
+ mutable OwnPtr<LoaderPlugin> m_plugin;
+};
+
+}
diff --git a/Userland/Libraries/LibAudio/WavLoader.cpp b/Userland/Libraries/LibAudio/WavLoader.cpp
new file mode 100644
index 0000000000..2cd4849eb0
--- /dev/null
+++ b/Userland/Libraries/LibAudio/WavLoader.cpp
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/NumericLimits.h>
+#include <AK/OwnPtr.h>
+#include <LibAudio/Buffer.h>
+#include <LibAudio/WavLoader.h>
+#include <LibCore/File.h>
+#include <LibCore/IODeviceStreamReader.h>
+
+namespace Audio {
+
+WavLoaderPlugin::WavLoaderPlugin(const StringView& path)
+ : m_file(Core::File::construct(path))
+{
+ if (!m_file->open(Core::IODevice::ReadOnly)) {
+ m_error_string = String::formatted("Can't open file: {}", m_file->error_string());
+ return;
+ }
+
+ valid = parse_header();
+ if (!valid)
+ return;
+
+ m_resampler = make<ResampleHelper>(m_sample_rate, 44100);
+}
+
+WavLoaderPlugin::WavLoaderPlugin(const ByteBuffer& buffer)
+{
+ m_stream = make<InputMemoryStream>(buffer);
+ if (!m_stream) {
+ m_error_string = String::formatted("Can't open memory stream");
+ return;
+ }
+
+ valid = parse_header();
+ if (!valid)
+ return;
+
+ m_resampler = make<ResampleHelper>(m_sample_rate, 44100);
+}
+
+bool WavLoaderPlugin::sniff()
+{
+ return valid;
+}
+
+RefPtr<Buffer> WavLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_input)
+{
+#ifdef AWAVLOADER_DEBUG
+ dbgln("Read WAV of format PCM with num_channels {} sample rate {}, bits per sample {}", m_num_channels, m_sample_rate, m_bits_per_sample);
+#endif
+ size_t samples_to_read = static_cast<int>(max_bytes_to_read_from_input) / (m_num_channels * (m_bits_per_sample / 8));
+ RefPtr<Buffer> buffer;
+ if (m_file) {
+ auto raw_samples = m_file->read(max_bytes_to_read_from_input);
+ if (raw_samples.is_empty())
+ return nullptr;
+ buffer = Buffer::from_pcm_data(raw_samples, *m_resampler, m_num_channels, m_bits_per_sample);
+ } else {
+ buffer = Buffer::from_pcm_stream(*m_stream, *m_resampler, m_num_channels, m_bits_per_sample, samples_to_read);
+ }
+ //Buffer contains normalized samples, but m_loaded_samples should contain the amount of actually loaded samples
+ m_loaded_samples += samples_to_read;
+ m_loaded_samples = min(m_total_samples, m_loaded_samples);
+ return buffer;
+}
+
+void WavLoaderPlugin::seek(const int position)
+{
+ if (position < 0 || position > m_total_samples)
+ return;
+
+ m_loaded_samples = position;
+ size_t byte_position = position * m_num_channels * (m_bits_per_sample / 8);
+
+ if (m_file)
+ m_file->seek(byte_position);
+ else
+ m_stream->seek(byte_position);
+}
+
+void WavLoaderPlugin::reset()
+{
+ seek(0);
+}
+
+bool WavLoaderPlugin::parse_header()
+{
+ OwnPtr<Core::IODeviceStreamReader> file_stream;
+ bool ok = true;
+
+ if (m_file)
+ file_stream = make<Core::IODeviceStreamReader>(*m_file);
+
+ auto read_u8 = [&]() -> u8 {
+ u8 value;
+ if (m_file) {
+ *file_stream >> value;
+ if (file_stream->handle_read_failure())
+ ok = false;
+ } else {
+ *m_stream >> value;
+ if (m_stream->has_any_error())
+ ok = false;
+ }
+ return value;
+ };
+
+ auto read_u16 = [&]() -> u16 {
+ u16 value;
+ if (m_file) {
+ *file_stream >> value;
+ if (file_stream->handle_read_failure())
+ ok = false;
+ } else {
+ *m_stream >> value;
+ if (m_stream->has_any_error())
+ ok = false;
+ }
+ return value;
+ };
+
+ auto read_u32 = [&]() -> u32 {
+ u32 value;
+ if (m_file) {
+ *file_stream >> value;
+ if (file_stream->handle_read_failure())
+ ok = false;
+ } else {
+ *m_stream >> value;
+ if (m_stream->has_any_error())
+ ok = false;
+ }
+ return value;
+ };
+
+#define CHECK_OK(msg) \
+ do { \
+ if (!ok) { \
+ m_error_string = String::formatted("Parsing failed: {}", msg); \
+ return {}; \
+ } \
+ } while (0);
+
+ u32 riff = read_u32();
+ ok = ok && riff == 0x46464952; // "RIFF"
+ CHECK_OK("RIFF header");
+
+ u32 sz = read_u32();
+ ok = ok && sz < 1024 * 1024 * 1024; // arbitrary
+ CHECK_OK("File size");
+ ASSERT(sz < 1024 * 1024 * 1024);
+
+ u32 wave = read_u32();
+ ok = ok && wave == 0x45564157; // "WAVE"
+ CHECK_OK("WAVE header");
+
+ u32 fmt_id = read_u32();
+ ok = ok && fmt_id == 0x20746D66; // "FMT"
+ CHECK_OK("FMT header");
+
+ u32 fmt_size = read_u32();
+ ok = ok && fmt_size == 16;
+ CHECK_OK("FMT size");
+ ASSERT(fmt_size == 16);
+
+ u16 audio_format = read_u16();
+ CHECK_OK("Audio format"); // incomplete read check
+ ok = ok && audio_format == 1; // WAVE_FORMAT_PCM
+ ASSERT(audio_format == 1);
+ CHECK_OK("Audio format"); // value check
+
+ m_num_channels = read_u16();
+ ok = ok && (m_num_channels == 1 || m_num_channels == 2);
+ CHECK_OK("Channel count");
+
+ m_sample_rate = read_u32();
+ CHECK_OK("Sample rate");
+
+ read_u32();
+ CHECK_OK("Byte rate");
+
+ read_u16();
+ CHECK_OK("Block align");
+
+ m_bits_per_sample = read_u16();
+ CHECK_OK("Bits per sample"); // incomplete read check
+ ok = ok && (m_bits_per_sample == 8 || m_bits_per_sample == 16 || m_bits_per_sample == 24);
+ ASSERT(m_bits_per_sample == 8 || m_bits_per_sample == 16 || m_bits_per_sample == 24);
+ CHECK_OK("Bits per sample"); // value check
+
+ // Read chunks until we find DATA
+ bool found_data = false;
+ u32 data_sz = 0;
+ u8 search_byte = 0;
+ while (true) {
+ search_byte = read_u8();
+ CHECK_OK("Reading byte searching for data");
+ if (search_byte != 0x64) //D
+ continue;
+
+ search_byte = read_u8();
+ CHECK_OK("Reading next byte searching for data");
+ if (search_byte != 0x61) //A
+ continue;
+
+ u16 search_remaining = read_u16();
+ CHECK_OK("Reading remaining bytes searching for data");
+ if (search_remaining != 0x6174) //TA
+ continue;
+
+ data_sz = read_u32();
+ found_data = true;
+ break;
+ }
+
+ ok = ok && found_data;
+ CHECK_OK("Found no data chunk");
+ ASSERT(found_data);
+
+ ok = ok && data_sz < INT32_MAX;
+ CHECK_OK("Data was too large");
+
+ int bytes_per_sample = (m_bits_per_sample / 8) * m_num_channels;
+ m_total_samples = data_sz / bytes_per_sample;
+
+ return true;
+}
+
+ResampleHelper::ResampleHelper(double source, double target)
+ : m_ratio(source / target)
+{
+}
+
+void ResampleHelper::process_sample(double sample_l, double sample_r)
+{
+ m_last_sample_l = sample_l;
+ m_last_sample_r = sample_r;
+ m_current_ratio += 1;
+}
+
+bool ResampleHelper::read_sample(double& next_l, double& next_r)
+{
+ if (m_current_ratio > 0) {
+ m_current_ratio -= m_ratio;
+ next_l = m_last_sample_l;
+ next_r = m_last_sample_r;
+ return true;
+ }
+
+ return false;
+}
+
+}
diff --git a/Userland/Libraries/LibAudio/WavLoader.h b/Userland/Libraries/LibAudio/WavLoader.h
new file mode 100644
index 0000000000..543d33dca2
--- /dev/null
+++ b/Userland/Libraries/LibAudio/WavLoader.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/MemoryStream.h>
+#include <AK/OwnPtr.h>
+#include <AK/RefPtr.h>
+#include <AK/String.h>
+#include <AK/StringView.h>
+#include <LibAudio/Buffer.h>
+#include <LibAudio/Loader.h>
+#include <LibCore/File.h>
+
+namespace Audio {
+class Buffer;
+
+// Parses a WAV file and produces an Audio::Buffer.
+class WavLoaderPlugin : public LoaderPlugin {
+public:
+ WavLoaderPlugin(const StringView& path);
+ WavLoaderPlugin(const ByteBuffer& buffer);
+
+ virtual bool sniff() override;
+
+ virtual bool has_error() override { return !m_error_string.is_null(); }
+ virtual const char* error_string() override { return m_error_string.characters(); }
+
+ virtual RefPtr<Buffer> get_more_samples(size_t max_bytes_to_read_from_input = 128 * KiB) override;
+
+ virtual void reset() override;
+ virtual void seek(const int position) override;
+
+ virtual int loaded_samples() override { return m_loaded_samples; }
+ virtual int total_samples() override { return m_total_samples; }
+ virtual u32 sample_rate() override { return m_sample_rate; }
+ virtual u16 num_channels() override { return m_num_channels; }
+ virtual u16 bits_per_sample() override { return m_bits_per_sample; }
+ virtual RefPtr<Core::File> file() override { return m_file; }
+
+private:
+ bool parse_header();
+
+ bool valid { false };
+ RefPtr<Core::File> m_file;
+ OwnPtr<InputMemoryStream> m_stream;
+ String m_error_string;
+ OwnPtr<ResampleHelper> m_resampler;
+
+ u32 m_sample_rate { 0 };
+ u16 m_num_channels { 0 };
+ u16 m_bits_per_sample { 0 };
+
+ int m_loaded_samples { 0 };
+ int m_total_samples { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibAudio/WavWriter.cpp b/Userland/Libraries/LibAudio/WavWriter.cpp
new file mode 100644
index 0000000000..e60c5ccfd9
--- /dev/null
+++ b/Userland/Libraries/LibAudio/WavWriter.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2020, William McPherson <willmcpherson2@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibAudio/WavWriter.h>
+
+namespace Audio {
+
+WavWriter::WavWriter(const StringView& path, int sample_rate, int num_channels, int bits_per_sample)
+ : m_sample_rate(sample_rate)
+ , m_num_channels(num_channels)
+ , m_bits_per_sample(bits_per_sample)
+{
+ set_file(path);
+}
+
+WavWriter::WavWriter(int sample_rate, int num_channels, int bits_per_sample)
+ : m_sample_rate(sample_rate)
+ , m_num_channels(num_channels)
+ , m_bits_per_sample(bits_per_sample)
+{
+}
+
+WavWriter::~WavWriter()
+{
+ if (!m_finalized)
+ finalize();
+}
+
+void WavWriter::set_file(const StringView& path)
+{
+ m_file = Core::File::construct(path);
+ if (!m_file->open(Core::IODevice::ReadWrite)) {
+ m_error_string = String::formatted("Can't open file: {}", m_file->error_string());
+ return;
+ }
+ m_file->seek(44);
+ m_finalized = false;
+}
+
+void WavWriter::write_samples(const u8* samples, size_t size)
+{
+ m_data_sz += size;
+ m_file->write(samples, size);
+}
+
+void WavWriter::finalize()
+{
+ ASSERT(!m_finalized);
+ m_finalized = true;
+ if (m_file) {
+ m_file->seek(0);
+ write_header();
+ m_file->close();
+ }
+ m_data_sz = 0;
+}
+
+void WavWriter::write_header()
+{
+ // "RIFF"
+ static u32 riff = 0x46464952;
+ m_file->write(reinterpret_cast<u8*>(&riff), sizeof(riff));
+
+ // Size of data + (size of header - previous field - this field)
+ u32 sz = m_data_sz + (44 - 4 - 4);
+ m_file->write(reinterpret_cast<u8*>(&sz), sizeof(sz));
+
+ // "WAVE"
+ static u32 wave = 0x45564157;
+ m_file->write(reinterpret_cast<u8*>(&wave), sizeof(wave));
+
+ // "fmt "
+ static u32 fmt_id = 0x20746D66;
+ m_file->write(reinterpret_cast<u8*>(&fmt_id), sizeof(fmt_id));
+
+ // Size of the next 6 fields
+ static u32 fmt_size = 16;
+ m_file->write(reinterpret_cast<u8*>(&fmt_size), sizeof(fmt_size));
+
+ // 1 for PCM
+ static u16 audio_format = 1;
+ m_file->write(reinterpret_cast<u8*>(&audio_format), sizeof(audio_format));
+
+ m_file->write(reinterpret_cast<u8*>(&m_num_channels), sizeof(m_num_channels));
+
+ m_file->write(reinterpret_cast<u8*>(&m_sample_rate), sizeof(m_sample_rate));
+
+ u32 byte_rate = m_sample_rate * m_num_channels * (m_bits_per_sample / 8);
+ m_file->write(reinterpret_cast<u8*>(&byte_rate), sizeof(byte_rate));
+
+ u16 block_align = m_num_channels * (m_bits_per_sample / 8);
+ m_file->write(reinterpret_cast<u8*>(&block_align), sizeof(block_align));
+
+ m_file->write(reinterpret_cast<u8*>(&m_bits_per_sample), sizeof(m_bits_per_sample));
+
+ // "data"
+ static u32 chunk_id = 0x61746164;
+ m_file->write(reinterpret_cast<u8*>(&chunk_id), sizeof(chunk_id));
+
+ m_file->write(reinterpret_cast<u8*>(&m_data_sz), sizeof(m_data_sz));
+}
+
+}
diff --git a/Userland/Libraries/LibAudio/WavWriter.h b/Userland/Libraries/LibAudio/WavWriter.h
new file mode 100644
index 0000000000..d65d5709d2
--- /dev/null
+++ b/Userland/Libraries/LibAudio/WavWriter.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2020, William McPherson <willmcpherson2@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/StringView.h>
+#include <LibCore/File.h>
+
+namespace Audio {
+
+class WavWriter {
+public:
+ WavWriter(const StringView& path, int sample_rate = 44100, int num_channels = 2, int bits_per_sample = 16);
+ WavWriter(int sample_rate = 44100, int num_channels = 2, int bits_per_sample = 16);
+ ~WavWriter();
+
+ bool has_error() const { return !m_error_string.is_null(); }
+ const char* error_string() const { return m_error_string.characters(); }
+
+ void write_samples(const u8* samples, size_t size);
+ void finalize(); // You can finalize manually or let the destructor do it.
+
+ u32 sample_rate() const { return m_sample_rate; }
+ u16 num_channels() const { return m_num_channels; }
+ u16 bits_per_sample() const { return m_bits_per_sample; }
+ RefPtr<Core::File> file() const { return m_file; }
+
+ void set_file(const StringView& path);
+ void set_num_channels(int num_channels) { m_num_channels = num_channels; }
+ void set_sample_rate(int sample_rate) { m_sample_rate = sample_rate; }
+ void set_bits_per_sample(int bits_per_sample) { m_bits_per_sample = bits_per_sample; }
+
+ void clear_error() { m_error_string = String(); }
+
+private:
+ void write_header();
+ RefPtr<Core::File> m_file;
+ String m_error_string;
+ bool m_finalized { false };
+
+ u32 m_sample_rate;
+ u16 m_num_channels;
+ u16 m_bits_per_sample;
+ u32 m_data_sz { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibC/CMakeLists.txt b/Userland/Libraries/LibC/CMakeLists.txt
new file mode 100644
index 0000000000..393addb934
--- /dev/null
+++ b/Userland/Libraries/LibC/CMakeLists.txt
@@ -0,0 +1,90 @@
+set(LIBC_SOURCES
+ arpa/inet.cpp
+ assert.cpp
+ ctype.cpp
+ cxxabi.cpp
+ dirent.cpp
+ dlfcn.cpp
+ fcntl.cpp
+ getopt.cpp
+ grp.cpp
+ ioctl.cpp
+ libcinit.cpp
+ libgen.cpp
+ locale.cpp
+ malloc.cpp
+ mman.cpp
+ mntent.cpp
+ netdb.cpp
+ poll.cpp
+ pwd.cpp
+ qsort.cpp
+ scanf.cpp
+ sched.cpp
+ serenity.cpp
+ setjmp.S
+ signal.cpp
+ spawn.cpp
+ stat.cpp
+ stdio.cpp
+ stdlib.cpp
+ string.cpp
+ strings.cpp
+ syslog.cpp
+ sys/prctl.cpp
+ sys/ptrace.cpp
+ sys/select.cpp
+ sys/socket.cpp
+ sys/uio.cpp
+ sys/wait.cpp
+ termcap.cpp
+ termios.cpp
+ time.cpp
+ times.cpp
+ ulimit.cpp
+ unistd.cpp
+ utime.cpp
+ utsname.cpp
+ wchar.cpp
+)
+
+file(GLOB AK_SOURCES CONFIGURE_DEPENDS "../../../AK/*.cpp")
+file(GLOB ELF_SOURCES CONFIGURE_DEPENDS "../LibELF/*.cpp")
+set(ELF_SOURCES ${ELF_SOURCES} ../LibELF/Arch/i386/plt_trampoline.S)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-warning-option -DSERENITY_LIBC_BUILD")
+
+find_program(INSTALL_COMMAND ginstall)
+if(NOT INSTALL_COMMAND)
+ set(INSTALL_COMMAND install)
+endif()
+
+add_library(crt0 STATIC crt0.cpp)
+add_custom_command(
+ TARGET crt0
+ COMMAND ${INSTALL_COMMAND} -D $<TARGET_OBJECTS:crt0> ${CMAKE_INSTALL_PREFIX}/usr/lib/crt0.o
+)
+add_library(crt0_shared STATIC crt0_shared.cpp)
+add_custom_command(
+ TARGET crt0_shared
+ COMMAND ${INSTALL_COMMAND} -D $<TARGET_OBJECTS:crt0_shared> ${CMAKE_INSTALL_PREFIX}/usr/lib/crt0_shared.o
+)
+
+set_source_files_properties (ssp.cpp PROPERTIES COMPILE_FLAGS
+ "-fno-stack-protector")
+add_library(ssp STATIC ssp.cpp)
+add_custom_command(
+ TARGET ssp
+ COMMAND ${INSTALL_COMMAND} -D $<TARGET_OBJECTS:ssp> ${CMAKE_INSTALL_PREFIX}/usr/lib/ssp.o
+)
+
+set(SOURCES ${LIBC_SOURCES} ${AK_SOURCES} ${ELF_SOURCES})
+
+serenity_libc_static(LibCStatic c)
+target_link_libraries(LibCStatic crt0 ssp)
+add_dependencies(LibCStatic LibM)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libstdc++")
+serenity_libc(LibC c)
+target_link_libraries(LibC crt0 ssp)
+add_dependencies(LibC LibM)
diff --git a/Userland/Libraries/LibC/alloca.h b/Userland/Libraries/LibC/alloca.h
new file mode 100644
index 0000000000..c31dccc400
--- /dev/null
+++ b/Userland/Libraries/LibC/alloca.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#define alloca __builtin_alloca
diff --git a/Userland/Libraries/LibC/arpa/inet.cpp b/Userland/Libraries/LibC/arpa/inet.cpp
new file mode 100644
index 0000000000..3cf7cc48a4
--- /dev/null
+++ b/Userland/Libraries/LibC/arpa/inet.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <stdio.h>
+
+extern "C" {
+
+const char* inet_ntop(int af, const void* src, char* dst, socklen_t len)
+{
+ if (af != AF_INET) {
+ errno = EAFNOSUPPORT;
+ return nullptr;
+ }
+ auto* bytes = (const unsigned char*)src;
+ snprintf(dst, len, "%u.%u.%u.%u", bytes[0], bytes[1], bytes[2], bytes[3]);
+ return (const char*)dst;
+}
+
+int inet_pton(int af, const char* src, void* dst)
+{
+ if (af != AF_INET) {
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+ unsigned a;
+ unsigned b;
+ unsigned c;
+ unsigned d;
+ int count = sscanf(src, "%u.%u.%u.%u", &a, &b, &c, &d);
+ if (count != 4) {
+ errno = EINVAL;
+ return 0;
+ }
+ union {
+ struct {
+ uint8_t a;
+ uint8_t b;
+ uint8_t c;
+ uint8_t d;
+ };
+ uint32_t l;
+ } u;
+ u.a = a;
+ u.b = b;
+ u.c = c;
+ u.d = d;
+ *(uint32_t*)dst = u.l;
+ return 1;
+}
+
+in_addr_t inet_addr(const char* str)
+{
+ in_addr_t tmp {};
+ int rc = inet_pton(AF_INET, str, &tmp);
+ if (rc < 0)
+ return INADDR_NONE;
+ return tmp;
+}
+
+char* inet_ntoa(struct in_addr in)
+{
+ static char buffer[32];
+ inet_ntop(AF_INET, &in.s_addr, buffer, sizeof(buffer));
+ return buffer;
+}
+}
diff --git a/Userland/Libraries/LibC/arpa/inet.h b/Userland/Libraries/LibC/arpa/inet.h
new file mode 100644
index 0000000000..28ac7096f9
--- /dev/null
+++ b/Userland/Libraries/LibC/arpa/inet.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <endian.h>
+#include <sys/cdefs.h>
+#include <sys/socket.h>
+
+__BEGIN_DECLS
+
+#define INET_ADDRSTRLEN 16
+
+const char* inet_ntop(int af, const void* src, char* dst, socklen_t);
+int inet_pton(int af, const char* src, void* dst);
+
+static inline int inet_aton(const char* cp, struct in_addr* inp)
+{
+ return inet_pton(AF_INET, cp, inp);
+}
+
+char* inet_ntoa(struct in_addr);
+
+inline uint16_t htons(uint16_t value)
+{
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ return __builtin_bswap16(value);
+#else
+ return value;
+#endif
+}
+
+inline uint16_t ntohs(uint16_t value)
+{
+ return htons(value);
+}
+
+inline uint32_t htonl(uint32_t value)
+{
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ return __builtin_bswap32(value);
+#else
+ return value;
+#endif
+}
+
+inline uint32_t ntohl(uint32_t value)
+{
+ return htonl(value);
+}
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/assert.cpp b/Userland/Libraries/LibC/assert.cpp
new file mode 100644
index 0000000000..7dcd7a5258
--- /dev/null
+++ b/Userland/Libraries/LibC/assert.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/internals.h>
+#include <unistd.h>
+
+extern "C" {
+
+extern bool __stdio_is_initialized;
+#ifdef DEBUG
+void __assertion_failed(const char* msg)
+{
+ dbgprintf("USERSPACE(%d) ASSERTION FAILED: %s\n", getpid(), msg);
+ if (__stdio_is_initialized)
+ fprintf(stderr, "ASSERTION FAILED: %s\n", msg);
+
+ Syscall::SC_set_coredump_metadata_params params {
+ { "assertion", strlen("assertion") },
+ { msg, strlen(msg) },
+ };
+ syscall(SC_set_coredump_metadata, &params);
+ syscall(SC_abort);
+ for (;;) { }
+}
+#endif
+}
diff --git a/Userland/Libraries/LibC/assert.h b/Userland/Libraries/LibC/assert.h
new file mode 100644
index 0000000000..055553514b
--- /dev/null
+++ b/Userland/Libraries/LibC/assert.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+#ifdef DEBUG
+__attribute__((noreturn)) void __assertion_failed(const char* msg);
+# define __stringify_helper(x) # x
+# define __stringify(x) __stringify_helper(x)
+# define assert(expr) \
+ do { \
+ if (__builtin_expect(!(expr), 0)) \
+ __assertion_failed(#expr "\n" __FILE__ ":" __stringify(__LINE__)); \
+ } while (0)
+# define ASSERT_NOT_REACHED() assert(false)
+#else
+# define assert(expr) ((void)(0))
+# define ASSERT_NOT_REACHED() CRASH()
+#endif
+
+#define CRASH() \
+ do { \
+ asm volatile("ud2"); \
+ } while (0)
+#define ASSERT assert
+#define RELEASE_ASSERT assert
+#define TODO ASSERT_NOT_REACHED
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/bits/FILE.h b/Userland/Libraries/LibC/bits/FILE.h
new file mode 100644
index 0000000000..94af1c5ed7
--- /dev/null
+++ b/Userland/Libraries/LibC/bits/FILE.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2020, Jesse Buhagiar <jooster669@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#define BUFSIZ 1024
+
+__BEGIN_DECLS
+
+typedef struct FILE FILE;
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/bits/posix1_lim.h b/Userland/Libraries/LibC/bits/posix1_lim.h
new file mode 100644
index 0000000000..9ec60a51e9
--- /dev/null
+++ b/Userland/Libraries/LibC/bits/posix1_lim.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#define _POSIX_AIO_LISTIO_MAX 2 /* The number of I/O operations that can be specified in a list I/O call. */
+#define _POSIX_AIO_MAX 1 /* The number of outstanding asynchronous I/O operations. */
+#define _POSIX_ARG_MAX 4096 /* Maximum length of argument to the exec functions including environment data. */
+#define _POSIX_CHILD_MAX 25 /* Maximum number of simultaneous processes per real user ID. */
+#define _POSIX_DELAYTIMER_MAX 32 /* The number of timer expiration overruns. */
+#define _POSIX_HOST_NAME_MAX 255 /* Maximum length of a host name (not including the terminating null) as returned from the gethostname() function. */
+#define _POSIX_LINK_MAX 8 /* Maximum number of links to a single file. */
+#define _POSIX_LOGIN_NAME_MAX 9 /* The size of the storage required for a login name, in bytes, including the terminating null. */
+#define _POSIX_MAX_CANON 255 /* Maximum number of bytes in a terminal canonical input queue. */
+#define _POSIX_MAX_INPUT 255 /* Maximum number of bytes allowed in a terminal input queue. */
+#define _POSIX_MQ_OPEN_MAX 8 /* The number of message queues that can be open for a single process.) */
+#define _POSIX_MQ_PRIO_MAX 32 /* The maximum number of message priorities supported by the implementation. */
+#define _POSIX_NAME_MAX 14 /* Maximum number of bytes in a filename (not including terminating null). */
+#define _POSIX_NGROUPS_MAX 8 /* Maximum number of simultaneous supplementary group IDs per process. */
+#define _POSIX_OPEN_MAX 20 /* Maximum number of files that one process can have open at any one time. */
+#define _POSIX_PATH_MAX 256 /* Maximum number of bytes in a pathname. */
+#define _POSIX_PIPE_BUF 512 /* Maximum number of bytes that is guaranteed to be atomic when writing to a pipe. */
+#define _POSIX_RE_DUP_MAX 255 /* The number of repeated occurrences of a BRE permitted by the regexec() and regcomp() functions when using the interval notation #define \(m,n\}; see BREs Matching Multiple Characters. */
+#define _POSIX_RTSIG_MAX 8 /* The number of realtime signal numbers reserved for application use. */
+#define _POSIX_SEM_NSEMS_MAX 256 /* The number of semaphores that a process may have. */
+#define _POSIX_SEM_VALUE_MAX 32767 /* The maximum value a semaphore may have. */
+#define _POSIX_SIGQUEUE_MAX 32 /* The number of queued signals that a process may send and have pending at the receiver(s) at any time. */
+#define _POSIX_SSIZE_MAX 32767 /* The value that can be stored in an object of type ssize_t. */
+#define _POSIX_SS_REPL_MAX 4 /* The number of replenishment operations that may be simultaneously pending for a particular sporadic server scheduler. */
+#define _POSIX_STREAM_MAX 8 /* The number of streams that one process can have open at one time. */
+#define _POSIX_SYMLINK_MAX 255 /* The number of bytes in a symbolic link. */
+#define _POSIX_SYMLOOP_MAX 8 /* The number of symbolic links that can be traversed in the resolution of a pathname in the absence of a loop. */
+#define _POSIX_THREAD_DESTRUCTOR_ITERATIONS 4 /* The number of attempts made to destroy a thread's thread-specific data values on thread exit. */
+#define _POSIX_THREAD_KEYS_MAX 128 /* The number of data keys per process. */
+#define _POSIX_THREAD_THREADS_MAX 64 /* The number of threads per process. */
+#define _POSIX_TIMER_MAX 32 /* The per-process number of timers. */
+#define _POSIX_TRACE_EVENT_NAME_MAX 30 /* The length in bytes of a trace event name. */
+#define _POSIX_TRACE_NAME_MAX 8 /* The length in bytes of a trace generation version string or a trace stream name. */
+#define _POSIX_TRACE_SYS_MAX 8 /* The number of trace streams that may simultaneously exist in the system. */
+#define _POSIX_TRACE_USER_EVENT_MAX 32 /* The number of user trace event type identifiers that may simultaneously exist in a traced process, including the predefined user trace event POSIX_TRACE_UNNAMED_USER_EVENT. */
+#define _POSIX_TTY_NAME_MAX 9 /* The size of the storage required for a terminal device name, in bytes, including the terminating null. */
+#define _POSIX_TZNAME_MAX 6 /* Maximum number of bytes supported for the name of a timezone (not of the TZ variable). */
diff --git a/Userland/Libraries/LibC/bits/stdint.h b/Userland/Libraries/LibC/bits/stdint.h
new file mode 100644
index 0000000000..7c07ea72a3
--- /dev/null
+++ b/Userland/Libraries/LibC/bits/stdint.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+typedef __UINT64_TYPE__ uint64_t;
+typedef __UINT32_TYPE__ uint32_t;
+typedef __UINT16_TYPE__ uint16_t;
+typedef __UINT8_TYPE__ uint8_t;
+
+typedef __INT64_TYPE__ int64_t;
+typedef __INT32_TYPE__ int32_t;
+typedef __INT16_TYPE__ int16_t;
+typedef __INT8_TYPE__ int8_t;
+
+typedef __UINT_FAST8_TYPE__ uint_fast8_t;
+typedef __UINT_FAST16_TYPE__ uint_fast16_t;
+typedef __UINT_FAST32_TYPE__ uint_fast32_t;
+typedef __UINT_FAST64_TYPE__ uint_fast64_t;
+
+typedef __INT_FAST8_TYPE__ int_fast8_t;
+typedef __INT_FAST16_TYPE__ int_fast16_t;
+typedef __INT_FAST32_TYPE__ int_fast32_t;
+typedef __INT_FAST64_TYPE__ int_fast64_t;
+
+typedef __UINT_LEAST8_TYPE__ uint_least8_t;
+typedef __UINT_LEAST16_TYPE__ uint_least16_t;
+typedef __UINT_LEAST32_TYPE__ uint_least32_t;
+typedef __UINT_LEAST64_TYPE__ uint_least64_t;
+
+typedef __INT_LEAST8_TYPE__ int_least8_t;
+typedef __INT_LEAST16_TYPE__ int_least16_t;
+typedef __INT_LEAST32_TYPE__ int_least32_t;
+typedef __INT_LEAST64_TYPE__ int_least64_t;
+
+#define __int8_t_defined 1
+#define __uint8_t_defined 1
+#define __int16_t_defined 1
+#define __uint16_t_defined 1
+#define __int32_t_defined 1
+#define __uint32_t_defined 1
+#define __int64_t_defined 1
+#define __uint64_t_defined 1
+
+typedef __UINTPTR_TYPE__ uintptr_t;
+typedef __INTPTR_TYPE__ intptr_t;
+
+typedef __UINTMAX_TYPE__ uintmax_t;
+#define UINTMAX_MAX __UINTMAX_MAX__
+#define UINTMAX_MIN __UINTMAX_MIN__
+
+typedef __INTMAX_TYPE__ intmax_t;
+#define INTMAX_MAX __INTMAX_MAX__
+#define INTMAX_MIN (-INTMAX_MAX - 1)
+
+#define INT8_MIN (-128)
+#define INT16_MIN (-32767 - 1)
+#define INT32_MIN (-2147483647 - 1)
+#define INT64_MIN (-9223372036854775807LL - 1LL)
+#define INT8_MAX (127)
+#define INT16_MAX (32767)
+#define INT32_MAX (2147483647)
+#define INT64_MAX (9223372036854775807LL)
+#define UINT8_MAX (255)
+#define UINT16_MAX (65535)
+#define UINT32_MAX (4294967295U)
+#define UINT64_MAX (18446744073709551615ULL)
+
+#define INTPTR_MAX INT32_MAX
+#define INTPTR_MIN INT32_MIN
+#define UINTPTR_MAX UINT32_MAX
+
+#define INT_FAST8_MIN INT8_MIN
+#define INT_FAST16_MIN INT16_MIN
+#define INT_FAST32_MIN INT32_MIN
+#define INT_FAST64_MIN INT64_MIN
+
+#define INT_FAST8_MAX INT8_MAX
+#define INT_FAST16_MAX INT16_MAX
+#define INT_FAST32_MAX INT32_MAX
+#define INT_FAST64_MAX INT64_MAX
+
+#define UINT_FAST8_MAX UINT8_MAX
+#define UINT_FAST16_MAX UINT16_MAX
+#define UINT_FAST32_MAX UINT32_MAX
+#define UINT_FAST64_MAX UINT64_MAX
+
+#define INT_LEAST8_MIN INT8_MIN
+#define INT_LEAST16_MIN INT16_MIN
+#define INT_LEAST32_MIN INT32_MIN
+#define INT_LEAST64_MIN INT64_MIN
+
+#define INT_LEAST8_MAX INT8_MAX
+#define INT_LEAST16_MAX INT16_MAX
+#define INT_LEAST32_MAX INT32_MAX
+#define INT_LEAST64_MAX INT64_MAX
+
+#define UINT_LEAST8_MAX UINT8_MAX
+#define UINT_LEAST16_MAX UINT16_MAX
+#define UINT_LEAST32_MAX UINT32_MAX
+#define UINT_LEAST64_MAX UINT64_MAX
+
+#define INT8_C(x) x
+#define UINT8_C(x) x
+
+#define INT16_C(x) x
+#define UINT16_C(x) x
+
+#define INT32_C(x) x
+#define UINT32_C(x) x
+
+#define INT64_C(x) x##LL
+#define UINT64_C(x) x##ULL
+
+#define SIZE_MAX ((size_t)-1)
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/byteswap.h b/Userland/Libraries/LibC/byteswap.h
new file mode 100644
index 0000000000..92ebf54db9
--- /dev/null
+++ b/Userland/Libraries/LibC/byteswap.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#pragma once
+
+__BEGIN_DECLS
+
+#define bswap_16(x) (__builtin_bswap16(x))
+#define bswap_32(x) (__builtin_bswap32(x))
+#define bswap_64(x) (__builtin_bswap64(x))
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/crt0.cpp b/Userland/Libraries/LibC/crt0.cpp
new file mode 100644
index 0000000000..a02a4d14b2
--- /dev/null
+++ b/Userland/Libraries/LibC/crt0.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Types.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/internals.h>
+#include <unistd.h>
+
+extern "C" {
+
+extern u32 __stack_chk_guard;
+
+int main(int, char**, char**);
+
+// Tell the compiler that this may be called from somewhere else.
+int _start(int argc, char** argv, char** env);
+
+int _start(int argc, char** argv, char** env)
+{
+ u32 original_stack_chk = __stack_chk_guard;
+ arc4random_buf(&__stack_chk_guard, sizeof(__stack_chk_guard));
+
+ if (__stack_chk_guard == 0)
+ __stack_chk_guard = original_stack_chk;
+
+ environ = env;
+ __environ_is_malloced = false;
+
+ __libc_init();
+
+ _init();
+
+ extern void (*__init_array_start[])(int, char**, char**) __attribute__((visibility("hidden")));
+ extern void (*__init_array_end[])(int, char**, char**) __attribute__((visibility("hidden")));
+
+ const size_t size = __init_array_end - __init_array_start;
+ for (size_t i = 0; i < size; i++)
+ (*__init_array_start[i])(argc, argv, env);
+
+ int status = main(argc, argv, environ);
+
+ exit(status);
+
+ // We should never get here, but if we ever do, make sure to
+ // restore the stack guard to the value we entered _start with.
+ // Then we won't trigger the stack canary check on the way out.
+ __stack_chk_guard = original_stack_chk;
+
+ return 20150614;
+}
+}
diff --git a/Userland/Libraries/LibC/crt0_shared.cpp b/Userland/Libraries/LibC/crt0_shared.cpp
new file mode 100644
index 0000000000..df834b7580
--- /dev/null
+++ b/Userland/Libraries/LibC/crt0_shared.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Types.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/internals.h>
+#include <unistd.h>
+
+extern "C" {
+
+extern u32 __stack_chk_guard;
+
+int main(int, char**, char**);
+
+extern void __libc_init();
+extern void _init();
+extern char** environ;
+extern bool __environ_is_malloced;
+
+int _start(int argc, char** argv, char** env);
+int _start(int argc, char** argv, char** env)
+{
+ u32 original_stack_chk = __stack_chk_guard;
+ arc4random_buf(&__stack_chk_guard, sizeof(__stack_chk_guard));
+
+ if (__stack_chk_guard == 0)
+ __stack_chk_guard = original_stack_chk;
+
+ _init();
+
+ int status = main(argc, argv, env);
+
+ // Restore the stack guard to the value we entered _start with,
+ // so we don't trigger the stack canary check on the way out.
+ __stack_chk_guard = original_stack_chk;
+
+ return status;
+}
+}
+
+void* __dso_handle __attribute__((__weak__));
diff --git a/Userland/Libraries/LibC/crti.S b/Userland/Libraries/LibC/crti.S
new file mode 100644
index 0000000000..10576a17c0
--- /dev/null
+++ b/Userland/Libraries/LibC/crti.S
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.global _init
+.section .init
+_init:
+ push %ebp
+
+.global _fini
+.section .fini
+_fini:
+ push %ebp
diff --git a/Userland/Libraries/LibC/crtn.S b/Userland/Libraries/LibC/crtn.S
new file mode 100644
index 0000000000..d7327a0090
--- /dev/null
+++ b/Userland/Libraries/LibC/crtn.S
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.section .init
+ pop %ebp
+ ret
+
+.section .fini
+ pop %ebp
+ ret
diff --git a/Userland/Libraries/LibC/ctype.cpp b/Userland/Libraries/LibC/ctype.cpp
new file mode 100644
index 0000000000..3649afba31
--- /dev/null
+++ b/Userland/Libraries/LibC/ctype.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <ctype.h>
+
+extern "C" {
+
+const char _ctype_[256] = {
+ _C, _C, _C, _C, _C, _C, _C, _C,
+ _C, _C | _S, _C | _S, _C | _S, _C | _S, _C | _S, _C, _C,
+ _C, _C, _C, _C, _C, _C, _C, _C,
+ _C, _C, _C, _C, _C, _C, _C, _C,
+ (char)(_S | _B), _P, _P, _P, _P, _P, _P, _P,
+ _P, _P, _P, _P, _P, _P, _P, _P,
+ _N, _N, _N, _N, _N, _N, _N, _N,
+ _N, _N, _P, _P, _P, _P, _P, _P,
+ _P, _U | _X, _U | _X, _U | _X, _U | _X, _U | _X, _U | _X, _U,
+ _U, _U, _U, _U, _U, _U, _U, _U,
+ _U, _U, _U, _U, _U, _U, _U, _U,
+ _U, _U, _U, _P, _P, _P, _P, _P,
+ _P, _L | _X, _L | _X, _L | _X, _L | _X, _L | _X, _L | _X, _L,
+ _L, _L, _L, _L, _L, _L, _L, _L,
+ _L, _L, _L, _L, _L, _L, _L, _L,
+ _L, _L, _L, _P, _P, _P, _P, _C
+};
+
+int tolower(int c)
+{
+ if (c >= 'A' && c <= 'Z')
+ return c | 0x20;
+ return c;
+}
+
+int toupper(int c)
+{
+ if (c >= 'a' && c <= 'z')
+ return c & ~0x20;
+ return c;
+}
+}
diff --git a/Userland/Libraries/LibC/ctype.h b/Userland/Libraries/LibC/ctype.h
new file mode 100644
index 0000000000..e49c7086d0
--- /dev/null
+++ b/Userland/Libraries/LibC/ctype.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+/* Do what newlib does to appease GCC's --with-newlib option. */
+#define _U 01
+#define _L 02
+#define _N 04
+#define _S 010
+#define _P 020
+#define _C 040
+#define _X 0100
+#define _B 0200
+
+extern const char _ctype_[256];
+
+int tolower(int);
+int toupper(int);
+
+static inline int isalnum(int c)
+{
+ return (_ctype_[(unsigned char)(c)] & (_U | _L | _N));
+}
+
+static inline int isalpha(int c)
+{
+ return (_ctype_[(unsigned char)(c)] & (_U | _L));
+}
+
+static inline int iscntrl(int c)
+{
+ return (_ctype_[(unsigned char)(c)] & (_C));
+}
+
+static inline int isdigit(int c)
+{
+ return (_ctype_[(unsigned char)(c)] & (_N));
+}
+
+static inline int isxdigit(int c)
+{
+ return (_ctype_[(unsigned char)(c)] & (_N | _X));
+}
+
+static inline int isspace(int c)
+{
+ return (_ctype_[(unsigned char)(c)] & (_S));
+}
+
+static inline int ispunct(int c)
+{
+ return (_ctype_[(unsigned char)(c)] & (_P));
+}
+
+static inline int isprint(int c)
+{
+ return (_ctype_[(unsigned char)(c)] & (_P | _U | _L | _N | _B));
+}
+
+static inline int isgraph(int c)
+{
+ return (_ctype_[(unsigned char)(c)] & (_P | _U | _L | _N));
+}
+
+static inline int islower(int c)
+{
+ return ((_ctype_[(unsigned char)(c)] & (_U | _L)) == _L);
+}
+
+static inline int isupper(int c)
+{
+ return ((_ctype_[(unsigned char)(c)] & (_U | _L)) == _U);
+}
+
+#define isascii(c) ((unsigned)c <= 127)
+#define toascii(c) ((c)&127)
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/cxxabi.cpp b/Userland/Libraries/LibC/cxxabi.cpp
new file mode 100644
index 0000000000..05978bfe9b
--- /dev/null
+++ b/Userland/Libraries/LibC/cxxabi.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2019-2020, Andrew Kaster <andrewdkaster@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/internals.h>
+
+//#define GLOBAL_DTORS_DEBUG
+
+extern "C" {
+
+struct __exit_entry {
+ AtExitFunction method;
+ void* parameter;
+ void* dso_handle;
+ bool has_been_called;
+};
+
+static __exit_entry __exit_entries[1024] {};
+static int __exit_entry_count = 0;
+
+int __cxa_atexit(AtExitFunction exit_function, void* parameter, void* dso_handle)
+{
+ if (__exit_entry_count >= 1024)
+ return -1;
+
+ __exit_entries[__exit_entry_count++] = { exit_function, parameter, dso_handle, false };
+
+ return 0;
+}
+
+void __cxa_finalize(void* dso_handle)
+{
+ // From the itanium abi, https://itanium-cxx-abi.github.io/cxx-abi/abi.html#dso-dtor-runtime-api
+ //
+ // When __cxa_finalize(d) is called, it should walk the termination function list, calling each in turn
+ // if d matches __dso_handle for the termination function entry. If d == NULL, it should call all of them.
+ // Multiple calls to __cxa_finalize shall not result in calling termination function entries multiple times;
+ // the implementation may either remove entries or mark them finished.
+
+ int entry_index = __exit_entry_count;
+
+#ifdef GLOBAL_DTORS_DEBUG
+ dbgprintf("__cxa_finalize: %d entries in the finalizer list\n", entry_index);
+#endif
+
+ while (--entry_index >= 0) {
+ auto& exit_entry = __exit_entries[entry_index];
+ bool needs_calling = !exit_entry.has_been_called && (!dso_handle || dso_handle == exit_entry.dso_handle);
+ if (needs_calling) {
+#ifdef GLOBAL_DTORS_DEBUG
+ dbgprintf("__cxa_finalize: calling entry[%d] %p(%p) dso: %p\n", entry_index, exit_entry.method, exit_entry.parameter, exit_entry.dso_handle);
+#endif
+ exit_entry.method(exit_entry.parameter);
+ exit_entry.has_been_called = true;
+ }
+ }
+}
+
+[[noreturn]] void __cxa_pure_virtual()
+{
+ ASSERT_NOT_REACHED();
+}
+
+} // extern "C"
diff --git a/Userland/Libraries/LibC/dirent.cpp b/Userland/Libraries/LibC/dirent.cpp
new file mode 100644
index 0000000000..8e9701177d
--- /dev/null
+++ b/Userland/Libraries/LibC/dirent.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/StdLibExtras.h>
+#include <Kernel/API/Syscall.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+extern "C" {
+
+DIR* opendir(const char* name)
+{
+ int fd = open(name, O_RDONLY | O_DIRECTORY);
+ if (fd == -1)
+ return nullptr;
+ DIR* dirp = (DIR*)malloc(sizeof(DIR));
+ dirp->fd = fd;
+ dirp->buffer = nullptr;
+ dirp->buffer_size = 0;
+ dirp->nextptr = nullptr;
+ return dirp;
+}
+
+int closedir(DIR* dirp)
+{
+ if (!dirp || dirp->fd == -1)
+ return -EBADF;
+ if (dirp->buffer)
+ free(dirp->buffer);
+ int rc = close(dirp->fd);
+ if (rc == 0)
+ dirp->fd = -1;
+ free(dirp);
+ return rc;
+}
+
+struct [[gnu::packed]] sys_dirent {
+ ino_t ino;
+ u8 file_type;
+ size_t namelen;
+ char name[];
+ size_t total_size()
+ {
+ return sizeof(ino_t) + sizeof(u8) + sizeof(size_t) + sizeof(char) * namelen;
+ }
+};
+
+static void create_struct_dirent(sys_dirent* sys_ent, struct dirent* str_ent)
+{
+ str_ent->d_ino = sys_ent->ino;
+ str_ent->d_type = sys_ent->file_type;
+ str_ent->d_off = 0;
+ str_ent->d_reclen = sys_ent->total_size();
+ for (size_t i = 0; i < sys_ent->namelen; ++i)
+ str_ent->d_name[i] = sys_ent->name[i];
+ // FIXME: I think this null termination behavior is not supposed to be here.
+ str_ent->d_name[sys_ent->namelen] = '\0';
+}
+
+static int allocate_dirp_buffer(DIR* dirp)
+{
+ if (dirp->buffer) {
+ return 0;
+ }
+
+ struct stat st;
+ // preserve errno since this could be a reentrant call
+ int old_errno = errno;
+ int rc = fstat(dirp->fd, &st);
+ if (rc < 0) {
+ int new_errno = errno;
+ errno = old_errno;
+ return new_errno;
+ }
+ size_t size_to_allocate = max(st.st_size, static_cast<off_t>(4096));
+ dirp->buffer = (char*)malloc(size_to_allocate);
+ ssize_t nread = syscall(SC_get_dir_entries, dirp->fd, dirp->buffer, size_to_allocate);
+ if (nread < 0) {
+ // uh-oh, the syscall returned an error
+ free(dirp->buffer);
+ dirp->buffer = nullptr;
+ return -nread;
+ }
+ dirp->buffer_size = nread;
+ dirp->nextptr = dirp->buffer;
+ return 0;
+}
+
+dirent* readdir(DIR* dirp)
+{
+ if (!dirp)
+ return nullptr;
+ if (dirp->fd == -1)
+ return nullptr;
+
+ if (int new_errno = allocate_dirp_buffer(dirp)) {
+ // readdir is allowed to mutate errno
+ errno = new_errno;
+ return nullptr;
+ }
+
+ if (dirp->nextptr >= (dirp->buffer + dirp->buffer_size))
+ return nullptr;
+
+ auto* sys_ent = (sys_dirent*)dirp->nextptr;
+ create_struct_dirent(sys_ent, &dirp->cur_ent);
+
+ dirp->nextptr += sys_ent->total_size();
+ return &dirp->cur_ent;
+}
+
+static bool compare_sys_struct_dirent(sys_dirent* sys_ent, struct dirent* str_ent)
+{
+ size_t namelen = min((size_t)256, sys_ent->namelen);
+ // These fields are guaranteed by create_struct_dirent to be the same
+ return sys_ent->ino == str_ent->d_ino
+ && sys_ent->file_type == str_ent->d_type
+ && sys_ent->total_size() == str_ent->d_reclen
+ && strncmp(sys_ent->name, str_ent->d_name, namelen) == 0;
+}
+
+int readdir_r(DIR* dirp, struct dirent* entry, struct dirent** result)
+{
+ if (!dirp || dirp->fd == -1) {
+ *result = nullptr;
+ return EBADF;
+ }
+
+ if (int new_errno = allocate_dirp_buffer(dirp)) {
+ *result = nullptr;
+ return new_errno;
+ }
+
+ // This doesn't care about dirp state; seek until we find the entry.
+ // Unfortunately, we can't just compare struct dirent to sys_dirent, so
+ // manually compare the fields. This seems a bit risky, but could work.
+ auto* buffer = dirp->buffer;
+ auto* sys_ent = (sys_dirent*)buffer;
+ bool found = false;
+ while (!(found || buffer >= dirp->buffer + dirp->buffer_size)) {
+ found = compare_sys_struct_dirent(sys_ent, entry);
+
+ // Make sure if we found one, it's the one after (end of buffer or not)
+ buffer += sys_ent->total_size();
+ sys_ent = (sys_dirent*)buffer;
+ }
+
+ // If we found one, but hit end of buffer, then EOD
+ if (found && buffer >= dirp->buffer + dirp->buffer_size) {
+ *result = nullptr;
+ return 0;
+ }
+ // If we never found a match for entry in buffer, start from the beginning
+ else if (!found) {
+ buffer = dirp->buffer;
+ sys_ent = (sys_dirent*)buffer;
+ }
+
+ *result = entry;
+ create_struct_dirent(sys_ent, entry);
+
+ return 0;
+}
+
+int dirfd(DIR* dirp)
+{
+ ASSERT(dirp);
+ return dirp->fd;
+}
+}
diff --git a/Userland/Libraries/LibC/dirent.h b/Userland/Libraries/LibC/dirent.h
new file mode 100644
index 0000000000..027f09a057
--- /dev/null
+++ b/Userland/Libraries/LibC/dirent.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+enum {
+ DT_UNKNOWN = 0,
+#define DT_UNKNOWN DT_UNKNOWN
+ DT_FIFO = 1,
+#define DT_FIFO DT_FIFO
+ DT_CHR = 2,
+#define DT_CHR DT_CHR
+ DT_DIR = 4,
+#define DT_DIR DT_DIR
+ DT_BLK = 6,
+#define DT_BLK DT_BLK
+ DT_REG = 8,
+#define DT_REG DT_REG
+ DT_LNK = 10,
+#define DT_LNK DT_LNK
+ DT_SOCK = 12,
+#define DT_SOCK DT_SOCK
+ DT_WHT = 14
+#define DT_WHT DT_WHT
+};
+
+struct dirent {
+ ino_t d_ino;
+ off_t d_off;
+ unsigned short d_reclen;
+ unsigned char d_type;
+ char d_name[256];
+};
+
+struct __DIR {
+ int fd;
+ struct dirent cur_ent;
+ char* buffer;
+ size_t buffer_size;
+ char* nextptr;
+};
+typedef struct __DIR DIR;
+
+DIR* opendir(const char* name);
+int closedir(DIR*);
+struct dirent* readdir(DIR*);
+int readdir_r(DIR*, struct dirent*, struct dirent**);
+int dirfd(DIR*);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/dlfcn.cpp b/Userland/Libraries/LibC/dlfcn.cpp
new file mode 100644
index 0000000000..04f742fcc3
--- /dev/null
+++ b/Userland/Libraries/LibC/dlfcn.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <mman.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include <AK/HashMap.h>
+#include <AK/LexicalPath.h>
+#include <AK/RefPtr.h>
+#include <AK/ScopeGuard.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <LibELF/DynamicLoader.h>
+
+// NOTE: The string here should never include a trailing newline (according to POSIX)
+String g_dlerror_msg;
+
+HashMap<String, RefPtr<ELF::DynamicLoader>> g_elf_objects;
+
+extern "C" {
+
+int dlclose(void*)
+{
+ g_dlerror_msg = "dlclose not implemented!";
+ return -1;
+}
+
+char* dlerror()
+{
+ return const_cast<char*>(g_dlerror_msg.characters());
+}
+
+void* dlopen(const char* filename, int flags)
+{
+ // FIXME: Create a global mutex/semaphore/lock for dlopen/dlclose/dlsym and (?) dlerror
+ // FIXME: refcount?
+
+ if (!filename) {
+ // FIXME: Return the handle for "the main executable"
+ // The Serenity Kernel will keep a mapping of the main elf binary resident in memory,
+ // But a future dynamic loader might have a different idea/way of letting us access this information
+ ASSERT_NOT_REACHED();
+ }
+
+ auto basename = LexicalPath(filename).basename();
+
+ auto existing_elf_object = g_elf_objects.get(basename);
+ if (existing_elf_object.has_value()) {
+ return const_cast<ELF::DynamicLoader*>(existing_elf_object.value());
+ }
+
+ int fd = open(filename, O_RDONLY);
+ if (!fd) {
+ g_dlerror_msg = String::format("Unable to open file %s", filename);
+ return nullptr;
+ }
+
+ ScopeGuard close_fd_guard([fd]() { close(fd); });
+
+ struct stat file_stats {
+ };
+
+ int ret = fstat(fd, &file_stats);
+ if (ret < 0) {
+ g_dlerror_msg = String::format("Unable to stat file %s", filename);
+ return nullptr;
+ }
+
+ auto loader = ELF::DynamicLoader::construct(filename, fd, file_stats.st_size);
+
+ if (!loader->is_valid()) {
+ g_dlerror_msg = String::format("%s is not a valid ELF dynamic shared object!", filename);
+ return nullptr;
+ }
+
+ if (!loader->load_from_image(flags,
+ 0 // total_tls_size = 0, FIXME: Support TLS when using dlopen()
+ )) {
+ g_dlerror_msg = String::format("Failed to load ELF object %s", filename);
+ return nullptr;
+ }
+
+ g_elf_objects.set(basename, move(loader));
+ g_dlerror_msg = "Successfully loaded ELF object.";
+
+ // we have one refcount already
+ return const_cast<ELF::DynamicLoader*>(g_elf_objects.get(basename).value());
+}
+
+void* dlsym(void* handle, const char* symbol_name)
+{
+ // FIXME: When called with a NULL handle we're supposed to search every dso in the process... that'll get expensive
+ ASSERT(handle);
+ auto* dso = reinterpret_cast<ELF::DynamicLoader*>(handle);
+ void* symbol = dso->symbol_for_name(symbol_name);
+ if (!symbol) {
+ g_dlerror_msg = "Symbol not found";
+ return nullptr;
+ }
+ return symbol;
+}
+
+} // extern "C"
diff --git a/Userland/Libraries/LibC/dlfcn.h b/Userland/Libraries/LibC/dlfcn.h
new file mode 100644
index 0000000000..9363261ca6
--- /dev/null
+++ b/Userland/Libraries/LibC/dlfcn.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+#define RTLD_DEFAULT 0
+#define RTLD_LAZY 2
+#define RTLD_NOW 4
+#define RTLD_GLOBAL 8
+#define RTLD_LOCAL 16
+
+int dlclose(void*);
+char* dlerror();
+void* dlopen(const char*, int);
+void* dlsym(void*, const char*);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/endian.h b/Userland/Libraries/LibC/endian.h
new file mode 100644
index 0000000000..ebaf6cde3e
--- /dev/null
+++ b/Userland/Libraries/LibC/endian.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+#define __LITTLE_ENDIAN 1234
+#define __BIG_ENDIAN 4321
+#define __PDP_ENDIAN 3412
+
+#if defined(__GNUC__) && defined(__BYTE_ORDER__)
+# define __BYTE_ORDER __BYTE_ORDER__
+#else
+# include <bits/endian.h>
+#endif
+
+#if defined(_GNU_SOURCE) || defined(_BSD_SOURCE)
+
+# include <stdint.h>
+
+static __inline uint16_t __bswap16(uint16_t x)
+{
+ return __builtin_bswap16(x);
+}
+
+static __inline uint32_t __bswap32(uint32_t x)
+{
+ return __builtin_bswap32(x);
+}
+
+static __inline uint64_t __bswap64(uint64_t x)
+{
+ return __builtin_bswap64(x);
+}
+
+# define LITTLE_ENDIAN __LITTLE_ENDIAN
+# define BIG_ENDIAN __BIG_ENDIAN
+# define PDP_ENDIAN __PDP_ENDIAN
+# define BYTE_ORDER __BYTE_ORDER
+
+# if __BYTE_ORDER == __LITTLE_ENDIAN
+# define htole16(x) ((uint16_t)(x))
+# define le16toh(x) ((uint16_t)(x))
+# define letoh16(x) ((uint16_t)(x))
+# define htole32(x) ((uint32_t)(x))
+# define le32toh(x) ((uint32_t)(x))
+# define letoh32(x) ((uint32_t)(x))
+# define htole64(x) ((uint64_t)(x))
+# define le64toh(x) ((uint64_t)(x))
+# define letoh64(x) ((uint64_t)(x))
+# define htobe16(x) (__builtin_bswap16(x))
+# define be16toh(x) (__builtin_bswap16(x))
+# define betoh16(x) (__builtin_bswap16(x))
+# define htobe32(x) (__builtin_bswap32(x))
+# define be32toh(x) (__builtin_bswap32(x))
+# define betoh32(x) (__builtin_bswap32(x))
+# define htobe64(x) (__builtin_bswap64(x))
+# define be64toh(x) (__builtin_bswap64(x))
+# define betoh64(x) (__builtin_bswap64(x))
+# else
+# define htole16(x) (__builtin_bswap16(x))
+# define le16toh(x) (__builtin_bswap16(x))
+# define letoh16(x) (__builtin_bswap16(x))
+# define htole32(x) (__builtin_bswap32(x))
+# define le32toh(x) (__builtin_bswap32(x))
+# define letoh32(x) (__builtin_bswap32(x))
+# define htole64(x) (__builtin_bswap64(x))
+# define le64toh(x) (__builtin_bswap64(x))
+# define letoh64(x) (__builtin_bswap64(x))
+# define htobe16(x) ((uint16_t)(x))
+# define be16toh(x) ((uint16_t)(x))
+# define betoh16(x) ((uint16_t)(x))
+# define htobe32(x) ((uint32_t)(x))
+# define be32toh(x) ((uint32_t)(x))
+# define betoh32(x) ((uint32_t)(x))
+# define htobe64(x) ((uint64_t)(x))
+# define be64toh(x) ((uint64_t)(x))
+# define betoh64(x) ((uint64_t)(x))
+# endif
+
+#endif
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/errno.h b/Userland/Libraries/LibC/errno.h
new file mode 100644
index 0000000000..f763dbc914
--- /dev/null
+++ b/Userland/Libraries/LibC/errno.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <errno_numbers.h>
+#include <sys/cdefs.h>
+
+#define __RETURN_WITH_ERRNO(rc, good_ret, bad_ret) \
+ do { \
+ if (rc < 0) { \
+ errno = -rc; \
+ return (bad_ret); \
+ } \
+ errno = 0; \
+ return (good_ret); \
+ } while (0)
+
+__BEGIN_DECLS
+
+extern const char* const sys_errlist[];
+extern int sys_nerr;
+
+#ifdef NO_TLS
+extern int errno;
+#else
+extern __thread int errno;
+#endif
+
+#define errno errno
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/errno_numbers.h b/Userland/Libraries/LibC/errno_numbers.h
new file mode 100644
index 0000000000..fc067b171d
--- /dev/null
+++ b/Userland/Libraries/LibC/errno_numbers.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#define EPERM 1
+#define ENOENT 2
+#define ESRCH 3
+#define EINTR 4
+#define EIO 5
+#define ENXIO 6
+#define E2BIG 7
+#define ENOEXEC 8
+#define EBADF 9
+#define ECHILD 10
+#define EAGAIN 11
+#define ENOMEM 12
+#define EACCES 13
+#define EFAULT 14
+#define ENOTBLK 15
+#define EBUSY 16
+#define EEXIST 17
+#define EXDEV 18
+#define ENODEV 19
+#define ENOTDIR 20
+#define EISDIR 21
+#define EINVAL 22
+#define ENFILE 23
+#define EMFILE 24
+#define ENOTTY 25
+#define ETXTBSY 26
+#define EFBIG 27
+#define ENOSPC 28
+#define ESPIPE 29
+#define EROFS 30
+#define EMLINK 31
+#define EPIPE 32
+#define ERANGE 33
+#define ENAMETOOLONG 34
+#define ELOOP 35
+#define EOVERFLOW 36
+#define EOPNOTSUPP 37
+#define ENOSYS 38
+#define ENOTIMPL 39
+#define EAFNOSUPPORT 40
+#define ENOTSOCK 41
+#define EADDRINUSE 42
+#define EWHYTHO 43
+#define ENOTEMPTY 44
+#define EDOM 45
+#define ECONNREFUSED 46
+#define EADDRNOTAVAIL 47
+#define EISCONN 48
+#define ECONNABORTED 49
+#define EALREADY 50
+#define ECONNRESET 51
+#define EDESTADDRREQ 52
+#define EHOSTUNREACH 53
+#define EILSEQ 54
+#define EMSGSIZE 55
+#define ENETDOWN 56
+#define ENETUNREACH 57
+#define ENETRESET 58
+#define ENOBUFS 59
+#define ENOLCK 60
+#define ENOMSG 61
+#define ENOPROTOOPT 62
+#define ENOTCONN 63
+#define EWOULDBLOCK 64
+#define EPROTONOSUPPORT 65
+#define EDEADLK 66
+#define ETIMEDOUT 67
+#define EPROTOTYPE 68
+#define EINPROGRESS 69
+#define ENOTHREAD 70
+#define EPROTO 71
+#define ENOTSUP 72
+#define EPFNOSUPPORT 73
+#define EDIRINTOSELF 74
+#define EMAXERRNO 75
diff --git a/Userland/Libraries/LibC/fcntl.cpp b/Userland/Libraries/LibC/fcntl.cpp
new file mode 100644
index 0000000000..dd71fd1559
--- /dev/null
+++ b/Userland/Libraries/LibC/fcntl.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <string.h>
+
+extern "C" {
+
+int fcntl(int fd, int cmd, ...)
+{
+ va_list ap;
+ va_start(ap, cmd);
+ u32 extra_arg = va_arg(ap, u32);
+ int rc = syscall(SC_fcntl, fd, cmd, extra_arg);
+ va_end(ap);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int watch_file(const char* path, size_t path_length)
+{
+ int rc = syscall(SC_watch_file, path, path_length);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int creat(const char* path, mode_t mode)
+{
+ return open(path, O_CREAT | O_WRONLY | O_TRUNC, mode);
+}
+
+int creat_with_path_length(const char* path, size_t path_length, mode_t mode)
+{
+ return open_with_path_length(path, path_length, O_CREAT | O_WRONLY | O_TRUNC, mode);
+}
+
+int open_with_path_length(const char* path, size_t path_length, int options, mode_t mode)
+{
+ return openat_with_path_length(AT_FDCWD, path, path_length, options, mode);
+}
+
+int openat_with_path_length(int dirfd, const char* path, size_t path_length, int options, mode_t mode)
+{
+ if (!path) {
+ errno = EFAULT;
+ return -1;
+ }
+ if (path_length > INT32_MAX) {
+ errno = EINVAL;
+ return -1;
+ }
+ Syscall::SC_open_params params { dirfd, { path, path_length }, options, mode };
+ int rc = syscall(SC_open, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int open(const char* path, int options, ...)
+{
+ if (!path) {
+ errno = EFAULT;
+ return -1;
+ }
+ va_list ap;
+ va_start(ap, options);
+ auto mode = (mode_t)va_arg(ap, unsigned);
+ va_end(ap);
+ return open_with_path_length(path, strlen(path), options, mode);
+}
+
+int openat(int dirfd, const char* path, int options, ...)
+{
+ if (!path) {
+ errno = EFAULT;
+ return -1;
+ }
+ va_list ap;
+ va_start(ap, options);
+ auto mode = (mode_t)va_arg(ap, unsigned);
+ va_end(ap);
+ return openat_with_path_length(dirfd, path, strlen(path), options, mode);
+}
+}
diff --git a/Userland/Libraries/LibC/fcntl.h b/Userland/Libraries/LibC/fcntl.h
new file mode 100644
index 0000000000..1aa6e6cd09
--- /dev/null
+++ b/Userland/Libraries/LibC/fcntl.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+#define F_DUPFD 0
+#define F_GETFD 1
+#define F_SETFD 2
+#define F_GETFL 3
+#define F_SETFL 4
+#define F_ISTTY 5
+
+#define FD_CLOEXEC 1
+
+#define O_RDONLY (1 << 0)
+#define O_WRONLY (1 << 1)
+#define O_RDWR (O_RDONLY | O_WRONLY)
+#define O_ACCMODE (O_RDONLY | O_WRONLY)
+#define O_EXEC (1 << 2)
+#define O_CREAT (1 << 3)
+#define O_EXCL (1 << 4)
+#define O_NOCTTY (1 << 5)
+#define O_TRUNC (1 << 6)
+#define O_APPEND (1 << 7)
+#define O_NONBLOCK (1 << 8)
+#define O_DIRECTORY (1 << 9)
+#define O_NOFOLLOW (1 << 10)
+#define O_CLOEXEC (1 << 11)
+#define O_DIRECT (1 << 12)
+
+#define S_IFMT 0170000
+#define S_IFDIR 0040000
+#define S_IFCHR 0020000
+#define S_IFBLK 0060000
+#define S_IFREG 0100000
+#define S_IFIFO 0010000
+#define S_IFLNK 0120000
+#define S_IFSOCK 0140000
+
+#define S_ISUID 04000
+#define S_ISGID 02000
+#define S_ISVTX 01000
+#define S_IRUSR 0400
+#define S_IWUSR 0200
+#define S_IXUSR 0100
+#define S_IRGRP 0040
+#define S_IWGRP 0020
+#define S_IXGRP 0010
+#define S_IROTH 0004
+#define S_IWOTH 0002
+#define S_IXOTH 0001
+
+#define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR)
+
+#define S_IRWXG (S_IRWXU >> 3)
+#define S_IRWXO (S_IRWXG >> 3)
+
+int creat(const char* path, mode_t);
+int open(const char* path, int options, ...);
+int creat_with_path_length(const char* path, size_t path_length, mode_t);
+int open_with_path_length(const char* path, size_t path_length, int options, mode_t);
+#define AT_FDCWD -100
+int openat(int dirfd, const char* path, int options, ...);
+int openat_with_path_length(int dirfd, const char* path, size_t path_length, int options, mode_t);
+
+int fcntl(int fd, int cmd, ...);
+int watch_file(const char* path, size_t path_length);
+
+#define F_RDLCK 0
+#define F_WRLCK 1
+#define F_UNLCK 2
+#define F_SETLK 6
+#define F_SETLKW 7
+
+struct flock {
+ short l_type;
+ short l_whence;
+ off_t l_start;
+ off_t l_len;
+ pid_t l_pid;
+};
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/fd_set.h b/Userland/Libraries/LibC/fd_set.h
new file mode 100644
index 0000000000..254a1ba49c
--- /dev/null
+++ b/Userland/Libraries/LibC/fd_set.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#define FD_SETSIZE 64
+#define FD_ZERO(set) memset((set), 0, sizeof(fd_set));
+#define FD_CLR(fd, set) ((set)->bits[(fd / 8)] &= ~(1 << (fd) % 8))
+#define FD_SET(fd, set) ((set)->bits[(fd / 8)] |= (1 << (fd) % 8))
+#define FD_ISSET(fd, set) ((set)->bits[(fd / 8)] & (1 << (fd) % 8))
+
+struct __fd_set {
+ unsigned char bits[FD_SETSIZE / 8];
+};
+
+typedef struct __fd_set fd_set;
diff --git a/Userland/Libraries/LibC/float.h b/Userland/Libraries/LibC/float.h
new file mode 100644
index 0000000000..2bf8abd099
--- /dev/null
+++ b/Userland/Libraries/LibC/float.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
diff --git a/Userland/Libraries/LibC/getopt.cpp b/Userland/Libraries/LibC/getopt.cpp
new file mode 100644
index 0000000000..a8142262d7
--- /dev/null
+++ b/Userland/Libraries/LibC/getopt.cpp
@@ -0,0 +1,369 @@
+/*
+ * Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringView.h>
+#include <AK/Vector.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+int opterr = 1;
+int optopt = 0;
+int optind = 1;
+int optreset = 0;
+char* optarg = nullptr;
+
+// POSIX says, "When an element of argv[] contains multiple option characters,
+// it is unspecified how getopt() determines which options have already been
+// processed". Well, this is how we do it.
+static size_t s_index_into_multioption_argument = 0;
+
+static inline void report_error(const char* format, ...)
+{
+ if (!opterr)
+ return;
+
+ fputs("\033[31m", stderr);
+
+ va_list ap;
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+
+ fputs("\033[0m\n", stderr);
+}
+
+namespace {
+
+class OptionParser {
+public:
+ OptionParser(int argc, char** argv, const StringView& short_options, const option* long_options, int* out_long_option_index = nullptr);
+ int getopt();
+
+private:
+ bool lookup_short_option(char option, int& needs_value) const;
+ int handle_short_option();
+
+ const option* lookup_long_option(char* raw) const;
+ int handle_long_option();
+
+ void shift_argv();
+ bool find_next_option();
+
+ size_t m_argc { 0 };
+ char** m_argv { nullptr };
+ StringView m_short_options;
+ const option* m_long_options { nullptr };
+ int* m_out_long_option_index { nullptr };
+ bool m_stop_on_first_non_option { false };
+
+ size_t m_arg_index { 0 };
+ size_t m_consumed_args { 0 };
+};
+
+OptionParser::OptionParser(int argc, char** argv, const StringView& short_options, const option* long_options, int* out_long_option_index)
+ : m_argc(argc)
+ , m_argv(argv)
+ , m_short_options(short_options)
+ , m_long_options(long_options)
+ , m_out_long_option_index(out_long_option_index)
+{
+ // In the following case:
+ // $ foo bar -o baz
+ // we want to parse the option (-o baz) first, and leave the argument (bar)
+ // in argv after we return -1 when invoked the second time. So we reorder
+ // argv to put options first and positional arguments next. To turn this
+ // behavior off, start the short options spec with a "+". This is a GNU
+ // extension that we support.
+ m_stop_on_first_non_option = short_options.starts_with('+');
+
+ // See if we should reset the internal state.
+ if (optreset || optind == 0) {
+ optreset = 0;
+ optind = 1;
+ s_index_into_multioption_argument = 0;
+ }
+
+ optopt = 0;
+ optarg = nullptr;
+}
+
+int OptionParser::getopt()
+{
+ bool should_reorder_argv = !m_stop_on_first_non_option;
+ int res = -1;
+
+ bool found_an_option = find_next_option();
+ StringView arg = m_argv[m_arg_index];
+
+ if (!found_an_option) {
+ res = -1;
+ if (arg == "--")
+ m_consumed_args = 1;
+ else
+ m_consumed_args = 0;
+ } else {
+ // Alright, so we have an option on our hands!
+ bool is_long_option = arg.starts_with("--");
+ if (is_long_option)
+ res = handle_long_option();
+ else
+ res = handle_short_option();
+
+ // If we encountered an error, return immediately.
+ if (res == '?')
+ return '?';
+ }
+
+ if (should_reorder_argv)
+ shift_argv();
+ else
+ ASSERT(optind == static_cast<int>(m_arg_index));
+ optind += m_consumed_args;
+
+ return res;
+}
+
+bool OptionParser::lookup_short_option(char option, int& needs_value) const
+{
+ Vector<StringView> parts = m_short_options.split_view(option, true);
+
+ ASSERT(parts.size() <= 2);
+ if (parts.size() < 2) {
+ // Haven't found the option in the spec.
+ return false;
+ }
+
+ if (parts[1].starts_with("::")) {
+ // If an option is followed by two colons, it optionally accepts an
+ // argument.
+ needs_value = optional_argument;
+ } else if (parts[1].starts_with(':')) {
+ // If it's followed by one colon, it requires an argument.
+ needs_value = required_argument;
+ } else {
+ // Otherwise, it doesn't accept arguments.
+ needs_value = no_argument;
+ }
+ return true;
+}
+
+int OptionParser::handle_short_option()
+{
+ StringView arg = m_argv[m_arg_index];
+ ASSERT(arg.starts_with('-'));
+
+ if (s_index_into_multioption_argument == 0) {
+ // Just starting to parse this argument, skip the "-".
+ s_index_into_multioption_argument = 1;
+ }
+ char option = arg[s_index_into_multioption_argument];
+ s_index_into_multioption_argument++;
+
+ int needs_value = no_argument;
+ bool ok = lookup_short_option(option, needs_value);
+ if (!ok) {
+ optopt = option;
+ report_error("Unrecognized option \033[1m-%c\033[22m", option);
+ return '?';
+ }
+
+ // Let's see if we're at the end of this argument already.
+ if (s_index_into_multioption_argument < arg.length()) {
+ // This not yet the end.
+ if (needs_value == no_argument) {
+ optarg = nullptr;
+ m_consumed_args = 0;
+ } else {
+ // Treat the rest of the argument as the value, the "-ovalue"
+ // syntax.
+ optarg = m_argv[m_arg_index] + s_index_into_multioption_argument;
+ // Next time, process the next argument.
+ s_index_into_multioption_argument = 0;
+ m_consumed_args = 1;
+ }
+ } else {
+ s_index_into_multioption_argument = 0;
+ if (needs_value != required_argument) {
+ optarg = nullptr;
+ m_consumed_args = 1;
+ } else if (m_arg_index + 1 < m_argc) {
+ // Treat the next argument as a value, the "-o value" syntax.
+ optarg = m_argv[m_arg_index + 1];
+ m_consumed_args = 2;
+ } else {
+ report_error("Missing value for option \033[1m-%c\033[22m", option);
+ return '?';
+ }
+ }
+
+ return option;
+}
+
+const option* OptionParser::lookup_long_option(char* raw) const
+{
+ StringView arg = raw;
+
+ for (size_t index = 0; m_long_options[index].name; index++) {
+ auto& option = m_long_options[index];
+ StringView name = option.name;
+
+ if (!arg.starts_with(name))
+ continue;
+
+ // It would be better to not write out the index at all unless we're
+ // sure we've found the right option, but whatever.
+ if (m_out_long_option_index)
+ *m_out_long_option_index = index;
+
+ // Can either be "--option" or "--option=value".
+ if (arg.length() == name.length()) {
+ optarg = nullptr;
+ return &option;
+ }
+ ASSERT(arg.length() > name.length());
+ if (arg[name.length()] == '=') {
+ optarg = raw + name.length() + 1;
+ return &option;
+ }
+ }
+
+ return nullptr;
+}
+
+int OptionParser::handle_long_option()
+{
+ ASSERT(StringView(m_argv[m_arg_index]).starts_with("--"));
+
+ // We cannot set optopt to anything sensible for long options, so set it to 0.
+ optopt = 0;
+
+ auto* option = lookup_long_option(m_argv[m_arg_index] + 2);
+ if (!option) {
+ report_error("Unrecognized option \033[1m%s\033[22m", m_argv[m_arg_index]);
+ return '?';
+ }
+ // lookup_long_option() will also set optarg if the value of the option is
+ // specified using "--option=value" syntax.
+
+ // Figure out whether this option needs and/or has a value (also called "an
+ // argument", but let's not call it that to distinguish it from argv
+ // elements).
+ switch (option->has_arg) {
+ case no_argument:
+ if (optarg) {
+ report_error("Option \033[1m--%s\033[22m doesn't accept an argument", option->name);
+ return '?';
+ }
+ m_consumed_args = 1;
+ break;
+ case optional_argument:
+ m_consumed_args = 1;
+ break;
+ case required_argument:
+ if (optarg) {
+ // Value specified using "--option=value" syntax.
+ m_consumed_args = 1;
+ } else if (m_arg_index + 1 < m_argc) {
+ // Treat the next argument as a value in "--option value" syntax.
+ optarg = m_argv[m_arg_index + 1];
+ m_consumed_args = 2;
+ } else {
+ report_error("Missing value for option \033[1m--%s\033[22m", option->name);
+ return '?';
+ }
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ // Now that we've figured the value out, see about reporting this option to
+ // our caller.
+ if (option->flag) {
+ *option->flag = option->val;
+ return 0;
+ }
+ return option->val;
+}
+
+void OptionParser::shift_argv()
+{
+ // We've just parsed an option (which perhaps has a value).
+ // Put the option (along with it value, if any) in front of other arguments.
+ ASSERT(optind <= static_cast<int>(m_arg_index));
+
+ if (optind == static_cast<int>(m_arg_index) || m_consumed_args == 0) {
+ // Nothing to do!
+ return;
+ }
+
+ char* buffer[m_consumed_args];
+ memcpy(buffer, &m_argv[m_arg_index], sizeof(char*) * m_consumed_args);
+ memmove(&m_argv[optind + m_consumed_args], &m_argv[optind], sizeof(char*) * (m_arg_index - optind));
+ memcpy(&m_argv[optind], buffer, sizeof(char*) * m_consumed_args);
+}
+
+bool OptionParser::find_next_option()
+{
+ for (m_arg_index = optind; m_arg_index < m_argc && m_argv[m_arg_index]; m_arg_index++) {
+ StringView arg = m_argv[m_arg_index];
+ // Anything that doesn't start with a "-" is not an option.
+ if (!arg.starts_with('-')) {
+ if (m_stop_on_first_non_option)
+ return false;
+ continue;
+ }
+ // As a special case, a single "-" is not an option either.
+ // (It's typically used by programs to refer to stdin).
+ if (arg == "-")
+ continue;
+ // As another special case, a "--" is not an option either, and we stop
+ // looking for further options if we encounter it.
+ if (arg == "--")
+ return false;
+ // Otherwise, we have found an option!
+ return true;
+ }
+
+ // Reached the end and still found no options.
+ return false;
+}
+
+}
+
+int getopt(int argc, char** argv, const char* short_options)
+{
+ option dummy { nullptr, 0, nullptr, 0 };
+ OptionParser parser { argc, argv, short_options, &dummy };
+ return parser.getopt();
+}
+
+int getopt_long(int argc, char** argv, const char* short_options, const struct option* long_options, int* out_long_option_index)
+{
+ OptionParser parser { argc, argv, short_options, long_options, out_long_option_index };
+ return parser.getopt();
+}
diff --git a/Userland/Libraries/LibC/getopt.h b/Userland/Libraries/LibC/getopt.h
new file mode 100644
index 0000000000..0aaddf2820
--- /dev/null
+++ b/Userland/Libraries/LibC/getopt.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+#define no_argument 0
+#define required_argument 1
+#define optional_argument 2
+
+struct option {
+ const char* name;
+ int has_arg;
+ int* flag;
+ int val;
+};
+
+int getopt_long(int argc, char** argv, const char* short_options, const struct option* long_options, int* out_long_option_index);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/grp.cpp b/Userland/Libraries/LibC/grp.cpp
new file mode 100644
index 0000000000..9750389281
--- /dev/null
+++ b/Userland/Libraries/LibC/grp.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <errno_numbers.h>
+#include <grp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+extern "C" {
+
+static FILE* s_stream = nullptr;
+static unsigned s_line_number = 0;
+static struct group s_group;
+
+static String s_name;
+static String s_passwd;
+static Vector<String> s_members;
+static Vector<const char*> s_members_ptrs;
+
+void setgrent()
+{
+ s_line_number = 0;
+ if (s_stream) {
+ rewind(s_stream);
+ } else {
+ s_stream = fopen("/etc/group", "r");
+ if (!s_stream) {
+ perror("open /etc/group");
+ }
+ }
+}
+
+void endgrent()
+{
+ s_line_number = 0;
+ if (s_stream) {
+ fclose(s_stream);
+ s_stream = nullptr;
+ }
+
+ memset(&s_group, 0, sizeof(s_group));
+
+ s_name = {};
+ s_passwd = {};
+ s_members = {};
+ s_members_ptrs = {};
+}
+
+struct group* getgrgid(gid_t gid)
+{
+ setgrent();
+ while (auto* gr = getgrent()) {
+ if (gr->gr_gid == gid)
+ return gr;
+ }
+ return nullptr;
+}
+
+struct group* getgrnam(const char* name)
+{
+ setgrent();
+ while (auto* gr = getgrent()) {
+ if (!strcmp(gr->gr_name, name))
+ return gr;
+ }
+ return nullptr;
+}
+
+static bool parse_grpdb_entry(const String& line)
+{
+ auto parts = line.split_view(':', true);
+ if (parts.size() != 4) {
+ fprintf(stderr, "getgrent(): Malformed entry on line %u: '%s' has %zu parts\n", s_line_number, line.characters(), parts.size());
+ return false;
+ }
+
+ s_name = parts[0];
+ s_passwd = parts[1];
+
+ auto& gid_string = parts[2];
+ String members_string = parts[3];
+
+ auto gid = gid_string.to_uint();
+ if (!gid.has_value()) {
+ fprintf(stderr, "getgrent(): Malformed GID on line %u\n", s_line_number);
+ return false;
+ }
+
+ s_members = members_string.split(',');
+ s_members_ptrs.clear_with_capacity();
+ s_members_ptrs.ensure_capacity(s_members.size() + 1);
+ for (auto& member : s_members) {
+ s_members_ptrs.append(member.characters());
+ }
+ s_members_ptrs.append(nullptr);
+
+ s_group.gr_gid = gid.value();
+ s_group.gr_name = const_cast<char*>(s_name.characters());
+ s_group.gr_passwd = const_cast<char*>(s_passwd.characters());
+ s_group.gr_mem = const_cast<char**>(s_members_ptrs.data());
+
+ return true;
+}
+
+struct group* getgrent()
+{
+ if (!s_stream)
+ setgrent();
+
+ while (true) {
+ if (!s_stream || feof(s_stream))
+ return nullptr;
+
+ if (ferror(s_stream)) {
+ fprintf(stderr, "getgrent(): Read error: %s\n", strerror(ferror(s_stream)));
+ return nullptr;
+ }
+
+ char buffer[1024];
+ ++s_line_number;
+ char* s = fgets(buffer, sizeof(buffer), s_stream);
+
+ // Silently tolerate an empty line at the end.
+ if ((!s || !s[0]) && feof(s_stream))
+ return nullptr;
+
+ String line(s, Chomp);
+ if (parse_grpdb_entry(line))
+ return &s_group;
+ // Otherwise, proceed to the next line.
+ }
+}
+
+int initgroups(const char* user, gid_t extra_gid)
+{
+ size_t count = 0;
+ gid_t gids[32];
+ bool extra_gid_added = false;
+ setgrent();
+ while (auto* gr = getgrent()) {
+ for (auto* mem = gr->gr_mem; *mem; ++mem) {
+ if (!strcmp(*mem, user)) {
+ gids[count++] = gr->gr_gid;
+ if (gr->gr_gid == extra_gid)
+ extra_gid_added = true;
+ break;
+ }
+ }
+ }
+ endgrent();
+ if (!extra_gid_added)
+ gids[count++] = extra_gid;
+ return setgroups(count, gids);
+}
+}
diff --git a/Userland/Libraries/LibC/grp.h b/Userland/Libraries/LibC/grp.h
new file mode 100644
index 0000000000..a4cb414871
--- /dev/null
+++ b/Userland/Libraries/LibC/grp.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+struct group {
+ char* gr_name;
+ char* gr_passwd;
+ gid_t gr_gid;
+ char** gr_mem;
+};
+
+struct group* getgrent();
+void setgrent();
+void endgrent();
+struct group* getgrnam(const char* name);
+struct group* getgrgid(gid_t);
+
+int initgroups(const char* user, gid_t);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/iconv.h b/Userland/Libraries/LibC/iconv.h
new file mode 100644
index 0000000000..afcede1e71
--- /dev/null
+++ b/Userland/Libraries/LibC/iconv.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+typedef void* iconv_t;
+
+extern iconv_t iconv_open(const char* tocode, const char* fromcode);
+extern size_t iconv(iconv_t, char** inbuf, size_t* inbytesleft, char** outbuf, size_t* outbytesleft);
+extern int iconv_close(iconv_t);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/inttypes.h b/Userland/Libraries/LibC/inttypes.h
new file mode 100644
index 0000000000..b983f16699
--- /dev/null
+++ b/Userland/Libraries/LibC/inttypes.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <bits/stdint.h>
+
+#define PRId8 "d"
+#define PRId16 "d"
+#define PRId32 "d"
+#define PRId64 "lld"
+#define PRIi8 "d"
+#define PRIi16 "d"
+#define PRIi32 "d"
+#define PRIi64 "lld"
+#define PRIu8 "u"
+#define PRIo8 "o"
+#define PRIo16 "o"
+#define PRIo32 "o"
+#define PRIo64 "llo"
+#define PRIu16 "u"
+#define PRIu32 "u"
+#define PRIu64 "llu"
+#define PRIx8 "b"
+#define PRIx16 "w"
+#define PRIx32 "x"
+#define PRIX32 "X"
+#define PRIx64 "llx"
+#define PRIX64 "llX"
+
+#define __PRI64_PREFIX "ll"
+#define __PRIPTR_PREFIX
+
+#define PRIdPTR __PRIPTR_PREFIX "d"
+#define PRIiPTR __PRIPTR_PREFIX "i"
+#define PRIXPTR __PRIPTR_PREFIX "X"
+
+#define PRIdMAX __PRI64_PREFIX "d"
+#define PRIoMAX __PRI64_PREFIX "o"
+#define PRIuMAX __PRI64_PREFIX "u"
+
+#define SCNdMAX __PRI64_PREFIX "d"
+#define SCNoMAX __PRI64_PREFIX "o"
+#define SCNuMAX __PRI64_PREFIX "u"
+
+#define SCNu64 __PRI64_PREFIX "u"
+#define SCNd64 __PRI64_PREFIX "d"
diff --git a/Userland/Libraries/LibC/ioctl.cpp b/Userland/Libraries/LibC/ioctl.cpp
new file mode 100644
index 0000000000..c5fee3951e
--- /dev/null
+++ b/Userland/Libraries/LibC/ioctl.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+
+extern "C" {
+
+int ioctl(int fd, unsigned request, ...)
+{
+ va_list ap;
+ va_start(ap, request);
+ unsigned arg = va_arg(ap, unsigned);
+ int rc = syscall(SC_ioctl, fd, request, arg);
+ va_end(ap);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+}
diff --git a/Userland/Libraries/LibC/libcinit.cpp b/Userland/Libraries/LibC/libcinit.cpp
new file mode 100644
index 0000000000..83209b9ca4
--- /dev/null
+++ b/Userland/Libraries/LibC/libcinit.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Types.h>
+#include <assert.h>
+#include <sys/internals.h>
+#include <unistd.h>
+
+extern "C" {
+
+#ifdef NO_TLS
+int errno;
+#else
+__thread int errno;
+#endif
+char** environ;
+bool __environ_is_malloced;
+bool __stdio_is_initialized;
+
+void __libc_init()
+{
+ __malloc_init();
+ __stdio_init();
+}
+}
diff --git a/Userland/Libraries/LibC/libgen.cpp b/Userland/Libraries/LibC/libgen.cpp
new file mode 100644
index 0000000000..13b6e668b8
--- /dev/null
+++ b/Userland/Libraries/LibC/libgen.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <libgen.h>
+#include <string.h>
+
+static char dot[] = ".";
+static char slash[] = "/";
+
+char* dirname(char* path)
+{
+ if (path == nullptr)
+ return dot;
+
+ int len = strlen(path);
+ if (len == 0)
+ return dot;
+
+ while (len > 1 && path[len - 1] == '/') {
+ path[len - 1] = 0;
+ len--;
+ }
+
+ char* last_slash = strrchr(path, '/');
+ if (last_slash == nullptr)
+ return dot;
+
+ if (last_slash == path)
+ return slash;
+
+ *last_slash = 0;
+ return path;
+}
+
+char* basename(char* path)
+{
+ if (path == nullptr)
+ return dot;
+
+ int len = strlen(path);
+ if (len == 0)
+ return dot;
+
+ while (len > 1 && path[len - 1] == '/') {
+ path[len - 1] = 0;
+ len--;
+ }
+
+ char* last_slash = strrchr(path, '/');
+ if (last_slash == nullptr)
+ return path;
+
+ if (len == 1) {
+ ASSERT(last_slash == path);
+ ASSERT(path[0] == '/');
+ return slash;
+ }
+
+ return last_slash + 1;
+}
diff --git a/Userland/Libraries/LibC/libgen.h b/Userland/Libraries/LibC/libgen.h
new file mode 100644
index 0000000000..9cbf66b0e6
--- /dev/null
+++ b/Userland/Libraries/LibC/libgen.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+char* dirname(char* path);
+char* basename(char* path);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/limits.h b/Userland/Libraries/LibC/limits.h
new file mode 100644
index 0000000000..ac53662cb7
--- /dev/null
+++ b/Userland/Libraries/LibC/limits.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <bits/stdint.h>
+
+#ifndef PAGE_SIZE
+# define PAGE_SIZE 4096
+#endif
+
+#define PATH_MAX 4096
+#if !defined MAXPATHLEN && defined PATH_MAX
+# define MAXPATHLEN PATH_MAX
+#endif
+
+#define NAME_MAX 255
+
+#define PIPE_BUF 4096
+
+#define INT_MAX INT32_MAX
+#define INT_MIN INT32_MIN
+
+#define UINT_MAX UINT32_MAX
+#define UINT_MIN UINT32_MIN
+
+#define CHAR_BIT 8
+#define SCHAR_MIN (-128)
+#define SCHAR_MAX 127
+#define UCHAR_MAX 255
+
+#define LONG_MAX 2147483647L
+#define LONG_MIN (-LONG_MAX - 1L)
+
+#define ULONG_MAX 4294967295UL
+
+#define LONG_LONG_MAX 9223372036854775807LL
+#define LONG_LONG_MIN (-LONG_LONG_MAX - 1LL)
+
+#define ULONG_LONG_MAX 18446744073709551615ULL
+
+#define CHAR_MIN SCHAR_MIN
+#define CHAR_MAX SCHAR_MAX
+
+#define MB_LEN_MAX 16
+
+#define ARG_MAX 65536
+
+#define PTHREAD_STACK_MIN 65536
+
+#define SSIZE_MAX 2147483647
+
+#ifdef __USE_POSIX
+# include <bits/posix1_lim.h>
+#endif
diff --git a/Userland/Libraries/LibC/locale.cpp b/Userland/Libraries/LibC/locale.cpp
new file mode 100644
index 0000000000..5bfcfc0e24
--- /dev/null
+++ b/Userland/Libraries/LibC/locale.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LogStream.h>
+#include <assert.h>
+#include <locale.h>
+#include <stdio.h>
+
+extern "C" {
+
+static char default_decimal_point[] = ".";
+static char default_thousands_sep[] = ",";
+static char default_grouping[] = "\x03\x03";
+
+static char default_empty_string[] = "";
+static char default_empty_value = 127;
+
+static struct lconv default_locale = {
+ default_decimal_point,
+ default_thousands_sep,
+ default_grouping,
+ default_empty_string,
+ default_empty_string,
+ default_empty_string,
+ default_empty_string,
+ default_empty_string,
+ default_empty_string,
+ default_empty_string,
+ default_empty_value,
+ default_empty_value,
+ default_empty_value,
+ default_empty_value,
+ default_empty_value,
+ default_empty_value,
+ default_empty_value,
+ default_empty_value,
+ default_empty_value,
+ default_empty_value,
+ default_empty_value,
+ default_empty_value,
+ default_empty_value,
+ default_empty_value
+};
+
+char* setlocale(int category, const char* locale)
+{
+ dbgln("FIXME(LibC): setlocale({}, '{}')", category, locale);
+ return nullptr;
+}
+
+struct lconv* localeconv()
+{
+ return &default_locale;
+}
+}
diff --git a/Userland/Libraries/LibC/locale.h b/Userland/Libraries/LibC/locale.h
new file mode 100644
index 0000000000..60afc205ee
--- /dev/null
+++ b/Userland/Libraries/LibC/locale.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+enum {
+ LC_ALL,
+ LC_NUMERIC,
+ LC_CTYPE,
+ LC_COLLATE,
+ LC_TIME,
+ LC_MONETARY,
+};
+
+struct lconv {
+ char* decimal_point;
+ char* thousands_sep;
+ char* grouping;
+ char* int_curr_symbol;
+ char* currency_symbol;
+ char* mon_decimal_point;
+ char* mon_thousands_sep;
+ char* mon_grouping;
+ char* positive_sign;
+ char* negative_sign;
+ char int_frac_digits;
+ char frac_digits;
+ char p_cs_precedes;
+ char p_sep_by_space;
+ char n_cs_precedes;
+ char n_sep_by_space;
+ char p_sign_posn;
+ char n_sign_posn;
+ char int_p_cs_precedes;
+ char int_p_sep_by_space;
+ char int_n_cs_precedes;
+ char int_n_sep_by_space;
+ char int_p_sign_posn;
+ char int_n_sign_posn;
+};
+
+struct lconv* localeconv();
+char* setlocale(int category, const char* locale);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/malloc.cpp b/Userland/Libraries/LibC/malloc.cpp
new file mode 100644
index 0000000000..3d1c4abe2c
--- /dev/null
+++ b/Userland/Libraries/LibC/malloc.cpp
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/InlineLinkedList.h>
+#include <AK/LogStream.h>
+#include <AK/ScopedValueRollback.h>
+#include <AK/Vector.h>
+#include <LibThread/Lock.h>
+#include <assert.h>
+#include <mallocdefs.h>
+#include <serenity.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/internals.h>
+#include <sys/mman.h>
+
+// FIXME: Thread safety.
+
+//#define MALLOC_DEBUG
+#define RECYCLE_BIG_ALLOCATIONS
+
+#define PAGE_ROUND_UP(x) ((((size_t)(x)) + PAGE_SIZE - 1) & (~(PAGE_SIZE - 1)))
+
+ALWAYS_INLINE static void ue_notify_malloc(const void* ptr, size_t size)
+{
+ send_secret_data_to_userspace_emulator(1, size, (FlatPtr)ptr);
+}
+
+ALWAYS_INLINE static void ue_notify_free(const void* ptr)
+{
+ send_secret_data_to_userspace_emulator(2, (FlatPtr)ptr, 0);
+}
+
+ALWAYS_INLINE static void ue_notify_realloc(const void* ptr, size_t size)
+{
+ send_secret_data_to_userspace_emulator(3, size, (FlatPtr)ptr);
+}
+
+static LibThread::Lock& malloc_lock()
+{
+ static u32 lock_storage[sizeof(LibThread::Lock) / sizeof(u32)];
+ return *reinterpret_cast<LibThread::Lock*>(&lock_storage);
+}
+
+constexpr size_t number_of_chunked_blocks_to_keep_around_per_size_class = 4;
+constexpr size_t number_of_big_blocks_to_keep_around_per_size_class = 8;
+
+static bool s_log_malloc = false;
+static bool s_scrub_malloc = true;
+static bool s_scrub_free = true;
+static bool s_profiling = false;
+
+struct MallocStats {
+ size_t number_of_malloc_calls;
+
+ size_t number_of_big_allocator_hits;
+ size_t number_of_big_allocator_purge_hits;
+ size_t number_of_big_allocs;
+
+ size_t number_of_empty_block_hits;
+ size_t number_of_empty_block_purge_hits;
+ size_t number_of_block_allocs;
+ size_t number_of_blocks_full;
+
+ size_t number_of_free_calls;
+
+ size_t number_of_big_allocator_keeps;
+ size_t number_of_big_allocator_frees;
+
+ size_t number_of_freed_full_blocks;
+ size_t number_of_keeps;
+ size_t number_of_frees;
+};
+static MallocStats g_malloc_stats = {};
+
+struct Allocator {
+ size_t size { 0 };
+ size_t block_count { 0 };
+ size_t empty_block_count { 0 };
+ ChunkedBlock* empty_blocks[number_of_chunked_blocks_to_keep_around_per_size_class] { nullptr };
+ InlineLinkedList<ChunkedBlock> usable_blocks;
+ InlineLinkedList<ChunkedBlock> full_blocks;
+};
+
+struct BigAllocator {
+ Vector<BigAllocationBlock*, number_of_big_blocks_to_keep_around_per_size_class> blocks;
+};
+
+// Allocators will be initialized in __malloc_init.
+// We can not rely on global constructors to initialize them,
+// because they must be initialized before other global constructors
+// are run. Similarly, we can not allow global destructors to destruct
+// them. We could have used AK::NeverDestoyed to prevent the latter,
+// but it would have not helped with the former.
+static u8 g_allocators_storage[sizeof(Allocator) * num_size_classes];
+static u8 g_big_allocators_storage[sizeof(BigAllocator)];
+
+static inline Allocator (&allocators())[num_size_classes]
+{
+ return reinterpret_cast<Allocator(&)[num_size_classes]>(g_allocators_storage);
+}
+
+static inline BigAllocator (&big_allocators())[1]
+{
+ return reinterpret_cast<BigAllocator(&)[1]>(g_big_allocators_storage);
+}
+
+static Allocator* allocator_for_size(size_t size, size_t& good_size)
+{
+ for (size_t i = 0; size_classes[i]; ++i) {
+ if (size <= size_classes[i]) {
+ good_size = size_classes[i];
+ return &allocators()[i];
+ }
+ }
+ good_size = PAGE_ROUND_UP(size);
+ return nullptr;
+}
+
+#ifdef RECYCLE_BIG_ALLOCATIONS
+static BigAllocator* big_allocator_for_size(size_t size)
+{
+ if (size == 65536)
+ return &big_allocators()[0];
+ return nullptr;
+}
+#endif
+
+extern "C" {
+
+static void* os_alloc(size_t size, const char* name)
+{
+ auto* ptr = serenity_mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0, ChunkedBlock::block_size, name);
+ ASSERT(ptr != MAP_FAILED);
+ return ptr;
+}
+
+static void os_free(void* ptr, size_t size)
+{
+ int rc = munmap(ptr, size);
+ assert(rc == 0);
+}
+
+static void* malloc_impl(size_t size)
+{
+ LOCKER(malloc_lock());
+
+ if (s_log_malloc)
+ dbgprintf("LibC: malloc(%zu)\n", size);
+
+ if (!size)
+ return nullptr;
+
+ g_malloc_stats.number_of_malloc_calls++;
+
+ size_t good_size;
+ auto* allocator = allocator_for_size(size, good_size);
+
+ if (!allocator) {
+ size_t real_size = round_up_to_power_of_two(sizeof(BigAllocationBlock) + size, ChunkedBlock::block_size);
+#ifdef RECYCLE_BIG_ALLOCATIONS
+ if (auto* allocator = big_allocator_for_size(real_size)) {
+ if (!allocator->blocks.is_empty()) {
+ g_malloc_stats.number_of_big_allocator_hits++;
+ auto* block = allocator->blocks.take_last();
+ int rc = madvise(block, real_size, MADV_SET_NONVOLATILE);
+ bool this_block_was_purged = rc == 1;
+ if (rc < 0) {
+ perror("madvise");
+ ASSERT_NOT_REACHED();
+ }
+ if (mprotect(block, real_size, PROT_READ | PROT_WRITE) < 0) {
+ perror("mprotect");
+ ASSERT_NOT_REACHED();
+ }
+ if (this_block_was_purged) {
+ g_malloc_stats.number_of_big_allocator_purge_hits++;
+ new (block) BigAllocationBlock(real_size);
+ }
+
+ ue_notify_malloc(&block->m_slot[0], size);
+ return &block->m_slot[0];
+ }
+ }
+#endif
+ g_malloc_stats.number_of_big_allocs++;
+ auto* block = (BigAllocationBlock*)os_alloc(real_size, "malloc: BigAllocationBlock");
+ new (block) BigAllocationBlock(real_size);
+ ue_notify_malloc(&block->m_slot[0], size);
+ return &block->m_slot[0];
+ }
+
+ ChunkedBlock* block = nullptr;
+
+ for (block = allocator->usable_blocks.head(); block; block = block->next()) {
+ if (block->free_chunks())
+ break;
+ }
+
+ if (!block && allocator->empty_block_count) {
+ g_malloc_stats.number_of_empty_block_hits++;
+ block = allocator->empty_blocks[--allocator->empty_block_count];
+ int rc = madvise(block, ChunkedBlock::block_size, MADV_SET_NONVOLATILE);
+ bool this_block_was_purged = rc == 1;
+ if (rc < 0) {
+ perror("madvise");
+ ASSERT_NOT_REACHED();
+ }
+ rc = mprotect(block, ChunkedBlock::block_size, PROT_READ | PROT_WRITE);
+ if (rc < 0) {
+ perror("mprotect");
+ ASSERT_NOT_REACHED();
+ }
+ if (this_block_was_purged) {
+ g_malloc_stats.number_of_empty_block_purge_hits++;
+ new (block) ChunkedBlock(good_size);
+ }
+ allocator->usable_blocks.append(block);
+ }
+
+ if (!block) {
+ g_malloc_stats.number_of_block_allocs++;
+ char buffer[64];
+ snprintf(buffer, sizeof(buffer), "malloc: ChunkedBlock(%zu)", good_size);
+ block = (ChunkedBlock*)os_alloc(ChunkedBlock::block_size, buffer);
+ new (block) ChunkedBlock(good_size);
+ allocator->usable_blocks.append(block);
+ ++allocator->block_count;
+ }
+
+ --block->m_free_chunks;
+ void* ptr = block->m_freelist;
+ ASSERT(ptr);
+ block->m_freelist = block->m_freelist->next;
+ if (block->is_full()) {
+ g_malloc_stats.number_of_blocks_full++;
+#ifdef MALLOC_DEBUG
+ dbgprintf("Block %p is now full in size class %zu\n", block, good_size);
+#endif
+ allocator->usable_blocks.remove(block);
+ allocator->full_blocks.append(block);
+ }
+#ifdef MALLOC_DEBUG
+ dbgprintf("LibC: allocated %p (chunk in block %p, size %zu)\n", ptr, block, block->bytes_per_chunk());
+#endif
+
+ if (s_scrub_malloc)
+ memset(ptr, MALLOC_SCRUB_BYTE, block->m_size);
+
+ ue_notify_malloc(ptr, size);
+ return ptr;
+}
+
+static void free_impl(void* ptr)
+{
+ ScopedValueRollback rollback(errno);
+
+ if (!ptr)
+ return;
+
+ g_malloc_stats.number_of_free_calls++;
+
+ LOCKER(malloc_lock());
+
+ void* block_base = (void*)((FlatPtr)ptr & ChunkedBlock::ChunkedBlock::block_mask);
+ size_t magic = *(size_t*)block_base;
+
+ if (magic == MAGIC_BIGALLOC_HEADER) {
+ auto* block = (BigAllocationBlock*)block_base;
+#ifdef RECYCLE_BIG_ALLOCATIONS
+ if (auto* allocator = big_allocator_for_size(block->m_size)) {
+ if (allocator->blocks.size() < number_of_big_blocks_to_keep_around_per_size_class) {
+ g_malloc_stats.number_of_big_allocator_keeps++;
+ allocator->blocks.append(block);
+ size_t this_block_size = block->m_size;
+ if (mprotect(block, this_block_size, PROT_NONE) < 0) {
+ perror("mprotect");
+ ASSERT_NOT_REACHED();
+ }
+ if (madvise(block, this_block_size, MADV_SET_VOLATILE) != 0) {
+ perror("madvise");
+ ASSERT_NOT_REACHED();
+ }
+ return;
+ }
+ }
+#endif
+ g_malloc_stats.number_of_big_allocator_frees++;
+ os_free(block, block->m_size);
+ return;
+ }
+
+ assert(magic == MAGIC_PAGE_HEADER);
+ auto* block = (ChunkedBlock*)block_base;
+
+#ifdef MALLOC_DEBUG
+ dbgprintf("LibC: freeing %p in allocator %p (size=%zu, used=%zu)\n", ptr, block, block->bytes_per_chunk(), block->used_chunks());
+#endif
+
+ if (s_scrub_free)
+ memset(ptr, FREE_SCRUB_BYTE, block->bytes_per_chunk());
+
+ auto* entry = (FreelistEntry*)ptr;
+ entry->next = block->m_freelist;
+ block->m_freelist = entry;
+
+ if (block->is_full()) {
+ size_t good_size;
+ auto* allocator = allocator_for_size(block->m_size, good_size);
+#ifdef MALLOC_DEBUG
+ dbgprintf("Block %p no longer full in size class %zu\n", block, good_size);
+#endif
+ g_malloc_stats.number_of_freed_full_blocks++;
+ allocator->full_blocks.remove(block);
+ allocator->usable_blocks.prepend(block);
+ }
+
+ ++block->m_free_chunks;
+
+ if (!block->used_chunks()) {
+ size_t good_size;
+ auto* allocator = allocator_for_size(block->m_size, good_size);
+ if (allocator->block_count < number_of_chunked_blocks_to_keep_around_per_size_class) {
+#ifdef MALLOC_DEBUG
+ dbgprintf("Keeping block %p around for size class %zu\n", block, good_size);
+#endif
+ g_malloc_stats.number_of_keeps++;
+ allocator->usable_blocks.remove(block);
+ allocator->empty_blocks[allocator->empty_block_count++] = block;
+ mprotect(block, ChunkedBlock::block_size, PROT_NONE);
+ madvise(block, ChunkedBlock::block_size, MADV_SET_VOLATILE);
+ return;
+ }
+#ifdef MALLOC_DEBUG
+ dbgprintf("Releasing block %p for size class %zu\n", block, good_size);
+#endif
+ g_malloc_stats.number_of_frees++;
+ allocator->usable_blocks.remove(block);
+ --allocator->block_count;
+ os_free(block, ChunkedBlock::block_size);
+ }
+}
+
+[[gnu::flatten]] void* malloc(size_t size)
+{
+ void* ptr = malloc_impl(size);
+ if (s_profiling)
+ perf_event(PERF_EVENT_MALLOC, size, reinterpret_cast<FlatPtr>(ptr));
+ return ptr;
+}
+
+[[gnu::flatten]] void free(void* ptr)
+{
+ if (s_profiling)
+ perf_event(PERF_EVENT_FREE, reinterpret_cast<FlatPtr>(ptr), 0);
+ ue_notify_free(ptr);
+ free_impl(ptr);
+}
+
+void* calloc(size_t count, size_t size)
+{
+ size_t new_size = count * size;
+ auto* ptr = malloc(new_size);
+ if (ptr)
+ memset(ptr, 0, new_size);
+ return ptr;
+}
+
+size_t malloc_size(void* ptr)
+{
+ if (!ptr)
+ return 0;
+ LOCKER(malloc_lock());
+ void* page_base = (void*)((FlatPtr)ptr & ChunkedBlock::block_mask);
+ auto* header = (const CommonHeader*)page_base;
+ auto size = header->m_size;
+ if (header->m_magic == MAGIC_BIGALLOC_HEADER)
+ size -= sizeof(CommonHeader);
+ else
+ ASSERT(header->m_magic == MAGIC_PAGE_HEADER);
+ return size;
+}
+
+void* realloc(void* ptr, size_t size)
+{
+ if (!ptr)
+ return malloc(size);
+ if (!size)
+ return nullptr;
+
+ LOCKER(malloc_lock());
+ auto existing_allocation_size = malloc_size(ptr);
+
+ if (size <= existing_allocation_size) {
+ ue_notify_realloc(ptr, size);
+ return ptr;
+ }
+ auto* new_ptr = malloc(size);
+ if (new_ptr) {
+ memcpy(new_ptr, ptr, min(existing_allocation_size, size));
+ free(ptr);
+ }
+ return new_ptr;
+}
+
+void __malloc_init()
+{
+ new (&malloc_lock()) LibThread::Lock();
+ if (getenv("LIBC_NOSCRUB_MALLOC"))
+ s_scrub_malloc = false;
+ if (getenv("LIBC_NOSCRUB_FREE"))
+ s_scrub_free = false;
+ if (getenv("LIBC_LOG_MALLOC"))
+ s_log_malloc = true;
+ if (getenv("LIBC_PROFILE_MALLOC"))
+ s_profiling = true;
+
+ for (size_t i = 0; i < num_size_classes; ++i) {
+ new (&allocators()[i]) Allocator();
+ allocators()[i].size = size_classes[i];
+ }
+
+ new (&big_allocators()[0])(BigAllocator);
+}
+
+void serenity_dump_malloc_stats()
+{
+ dbgln("# malloc() calls: {}", g_malloc_stats.number_of_malloc_calls);
+ dbgln();
+ dbgln("big alloc hits: {}", g_malloc_stats.number_of_big_allocator_hits);
+ dbgln("big alloc hits that were purged: {}", g_malloc_stats.number_of_big_allocator_purge_hits);
+ dbgln("big allocs: {}", g_malloc_stats.number_of_big_allocs);
+ dbgln();
+ dbgln("empty block hits: {}", g_malloc_stats.number_of_empty_block_hits);
+ dbgln("empty block hits that were purged: {}", g_malloc_stats.number_of_empty_block_purge_hits);
+ dbgln("block allocs: {}", g_malloc_stats.number_of_block_allocs);
+ dbgln("filled blocks: {}", g_malloc_stats.number_of_blocks_full);
+ dbgln();
+ dbgln("# free() calls: {}", g_malloc_stats.number_of_free_calls);
+ dbgln();
+ dbgln("big alloc keeps: {}", g_malloc_stats.number_of_big_allocator_keeps);
+ dbgln("big alloc frees: {}", g_malloc_stats.number_of_big_allocator_frees);
+ dbgln();
+ dbgln("full block frees: {}", g_malloc_stats.number_of_freed_full_blocks);
+ dbgln("number of keeps: {}", g_malloc_stats.number_of_keeps);
+ dbgln("number of frees: {}", g_malloc_stats.number_of_frees);
+}
+}
diff --git a/Userland/Libraries/LibC/mallocdefs.h b/Userland/Libraries/LibC/mallocdefs.h
new file mode 100644
index 0000000000..d639c512cd
--- /dev/null
+++ b/Userland/Libraries/LibC/mallocdefs.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/InlineLinkedList.h>
+#include <AK/Types.h>
+
+#define MAGIC_PAGE_HEADER 0x42657274 // 'Bert'
+#define MAGIC_BIGALLOC_HEADER 0x42697267 // 'Birg'
+#define MALLOC_SCRUB_BYTE 0xdc
+#define FREE_SCRUB_BYTE 0xed
+
+static constexpr unsigned short size_classes[] = { 8, 16, 32, 64, 128, 256, 500, 1016, 2032, 4088, 8184, 16376, 32752, 0 };
+static constexpr size_t num_size_classes = (sizeof(size_classes) / sizeof(unsigned short)) - 1;
+
+struct CommonHeader {
+ size_t m_magic;
+ size_t m_size;
+};
+
+struct BigAllocationBlock : public CommonHeader {
+ BigAllocationBlock(size_t size)
+ {
+ m_magic = MAGIC_BIGALLOC_HEADER;
+ m_size = size;
+ }
+ unsigned char* m_slot[0];
+};
+
+struct FreelistEntry {
+ FreelistEntry* next;
+};
+
+struct ChunkedBlock
+ : public CommonHeader
+ , public InlineLinkedListNode<ChunkedBlock> {
+
+ static constexpr size_t block_size = 64 * KiB;
+ static constexpr size_t block_mask = ~(block_size - 1);
+
+ ChunkedBlock(size_t bytes_per_chunk)
+ {
+ m_magic = MAGIC_PAGE_HEADER;
+ m_size = bytes_per_chunk;
+ m_free_chunks = chunk_capacity();
+ m_freelist = (FreelistEntry*)chunk(0);
+ for (size_t i = 0; i < chunk_capacity(); ++i) {
+ auto* entry = (FreelistEntry*)chunk(i);
+ if (i != chunk_capacity() - 1)
+ entry->next = (FreelistEntry*)chunk(i + 1);
+ else
+ entry->next = nullptr;
+ }
+ }
+
+ ChunkedBlock* m_prev { nullptr };
+ ChunkedBlock* m_next { nullptr };
+ FreelistEntry* m_freelist { nullptr };
+ size_t m_free_chunks { 0 };
+ [[gnu::aligned(8)]] unsigned char m_slot[0];
+
+ void* chunk(size_t index)
+ {
+ return &m_slot[index * m_size];
+ }
+ bool is_full() const { return m_free_chunks == 0; }
+ size_t bytes_per_chunk() const { return m_size; }
+ size_t free_chunks() const { return m_free_chunks; }
+ size_t used_chunks() const { return chunk_capacity() - m_free_chunks; }
+ size_t chunk_capacity() const { return (block_size - sizeof(ChunkedBlock)) / m_size; }
+};
diff --git a/Userland/Libraries/LibC/memory.h b/Userland/Libraries/LibC/memory.h
new file mode 100644
index 0000000000..506244f4ce
--- /dev/null
+++ b/Userland/Libraries/LibC/memory.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <string.h>
diff --git a/Userland/Libraries/LibC/mman.cpp b/Userland/Libraries/LibC/mman.cpp
new file mode 100644
index 0000000000..3df02a46da
--- /dev/null
+++ b/Userland/Libraries/LibC/mman.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <errno.h>
+#include <mman.h>
+#include <stdio.h>
+#include <string.h>
+
+extern "C" {
+
+void* serenity_mmap(void* addr, size_t size, int prot, int flags, int fd, off_t offset, size_t alignment, const char* name)
+{
+ Syscall::SC_mmap_params params { (uintptr_t)addr, size, alignment, prot, flags, fd, offset, { name, name ? strlen(name) : 0 } };
+ ssize_t rc = syscall(SC_mmap, &params);
+ if (rc < 0 && -rc < EMAXERRNO) {
+ errno = -rc;
+ return MAP_FAILED;
+ }
+ return (void*)rc;
+}
+
+void* mmap(void* addr, size_t size, int prot, int flags, int fd, off_t offset)
+{
+ return serenity_mmap(addr, size, prot, flags, fd, offset, PAGE_SIZE, nullptr);
+}
+
+void* mmap_with_name(void* addr, size_t size, int prot, int flags, int fd, off_t offset, const char* name)
+{
+ return serenity_mmap(addr, size, prot, flags, fd, offset, PAGE_SIZE, name);
+}
+
+void* mremap(void* old_address, size_t old_size, size_t new_size, int flags)
+{
+ Syscall::SC_mremap_params params { (uintptr_t)old_address, old_size, new_size, flags };
+ ssize_t rc = syscall(SC_mremap, &params);
+ if (rc < 0 && -rc < EMAXERRNO) {
+ errno = -rc;
+ return MAP_FAILED;
+ }
+ return (void*)rc;
+}
+
+int munmap(void* addr, size_t size)
+{
+ int rc = syscall(SC_munmap, addr, size);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int mprotect(void* addr, size_t size, int prot)
+{
+ int rc = syscall(SC_mprotect, addr, size, prot);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int set_mmap_name(void* addr, size_t size, const char* name)
+{
+ if (!name) {
+ errno = EFAULT;
+ return -1;
+ }
+ Syscall::SC_set_mmap_name_params params { addr, size, { name, strlen(name) } };
+ int rc = syscall(SC_set_mmap_name, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int madvise(void* address, size_t size, int advice)
+{
+ int rc = syscall(SC_madvise, address, size, advice);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int minherit(void* address, size_t size, int inherit)
+{
+ int rc = syscall(SC_minherit, address, size, inherit);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+void* allocate_tls(size_t size)
+{
+ int rc = syscall(SC_allocate_tls, size);
+ if (rc < 0 && -rc < EMAXERRNO) {
+ errno = -rc;
+ return MAP_FAILED;
+ }
+ return (void*)rc;
+}
+}
diff --git a/Userland/Libraries/LibC/mman.h b/Userland/Libraries/LibC/mman.h
new file mode 100644
index 0000000000..5b02c59902
--- /dev/null
+++ b/Userland/Libraries/LibC/mman.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#define MAP_FILE 0x00
+#define MAP_SHARED 0x01
+#define MAP_PRIVATE 0x02
+#define MAP_FIXED 0x10
+#define MAP_ANONYMOUS 0x20
+#define MAP_ANON MAP_ANONYMOUS
+#define MAP_STACK 0x40
+#define MAP_NORESERVE 0x80
+
+#define PROT_READ 0x1
+#define PROT_WRITE 0x2
+#define PROT_EXEC 0x4
+#define PROT_NONE 0x0
+
+#define MAP_FAILED ((void*)-1)
+
+#define MADV_SET_VOLATILE 0x100
+#define MADV_SET_NONVOLATILE 0x200
+#define MADV_GET_VOLATILE 0x400
+
+#define MAP_INHERIT_ZERO 1
+
+__BEGIN_DECLS
+
+void* mmap(void* addr, size_t, int prot, int flags, int fd, off_t);
+void* mmap_with_name(void* addr, size_t, int prot, int flags, int fd, off_t, const char* name);
+void* serenity_mmap(void* addr, size_t, int prot, int flags, int fd, off_t, size_t alignment, const char* name);
+void* mremap(void* old_address, size_t old_size, size_t new_size, int flags);
+int munmap(void*, size_t);
+int mprotect(void*, size_t, int prot);
+int set_mmap_name(void*, size_t, const char*);
+int madvise(void*, size_t, int advice);
+int minherit(void*, size_t, int inherit);
+void* allocate_tls(size_t);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/mntent.cpp b/Userland/Libraries/LibC/mntent.cpp
new file mode 100644
index 0000000000..c1f234bfb2
--- /dev/null
+++ b/Userland/Libraries/LibC/mntent.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <mntent.h>
+
+extern "C" {
+
+struct mntent* getmntent(FILE*)
+{
+ ASSERT_NOT_REACHED();
+ return nullptr;
+}
+}
diff --git a/Userland/Libraries/LibC/mntent.h b/Userland/Libraries/LibC/mntent.h
new file mode 100644
index 0000000000..fc319b3dc9
--- /dev/null
+++ b/Userland/Libraries/LibC/mntent.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <stdio.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+#define MOUNTED "/etc/mtab"
+#define MNTTAB "/etc/fstab"
+
+struct mntent {
+ char* mnt_fsname;
+ char* mnt_dir;
+ char* mnt_type;
+ char* mnt_opts;
+ int mnt_freq;
+ int mnt_passno;
+};
+
+struct mntent* getmntent(FILE* stream);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/net/if.h b/Userland/Libraries/LibC/net/if.h
new file mode 100644
index 0000000000..b82258a7b0
--- /dev/null
+++ b/Userland/Libraries/LibC/net/if.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/socket.h>
+
+__BEGIN_DECLS
+
+struct ifreq {
+#define IFNAMSIZ 16
+ char ifr_name[IFNAMSIZ];
+ union {
+ struct sockaddr ifru_addr;
+ struct sockaddr ifru_dstaddr;
+ struct sockaddr ifru_broadaddr;
+ struct sockaddr ifru_netmask;
+ struct sockaddr ifru_hwaddr;
+ short ifru_flags;
+ int ifru_metric;
+ int64_t ifru_vnetid;
+ uint64_t ifru_media;
+ void* ifru_data;
+ unsigned int ifru_index;
+ } ifr_ifru;
+
+ // clang-format off
+#define ifr_addr ifr_ifru.ifru_addr // address
+#define ifr_dstaddr ifr_ifru.ifru_dstaddr // other end of p-to-p link
+#define ifr_broadaddr ifr_ifru.ifru_broadaddr // broadcast address
+#define ifr_netmask ifr_ifru.ifru_netmask // network mask
+#define ifr_flags ifr_ifru.ifru_flags // flags
+#define ifr_metric ifr_ifru.ifru_metric // metric
+#define ifr_mtu ifr_ifru.ifru_metric // mtu (overload)
+#define ifr_hardmtu ifr_ifru.ifru_metric // hardmtu (overload)
+#define ifr_media ifr_ifru.ifru_media // media options
+#define ifr_rdomainid ifr_ifru.ifru_metric // VRF instance (overload)
+#define ifr_vnetid ifr_ifru.ifru_vnetid // Virtual Net Id
+#define ifr_ttl ifr_ifru.ifru_metric // tunnel TTL (overload)
+#define ifr_data ifr_ifru.ifru_data // for use by interface
+#define ifr_index ifr_ifru.ifru_index // interface index
+#define ifr_llprio ifr_ifru.ifru_metric // link layer priority
+#define ifr_hwaddr ifr_ifru.ifru_hwaddr // MAC address
+ // clang-format on
+};
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/net/route.h b/Userland/Libraries/LibC/net/route.h
new file mode 100644
index 0000000000..a059c28708
--- /dev/null
+++ b/Userland/Libraries/LibC/net/route.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, Marios Prokopakis <mariosprokopakis@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/socket.h>
+
+struct rtentry {
+ struct sockaddr rt_gateway; /* the gateway address */
+ struct sockaddr rt_genmask; /* the target network mask */
+ unsigned short int rt_flags;
+ char* rt_dev;
+ /* FIXME: complete the struct */
+};
+
+#define RTF_UP 0x1 /* do not delete the route */
+#define RTF_GATEWAY 0x2 /* the route is a gateway and not an end host */
diff --git a/Userland/Libraries/LibC/netdb.cpp b/Userland/Libraries/LibC/netdb.cpp
new file mode 100644
index 0000000000..6f0af1bf68
--- /dev/null
+++ b/Userland/Libraries/LibC/netdb.cpp
@@ -0,0 +1,590 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/ByteBuffer.h>
+#include <AK/ScopeGuard.h>
+#include <AK/String.h>
+#include <Kernel/Net/IPv4.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+extern "C" {
+
+int h_errno;
+
+static hostent __gethostbyname_buffer;
+static in_addr_t __gethostbyname_address;
+static in_addr_t* __gethostbyname_address_list_buffer[2];
+
+static hostent __gethostbyaddr_buffer;
+static in_addr_t* __gethostbyaddr_address_list_buffer[2];
+
+// Get service entry buffers and file information for the getservent() family of functions.
+static FILE* services_file = nullptr;
+static const char* services_path = "/etc/services";
+
+static bool fill_getserv_buffers(const char* line, ssize_t read);
+static servent __getserv_buffer;
+static String __getserv_name_buffer;
+static String __getserv_protocol_buffer;
+static int __getserv_port_buffer;
+static Vector<ByteBuffer> __getserv_alias_list_buffer;
+static Vector<char*> __getserv_alias_list;
+static bool keep_service_file_open = false;
+static ssize_t service_file_offset = 0;
+
+// Get protocol entry buffers and file information for the getprotent() family of functions.
+static FILE* protocols_file = nullptr;
+static const char* protocols_path = "/etc/protocols";
+
+static bool fill_getproto_buffers(const char* line, ssize_t read);
+static protoent __getproto_buffer;
+static String __getproto_name_buffer;
+static Vector<ByteBuffer> __getproto_alias_list_buffer;
+static Vector<char*> __getproto_alias_list;
+static int __getproto_protocol_buffer;
+static bool keep_protocols_file_open = false;
+static ssize_t protocol_file_offset = 0;
+
+static int connect_to_lookup_server()
+{
+ int fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (fd < 0) {
+ perror("socket");
+ return -1;
+ }
+
+ sockaddr_un address {
+ AF_LOCAL,
+ "/tmp/portal/lookup"
+ };
+
+ if (connect(fd, (const sockaddr*)&address, sizeof(address)) < 0) {
+ perror("connect_to_lookup_server");
+ close(fd);
+ return -1;
+ }
+ return fd;
+}
+
+static String gethostbyname_name_buffer;
+
+hostent* gethostbyname(const char* name)
+{
+ auto ipv4_address = IPv4Address::from_string(name);
+
+ if (ipv4_address.has_value()) {
+ gethostbyname_name_buffer = ipv4_address.value().to_string();
+ __gethostbyname_buffer.h_name = const_cast<char*>(gethostbyname_name_buffer.characters());
+ __gethostbyname_buffer.h_aliases = nullptr;
+ __gethostbyname_buffer.h_addrtype = AF_INET;
+ new (&__gethostbyname_address) IPv4Address(ipv4_address.value());
+ __gethostbyname_address_list_buffer[0] = &__gethostbyname_address;
+ __gethostbyname_address_list_buffer[1] = nullptr;
+ __gethostbyname_buffer.h_addr_list = (char**)__gethostbyname_address_list_buffer;
+ __gethostbyname_buffer.h_length = 4;
+
+ return &__gethostbyname_buffer;
+ }
+
+ int fd = connect_to_lookup_server();
+ if (fd < 0)
+ return nullptr;
+
+ auto close_fd_on_exit = ScopeGuard([fd] {
+ close(fd);
+ });
+
+ auto line = String::format("L%s\n", name);
+ int nsent = write(fd, line.characters(), line.length());
+ if (nsent < 0) {
+ perror("write");
+ return nullptr;
+ }
+
+ ASSERT((size_t)nsent == line.length());
+
+ char buffer[1024];
+ int nrecv = read(fd, buffer, sizeof(buffer) - 1);
+ if (nrecv < 0) {
+ perror("recv");
+ return nullptr;
+ }
+
+ if (!memcmp(buffer, "Not found.", sizeof("Not found.") - 1))
+ return nullptr;
+
+ auto responses = String(buffer, nrecv).split('\n');
+ if (responses.is_empty())
+ return nullptr;
+
+ auto& response = responses[0];
+
+ int rc = inet_pton(AF_INET, response.characters(), &__gethostbyname_address);
+ if (rc <= 0)
+ return nullptr;
+
+ gethostbyname_name_buffer = name;
+ __gethostbyname_buffer.h_name = const_cast<char*>(gethostbyname_name_buffer.characters());
+ __gethostbyname_buffer.h_aliases = nullptr;
+ __gethostbyname_buffer.h_addrtype = AF_INET;
+ __gethostbyname_address_list_buffer[0] = &__gethostbyname_address;
+ __gethostbyname_address_list_buffer[1] = nullptr;
+ __gethostbyname_buffer.h_addr_list = (char**)__gethostbyname_address_list_buffer;
+ __gethostbyname_buffer.h_length = 4;
+
+ return &__gethostbyname_buffer;
+}
+
+static String gethostbyaddr_name_buffer;
+
+hostent* gethostbyaddr(const void* addr, socklen_t addr_size, int type)
+{
+
+ if (type != AF_INET) {
+ errno = EAFNOSUPPORT;
+ return nullptr;
+ }
+
+ if (addr_size < sizeof(in_addr)) {
+ errno = EINVAL;
+ return nullptr;
+ }
+
+ int fd = connect_to_lookup_server();
+ if (fd < 0)
+ return nullptr;
+
+ auto close_fd_on_exit = ScopeGuard([fd] {
+ close(fd);
+ });
+
+ IPv4Address ipv4_address((const u8*)&((const in_addr*)addr)->s_addr);
+
+ auto line = String::format("R%d.%d.%d.%d.in-addr.arpa\n",
+ ipv4_address[3],
+ ipv4_address[2],
+ ipv4_address[1],
+ ipv4_address[0]);
+ int nsent = write(fd, line.characters(), line.length());
+ if (nsent < 0) {
+ perror("write");
+ return nullptr;
+ }
+
+ ASSERT((size_t)nsent == line.length());
+
+ char buffer[1024];
+ int nrecv = read(fd, buffer, sizeof(buffer) - 1);
+ if (nrecv < 0) {
+ perror("recv");
+ return nullptr;
+ }
+
+ if (!memcmp(buffer, "Not found.", sizeof("Not found.") - 1))
+ return nullptr;
+
+ auto responses = String(buffer, nrecv).split('\n');
+ if (responses.is_empty())
+ return nullptr;
+
+ gethostbyaddr_name_buffer = responses[0];
+ __gethostbyaddr_buffer.h_name = const_cast<char*>(gethostbyaddr_name_buffer.characters());
+ __gethostbyaddr_buffer.h_aliases = nullptr;
+ __gethostbyaddr_buffer.h_addrtype = AF_INET;
+ // FIXME: Should we populate the hostent's address list here with a sockaddr_in for the provided host?
+ __gethostbyaddr_address_list_buffer[0] = nullptr;
+ __gethostbyaddr_buffer.h_addr_list = (char**)__gethostbyaddr_address_list_buffer;
+ __gethostbyaddr_buffer.h_length = 4;
+
+ return &__gethostbyaddr_buffer;
+}
+
+struct servent* getservent()
+{
+ //If the services file is not open, attempt to open it and return null if it fails.
+ if (!services_file) {
+ services_file = fopen(services_path, "r");
+
+ if (!services_file) {
+ perror("error opening services file");
+ return nullptr;
+ }
+ }
+
+ if (fseek(services_file, service_file_offset, SEEK_SET) != 0) {
+ perror("error seeking file");
+ fclose(services_file);
+ return nullptr;
+ }
+ char* line = nullptr;
+ size_t len = 0;
+ ssize_t read;
+
+ auto free_line_on_exit = ScopeGuard([line] {
+ if (line) {
+ free(line);
+ }
+ });
+
+ // Read lines from services file until an actual service name is found.
+ do {
+ read = getline(&line, &len, services_file);
+ service_file_offset += read;
+ if (read > 0 && (line[0] >= 65 && line[0] <= 122)) {
+ break;
+ }
+ } while (read != -1);
+ if (read == -1) {
+ fclose(services_file);
+ services_file = nullptr;
+ service_file_offset = 0;
+ return nullptr;
+ }
+
+ servent* service_entry = nullptr;
+ if (!fill_getserv_buffers(line, read))
+ return nullptr;
+
+ __getserv_buffer.s_name = const_cast<char*>(__getserv_name_buffer.characters());
+ __getserv_buffer.s_port = __getserv_port_buffer;
+ __getserv_buffer.s_proto = const_cast<char*>(__getserv_protocol_buffer.characters());
+
+ __getserv_alias_list.clear_with_capacity();
+ __getserv_alias_list.ensure_capacity(__getserv_alias_list_buffer.size() + 1);
+ for (auto& alias : __getserv_alias_list_buffer)
+ __getserv_alias_list.unchecked_append(reinterpret_cast<char*>(alias.data()));
+ __getserv_alias_list.unchecked_append(nullptr);
+
+ __getserv_buffer.s_aliases = __getserv_alias_list.data();
+ service_entry = &__getserv_buffer;
+
+ if (!keep_service_file_open) {
+ endservent();
+ }
+ return service_entry;
+}
+
+struct servent* getservbyname(const char* name, const char* protocol)
+{
+ bool previous_file_open_setting = keep_service_file_open;
+ setservent(1);
+ struct servent* current_service = nullptr;
+ auto service_file_handler = ScopeGuard([previous_file_open_setting] {
+ if (!previous_file_open_setting) {
+ endservent();
+ }
+ });
+
+ while (true) {
+ current_service = getservent();
+ if (current_service == nullptr)
+ break;
+ else if (!protocol && strcmp(current_service->s_name, name) == 0)
+ break;
+ else if (strcmp(current_service->s_name, name) == 0 && strcmp(current_service->s_proto, protocol) == 0)
+ break;
+ }
+
+ return current_service;
+}
+
+struct servent* getservbyport(int port, const char* protocol)
+{
+ bool previous_file_open_setting = keep_service_file_open;
+ setservent(1);
+ struct servent* current_service = nullptr;
+ auto service_file_handler = ScopeGuard([previous_file_open_setting] {
+ if (!previous_file_open_setting) {
+ endservent();
+ }
+ });
+ while (true) {
+ current_service = getservent();
+ if (current_service == nullptr)
+ break;
+ else if (!protocol && current_service->s_port == port)
+ break;
+ else if (current_service->s_port == port && (strcmp(current_service->s_proto, protocol) == 0))
+ break;
+ }
+
+ return current_service;
+}
+
+void setservent(int stay_open)
+{
+ if (!services_file) {
+ services_file = fopen(services_path, "r");
+
+ if (!services_file) {
+ perror("error opening services file");
+ return;
+ }
+ }
+ rewind(services_file);
+ keep_service_file_open = stay_open;
+ service_file_offset = 0;
+}
+
+void endservent()
+{
+ if (!services_file) {
+ return;
+ }
+ fclose(services_file);
+ services_file = nullptr;
+}
+
+// Fill the service entry buffer with the information contained
+// in the currently read line, returns true if successful,
+// false if failure occurs.
+static bool fill_getserv_buffers(const char* line, ssize_t read)
+{
+ //Splitting the line by tab delimiter and filling the servent buffers name, port, and protocol members.
+ String string_line = String(line, read);
+ string_line.replace(" ", "\t", true);
+ auto split_line = string_line.split('\t');
+
+ // This indicates an incorrect file format.
+ // Services file entries should always at least contain
+ // name and port/protocol, separated by tabs.
+ if (split_line.size() < 2) {
+ fprintf(stderr, "getservent(): malformed services file\n");
+ return false;
+ }
+ __getserv_name_buffer = split_line[0];
+
+ auto port_protocol_split = String(split_line[1]).split('/');
+ if (port_protocol_split.size() < 2) {
+ fprintf(stderr, "getservent(): malformed services file\n");
+ return false;
+ }
+ auto number = port_protocol_split[0].to_int();
+ if (!number.has_value())
+ return false;
+
+ __getserv_port_buffer = number.value();
+
+ // Remove any annoying whitespace at the end of the protocol.
+ port_protocol_split[1].replace(" ", "", true);
+ port_protocol_split[1].replace("\t", "", true);
+ port_protocol_split[1].replace("\n", "", true);
+
+ __getserv_protocol_buffer = port_protocol_split[1];
+ __getserv_alias_list_buffer.clear();
+
+ // If there are aliases for the service, we will fill the alias list buffer.
+ if (split_line.size() > 2 && !split_line[2].starts_with('#')) {
+
+ for (size_t i = 2; i < split_line.size(); i++) {
+ if (split_line[i].starts_with('#')) {
+ break;
+ }
+ auto alias = split_line[i].to_byte_buffer();
+ alias.append("\0", sizeof(char));
+ __getserv_alias_list_buffer.append(alias);
+ }
+ }
+
+ return true;
+}
+
+struct protoent* getprotoent()
+{
+ // If protocols file isn't open, attempt to open and return null on failure.
+ if (!protocols_file) {
+ protocols_file = fopen(protocols_path, "r");
+
+ if (!protocols_file) {
+ perror("error opening protocols file");
+ return nullptr;
+ }
+ }
+
+ if (fseek(protocols_file, protocol_file_offset, SEEK_SET) != 0) {
+ perror("error seeking protocols file");
+ fclose(protocols_file);
+ return nullptr;
+ }
+
+ char* line = nullptr;
+ size_t len = 0;
+ ssize_t read;
+
+ auto free_line_on_exit = ScopeGuard([line] {
+ if (line) {
+ free(line);
+ }
+ });
+
+ do {
+ read = getline(&line, &len, protocols_file);
+ protocol_file_offset += read;
+ if (read > 0 && (line[0] >= 65 && line[0] <= 122)) {
+ break;
+ }
+ } while (read != -1);
+
+ if (read == -1) {
+ fclose(protocols_file);
+ protocols_file = nullptr;
+ protocol_file_offset = 0;
+ return nullptr;
+ }
+
+ struct protoent* protocol_entry = nullptr;
+ if (!fill_getproto_buffers(line, read))
+ return nullptr;
+
+ __getproto_buffer.p_name = const_cast<char*>(__getproto_name_buffer.characters());
+ __getproto_buffer.p_proto = __getproto_protocol_buffer;
+
+ __getproto_alias_list.clear_with_capacity();
+ __getproto_alias_list.ensure_capacity(__getproto_alias_list_buffer.size() + 1);
+ for (auto& alias : __getproto_alias_list_buffer)
+ __getproto_alias_list.unchecked_append(reinterpret_cast<char*>(alias.data()));
+ __getserv_alias_list.unchecked_append(nullptr);
+
+ __getproto_buffer.p_aliases = __getproto_alias_list.data();
+ protocol_entry = &__getproto_buffer;
+
+ if (!keep_protocols_file_open)
+ endprotoent();
+
+ return protocol_entry;
+}
+
+struct protoent* getprotobyname(const char* name)
+{
+ bool previous_file_open_setting = keep_protocols_file_open;
+ setprotoent(1);
+ struct protoent* current_protocol = nullptr;
+ auto protocol_file_handler = ScopeGuard([previous_file_open_setting] {
+ if (!previous_file_open_setting) {
+ endprotoent();
+ }
+ });
+
+ while (true) {
+ current_protocol = getprotoent();
+ if (current_protocol == nullptr)
+ break;
+ else if (strcmp(current_protocol->p_name, name) == 0)
+ break;
+ }
+
+ return current_protocol;
+}
+
+struct protoent* getprotobynumber(int proto)
+{
+ bool previous_file_open_setting = keep_protocols_file_open;
+ setprotoent(1);
+ struct protoent* current_protocol = nullptr;
+ auto protocol_file_handler = ScopeGuard([previous_file_open_setting] {
+ if (!previous_file_open_setting) {
+ endprotoent();
+ }
+ });
+
+ while (true) {
+ current_protocol = getprotoent();
+ if (current_protocol == nullptr)
+ break;
+ else if (current_protocol->p_proto == proto)
+ break;
+ }
+
+ return current_protocol;
+}
+
+void setprotoent(int stay_open)
+{
+ if (!protocols_file) {
+ protocols_file = fopen(protocols_path, "r");
+
+ if (!protocols_file) {
+ perror("setprotoent(): error opening protocols file");
+ return;
+ }
+ }
+ rewind(protocols_file);
+ keep_protocols_file_open = stay_open;
+ protocol_file_offset = 0;
+}
+
+void endprotoent()
+{
+ if (!protocols_file) {
+ return;
+ }
+ fclose(protocols_file);
+ protocols_file = nullptr;
+}
+
+static bool fill_getproto_buffers(const char* line, ssize_t read)
+{
+ String string_line = String(line, read);
+ string_line.replace(" ", "\t", true);
+ auto split_line = string_line.split('\t');
+
+ // This indicates an incorrect file format. Protocols file entries should
+ // always have at least a name and a protocol.
+ if (split_line.size() < 2) {
+ fprintf(stderr, "getprotoent(): malformed protocols file\n");
+ return false;
+ }
+ __getproto_name_buffer = split_line[0];
+
+ auto number = split_line[1].to_int();
+ if (!number.has_value())
+ return false;
+
+ __getproto_protocol_buffer = number.value();
+
+ __getproto_alias_list_buffer.clear();
+
+ // If there are aliases for the protocol, we will fill the alias list buffer.
+ if (split_line.size() > 2 && !split_line[2].starts_with('#')) {
+
+ for (size_t i = 2; i < split_line.size(); i++) {
+ if (split_line[i].starts_with('#'))
+ break;
+ auto alias = split_line[i].to_byte_buffer();
+ alias.append("\0", sizeof(char));
+ __getproto_alias_list_buffer.append(alias);
+ }
+ }
+
+ return true;
+}
+}
diff --git a/Userland/Libraries/LibC/netdb.h b/Userland/Libraries/LibC/netdb.h
new file mode 100644
index 0000000000..c2bd5a130f
--- /dev/null
+++ b/Userland/Libraries/LibC/netdb.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+struct hostent {
+ char* h_name;
+ char** h_aliases;
+ int h_addrtype;
+ int h_length;
+ char** h_addr_list;
+#define h_addr h_addr_list[0]
+};
+
+struct hostent* gethostbyname(const char*);
+struct hostent* gethostbyaddr(const void* addr, socklen_t len, int type);
+
+struct servent {
+ char* s_name;
+ char** s_aliases;
+ int s_port;
+ char* s_proto;
+};
+
+struct servent* getservent();
+struct servent* getservbyname(const char* name, const char* protocol);
+struct servent* getservbyport(int port, const char* protocol);
+void setservent(int stay_open);
+void endservent();
+
+struct protoent {
+ char* p_name;
+ char** p_aliases;
+ int p_proto;
+};
+
+void endprotoent();
+struct protoent* getprotobyname(const char* name);
+struct protoent* getprotobynumber(int proto);
+struct protoent* getprotoent();
+void setprotoent(int stay_open);
+
+extern int h_errno;
+
+#define HOST_NOT_FOUND 101
+#define NO_DATA 102
+#define NO_RECOVERY 103
+#define TRY_AGAIN 104
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/netinet/in.h b/Userland/Libraries/LibC/netinet/in.h
new file mode 100644
index 0000000000..a08040ca2a
--- /dev/null
+++ b/Userland/Libraries/LibC/netinet/in.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <bits/stdint.h>
+#include <sys/cdefs.h>
+#include <sys/socket.h>
+
+__BEGIN_DECLS
+
+typedef uint32_t in_addr_t;
+in_addr_t inet_addr(const char*);
+
+#define INADDR_ANY ((in_addr_t)0)
+#define INADDR_NONE ((in_addr_t)-1)
+#define INADDR_LOOPBACK 0x7f000001
+
+#define IN_LOOPBACKNET 127
+
+#define IP_TTL 2
+
+#define IPPORT_RESERVED 1024
+#define IPPORT_USERRESERVED 5000
+
+typedef uint16_t in_port_t;
+
+struct in_addr {
+ uint32_t s_addr;
+};
+
+struct sockaddr_in {
+ sa_family_t sin_family;
+ in_port_t sin_port;
+ struct in_addr sin_addr;
+ char sin_zero[8];
+};
+
+struct in6_addr {
+ uint8_t s6_addr[16];
+};
+
+struct sockaddr_in6 {
+ sa_family_t sin6_family; // AF_INET6.
+ in_port_t sin6_port; // Port number.
+ uint32_t sin6_flowinfo; // IPv6 traffic class and flow information.
+ struct in6_addr sin6_addr; // IPv6 address.
+ uint32_t sin6_scope_id; // Set of interfaces for a scop
+};
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/netinet/ip.h b/Userland/Libraries/LibC/netinet/ip.h
new file mode 100644
index 0000000000..3fc6b40a93
--- /dev/null
+++ b/Userland/Libraries/LibC/netinet/ip.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "in.h"
diff --git a/Userland/Libraries/LibC/netinet/ip_icmp.h b/Userland/Libraries/LibC/netinet/ip_icmp.h
new file mode 100644
index 0000000000..14d1f8c352
--- /dev/null
+++ b/Userland/Libraries/LibC/netinet/ip_icmp.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <bits/stdint.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+struct icmphdr {
+ uint8_t type;
+ uint8_t code;
+ uint16_t checksum;
+ union {
+ struct {
+ uint16_t id;
+ uint16_t sequence;
+ } echo;
+ uint32_t gateway;
+ } un;
+};
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/netinet/tcp.h b/Userland/Libraries/LibC/netinet/tcp.h
new file mode 100644
index 0000000000..8dc5da8e32
--- /dev/null
+++ b/Userland/Libraries/LibC/netinet/tcp.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#define TCP_NODELAY 10
diff --git a/Userland/Libraries/LibC/paths.h b/Userland/Libraries/LibC/paths.h
new file mode 100644
index 0000000000..07cfcdbb0d
--- /dev/null
+++ b/Userland/Libraries/LibC/paths.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+// FIXME: This is just a default value to satisfy OpenSSH, feel free to change it.
+#define _PATH_MAILDIR "/var/mail"
diff --git a/Userland/Libraries/LibC/poll.cpp b/Userland/Libraries/LibC/poll.cpp
new file mode 100644
index 0000000000..437ea8243a
--- /dev/null
+++ b/Userland/Libraries/LibC/poll.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <errno.h>
+#include <poll.h>
+#include <sys/time.h>
+
+extern "C" {
+
+int poll(pollfd* fds, nfds_t nfds, int timeout_ms)
+{
+ timespec timeout;
+ timespec* timeout_ts = &timeout;
+ if (timeout_ms < 0)
+ timeout_ts = nullptr;
+ else
+ timeout = { timeout_ms / 1000, (timeout_ms % 1000) * 1'000'000 };
+ return ppoll(fds, nfds, timeout_ts, nullptr);
+}
+
+int ppoll(pollfd* fds, nfds_t nfds, const timespec* timeout, const sigset_t* sigmask)
+{
+ Syscall::SC_poll_params params { fds, nfds, timeout, sigmask };
+ int rc = syscall(SC_poll, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+}
diff --git a/Userland/Libraries/LibC/poll.h b/Userland/Libraries/LibC/poll.h
new file mode 100644
index 0000000000..ff0a1112b7
--- /dev/null
+++ b/Userland/Libraries/LibC/poll.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <signal.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+#define POLLIN (1u << 0)
+#define POLLPRI (1u << 1)
+#define POLLOUT (1u << 2)
+#define POLLERR (1u << 3)
+#define POLLHUP (1u << 4)
+#define POLLNVAL (1u << 5)
+#define POLLRDHUP (1u << 13)
+
+struct pollfd {
+ int fd;
+ short events;
+ short revents;
+};
+
+typedef unsigned nfds_t;
+
+int poll(struct pollfd* fds, nfds_t nfds, int timeout);
+int ppoll(struct pollfd* fds, nfds_t nfds, const struct timespec* timeout, const sigset_t* sigmask);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/pwd.cpp b/Userland/Libraries/LibC/pwd.cpp
new file mode 100644
index 0000000000..7af0d67d68
--- /dev/null
+++ b/Userland/Libraries/LibC/pwd.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <errno_numbers.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+extern "C" {
+
+static FILE* s_stream = nullptr;
+static unsigned s_line_number = 0;
+static struct passwd s_passwd_entry;
+
+static String s_name;
+static String s_passwd;
+static String s_gecos;
+static String s_dir;
+static String s_shell;
+
+void setpwent()
+{
+ s_line_number = 0;
+ if (s_stream) {
+ rewind(s_stream);
+ } else {
+ s_stream = fopen("/etc/passwd", "r");
+ if (!s_stream) {
+ perror("open /etc/passwd");
+ }
+ }
+}
+
+void endpwent()
+{
+ s_line_number = 0;
+ if (s_stream) {
+ fclose(s_stream);
+ s_stream = nullptr;
+ }
+
+ memset(&s_passwd_entry, 0, sizeof(s_passwd_entry));
+
+ s_name = {};
+ s_passwd = {};
+ s_gecos = {};
+ s_dir = {};
+ s_shell = {};
+}
+
+struct passwd* getpwuid(uid_t uid)
+{
+ setpwent();
+ while (auto* pw = getpwent()) {
+ if (pw->pw_uid == uid)
+ return pw;
+ }
+ return nullptr;
+}
+
+struct passwd* getpwnam(const char* name)
+{
+ setpwent();
+ while (auto* pw = getpwent()) {
+ if (!strcmp(pw->pw_name, name))
+ return pw;
+ }
+ return nullptr;
+}
+
+static bool parse_pwddb_entry(const String& line)
+{
+ auto parts = line.split_view(':', true);
+ if (parts.size() != 7) {
+ fprintf(stderr, "getpwent(): Malformed entry on line %u\n", s_line_number);
+ return false;
+ }
+
+ s_name = parts[0];
+ s_passwd = parts[1];
+ auto& uid_string = parts[2];
+ auto& gid_string = parts[3];
+ s_gecos = parts[4];
+ s_dir = parts[5];
+ s_shell = parts[6];
+
+ auto uid = uid_string.to_uint();
+ if (!uid.has_value()) {
+ fprintf(stderr, "getpwent(): Malformed UID on line %u\n", s_line_number);
+ return false;
+ }
+ auto gid = gid_string.to_uint();
+ if (!gid.has_value()) {
+ fprintf(stderr, "getpwent(): Malformed GID on line %u\n", s_line_number);
+ return false;
+ }
+
+ s_passwd_entry.pw_name = const_cast<char*>(s_name.characters());
+ s_passwd_entry.pw_passwd = const_cast<char*>(s_passwd.characters());
+ s_passwd_entry.pw_uid = uid.value();
+ s_passwd_entry.pw_gid = gid.value();
+ s_passwd_entry.pw_gecos = const_cast<char*>(s_gecos.characters());
+ s_passwd_entry.pw_dir = const_cast<char*>(s_dir.characters());
+ s_passwd_entry.pw_shell = const_cast<char*>(s_shell.characters());
+
+ return true;
+}
+
+struct passwd* getpwent()
+{
+ if (!s_stream)
+ setpwent();
+
+ while (true) {
+ if (!s_stream || feof(s_stream))
+ return nullptr;
+
+ if (ferror(s_stream)) {
+ fprintf(stderr, "getpwent(): Read error: %s\n", strerror(ferror(s_stream)));
+ return nullptr;
+ }
+
+ char buffer[1024];
+ ++s_line_number;
+ char* s = fgets(buffer, sizeof(buffer), s_stream);
+
+ // Silently tolerate an empty line at the end.
+ if ((!s || !s[0]) && feof(s_stream))
+ return nullptr;
+
+ String line(s, Chomp);
+ if (parse_pwddb_entry(line))
+ return &s_passwd_entry;
+ // Otherwise, proceed to the next line.
+ }
+}
+
+int putpwent(const struct passwd* p, FILE* stream)
+{
+ if (!p || !stream || !p->pw_passwd || !p->pw_name || !p->pw_dir || !p->pw_gecos || !p->pw_shell) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ auto is_valid_field = [](const char* str) {
+ return str && !strpbrk(str, ":\n");
+ };
+
+ if (!is_valid_field(p->pw_name) || !is_valid_field(p->pw_dir) || !is_valid_field(p->pw_gecos) || !is_valid_field(p->pw_shell)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ int nwritten = fprintf(stream, "%s:%s:%u:%u:%s,,,:%s:%s\n", p->pw_name, p->pw_passwd, p->pw_uid, p->pw_gid, p->pw_gecos, p->pw_dir, p->pw_shell);
+ if (!nwritten || nwritten < 0) {
+ errno = ferror(stream);
+ return -1;
+ }
+
+ return 0;
+}
+}
diff --git a/Userland/Libraries/LibC/pwd.h b/Userland/Libraries/LibC/pwd.h
new file mode 100644
index 0000000000..334b04b05f
--- /dev/null
+++ b/Userland/Libraries/LibC/pwd.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <bits/FILE.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+struct passwd {
+ char* pw_name;
+ char* pw_passwd;
+ uid_t pw_uid;
+ gid_t pw_gid;
+ char* pw_gecos;
+ char* pw_dir;
+ char* pw_shell;
+};
+
+struct passwd* getpwent();
+void setpwent();
+void endpwent();
+struct passwd* getpwnam(const char* name);
+struct passwd* getpwuid(uid_t);
+int putpwent(const struct passwd* p, FILE* stream);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/qsort.cpp b/Userland/Libraries/LibC/qsort.cpp
new file mode 100644
index 0000000000..145cb1b19f
--- /dev/null
+++ b/Userland/Libraries/LibC/qsort.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/QuickSort.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+class SizedObject {
+public:
+ SizedObject() = delete;
+ SizedObject(void* data, size_t size)
+ : m_data(data)
+ , m_size(size)
+ {
+ }
+ void* data() const { return m_data; }
+ size_t size() const { return m_size; }
+
+private:
+ void* m_data;
+ size_t m_size;
+};
+
+namespace AK {
+
+template<>
+inline void swap(const SizedObject& a, const SizedObject& b)
+{
+ ASSERT(a.size() == b.size());
+ const size_t size = a.size();
+ const auto a_data = reinterpret_cast<char*>(a.data());
+ const auto b_data = reinterpret_cast<char*>(b.data());
+ for (auto i = 0u; i < size; ++i) {
+ swap(a_data[i], b_data[i]);
+ }
+}
+
+}
+
+class SizedObjectSlice {
+public:
+ SizedObjectSlice() = delete;
+ SizedObjectSlice(void* data, size_t element_size)
+ : m_data(data)
+ , m_element_size(element_size)
+ {
+ }
+ const SizedObject operator[](size_t index)
+ {
+ return { static_cast<char*>(m_data) + index * m_element_size, m_element_size };
+ }
+
+private:
+ void* m_data;
+ size_t m_element_size;
+};
+
+void qsort(void* bot, size_t nmemb, size_t size, int (*compar)(const void*, const void*))
+{
+ if (nmemb <= 1) {
+ return;
+ }
+
+ SizedObjectSlice slice { bot, size };
+
+ AK::dual_pivot_quick_sort(slice, 0, nmemb - 1, [=](const SizedObject& a, const SizedObject& b) { return compar(a.data(), b.data()) < 0; });
+}
+
+void qsort_r(void* bot, size_t nmemb, size_t size, int (*compar)(const void*, const void*, void*), void* arg)
+{
+ if (nmemb <= 1) {
+ return;
+ }
+
+ SizedObjectSlice slice { bot, size };
+
+ AK::dual_pivot_quick_sort(slice, 0, nmemb - 1, [=](const SizedObject& a, const SizedObject& b) { return compar(a.data(), b.data(), arg) < 0; });
+}
diff --git a/Userland/Libraries/LibC/regex.h b/Userland/Libraries/LibC/regex.h
new file mode 100644
index 0000000000..b7e01ae981
--- /dev/null
+++ b/Userland/Libraries/LibC/regex.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+typedef ssize_t regoff_t;
+
+struct regex_t {
+ void* __data;
+};
+
+enum __Regex_Error {
+ __Regex_NoError,
+ __Regex_InvalidPattern, // Invalid regular expression.
+ __Regex_InvalidCollationElement, // Invalid collating element referenced.
+ __Regex_InvalidCharacterClass, // Invalid character class type referenced.
+ __Regex_InvalidTrailingEscape, // Trailing \ in pattern.
+ __Regex_InvalidNumber, // Number in \digit invalid or in error.
+ __Regex_MismatchingBracket, // [ ] imbalance.
+ __Regex_MismatchingParen, // ( ) imbalance.
+ __Regex_MismatchingBrace, // { } imbalance.
+ __Regex_InvalidBraceContent, // Content of {} invalid: not a number, number too large, more than two numbers, first larger than second.
+ __Regex_InvalidBracketContent, // Content of [] invalid.
+ __Regex_InvalidRange, // Invalid endpoint in range expression.
+ __Regex_InvalidRepetitionMarker, // ?, * or + not preceded by valid regular expression.
+ __Regex_ReachedMaxRecursion, // MaximumRecursion has been reached.
+ __Regex_EmptySubExpression, // Sub expression has empty content.
+ __Regex_InvalidCaptureGroup, // Content of capture group is invalid.
+ __Regex_InvalidNameForCaptureGroup, // Name of capture group is invalid.
+};
+
+enum ReError {
+ REG_NOERR = __Regex_NoError,
+ REG_BADPAT = __Regex_InvalidPattern, // Invalid regular expression.
+ REG_ECOLLATE = __Regex_InvalidCollationElement, // Invalid collating element referenced.
+ REG_ECTYPE = __Regex_InvalidCharacterClass, // Invalid character class type referenced.
+ REG_EESCAPE = __Regex_InvalidTrailingEscape, // Trailing \ in pattern.
+ REG_ESUBREG = __Regex_InvalidNumber, // Number in \digit invalid or in error.
+ REG_EBRACK = __Regex_MismatchingBracket, // [ ] imbalance.
+ REG_EPAREN = __Regex_MismatchingParen, // \( \) or ( ) imbalance.
+ REG_EBRACE = __Regex_MismatchingBrace, // \{ \} imbalance.
+ REG_BADBR = __Regex_InvalidBraceContent, // Content of \{ \} invalid: not a number, number too large, more than two numbers, first larger than second.
+ REG_ERANGE = __Regex_InvalidRange, // Invalid endpoint in range expression.
+ REG_BADRPT = __Regex_InvalidRepetitionMarker, // ?, * or + not preceded by valid regular expression.
+ REG_EMPTY_EXPR = __Regex_EmptySubExpression, // Empty expression
+ REG_ENOSYS, // The implementation does not support the function.
+ REG_ESPACE, // Out of memory.
+ REG_NOMATCH, // regexec() failed to match.
+};
+
+struct regmatch_t {
+ regoff_t rm_so; // byte offset from start of string to start of substring
+ regoff_t rm_eo; // byte offset from start of string of the first character after the end of substring
+ regoff_t rm_cnt; // number of matches
+};
+
+enum __RegexAllFlags {
+ __Regex_Global = 1, // All matches (don't return after first match)
+ __Regex_Insensitive = __Regex_Global << 1, // Case insensitive match (ignores case of [a-zA-Z])
+ __Regex_Ungreedy = __Regex_Global << 2, // The match becomes lazy by default. Now a ? following a quantifier makes it greedy
+ __Regex_Unicode = __Regex_Global << 3, // Enable all unicode features and interpret all unicode escape sequences as such
+ __Regex_Extended = __Regex_Global << 4, // Ignore whitespaces. Spaces and text after a # in the pattern are ignored
+ __Regex_Extra = __Regex_Global << 5, // Disallow meaningless escapes. A \ followed by a letter with no special meaning is faulted
+ __Regex_MatchNotBeginOfLine = __Regex_Global << 6, // Pattern is not forced to ^ -> search in whole string!
+ __Regex_MatchNotEndOfLine = __Regex_Global << 7, // Don't Force the dollar sign, $, to always match end of the string, instead of end of the line. This option is ignored if the Multiline-flag is set
+ __Regex_SkipSubExprResults = __Regex_Global << 8, // Do not return sub expressions in the result
+ __Regex_StringCopyMatches = __Regex_Global << 9, // Do explicitly copy results into new allocated string instead of StringView to original string.
+ __Regex_SingleLine = __Regex_Global << 10, // Dot matches newline characters
+ __Regex_Sticky = __Regex_Global << 11, // Force the pattern to only match consecutive matches from where the previous match ended.
+ __Regex_Multiline = __Regex_Global << 12, // Handle newline characters. Match each line, one by one.
+ __Regex_SkipTrimEmptyMatches = __Regex_Global << 13, // Do not remove empty capture group results.
+ __Regex_Internal_Stateful = __Regex_Global << 14, // Internal flag; enables stateful matches.
+ __Regex_Last = __Regex_SkipTrimEmptyMatches
+};
+
+// clang-format off
+// Values for the cflags parameter to the regcomp() function:
+#define REG_EXTENDED __Regex_Extended // Use Extended Regular Expressions.
+#define REG_ICASE __Regex_Insensitive // Ignore case in match.
+#define REG_NOSUB __Regex_SkipSubExprResults // Report only success or fail in regexec().
+#define REG_GLOBAL __Regex_Global // Don't stop searching for more match
+#define REG_NEWLINE (__Regex_Multiline | REG_GLOBAL) // Change the handling of newline.
+
+// Values for the eflags parameter to the regexec() function:
+#define REG_NOTBOL __Regex_MatchNotBeginOfLine // The circumflex character (^), when taken as a special character, will not match the beginning of string.
+#define REG_NOTEOL __Regex_MatchNotEndOfLine // The dollar sign ($), when taken as a special character, will not match the end of string.
+
+//static_assert (sizeof(FlagsUnderlyingType) * 8 >= regex::POSIXFlags::Last << 1), "flags type too small")
+#define REG_SEARCH __Regex_Last << 1
+// clang-format on
+
+int regcomp(regex_t*, const char*, int);
+int regexec(const regex_t*, const char*, size_t, regmatch_t[], int);
+size_t regerror(int, const regex_t*, char*, size_t);
+void regfree(regex_t*);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/scanf.cpp b/Userland/Libraries/LibC/scanf.cpp
new file mode 100644
index 0000000000..550240fa23
--- /dev/null
+++ b/Userland/Libraries/LibC/scanf.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2000-2002 Opsycon AB (www.opsycon.se)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Opsycon AB.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+#include <AK/Assertions.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+static const char* determine_base(const char* p, int& base)
+{
+ if (p[0] == '0') {
+ switch (p[1]) {
+ case 'x':
+ base = 16;
+ break;
+ case 't':
+ case 'n':
+ base = 10;
+ break;
+ case 'o':
+ base = 8;
+ break;
+ default:
+ base = 10;
+ return p;
+ }
+ return p + 2;
+ }
+ base = 10;
+ return p;
+}
+
+static int _atob(unsigned long* vp, const char* p, int base)
+{
+ unsigned long value, v1, v2;
+ const char* q;
+ char tmp[20];
+ int digit;
+
+ if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
+ base = 16;
+ p += 2;
+ }
+
+ if (base == 16 && (q = strchr(p, '.')) != 0) {
+ if (q - p > (ssize_t)sizeof(tmp) - 1)
+ return 0;
+ memcpy(tmp, p, q - p);
+ tmp[q - p] = '\0';
+
+ if (!_atob(&v1, tmp, 16))
+ return 0;
+ ++q;
+ if (strchr(q, '.'))
+ return 0;
+ if (!_atob(&v2, q, 16))
+ return 0;
+ *vp = (v1 << 16) + v2;
+ return 1;
+ }
+
+ value = *vp = 0;
+ for (; *p; p++) {
+ if (*p >= '0' && *p <= '9')
+ digit = *p - '0';
+ else if (*p >= 'a' && *p <= 'f')
+ digit = *p - 'a' + 10;
+ else if (*p >= 'A' && *p <= 'F')
+ digit = *p - 'A' + 10;
+ else
+ return 0;
+
+ if (digit >= base)
+ return 0;
+ value *= base;
+ value += digit;
+ }
+ *vp = value;
+ return 1;
+}
+
+static int atob(unsigned int* vp, const char* p, int base)
+{
+ unsigned long v;
+
+ if (base == 0)
+ p = determine_base(p, base);
+ if (_atob(&v, p, base)) {
+ *vp = v;
+ return 1;
+ }
+ return 0;
+}
+
+#define ISSPACE " \t\n\r\f\v"
+
+int vsscanf(const char* buf, const char* s, va_list ap)
+{
+ int base = 10;
+ char* t;
+ char tmp[BUFSIZ];
+ bool noassign = false;
+ int count = 0;
+ int width = 0;
+
+ // FIXME: This doesn't work quite right. For example, it fails to match 'SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1\r\n'
+ // with 'SSH-%d.%d-%[^\n]\n'
+
+ while (*s && *buf) {
+ while (isspace(*s))
+ s++;
+ if (*s == '%') {
+ s++;
+ for (; *s; s++) {
+ if (strchr("dibouxcsefg%", *s))
+ break;
+ if (*s == '*')
+ noassign = true;
+ else if (*s >= '1' && *s <= '9') {
+ const char* tc;
+ for (tc = s; isdigit(*s); s++)
+ ;
+ ASSERT((ssize_t)sizeof(tmp) >= s - tc + 1);
+ memcpy(tmp, tc, s - tc);
+ tmp[s - tc] = '\0';
+ atob((uint32_t*)&width, tmp, 10);
+ s--;
+ }
+ }
+ if (*s == 's') {
+ while (isspace(*buf))
+ buf++;
+ if (!width)
+ width = strcspn(buf, ISSPACE);
+ if (!noassign) {
+ // In this case, we have no way to ensure the user buffer is not overflown :(
+ memcpy(t = va_arg(ap, char*), buf, width);
+ t[width] = '\0';
+ }
+ buf += width;
+ } else if (*s == 'c') {
+ if (!width)
+ width = 1;
+ if (!noassign) {
+ memcpy(t = va_arg(ap, char*), buf, width);
+ // No null terminator!
+ }
+ buf += width;
+ } else if (strchr("dobxu", *s)) {
+ while (isspace(*buf))
+ buf++;
+ if (*s == 'd' || *s == 'u')
+ base = 10;
+ else if (*s == 'x')
+ base = 16;
+ else if (*s == 'o')
+ base = 8;
+ else if (*s == 'b')
+ base = 2;
+ if (!width) {
+ if (isspace(*(s + 1)) || *(s + 1) == 0) {
+ width = strcspn(buf, ISSPACE);
+ } else {
+ auto* p = strchr(buf, *(s + 1));
+ if (p)
+ width = p - buf;
+ else {
+ noassign = true;
+ width = 0;
+ }
+ }
+ }
+ memcpy(tmp, buf, width);
+ tmp[width] = '\0';
+ buf += width;
+ if (!noassign) {
+ if (!atob(va_arg(ap, uint32_t*), tmp, base))
+ noassign = true;
+ }
+ }
+ if (!noassign)
+ ++count;
+ width = 0;
+ noassign = false;
+ ++s;
+ } else {
+ while (isspace(*buf))
+ buf++;
+ if (*s != *buf)
+ break;
+ else {
+ ++s;
+ ++buf;
+ }
+ }
+ }
+ return count;
+}
diff --git a/Userland/Libraries/LibC/sched.cpp b/Userland/Libraries/LibC/sched.cpp
new file mode 100644
index 0000000000..4e3643bd01
--- /dev/null
+++ b/Userland/Libraries/LibC/sched.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <errno.h>
+#include <sched.h>
+
+extern "C" {
+
+int sched_yield()
+{
+ int rc = syscall(SC_yield);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int sched_get_priority_min([[maybe_unused]] int policy)
+{
+ return 0; // Idle
+}
+
+int sched_get_priority_max([[maybe_unused]] int policy)
+{
+ return 3; // High
+}
+
+int sched_setparam(pid_t pid, const struct sched_param* param)
+{
+ int rc = syscall(SC_sched_setparam, pid, param);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int sched_getparam(pid_t pid, struct sched_param* param)
+{
+ int rc = syscall(SC_sched_getparam, pid, param);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+}
diff --git a/Userland/Libraries/LibC/sched.h b/Userland/Libraries/LibC/sched.h
new file mode 100644
index 0000000000..ac7bf9eff9
--- /dev/null
+++ b/Userland/Libraries/LibC/sched.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+int sched_yield();
+
+struct sched_param {
+ int sched_priority;
+};
+
+#define SCHED_FIFO 0
+#define SCHED_RR 1
+#define SCHED_OTHER 2
+#define SCHED_BATCH 3
+
+int sched_get_priority_min(int policy);
+int sched_get_priority_max(int policy);
+int sched_setparam(pid_t pid, const struct sched_param* param);
+int sched_getparam(pid_t pid, struct sched_param* param);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/serenity.cpp b/Userland/Libraries/LibC/serenity.cpp
new file mode 100644
index 0000000000..626a464ee8
--- /dev/null
+++ b/Userland/Libraries/LibC/serenity.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <errno.h>
+#include <serenity.h>
+
+extern "C" {
+
+int disown(pid_t pid)
+{
+ int rc = syscall(SC_disown, pid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int module_load(const char* path, size_t path_length)
+{
+ int rc = syscall(SC_module_load, path, path_length);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int module_unload(const char* name, size_t name_length)
+{
+ int rc = syscall(SC_module_unload, name, name_length);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int profiling_enable(pid_t pid)
+{
+ int rc = syscall(SC_profiling_enable, pid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int profiling_disable(pid_t pid)
+{
+ int rc = syscall(SC_profiling_disable, pid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int set_thread_boost(pid_t tid, int amount)
+{
+ int rc = syscall(SC_set_thread_boost, tid, amount);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int set_process_boost(pid_t tid, int amount)
+{
+ int rc = syscall(SC_set_process_boost, tid, amount);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int futex(int32_t* userspace_address, int futex_op, int32_t value, const struct timespec* timeout)
+{
+ Syscall::SC_futex_params params { userspace_address, futex_op, value, timeout };
+ int rc = syscall(SC_futex, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int purge(int mode)
+{
+ int rc = syscall(SC_purge, mode);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int perf_event(int type, uintptr_t arg1, FlatPtr arg2)
+{
+ int rc = syscall(SC_perf_event, type, arg1, arg2);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+void* shbuf_get(int shbuf_id, size_t* size)
+{
+ ssize_t rc = syscall(SC_shbuf_get, shbuf_id, size);
+ if (rc < 0 && -rc < EMAXERRNO) {
+ errno = -rc;
+ return (void*)-1;
+ }
+ return (void*)rc;
+}
+
+int shbuf_release(int shbuf_id)
+{
+ int rc = syscall(SC_shbuf_release, shbuf_id);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int shbuf_seal(int shbuf_id)
+{
+ int rc = syscall(SC_shbuf_seal, shbuf_id);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int shbuf_create(int size, void** buffer)
+{
+ int rc = syscall(SC_shbuf_create, size, buffer);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int shbuf_allow_pid(int shbuf_id, pid_t peer_pid)
+{
+ int rc = syscall(SC_shbuf_allow_pid, shbuf_id, peer_pid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int shbuf_allow_all(int shbuf_id)
+{
+ int rc = syscall(SC_shbuf_allow_all, shbuf_id);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int get_stack_bounds(uintptr_t* user_stack_base, size_t* user_stack_size)
+{
+ int rc = syscall(SC_get_stack_bounds, user_stack_base, user_stack_size);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+}
diff --git a/Userland/Libraries/LibC/serenity.h b/Userland/Libraries/LibC/serenity.h
new file mode 100644
index 0000000000..4654476469
--- /dev/null
+++ b/Userland/Libraries/LibC/serenity.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <stdio.h>
+#include <unistd.h>
+
+__BEGIN_DECLS
+
+int disown(pid_t);
+
+int shbuf_create(int, void** buffer);
+int shbuf_allow_pid(int, pid_t peer_pid);
+int shbuf_allow_all(int);
+void* shbuf_get(int shbuf_id, size_t* size);
+int shbuf_release(int shbuf_id);
+int shbuf_seal(int shbuf_id);
+
+int module_load(const char* path, size_t path_length);
+int module_unload(const char* name, size_t name_length);
+
+int profiling_enable(pid_t);
+int profiling_disable(pid_t);
+
+#define THREAD_PRIORITY_MIN 1
+#define THREAD_PRIORITY_LOW 10
+#define THREAD_PRIORITY_NORMAL 30
+#define THREAD_PRIORITY_HIGH 50
+#define THREAD_PRIORITY_MAX 99
+
+int set_thread_boost(pid_t tid, int amount);
+int set_process_boost(pid_t, int amount);
+
+#define FUTEX_WAIT 1
+#define FUTEX_WAKE 2
+
+int futex(int32_t* userspace_address, int futex_op, int32_t value, const struct timespec* timeout);
+
+#define PURGE_ALL_VOLATILE 0x1
+#define PURGE_ALL_CLEAN_INODE 0x2
+
+int purge(int mode);
+
+#define PERF_EVENT_SAMPLE 0
+#define PERF_EVENT_MALLOC 1
+#define PERF_EVENT_FREE 2
+
+int perf_event(int type, uintptr_t arg1, uintptr_t arg2);
+
+int get_stack_bounds(uintptr_t* user_stack_base, size_t* user_stack_size);
+
+#ifdef __i386__
+ALWAYS_INLINE void send_secret_data_to_userspace_emulator(uintptr_t data1, uintptr_t data2, uintptr_t data3)
+{
+ asm volatile(
+ ".byte 0xd6\n"
+ ".byte 0xd6\n" ::
+ : "eax");
+ asm volatile(
+ "push %%eax\n"
+ "push %%ecx\n"
+ "push %%edx\n"
+ "pop %%edx\n"
+ "pop %%ecx\n"
+ "pop %%eax\n" ::"a"(data1),
+ "c"(data2), "d"(data3)
+ : "memory");
+}
+#elif __x86_64__
+ALWAYS_INLINE void send_secret_data_to_userspace_emulator(uintptr_t, uintptr_t, uintptr_t)
+{
+}
+#endif
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/setjmp.S b/Userland/Libraries/LibC/setjmp.S
new file mode 100644
index 0000000000..6d2c4c4acf
--- /dev/null
+++ b/Userland/Libraries/LibC/setjmp.S
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.global setjmp
+setjmp:
+ mov 4(%esp), %eax
+ mov %ebx, 0(%eax)
+ mov %esi, 4(%eax)
+ mov %edi, 8(%eax)
+ mov %ebp, 12(%eax)
+ lea 4(%esp), %ecx
+ mov %ecx, 16(%eax)
+ mov (%esp), %ecx
+ mov %ecx, 20(%eax)
+ xor %eax, %eax
+ ret
+
+.global longjmp
+longjmp:
+ mov 4(%esp), %edx
+ mov 8(%esp), %eax
+ mov 0(%edx), %ebx
+ mov 4(%edx), %esi
+ mov 8(%edx), %edi
+ mov 12(%edx), %ebp
+ mov 16(%edx), %ecx
+ mov %ecx, %esp
+ mov 20(%edx), %ecx
+ test %eax, %eax
+ jnz .nonzero
+ mov 1, %eax
+.nonzero:
+ jmp *%ecx
+
diff --git a/Userland/Libraries/LibC/setjmp.h b/Userland/Libraries/LibC/setjmp.h
new file mode 100644
index 0000000000..78f3120d14
--- /dev/null
+++ b/Userland/Libraries/LibC/setjmp.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <bits/stdint.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+struct __jmp_buf {
+ uint32_t regs[6];
+ bool did_save_signal_mask;
+ sigset_t saved_signal_mask;
+};
+
+typedef struct __jmp_buf jmp_buf[1];
+typedef struct __jmp_buf sigjmp_buf[1];
+
+int setjmp(jmp_buf);
+void longjmp(jmp_buf, int val);
+
+int sigsetjmp(sigjmp_buf, int savesigs);
+void siglongjmp(sigjmp_buf, int val);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/signal.cpp b/Userland/Libraries/LibC/signal.cpp
new file mode 100644
index 0000000000..a7a7ede8ba
--- /dev/null
+++ b/Userland/Libraries/LibC/signal.cpp
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <assert.h>
+#include <errno.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+extern "C" {
+
+int kill(pid_t pid, int sig)
+{
+ int rc = syscall(SC_kill, pid, sig);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int killpg(int pgrp, int sig)
+{
+ int rc = syscall(SC_killpg, pgrp, sig);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int raise(int sig)
+{
+ // FIXME: Support multi-threaded programs.
+ return kill(getpid(), sig);
+}
+
+sighandler_t signal(int signum, sighandler_t handler)
+{
+ struct sigaction new_act;
+ struct sigaction old_act;
+ new_act.sa_handler = handler;
+ new_act.sa_flags = 0;
+ new_act.sa_mask = 0;
+ int rc = sigaction(signum, &new_act, &old_act);
+ if (rc < 0)
+ return SIG_ERR;
+ return old_act.sa_handler;
+}
+
+int sigaction(int signum, const struct sigaction* act, struct sigaction* old_act)
+{
+ int rc = syscall(SC_sigaction, signum, act, old_act);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int sigemptyset(sigset_t* set)
+{
+ *set = 0;
+ return 0;
+}
+
+int sigfillset(sigset_t* set)
+{
+ *set = 0xffffffff;
+ return 0;
+}
+
+int sigaddset(sigset_t* set, int sig)
+{
+ if (sig < 1 || sig > 32) {
+ errno = EINVAL;
+ return -1;
+ }
+ *set |= 1 << (sig - 1);
+ return 0;
+}
+
+int sigdelset(sigset_t* set, int sig)
+{
+ if (sig < 1 || sig > 32) {
+ errno = EINVAL;
+ return -1;
+ }
+ *set &= ~(1 << (sig - 1));
+ return 0;
+}
+
+int sigismember(const sigset_t* set, int sig)
+{
+ if (sig < 1 || sig > 32) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (*set & (1 << (sig - 1)))
+ return 1;
+ return 0;
+}
+
+int sigprocmask(int how, const sigset_t* set, sigset_t* old_set)
+{
+ int rc = syscall(SC_sigprocmask, how, set, old_set);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int sigpending(sigset_t* set)
+{
+ int rc = syscall(SC_sigpending, set);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+const char* sys_siglist[NSIG] = {
+ "Invalid signal number",
+ "Hangup",
+ "Interrupt",
+ "Quit",
+ "Illegal instruction",
+ "Trap",
+ "Aborted",
+ "Bus error",
+ "Division by zero",
+ "Killed",
+ "User signal 1",
+ "Segmentation violation",
+ "User signal 2",
+ "Broken pipe",
+ "Alarm clock",
+ "Terminated",
+ "Stack fault",
+ "Child exited",
+ "Continued",
+ "Stopped (signal)",
+ "Stopped",
+ "Stopped (tty input)",
+ "Stopped (tty output)",
+ "Urgent I/O condition)",
+ "CPU limit exceeded",
+ "File size limit exceeded",
+ "Virtual timer expired",
+ "Profiling timer expired",
+ "Window changed",
+ "I/O possible",
+ "Power failure",
+ "Bad system call",
+};
+
+int sigsetjmp(jmp_buf env, int savesigs)
+{
+ if (savesigs) {
+ int rc = sigprocmask(0, nullptr, &env->saved_signal_mask);
+ assert(rc == 0);
+ env->did_save_signal_mask = true;
+ } else {
+ env->did_save_signal_mask = false;
+ }
+ return setjmp(env);
+}
+void siglongjmp(jmp_buf env, int val)
+{
+ if (env->did_save_signal_mask) {
+ int rc = sigprocmask(SIG_SETMASK, &env->saved_signal_mask, nullptr);
+ assert(rc == 0);
+ }
+ longjmp(env, val);
+}
+
+int sigsuspend(const sigset_t*)
+{
+ dbgprintf("FIXME: Implement sigsuspend()\n");
+ ASSERT_NOT_REACHED();
+}
+
+static const char* signal_names[] = {
+ "INVAL",
+ "HUP",
+ "INT",
+ "QUIT",
+ "ILL",
+ "TRAP",
+ "ABRT",
+ "BUS",
+ "FPE",
+ "KILL",
+ "USR1",
+ "SEGV",
+ "USR2",
+ "PIPE",
+ "ALRM",
+ "TERM",
+ "STKFLT",
+ "CHLD",
+ "CONT",
+ "STOP",
+ "TSTP",
+ "TTIN",
+ "TTOU",
+ "URG",
+ "XCPU",
+ "XFSZ",
+ "VTALRM",
+ "PROF",
+ "WINCH",
+ "IO",
+ "INFO",
+ "SYS",
+};
+
+static_assert(sizeof(signal_names) == sizeof(const char*) * NSIG);
+
+int getsignalbyname(const char* name)
+{
+ ASSERT(name);
+ for (size_t i = 0; i < NSIG; ++i) {
+ auto* signal_name = signal_names[i];
+ if (!strcmp(signal_name, name))
+ return i;
+ }
+ errno = EINVAL;
+ return -1;
+}
+
+const char* getsignalname(int signal)
+{
+ if (signal < 0 || signal >= NSIG) {
+ errno = EINVAL;
+ return nullptr;
+ }
+ return signal_names[signal];
+}
+}
diff --git a/Userland/Libraries/LibC/signal.h b/Userland/Libraries/LibC/signal.h
new file mode 100644
index 0000000000..65c98603d4
--- /dev/null
+++ b/Userland/Libraries/LibC/signal.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <signal_numbers.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+typedef void (*__sighandler_t)(int);
+typedef __sighandler_t sighandler_t;
+
+typedef uint32_t sigset_t;
+typedef uint32_t sig_atomic_t;
+
+union sigval {
+ int sival_int;
+ void* sival_ptr;
+};
+
+typedef struct siginfo {
+ int si_signo;
+ int si_code;
+ pid_t si_pid;
+ uid_t si_uid;
+ void* si_addr;
+ int si_status;
+ union sigval si_value;
+} siginfo_t;
+
+struct sigaction {
+ union {
+ void (*sa_handler)(int);
+ void (*sa_sigaction)(int, siginfo_t*, void*);
+ };
+ sigset_t sa_mask;
+ int sa_flags;
+};
+
+int kill(pid_t, int sig);
+int killpg(int pgrp, int sig);
+sighandler_t signal(int sig, sighandler_t);
+int pthread_sigmask(int how, const sigset_t* set, sigset_t* ol_dset);
+int sigaction(int sig, const struct sigaction* act, struct sigaction* old_act);
+int sigemptyset(sigset_t*);
+int sigfillset(sigset_t*);
+int sigaddset(sigset_t*, int sig);
+int sigdelset(sigset_t*, int sig);
+int sigismember(const sigset_t*, int sig);
+int sigprocmask(int how, const sigset_t* set, sigset_t* old_set);
+int sigpending(sigset_t*);
+int sigsuspend(const sigset_t*);
+int raise(int sig);
+int getsignalbyname(const char*);
+const char* getsignalname(int);
+
+extern const char* sys_siglist[NSIG];
+
+#define SIG_DFL ((__sighandler_t)0)
+#define SIG_ERR ((__sighandler_t)-1)
+#define SIG_IGN ((__sighandler_t)1)
+
+#define SA_NOCLDSTOP 1
+#define SA_NOCLDWAIT 2
+#define SA_SIGINFO 4
+#define SA_ONSTACK 0x08000000
+#define SA_RESTART 0x10000000
+#define SA_NODEFER 0x40000000
+#define SA_RESETHAND 0x80000000
+
+#define SA_NOMASK SA_NODEFER
+#define SA_ONESHOT SA_RESETHAND
+
+#define SIG_BLOCK 0
+#define SIG_UNBLOCK 1
+#define SIG_SETMASK 2
+
+#define CLD_EXITED 0
+#define CLD_KILLED 1
+#define CLD_DUMPED 2
+#define CLD_TRAPPED 3
+#define CLD_STOPPED 4
+#define CLD_CONTINUED 5
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/signal_numbers.h b/Userland/Libraries/LibC/signal_numbers.h
new file mode 100644
index 0000000000..bc59c0d97c
--- /dev/null
+++ b/Userland/Libraries/LibC/signal_numbers.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#define SIGINVAL 0
+#define SIGHUP 1
+#define SIGINT 2
+#define SIGQUIT 3
+#define SIGILL 4
+#define SIGTRAP 5
+#define SIGABRT 6
+#define SIGBUS 7
+#define SIGFPE 8
+#define SIGKILL 9
+#define SIGUSR1 10
+#define SIGSEGV 11
+#define SIGUSR2 12
+#define SIGPIPE 13
+#define SIGALRM 14
+#define SIGTERM 15
+#define SIGSTKFLT 16
+#define SIGCHLD 17
+#define SIGCONT 18
+#define SIGSTOP 19
+#define SIGTSTP 20
+#define SIGTTIN 21
+#define SIGTTOU 22
+#define SIGURG 23
+#define SIGXCPU 24
+#define SIGXFSZ 25
+#define SIGVTALRM 26
+#define SIGPROF 27
+#define SIGWINCH 28
+#define SIGIO 29
+#define SIGINFO 30
+#define SIGSYS 31
+#define NSIG 32
diff --git a/Userland/Libraries/LibC/spawn.cpp b/Userland/Libraries/LibC/spawn.cpp
new file mode 100644
index 0000000000..797c946c68
--- /dev/null
+++ b/Userland/Libraries/LibC/spawn.cpp
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2020, Nico Weber <thakis@chromium.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* posix_spawn and friends
+ *
+ * values from POSIX standard unix specification
+ *
+ * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/spawn.h.html
+ */
+
+#include <spawn.h>
+
+#include <AK/Function.h>
+#include <AK/Vector.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+struct posix_spawn_file_actions_state {
+ Vector<Function<int()>, 4> actions;
+};
+
+extern "C" {
+
+[[noreturn]] static void posix_spawn_child(const char* path, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attr, char* const argv[], char* const envp[], int (*exec)(const char*, char* const[], char* const[]))
+{
+ if (attr) {
+ short flags = attr->flags;
+ if (flags & POSIX_SPAWN_RESETIDS) {
+ if (seteuid(getuid()) < 0) {
+ perror("posix_spawn seteuid");
+ _exit(127);
+ }
+ if (setegid(getgid()) < 0) {
+ perror("posix_spawn setegid");
+ _exit(127);
+ }
+ }
+ if (flags & POSIX_SPAWN_SETPGROUP) {
+ if (setpgid(0, attr->pgroup) < 0) {
+ perror("posix_spawn setpgid");
+ _exit(127);
+ }
+ }
+ if (flags & POSIX_SPAWN_SETSCHEDPARAM) {
+ if (sched_setparam(0, &attr->schedparam) < 0) {
+ perror("posix_spawn sched_setparam");
+ _exit(127);
+ }
+ }
+ if (flags & POSIX_SPAWN_SETSIGDEF) {
+ struct sigaction default_action;
+ default_action.sa_flags = 0;
+ sigemptyset(&default_action.sa_mask);
+ default_action.sa_handler = SIG_DFL;
+
+ sigset_t sigdefault = attr->sigdefault;
+ for (int i = 0; i < NSIG; ++i) {
+ if (sigismember(&sigdefault, i) && sigaction(i, &default_action, nullptr) < 0) {
+ perror("posix_spawn sigaction");
+ _exit(127);
+ }
+ }
+ }
+ if (flags & POSIX_SPAWN_SETSIGMASK) {
+ if (sigprocmask(SIG_SETMASK, &attr->sigmask, nullptr) < 0) {
+ perror("posix_spawn sigprocmask");
+ _exit(127);
+ }
+ }
+ if (flags & POSIX_SPAWN_SETSID) {
+ if (setsid() < 0) {
+ perror("posix_spawn setsid");
+ _exit(127);
+ }
+ }
+
+ // FIXME: POSIX_SPAWN_SETSCHEDULER
+ }
+
+ if (file_actions) {
+ for (const auto& action : file_actions->state->actions) {
+ if (action() < 0) {
+ perror("posix_spawn file action");
+ _exit(127);
+ }
+ }
+ }
+
+ exec(path, argv, envp);
+ perror("posix_spawn exec");
+ _exit(127);
+}
+
+int posix_spawn(pid_t* out_pid, const char* path, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attr, char* const argv[], char* const envp[])
+{
+ pid_t child_pid = fork();
+ if (child_pid < 0)
+ return errno;
+
+ if (child_pid != 0) {
+ *out_pid = child_pid;
+ return 0;
+ }
+
+ posix_spawn_child(path, file_actions, attr, argv, envp, execve);
+}
+
+int posix_spawnp(pid_t* out_pid, const char* path, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attr, char* const argv[], char* const envp[])
+{
+ pid_t child_pid = fork();
+ if (child_pid < 0)
+ return errno;
+
+ if (child_pid != 0) {
+ *out_pid = child_pid;
+ return 0;
+ }
+
+ posix_spawn_child(path, file_actions, attr, argv, envp, execvpe);
+}
+
+int posix_spawn_file_actions_addchdir(posix_spawn_file_actions_t* actions, const char* path)
+{
+ actions->state->actions.append([path]() { return chdir(path); });
+ return 0;
+}
+
+int posix_spawn_file_actions_addfchdir(posix_spawn_file_actions_t* actions, int fd)
+{
+ actions->state->actions.append([fd]() { return fchdir(fd); });
+ return 0;
+}
+
+int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t* actions, int fd)
+{
+ actions->state->actions.append([fd]() { return close(fd); });
+ return 0;
+}
+
+int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t* actions, int old_fd, int new_fd)
+{
+ actions->state->actions.append([old_fd, new_fd]() { return dup2(old_fd, new_fd); });
+ return 0;
+}
+
+int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t* actions, int want_fd, const char* path, int flags, mode_t mode)
+{
+ actions->state->actions.append([want_fd, path, flags, mode]() {
+ int opened_fd = open(path, flags, mode);
+ if (opened_fd < 0 || opened_fd == want_fd)
+ return opened_fd;
+ if (int rc = dup2(opened_fd, want_fd); rc < 0)
+ return rc;
+ return close(opened_fd);
+ });
+ return 0;
+}
+
+int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t* actions)
+{
+ delete actions->state;
+ return 0;
+}
+
+int posix_spawn_file_actions_init(posix_spawn_file_actions_t* actions)
+{
+ actions->state = new posix_spawn_file_actions_state;
+ return 0;
+}
+
+int posix_spawnattr_destroy(posix_spawnattr_t*)
+{
+ return 0;
+}
+
+int posix_spawnattr_getflags(const posix_spawnattr_t* attr, short* out_flags)
+{
+ *out_flags = attr->flags;
+ return 0;
+}
+
+int posix_spawnattr_getpgroup(const posix_spawnattr_t* attr, pid_t* out_pgroup)
+{
+ *out_pgroup = attr->pgroup;
+ return 0;
+}
+
+int posix_spawnattr_getschedparam(const posix_spawnattr_t* attr, struct sched_param* out_schedparam)
+{
+ *out_schedparam = attr->schedparam;
+ return 0;
+}
+
+int posix_spawnattr_getschedpolicy(const posix_spawnattr_t* attr, int* out_schedpolicty)
+{
+ *out_schedpolicty = attr->schedpolicy;
+ return 0;
+}
+
+int posix_spawnattr_getsigdefault(const posix_spawnattr_t* attr, sigset_t* out_sigdefault)
+{
+ *out_sigdefault = attr->sigdefault;
+ return 0;
+}
+
+int posix_spawnattr_getsigmask(const posix_spawnattr_t* attr, sigset_t* out_sigmask)
+{
+ *out_sigmask = attr->sigmask;
+ return 0;
+}
+
+int posix_spawnattr_init(posix_spawnattr_t* attr)
+{
+ attr->flags = 0;
+ attr->pgroup = 0;
+ // attr->schedparam intentionally not written; its default value is unspecified.
+ // attr->schedpolicy intentionally not written; its default value is unspecified.
+ sigemptyset(&attr->sigdefault);
+ // attr->sigmask intentionally not written; its default value is unspecified.
+ return 0;
+}
+
+int posix_spawnattr_setflags(posix_spawnattr_t* attr, short flags)
+{
+ if (flags & ~(POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER | POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSID))
+ return EINVAL;
+
+ attr->flags = flags;
+ return 0;
+}
+
+int posix_spawnattr_setpgroup(posix_spawnattr_t* attr, pid_t pgroup)
+{
+ attr->pgroup = pgroup;
+ return 0;
+}
+
+int posix_spawnattr_setschedparam(posix_spawnattr_t* attr, const struct sched_param* schedparam)
+{
+ attr->schedparam = *schedparam;
+ return 0;
+}
+
+int posix_spawnattr_setschedpolicy(posix_spawnattr_t* attr, int schedpolicy)
+{
+ attr->schedpolicy = schedpolicy;
+ return 0;
+}
+
+int posix_spawnattr_setsigdefault(posix_spawnattr_t* attr, const sigset_t* sigdefault)
+{
+ attr->sigdefault = *sigdefault;
+ return 0;
+}
+
+int posix_spawnattr_setsigmask(posix_spawnattr_t* attr, const sigset_t* sigmask)
+{
+ attr->sigmask = *sigmask;
+ return 0;
+}
+}
diff --git a/Userland/Libraries/LibC/spawn.h b/Userland/Libraries/LibC/spawn.h
new file mode 100644
index 0000000000..05435ef466
--- /dev/null
+++ b/Userland/Libraries/LibC/spawn.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2020, Nico Weber <thakis@chromium.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* posix_spawn and friends
+ *
+ * values from POSIX standard unix specification
+ *
+ * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/spawn.h.html
+ */
+
+#pragma once
+
+#include <sched.h>
+#include <signal.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+enum {
+ POSIX_SPAWN_RESETIDS = 1 << 0,
+ POSIX_SPAWN_SETPGROUP = 1 << 1,
+
+ POSIX_SPAWN_SETSCHEDPARAM = 1 << 2,
+ POSIX_SPAWN_SETSCHEDULER = 1 << 3,
+
+ POSIX_SPAWN_SETSIGDEF = 1 << 4,
+ POSIX_SPAWN_SETSIGMASK = 1 << 5,
+
+ POSIX_SPAWN_SETSID = 1 << 6,
+};
+
+#define POSIX_SPAWN_SETSID POSIX_SPAWN_SETSID
+
+struct posix_spawn_file_actions_state;
+typedef struct {
+ struct posix_spawn_file_actions_state* state;
+} posix_spawn_file_actions_t;
+
+typedef struct {
+ short flags;
+ pid_t pgroup;
+ struct sched_param schedparam;
+ int schedpolicy;
+ sigset_t sigdefault;
+ sigset_t sigmask;
+} posix_spawnattr_t;
+
+int posix_spawn(pid_t*, const char*, const posix_spawn_file_actions_t*, const posix_spawnattr_t*, char* const argv[], char* const envp[]);
+int posix_spawnp(pid_t*, const char*, const posix_spawn_file_actions_t*, const posix_spawnattr_t*, char* const argv[], char* const envp[]);
+
+int posix_spawn_file_actions_addchdir(posix_spawn_file_actions_t*, const char*);
+int posix_spawn_file_actions_addfchdir(posix_spawn_file_actions_t*, int);
+int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t*, int);
+int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t*, int old_fd, int new_fd);
+int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t*, int fd, const char*, int flags, mode_t);
+int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t*);
+int posix_spawn_file_actions_init(posix_spawn_file_actions_t*);
+
+int posix_spawnattr_destroy(posix_spawnattr_t*);
+int posix_spawnattr_getflags(const posix_spawnattr_t*, short*);
+int posix_spawnattr_getpgroup(const posix_spawnattr_t*, pid_t*);
+int posix_spawnattr_getschedparam(const posix_spawnattr_t*, struct sched_param*);
+int posix_spawnattr_getschedpolicy(const posix_spawnattr_t*, int*);
+int posix_spawnattr_getsigdefault(const posix_spawnattr_t*, sigset_t*);
+int posix_spawnattr_getsigmask(const posix_spawnattr_t*, sigset_t*);
+int posix_spawnattr_init(posix_spawnattr_t*);
+int posix_spawnattr_setflags(posix_spawnattr_t*, short);
+int posix_spawnattr_setpgroup(posix_spawnattr_t*, pid_t);
+int posix_spawnattr_setschedparam(posix_spawnattr_t*, const struct sched_param*);
+int posix_spawnattr_setschedpolicy(posix_spawnattr_t*, int);
+int posix_spawnattr_setsigdefault(posix_spawnattr_t*, const sigset_t*);
+int posix_spawnattr_setsigmask(posix_spawnattr_t*, const sigset_t*);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/ssp.cpp b/Userland/Libraries/LibC/ssp.cpp
new file mode 100644
index 0000000000..c2715d6ba0
--- /dev/null
+++ b/Userland/Libraries/LibC/ssp.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2021, Brian Gianforcaro <b.gianfo@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/internals.h>
+#include <unistd.h>
+
+#if defined __SSP__ || defined __SSP_ALL__
+# error "file must not be compiled with stack protection enabled on it. Use -fno-stack-protector"
+#endif
+
+extern "C" {
+
+extern u32 __stack_chk_guard;
+u32 __stack_chk_guard = (u32)0xc6c7c8c9;
+
+[[noreturn]] void __stack_chk_fail()
+{
+ dbgprintf("Error: USERSPACE(%d) Stack protector failure, stack smashing detected!\n", getpid());
+ if (__stdio_is_initialized)
+ fprintf(stderr, "Error: Stack protector failure, stack smashing detected!\n");
+ abort();
+}
+
+[[noreturn]] void __stack_chk_fail_local()
+{
+ __stack_chk_fail();
+}
+
+} // extern "C"
diff --git a/Userland/Libraries/LibC/stat.cpp b/Userland/Libraries/LibC/stat.cpp
new file mode 100644
index 0000000000..5de2472dfa
--- /dev/null
+++ b/Userland/Libraries/LibC/stat.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+extern "C" {
+
+mode_t umask(mode_t mask)
+{
+ return syscall(SC_umask, mask);
+}
+
+int mkdir(const char* pathname, mode_t mode)
+{
+ if (!pathname) {
+ errno = EFAULT;
+ return -1;
+ }
+ int rc = syscall(SC_mkdir, pathname, strlen(pathname), mode);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int chmod(const char* pathname, mode_t mode)
+{
+ if (!pathname) {
+ errno = EFAULT;
+ return -1;
+ }
+ int rc = syscall(SC_chmod, pathname, strlen(pathname), mode);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int fchmod(int fd, mode_t mode)
+{
+ int rc = syscall(SC_fchmod, fd, mode);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int mkfifo(const char* pathname, mode_t mode)
+{
+ return mknod(pathname, mode | S_IFIFO, 0);
+}
+
+static int do_stat(const char* path, struct stat* statbuf, bool follow_symlinks)
+{
+ if (!path) {
+ errno = EFAULT;
+ return -1;
+ }
+ Syscall::SC_stat_params params { { path, strlen(path) }, statbuf, follow_symlinks };
+ int rc = syscall(SC_stat, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int lstat(const char* path, struct stat* statbuf)
+{
+ return do_stat(path, statbuf, false);
+}
+
+int stat(const char* path, struct stat* statbuf)
+{
+ return do_stat(path, statbuf, true);
+}
+
+int fstat(int fd, struct stat* statbuf)
+{
+ int rc = syscall(SC_fstat, fd, statbuf);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+}
diff --git a/Userland/Libraries/LibC/stdarg.h b/Userland/Libraries/LibC/stdarg.h
new file mode 100644
index 0000000000..5725ead619
--- /dev/null
+++ b/Userland/Libraries/LibC/stdarg.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if defined(KERNEL)
+# define __BEGIN_DECLS
+# define __END_DECLS
+#else
+# include <sys/cdefs.h>
+#endif
+
+__BEGIN_DECLS
+
+typedef __builtin_va_list va_list;
+
+#define va_start(v, l) __builtin_va_start(v, l)
+#define va_end(v) __builtin_va_end(v)
+#define va_arg(v, l) __builtin_va_arg(v, l)
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/stdbool.h b/Userland/Libraries/LibC/stdbool.h
new file mode 100644
index 0000000000..29be4f8549
--- /dev/null
+++ b/Userland/Libraries/LibC/stdbool.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#ifndef __cplusplus
+
+# include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+# define bool _Bool
+# define true 1
+# define false 0
+# define __bool_true_false_are_Defined 1
+
+__END_DECLS
+
+#endif
diff --git a/Userland/Libraries/LibC/stddef.h b/Userland/Libraries/LibC/stddef.h
new file mode 100644
index 0000000000..4f7b710329
--- /dev/null
+++ b/Userland/Libraries/LibC/stddef.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#ifndef KERNEL
+
+# include <sys/cdefs.h>
+
+# ifdef __cplusplus
+# define NULL nullptr
+# else
+# define NULL ((void*)0)
+# endif
+
+typedef __PTRDIFF_TYPE__ ptrdiff_t;
+typedef __SIZE_TYPE__ size_t;
+
+#endif
diff --git a/Userland/Libraries/LibC/stdint.h b/Userland/Libraries/LibC/stdint.h
new file mode 100644
index 0000000000..4f1b66e766
--- /dev/null
+++ b/Userland/Libraries/LibC/stdint.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+#include <bits/stdint.h>
diff --git a/Userland/Libraries/LibC/stdio.cpp b/Userland/Libraries/LibC/stdio.cpp
new file mode 100644
index 0000000000..1cdf9d4a73
--- /dev/null
+++ b/Userland/Libraries/LibC/stdio.cpp
@@ -0,0 +1,1219 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LogStream.h>
+#include <AK/PrintfImplementation.h>
+#include <AK/ScopedValueRollback.h>
+#include <AK/StdLibExtras.h>
+#include <AK/kmalloc.h>
+#include <Kernel/API/Syscall.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/internals.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+struct FILE {
+public:
+ FILE(int fd, int mode)
+ : m_fd(fd)
+ , m_mode(mode)
+ {
+ }
+ ~FILE();
+
+ static FILE* create(int fd, int mode);
+
+ void setbuf(u8* data, int mode, size_t size) { m_buffer.setbuf(data, mode, size); }
+
+ bool flush();
+ bool close();
+
+ int fileno() const { return m_fd; }
+ bool eof() const { return m_eof; }
+
+ int error() const { return m_error; }
+ void clear_err() { m_error = 0; }
+
+ size_t read(u8*, size_t);
+ size_t write(const u8*, size_t);
+
+ bool gets(u8*, size_t);
+ bool ungetc(u8 byte) { return m_buffer.enqueue_front(byte); }
+
+ int seek(off_t offset, int whence);
+ off_t tell();
+
+ pid_t popen_child() { return m_popen_child; }
+ void set_popen_child(pid_t child_pid) { m_popen_child = child_pid; }
+
+ void reopen(int fd, int mode);
+
+private:
+ struct Buffer {
+ // A ringbuffer that also transparently implements ungetc().
+ public:
+ ~Buffer();
+
+ int mode() const { return m_mode; }
+ void setbuf(u8* data, int mode, size_t size);
+ // Make sure to call realize() before enqueuing any data.
+ // Dequeuing can be attempted without it.
+ void realize(int fd);
+ void drop();
+
+ bool may_use() const { return m_ungotten || m_mode != _IONBF; }
+ bool is_not_empty() const { return m_ungotten || !m_empty; }
+ size_t buffered_size() const;
+
+ const u8* begin_dequeue(size_t& available_size) const;
+ void did_dequeue(size_t actual_size);
+
+ u8* begin_enqueue(size_t& available_size) const;
+ void did_enqueue(size_t actual_size);
+
+ bool enqueue_front(u8 byte);
+
+ private:
+ // Note: the fields here are arranged this way
+ // to make sizeof(Buffer) smaller.
+ u8* m_data { nullptr };
+ size_t m_capacity { BUFSIZ };
+ size_t m_begin { 0 };
+ size_t m_end { 0 };
+
+ int m_mode { -1 };
+ u8 m_unget_buffer { 0 };
+ bool m_ungotten : 1 { false };
+ bool m_data_is_malloced : 1 { false };
+ // When m_begin == m_end, we want to distinguish whether
+ // the buffer is full or empty.
+ bool m_empty : 1 { true };
+ };
+
+ // Read or write using the underlying fd, bypassing the buffer.
+ ssize_t do_read(u8*, size_t);
+ ssize_t do_write(const u8*, size_t);
+
+ // Read some data into the buffer.
+ bool read_into_buffer();
+ // Flush *some* data from the buffer.
+ bool write_from_buffer();
+
+ int m_fd { -1 };
+ int m_mode { 0 };
+ int m_error { 0 };
+ bool m_eof { false };
+ pid_t m_popen_child { -1 };
+ Buffer m_buffer;
+};
+
+FILE::~FILE()
+{
+ bool already_closed = m_fd == -1;
+ ASSERT(already_closed);
+}
+
+FILE* FILE::create(int fd, int mode)
+{
+ void* file = calloc(1, sizeof(FILE));
+ new (file) FILE(fd, mode);
+ return (FILE*)file;
+}
+
+bool FILE::close()
+{
+ bool flush_ok = flush();
+ int rc = ::close(m_fd);
+ m_fd = -1;
+ if (!flush_ok) {
+ // Restore the original error from flush().
+ errno = m_error;
+ }
+ return flush_ok && rc == 0;
+}
+
+bool FILE::flush()
+{
+ if (m_mode & O_WRONLY && m_buffer.may_use()) {
+ // When open for writing, write out all the buffered data.
+ while (m_buffer.is_not_empty()) {
+ bool ok = write_from_buffer();
+ if (!ok)
+ return false;
+ }
+ }
+ if (m_mode & O_RDONLY) {
+ // When open for reading, just drop the buffered data.
+ size_t had_buffered = m_buffer.buffered_size();
+ m_buffer.drop();
+ // Attempt to reset the underlying file position to what the user
+ // expects.
+ int rc = lseek(m_fd, -had_buffered, SEEK_CUR);
+ if (rc < 0) {
+ if (errno == ESPIPE) {
+ // We can't set offset on this file; oh well, the user will just
+ // have to cope.
+ errno = 0;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+ssize_t FILE::do_read(u8* data, size_t size)
+{
+ int nread = ::read(m_fd, data, size);
+
+ if (nread < 0) {
+ m_error = errno;
+ } else if (nread == 0) {
+ m_eof = true;
+ }
+ return nread;
+}
+
+ssize_t FILE::do_write(const u8* data, size_t size)
+{
+ int nwritten = ::write(m_fd, data, size);
+
+ if (nwritten < 0)
+ m_error = errno;
+ return nwritten;
+}
+
+bool FILE::read_into_buffer()
+{
+ m_buffer.realize(m_fd);
+
+ size_t available_size;
+ u8* data = m_buffer.begin_enqueue(available_size);
+ // If we want to read, the buffer must have some space!
+ ASSERT(available_size);
+
+ ssize_t nread = do_read(data, available_size);
+
+ if (nread <= 0)
+ return false;
+
+ m_buffer.did_enqueue(nread);
+ return true;
+}
+
+bool FILE::write_from_buffer()
+{
+ size_t size;
+ const u8* data = m_buffer.begin_dequeue(size);
+ // If we want to write, the buffer must have something in it!
+ ASSERT(size);
+
+ ssize_t nwritten = do_write(data, size);
+
+ if (nwritten < 0)
+ return false;
+
+ m_buffer.did_dequeue(nwritten);
+ return true;
+}
+
+size_t FILE::read(u8* data, size_t size)
+{
+ size_t total_read = 0;
+
+ while (size > 0) {
+ size_t actual_size;
+
+ if (m_buffer.may_use()) {
+ // Let's see if the buffer has something queued for us.
+ size_t queued_size;
+ const u8* queued_data = m_buffer.begin_dequeue(queued_size);
+ if (queued_size == 0) {
+ // Nothing buffered; we're going to have to read some.
+ bool read_some_more = read_into_buffer();
+ if (read_some_more) {
+ // Great, now try this again.
+ continue;
+ }
+ return total_read;
+ }
+ actual_size = min(size, queued_size);
+ memcpy(data, queued_data, actual_size);
+ m_buffer.did_dequeue(actual_size);
+ } else {
+ // Read directly into the user buffer.
+ ssize_t nread = do_read(data, size);
+ if (nread <= 0)
+ return total_read;
+ actual_size = nread;
+ }
+
+ total_read += actual_size;
+ data += actual_size;
+ size -= actual_size;
+ }
+
+ return total_read;
+}
+
+size_t FILE::write(const u8* data, size_t size)
+{
+ size_t total_written = 0;
+
+ while (size > 0) {
+ size_t actual_size;
+
+ if (m_buffer.may_use()) {
+ m_buffer.realize(m_fd);
+ // Try writing into the buffer.
+ size_t available_size;
+ u8* buffer_data = m_buffer.begin_enqueue(available_size);
+ if (available_size == 0) {
+ // There's no space in the buffer; we're going to free some.
+ bool freed_some_space = write_from_buffer();
+ if (freed_some_space) {
+ // Great, now try this again.
+ continue;
+ }
+ return total_written;
+ }
+ actual_size = min(size, available_size);
+ memcpy(buffer_data, data, actual_size);
+ m_buffer.did_enqueue(actual_size);
+ // See if we have to flush it.
+ if (m_buffer.mode() == _IOLBF) {
+ bool includes_newline = memchr(data, '\n', actual_size);
+ if (includes_newline)
+ flush();
+ }
+ } else {
+ // Write directly from the user buffer.
+ ssize_t nwritten = do_write(data, size);
+ if (nwritten < 0)
+ return total_written;
+ actual_size = nwritten;
+ }
+
+ total_written += actual_size;
+ data += actual_size;
+ size -= actual_size;
+ }
+
+ return total_written;
+}
+
+bool FILE::gets(u8* data, size_t size)
+{
+ // gets() is a lot like read(), but it is different enough in how it
+ // processes newlines and null-terminates the buffer that it deserves a
+ // separate implementation.
+ size_t total_read = 0;
+
+ if (size == 0)
+ return false;
+
+ while (size > 1) {
+ if (m_buffer.may_use()) {
+ // Let's see if the buffer has something queued for us.
+ size_t queued_size;
+ const u8* queued_data = m_buffer.begin_dequeue(queued_size);
+ if (queued_size == 0) {
+ // Nothing buffered; we're going to have to read some.
+ bool read_some_more = read_into_buffer();
+ if (read_some_more) {
+ // Great, now try this again.
+ continue;
+ }
+ *data = 0;
+ return total_read > 0;
+ }
+ size_t actual_size = min(size - 1, queued_size);
+ u8* newline = reinterpret_cast<u8*>(memchr(queued_data, '\n', actual_size));
+ if (newline)
+ actual_size = newline - queued_data + 1;
+ memcpy(data, queued_data, actual_size);
+ m_buffer.did_dequeue(actual_size);
+ total_read += actual_size;
+ data += actual_size;
+ size -= actual_size;
+ if (newline)
+ break;
+ } else {
+ // Sadly, we have to actually read these characters one by one.
+ u8 byte;
+ ssize_t nread = do_read(&byte, 1);
+ if (nread <= 0) {
+ *data = 0;
+ return total_read > 0;
+ }
+ ASSERT(nread == 1);
+ *data = byte;
+ total_read++;
+ data++;
+ size--;
+ if (byte == '\n')
+ break;
+ }
+ }
+
+ *data = 0;
+ return total_read > 0;
+}
+
+int FILE::seek(off_t offset, int whence)
+{
+ bool ok = flush();
+ if (!ok)
+ return -1;
+
+ off_t off = lseek(m_fd, offset, whence);
+ if (off < 0) {
+ // Note: do not set m_error.
+ return off;
+ }
+
+ m_eof = false;
+ return 0;
+}
+
+off_t FILE::tell()
+{
+ bool ok = flush();
+ if (!ok)
+ return -1;
+
+ return lseek(m_fd, 0, SEEK_CUR);
+}
+
+void FILE::reopen(int fd, int mode)
+{
+ // Dr. POSIX says: "Failure to flush or close the file descriptor
+ // successfully shall be ignored"
+ // and so we ignore any failures these two might have.
+ flush();
+ close();
+
+ // Just in case flush() and close() didn't drop the buffer.
+ m_buffer.drop();
+
+ m_fd = fd;
+ m_mode = mode;
+ m_error = 0;
+ m_eof = false;
+}
+
+FILE::Buffer::~Buffer()
+{
+ if (m_data_is_malloced)
+ free(m_data);
+}
+
+void FILE::Buffer::realize(int fd)
+{
+ if (m_mode == -1)
+ m_mode = isatty(fd) ? _IOLBF : _IOFBF;
+
+ if (m_mode != _IONBF && m_data == nullptr) {
+ m_data = reinterpret_cast<u8*>(malloc(m_capacity));
+ m_data_is_malloced = true;
+ }
+}
+
+void FILE::Buffer::setbuf(u8* data, int mode, size_t size)
+{
+ drop();
+ m_mode = mode;
+ if (data != nullptr) {
+ m_data = data;
+ m_capacity = size;
+ }
+}
+
+void FILE::Buffer::drop()
+{
+ if (m_data_is_malloced) {
+ free(m_data);
+ m_data = nullptr;
+ m_data_is_malloced = false;
+ }
+ m_begin = m_end = 0;
+ m_empty = true;
+ m_ungotten = false;
+}
+
+size_t FILE::Buffer::buffered_size() const
+{
+ // Note: does not include the ungetc() buffer.
+
+ if (m_empty)
+ return 0;
+
+ if (m_begin < m_end)
+ return m_end - m_begin;
+ else
+ return m_capacity - (m_begin - m_end);
+}
+
+const u8* FILE::Buffer::begin_dequeue(size_t& available_size) const
+{
+ if (m_ungotten) {
+ available_size = 1;
+ return &m_unget_buffer;
+ }
+
+ if (m_empty) {
+ available_size = 0;
+ return nullptr;
+ }
+
+ if (m_begin < m_end)
+ available_size = m_end - m_begin;
+ else
+ available_size = m_capacity - m_begin;
+
+ return &m_data[m_begin];
+}
+
+void FILE::Buffer::did_dequeue(size_t actual_size)
+{
+ ASSERT(actual_size > 0);
+
+ if (m_ungotten) {
+ ASSERT(actual_size == 1);
+ m_ungotten = false;
+ return;
+ }
+
+ m_begin += actual_size;
+
+ ASSERT(m_begin <= m_capacity);
+ if (m_begin == m_capacity) {
+ // Wrap around.
+ m_begin = 0;
+ }
+
+ if (m_begin == m_end) {
+ m_empty = true;
+ // As an optimization, move both pointers to the beginning of the
+ // buffer, so that more consecutive space is available next time.
+ m_begin = m_end = 0;
+ }
+}
+
+u8* FILE::Buffer::begin_enqueue(size_t& available_size) const
+{
+ ASSERT(m_data != nullptr);
+
+ if (m_begin < m_end || m_empty)
+ available_size = m_capacity - m_end;
+ else
+ available_size = m_begin - m_end;
+
+ return const_cast<u8*>(&m_data[m_end]);
+}
+
+void FILE::Buffer::did_enqueue(size_t actual_size)
+{
+ ASSERT(m_data != nullptr);
+ ASSERT(actual_size > 0);
+
+ m_end += actual_size;
+
+ ASSERT(m_end <= m_capacity);
+ if (m_end == m_capacity) {
+ // Wrap around.
+ m_end = 0;
+ }
+
+ m_empty = false;
+}
+
+bool FILE::Buffer::enqueue_front(u8 byte)
+{
+ if (m_ungotten) {
+ // Sorry, the place is already taken!
+ return false;
+ }
+
+ m_ungotten = true;
+ m_unget_buffer = byte;
+ return true;
+}
+
+extern "C" {
+
+static u8 default_streams[3][sizeof(FILE)];
+FILE* stdin = reinterpret_cast<FILE*>(&default_streams[0]);
+FILE* stdout = reinterpret_cast<FILE*>(&default_streams[1]);
+FILE* stderr = reinterpret_cast<FILE*>(&default_streams[2]);
+
+void __stdio_init()
+{
+ new (stdin) FILE(0, O_RDONLY);
+ new (stdout) FILE(1, O_WRONLY);
+ new (stderr) FILE(2, O_WRONLY);
+ stderr->setbuf(nullptr, _IONBF, 0);
+ __stdio_is_initialized = true;
+}
+
+int setvbuf(FILE* stream, char* buf, int mode, size_t size)
+{
+ ASSERT(stream);
+ if (mode != _IONBF && mode != _IOLBF && mode != _IOFBF) {
+ errno = EINVAL;
+ return -1;
+ }
+ stream->setbuf(reinterpret_cast<u8*>(buf), mode, size);
+ return 0;
+}
+
+void setbuf(FILE* stream, char* buf)
+{
+ setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ);
+}
+
+void setlinebuf(FILE* stream)
+{
+ setvbuf(stream, nullptr, _IOLBF, 0);
+}
+
+int fileno(FILE* stream)
+{
+ ASSERT(stream);
+ return stream->fileno();
+}
+
+int feof(FILE* stream)
+{
+ ASSERT(stream);
+ return stream->eof();
+}
+
+int fflush(FILE* stream)
+{
+ if (!stream) {
+ dbgln("FIXME: fflush(nullptr) should flush all open streams");
+ return 0;
+ }
+ return stream->flush() ? 0 : EOF;
+}
+
+char* fgets(char* buffer, int size, FILE* stream)
+{
+ ASSERT(stream);
+ bool ok = stream->gets(reinterpret_cast<u8*>(buffer), size);
+ return ok ? buffer : nullptr;
+}
+
+int fgetc(FILE* stream)
+{
+ ASSERT(stream);
+ char ch;
+ size_t nread = fread(&ch, sizeof(char), 1, stream);
+ if (nread == 1)
+ return ch;
+ return EOF;
+}
+
+int getc(FILE* stream)
+{
+ return fgetc(stream);
+}
+
+int getc_unlocked(FILE* stream)
+{
+ return fgetc(stream);
+}
+
+int getchar()
+{
+ return getc(stdin);
+}
+
+ssize_t getdelim(char** lineptr, size_t* n, int delim, FILE* stream)
+{
+ if (!lineptr || !n) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (*lineptr == nullptr || *n == 0) {
+ *n = BUFSIZ;
+ if ((*lineptr = static_cast<char*>(malloc(*n))) == nullptr) {
+ return -1;
+ }
+ }
+
+ char* ptr;
+ char* eptr;
+ for (ptr = *lineptr, eptr = *lineptr + *n;;) {
+ int c = fgetc(stream);
+ if (c == -1) {
+ if (feof(stream)) {
+ *ptr = '\0';
+ return ptr == *lineptr ? -1 : ptr - *lineptr;
+ } else {
+ return -1;
+ }
+ }
+ *ptr++ = c;
+ if (c == delim) {
+ *ptr = '\0';
+ return ptr - *lineptr;
+ }
+ if (ptr + 2 >= eptr) {
+ char* nbuf;
+ size_t nbuf_sz = *n * 2;
+ ssize_t d = ptr - *lineptr;
+ if ((nbuf = static_cast<char*>(realloc(*lineptr, nbuf_sz))) == nullptr) {
+ return -1;
+ }
+ *lineptr = nbuf;
+ *n = nbuf_sz;
+ eptr = nbuf + nbuf_sz;
+ ptr = nbuf + d;
+ }
+ }
+}
+
+ssize_t getline(char** lineptr, size_t* n, FILE* stream)
+{
+ return getdelim(lineptr, n, '\n', stream);
+}
+
+int ungetc(int c, FILE* stream)
+{
+ ASSERT(stream);
+ bool ok = stream->ungetc(c);
+ return ok ? c : EOF;
+}
+
+int fputc(int ch, FILE* stream)
+{
+ ASSERT(stream);
+ u8 byte = ch;
+ size_t nwritten = stream->write(&byte, 1);
+ if (nwritten == 0)
+ return EOF;
+ ASSERT(nwritten == 1);
+ return byte;
+}
+
+int putc(int ch, FILE* stream)
+{
+ return fputc(ch, stream);
+}
+
+int putchar(int ch)
+{
+ return putc(ch, stdout);
+}
+
+int fputs(const char* s, FILE* stream)
+{
+ ASSERT(stream);
+ size_t len = strlen(s);
+ size_t nwritten = stream->write(reinterpret_cast<const u8*>(s), len);
+ if (nwritten < len)
+ return EOF;
+ return 1;
+}
+
+int puts(const char* s)
+{
+ int rc = fputs(s, stdout);
+ if (rc == EOF)
+ return EOF;
+ return fputc('\n', stdout);
+}
+
+void clearerr(FILE* stream)
+{
+ ASSERT(stream);
+ stream->clear_err();
+}
+
+int ferror(FILE* stream)
+{
+ ASSERT(stream);
+ return stream->error();
+}
+
+size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream)
+{
+ ASSERT(stream);
+ ASSERT(!Checked<size_t>::multiplication_would_overflow(size, nmemb));
+
+ size_t nread = stream->read(reinterpret_cast<u8*>(ptr), size * nmemb);
+ return nread / size;
+}
+
+size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream)
+{
+ ASSERT(stream);
+ ASSERT(!Checked<size_t>::multiplication_would_overflow(size, nmemb));
+
+ size_t nwritten = stream->write(reinterpret_cast<const u8*>(ptr), size * nmemb);
+ return nwritten / size;
+}
+
+int fseek(FILE* stream, long offset, int whence)
+{
+ ASSERT(stream);
+ return stream->seek(offset, whence);
+}
+
+int fseeko(FILE* stream, off_t offset, int whence)
+{
+ ASSERT(stream);
+ return stream->seek(offset, whence);
+}
+
+long ftell(FILE* stream)
+{
+ ASSERT(stream);
+ return stream->tell();
+}
+
+off_t ftello(FILE* stream)
+{
+ ASSERT(stream);
+ return stream->tell();
+}
+
+int fgetpos(FILE* stream, fpos_t* pos)
+{
+ ASSERT(stream);
+ ASSERT(pos);
+
+ off_t val = stream->tell();
+ if (val == -1L)
+ return 1;
+
+ *pos = val;
+ return 0;
+}
+
+int fsetpos(FILE* stream, const fpos_t* pos)
+{
+ ASSERT(stream);
+ ASSERT(pos);
+
+ return stream->seek(*pos, SEEK_SET);
+}
+
+void rewind(FILE* stream)
+{
+ ASSERT(stream);
+ int rc = stream->seek(0, SEEK_SET);
+ ASSERT(rc == 0);
+}
+
+int vdbgprintf(const char* fmt, va_list ap)
+{
+ return printf_internal([](char*&, char ch) { dbgputch(ch); }, nullptr, fmt, ap);
+}
+
+int dbgprintf(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ int ret = printf_internal([](char*&, char ch) { dbgputch(ch); }, nullptr, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+ALWAYS_INLINE void stdout_putch(char*&, char ch)
+{
+ putchar(ch);
+}
+
+static FILE* __current_stream = nullptr;
+ALWAYS_INLINE static void stream_putch(char*&, char ch)
+{
+ fputc(ch, __current_stream);
+}
+
+int vfprintf(FILE* stream, const char* fmt, va_list ap)
+{
+ __current_stream = stream;
+ return printf_internal(stream_putch, nullptr, fmt, ap);
+}
+
+int fprintf(FILE* stream, const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ int ret = vfprintf(stream, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+int vprintf(const char* fmt, va_list ap)
+{
+ return printf_internal(stdout_putch, nullptr, fmt, ap);
+}
+
+int printf(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ int ret = vprintf(fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+static void buffer_putch(char*& bufptr, char ch)
+{
+ *bufptr++ = ch;
+}
+
+int vsprintf(char* buffer, const char* fmt, va_list ap)
+{
+ int ret = printf_internal(buffer_putch, buffer, fmt, ap);
+ buffer[ret] = '\0';
+ return ret;
+}
+
+int sprintf(char* buffer, const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ int ret = vsprintf(buffer, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+static size_t __vsnprintf_space_remaining;
+ALWAYS_INLINE void sized_buffer_putch(char*& bufptr, char ch)
+{
+ if (__vsnprintf_space_remaining) {
+ *bufptr++ = ch;
+ --__vsnprintf_space_remaining;
+ }
+}
+
+int vsnprintf(char* buffer, size_t size, const char* fmt, va_list ap)
+{
+ if (size) {
+ __vsnprintf_space_remaining = size - 1;
+ } else {
+ __vsnprintf_space_remaining = 0;
+ }
+ int ret = printf_internal(sized_buffer_putch, buffer, fmt, ap);
+ if (__vsnprintf_space_remaining) {
+ buffer[ret] = '\0';
+ } else if (size > 0) {
+ buffer[size - 1] = '\0';
+ }
+ return ret;
+}
+
+int snprintf(char* buffer, size_t size, const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ int ret = vsnprintf(buffer, size, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+void perror(const char* s)
+{
+ int saved_errno = errno;
+ dbgln("perror(): {}: {}", s, strerror(saved_errno));
+ warnln("{}: {}", s, strerror(saved_errno));
+}
+
+static int parse_mode(const char* mode)
+{
+ int flags = 0;
+
+ // NOTE: rt is a non-standard mode which opens a file for read, explicitly
+ // specifying that it's a text file
+ for (auto* ptr = mode; *ptr; ++ptr) {
+ switch (*ptr) {
+ case 'r':
+ flags |= O_RDONLY;
+ break;
+ case 'w':
+ flags |= O_WRONLY | O_CREAT | O_TRUNC;
+ break;
+ case 'a':
+ flags |= O_WRONLY | O_APPEND | O_CREAT;
+ break;
+ case '+':
+ flags |= O_RDWR;
+ break;
+ case 'e':
+ flags |= O_CLOEXEC;
+ break;
+ case 'b':
+ // Ok...
+ break;
+ case 't':
+ // Ok...
+ break;
+ default:
+ dbgln("Potentially unsupported fopen mode _{}_ (because of '{}')", mode, *ptr);
+ }
+ }
+
+ return flags;
+}
+
+FILE* fopen(const char* pathname, const char* mode)
+{
+ int flags = parse_mode(mode);
+ int fd = open(pathname, flags, 0666);
+ if (fd < 0)
+ return nullptr;
+ return FILE::create(fd, flags);
+}
+
+FILE* freopen(const char* pathname, const char* mode, FILE* stream)
+{
+ ASSERT(stream);
+ if (!pathname) {
+ // FIXME: Someone should probably implement this path.
+ TODO();
+ }
+
+ int flags = parse_mode(mode);
+ int fd = open(pathname, flags, 0666);
+ if (fd < 0)
+ return nullptr;
+
+ stream->reopen(fd, flags);
+ return stream;
+}
+
+FILE* fdopen(int fd, const char* mode)
+{
+ int flags = parse_mode(mode);
+ // FIXME: Verify that the mode matches how fd is already open.
+ if (fd < 0)
+ return nullptr;
+ return FILE::create(fd, flags);
+}
+
+static inline bool is_default_stream(FILE* stream)
+{
+ return stream == stdin || stream == stdout || stream == stderr;
+}
+
+int fclose(FILE* stream)
+{
+ ASSERT(stream);
+ bool ok = stream->close();
+ ScopedValueRollback errno_restorer(errno);
+
+ stream->~FILE();
+ if (!is_default_stream(stream))
+ free(stream);
+
+ return ok ? 0 : EOF;
+}
+
+int rename(const char* oldpath, const char* newpath)
+{
+ if (!oldpath || !newpath) {
+ errno = EFAULT;
+ return -1;
+ }
+ Syscall::SC_rename_params params { { oldpath, strlen(oldpath) }, { newpath, strlen(newpath) } };
+ int rc = syscall(SC_rename, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+void dbgputch(char ch)
+{
+ syscall(SC_dbgputch, ch);
+}
+
+int dbgputstr(const char* characters, ssize_t length)
+{
+ int rc = syscall(SC_dbgputstr, characters, length);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+char* tmpnam(char*)
+{
+ ASSERT_NOT_REACHED();
+}
+
+FILE* popen(const char* command, const char* type)
+{
+ if (!type || (*type != 'r' && *type != 'w')) {
+ errno = EINVAL;
+ return nullptr;
+ }
+
+ int pipe_fds[2];
+
+ int rc = pipe(pipe_fds);
+ if (rc < 0) {
+ ScopedValueRollback rollback(errno);
+ perror("pipe");
+ return nullptr;
+ }
+
+ pid_t child_pid = fork();
+ if (child_pid < 0) {
+ ScopedValueRollback rollback(errno);
+ perror("fork");
+ close(pipe_fds[0]);
+ close(pipe_fds[1]);
+ return nullptr;
+ } else if (child_pid == 0) {
+ if (*type == 'r') {
+ int rc = dup2(pipe_fds[1], STDOUT_FILENO);
+ if (rc < 0) {
+ perror("dup2");
+ exit(1);
+ }
+ close(pipe_fds[0]);
+ close(pipe_fds[1]);
+ } else if (*type == 'w') {
+ int rc = dup2(pipe_fds[0], STDIN_FILENO);
+ if (rc < 0) {
+ perror("dup2");
+ exit(1);
+ }
+ close(pipe_fds[0]);
+ close(pipe_fds[1]);
+ }
+
+ int rc = execl("/bin/sh", "sh", "-c", command, nullptr);
+ if (rc < 0)
+ perror("execl");
+ exit(1);
+ }
+
+ FILE* file = nullptr;
+ if (*type == 'r') {
+ file = FILE::create(pipe_fds[0], O_RDONLY);
+ close(pipe_fds[1]);
+ } else if (*type == 'w') {
+ file = FILE::create(pipe_fds[1], O_WRONLY);
+ close(pipe_fds[0]);
+ }
+
+ file->set_popen_child(child_pid);
+ return file;
+}
+
+int pclose(FILE* stream)
+{
+ ASSERT(stream);
+ ASSERT(stream->popen_child() != 0);
+
+ int wstatus = 0;
+ int rc = waitpid(stream->popen_child(), &wstatus, 0);
+ if (rc < 0)
+ return rc;
+
+ return wstatus;
+}
+
+int remove(const char* pathname)
+{
+ int rc = unlink(pathname);
+ if (rc < 0 && errno != EISDIR)
+ return -1;
+ return rmdir(pathname);
+}
+
+int scanf(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ int count = vfscanf(stdin, fmt, ap);
+ va_end(ap);
+ return count;
+}
+
+int fscanf(FILE* stream, const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ int count = vfscanf(stream, fmt, ap);
+ va_end(ap);
+ return count;
+}
+
+int sscanf(const char* buffer, const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ int count = vsscanf(buffer, fmt, ap);
+ va_end(ap);
+ return count;
+}
+
+int vfscanf(FILE* stream, const char* fmt, va_list ap)
+{
+ char buffer[BUFSIZ];
+ if (!fgets(buffer, sizeof(buffer) - 1, stream))
+ return -1;
+ return vsscanf(buffer, fmt, ap);
+}
+
+void flockfile([[maybe_unused]] FILE* filehandle)
+{
+ dbgprintf("FIXME: Implement flockfile()\n");
+}
+
+void funlockfile([[maybe_unused]] FILE* filehandle)
+{
+ dbgprintf("FIXME: Implement funlockfile()\n");
+}
+
+FILE* tmpfile()
+{
+ char tmp_path[] = "/tmp/XXXXXX";
+ if (__generate_unique_filename(tmp_path) < 0)
+ return nullptr;
+
+ int fd = open(tmp_path, O_CREAT | O_EXCL | O_RDWR, S_IWUSR | S_IRUSR);
+ if (fd < 0)
+ return nullptr;
+
+ // FIXME: instead of using this hack, implement with O_TMPFILE or similar
+ unlink(tmp_path);
+
+ return fdopen(fd, "rw");
+}
+}
diff --git a/Userland/Libraries/LibC/stdio.h b/Userland/Libraries/LibC/stdio.h
new file mode 100644
index 0000000000..56d0feb642
--- /dev/null
+++ b/Userland/Libraries/LibC/stdio.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#define _STDIO_H // Make GMP believe we exist.
+
+#include <bits/FILE.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#define FILENAME_MAX 1024
+
+__BEGIN_DECLS
+#ifndef EOF
+# define EOF (-1)
+#endif
+
+#define SEEK_SET 0
+#define SEEK_CUR 1
+#define SEEK_END 2
+
+#define _IOFBF 0
+#define _IOLBF 1
+#define _IONBF 2
+
+#define L_tmpnam 256
+
+extern FILE* stdin;
+extern FILE* stdout;
+extern FILE* stderr;
+
+typedef off_t fpos_t;
+
+int fseek(FILE*, long offset, int whence);
+int fseeko(FILE*, off_t offset, int whence);
+int fgetpos(FILE*, fpos_t*);
+int fsetpos(FILE*, const fpos_t*);
+long ftell(FILE*);
+off_t ftello(FILE*);
+char* fgets(char* buffer, int size, FILE*);
+int fputc(int ch, FILE*);
+int fileno(FILE*);
+int fgetc(FILE*);
+int getc(FILE*);
+int getc_unlocked(FILE* stream);
+int getchar();
+ssize_t getdelim(char**, size_t*, int, FILE*);
+ssize_t getline(char**, size_t*, FILE*);
+int ungetc(int c, FILE*);
+int remove(const char* pathname);
+FILE* fdopen(int fd, const char* mode);
+FILE* fopen(const char* pathname, const char* mode);
+FILE* freopen(const char* pathname, const char* mode, FILE*);
+void flockfile(FILE* filehandle);
+void funlockfile(FILE* filehandle);
+int fclose(FILE*);
+void rewind(FILE*);
+void clearerr(FILE*);
+int ferror(FILE*);
+int feof(FILE*);
+int fflush(FILE*);
+size_t fread(void* ptr, size_t size, size_t nmemb, FILE*);
+size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE*);
+int vprintf(const char* fmt, va_list) __attribute__((format(printf, 1, 0)));
+int vfprintf(FILE*, const char* fmt, va_list) __attribute__((format(printf, 2, 0)));
+int vsprintf(char* buffer, const char* fmt, va_list) __attribute__((format(printf, 2, 0)));
+int vsnprintf(char* buffer, size_t, const char* fmt, va_list) __attribute__((format(printf, 3, 0)));
+int fprintf(FILE*, const char* fmt, ...) __attribute__((format(printf, 2, 3)));
+int printf(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
+int dbgprintf(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
+void dbgputch(char);
+int dbgputstr(const char*, ssize_t);
+int sprintf(char* buffer, const char* fmt, ...) __attribute__((format(printf, 2, 3)));
+int snprintf(char* buffer, size_t, const char* fmt, ...) __attribute__((format(printf, 3, 4)));
+int putchar(int ch);
+int putc(int ch, FILE*);
+int puts(const char*);
+int fputs(const char*, FILE*);
+void perror(const char*);
+int scanf(const char* fmt, ...) __attribute__((format(scanf, 1, 2)));
+int sscanf(const char* str, const char* fmt, ...) __attribute__((format(scanf, 2, 3)));
+int fscanf(FILE*, const char* fmt, ...) __attribute__((format(scanf, 2, 3)));
+int vfscanf(FILE*, const char*, va_list) __attribute__((format(scanf, 2, 0)));
+int vsscanf(const char*, const char*, va_list) __attribute__((format(scanf, 2, 0)));
+int setvbuf(FILE*, char* buf, int mode, size_t);
+void setbuf(FILE*, char* buf);
+void setlinebuf(FILE*);
+int rename(const char* oldpath, const char* newpath);
+FILE* tmpfile();
+char* tmpnam(char*);
+FILE* popen(const char* command, const char* type);
+int pclose(FILE*);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/stdlib.cpp b/Userland/Libraries/LibC/stdlib.cpp
new file mode 100644
index 0000000000..07f7199bcf
--- /dev/null
+++ b/Userland/Libraries/LibC/stdlib.cpp
@@ -0,0 +1,1083 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/HashMap.h>
+#include <AK/Noncopyable.h>
+#include <AK/StdLibExtras.h>
+#include <AK/Types.h>
+#include <AK/Utf8View.h>
+#include <Kernel/API/Syscall.h>
+#include <alloca.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <spawn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/internals.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+static void strtons(const char* str, char** endptr)
+{
+ assert(endptr);
+ char* ptr = const_cast<char*>(str);
+ while (isspace(*ptr)) {
+ ptr += 1;
+ }
+ *endptr = ptr;
+}
+
+enum Sign {
+ Negative,
+ Positive,
+};
+
+static Sign strtosign(const char* str, char** endptr)
+{
+ assert(endptr);
+ if (*str == '+') {
+ *endptr = const_cast<char*>(str + 1);
+ return Sign::Positive;
+ } else if (*str == '-') {
+ *endptr = const_cast<char*>(str + 1);
+ return Sign::Negative;
+ } else {
+ *endptr = const_cast<char*>(str);
+ return Sign::Positive;
+ }
+}
+
+enum DigitConsumeDecision {
+ Consumed,
+ PosOverflow,
+ NegOverflow,
+ Invalid,
+};
+
+template<typename T, T min_value, T max_value>
+class NumParser {
+ AK_MAKE_NONMOVABLE(NumParser);
+
+public:
+ NumParser(Sign sign, int base)
+ : m_base(base)
+ , m_num(0)
+ , m_sign(sign)
+ {
+ m_cutoff = positive() ? (max_value / base) : (min_value / base);
+ m_max_digit_after_cutoff = positive() ? (max_value % base) : (min_value % base);
+ }
+
+ int parse_digit(char ch)
+ {
+ int digit;
+ if (isdigit(ch))
+ digit = ch - '0';
+ else if (islower(ch))
+ digit = ch - ('a' - 10);
+ else if (isupper(ch))
+ digit = ch - ('A' - 10);
+ else
+ return -1;
+
+ if (static_cast<T>(digit) >= m_base)
+ return -1;
+
+ return digit;
+ }
+
+ DigitConsumeDecision consume(char ch)
+ {
+ int digit = parse_digit(ch);
+ if (digit == -1)
+ return DigitConsumeDecision::Invalid;
+
+ if (!can_append_digit(digit)) {
+ if (m_sign != Sign::Negative) {
+ return DigitConsumeDecision::PosOverflow;
+ } else {
+ return DigitConsumeDecision::NegOverflow;
+ }
+ }
+
+ m_num *= m_base;
+ m_num += positive() ? digit : -digit;
+
+ return DigitConsumeDecision::Consumed;
+ }
+
+ T number() const { return m_num; };
+
+private:
+ bool can_append_digit(int digit)
+ {
+ const bool is_below_cutoff = positive() ? (m_num < m_cutoff) : (m_num > m_cutoff);
+
+ if (is_below_cutoff) {
+ return true;
+ } else {
+ return m_num == m_cutoff && digit < m_max_digit_after_cutoff;
+ }
+ }
+
+ bool positive() const
+ {
+ return m_sign != Sign::Negative;
+ }
+
+ const T m_base;
+ T m_num;
+ T m_cutoff;
+ int m_max_digit_after_cutoff;
+ Sign m_sign;
+};
+
+typedef NumParser<int, INT_MIN, INT_MAX> IntParser;
+typedef NumParser<long long, LONG_LONG_MIN, LONG_LONG_MAX> LongLongParser;
+typedef NumParser<unsigned long long, 0ULL, ULONG_LONG_MAX> ULongLongParser;
+
+static bool is_either(char* str, int offset, char lower, char upper)
+{
+ char ch = *(str + offset);
+ return ch == lower || ch == upper;
+}
+
+__attribute__((warn_unused_result)) int __generate_unique_filename(char* pattern)
+{
+ size_t length = strlen(pattern);
+
+ if (length < 6 || memcmp(pattern + length - 6, "XXXXXX", 6)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ size_t start = length - 6;
+
+ static constexpr char random_characters[] = "abcdefghijklmnopqrstuvwxyz0123456789";
+
+ for (int attempt = 0; attempt < 100; ++attempt) {
+ for (int i = 0; i < 6; ++i)
+ pattern[start + i] = random_characters[(arc4random() % (sizeof(random_characters) - 1))];
+ struct stat st;
+ int rc = lstat(pattern, &st);
+ if (rc < 0 && errno == ENOENT)
+ return 0;
+ }
+ errno = EEXIST;
+ return -1;
+}
+
+extern "C" {
+
+void exit(int status)
+{
+ __cxa_finalize(nullptr);
+
+ if (getenv("LIBC_DUMP_MALLOC_STATS"))
+ serenity_dump_malloc_stats();
+
+ extern void _fini();
+ _fini();
+ fflush(stdout);
+ fflush(stderr);
+ _exit(status);
+}
+
+static void __atexit_to_cxa_atexit(void* handler)
+{
+ reinterpret_cast<void (*)()>(handler)();
+}
+
+int atexit(void (*handler)())
+{
+ return __cxa_atexit(__atexit_to_cxa_atexit, (void*)handler, nullptr);
+}
+
+void abort()
+{
+ // For starters, send ourselves a SIGABRT.
+ raise(SIGABRT);
+ // If that didn't kill us, try harder.
+ raise(SIGKILL);
+ _exit(127);
+}
+
+static HashTable<const char*> s_malloced_environment_variables;
+
+static void free_environment_variable_if_needed(const char* var)
+{
+ if (!s_malloced_environment_variables.contains(var))
+ return;
+ free(const_cast<char*>(var));
+ s_malloced_environment_variables.remove(var);
+}
+
+char* getenv(const char* name)
+{
+ size_t vl = strlen(name);
+ for (size_t i = 0; environ[i]; ++i) {
+ const char* decl = environ[i];
+ char* eq = strchr(decl, '=');
+ if (!eq)
+ continue;
+ size_t varLength = eq - decl;
+ if (vl != varLength)
+ continue;
+ if (strncmp(decl, name, varLength) == 0) {
+ return eq + 1;
+ }
+ }
+ return nullptr;
+}
+
+int unsetenv(const char* name)
+{
+ auto new_var_len = strlen(name);
+ size_t environ_size = 0;
+ int skip = -1;
+
+ for (; environ[environ_size]; ++environ_size) {
+ char* old_var = environ[environ_size];
+ char* old_eq = strchr(old_var, '=');
+ ASSERT(old_eq);
+ size_t old_var_len = old_eq - old_var;
+
+ if (new_var_len != old_var_len)
+ continue; // can't match
+
+ if (strncmp(name, old_var, new_var_len) == 0)
+ skip = environ_size;
+ }
+
+ if (skip == -1)
+ return 0; // not found: no failure.
+
+ // Shuffle the existing array down by one.
+ memmove(&environ[skip], &environ[skip + 1], ((environ_size - 1) - skip) * sizeof(environ[0]));
+ environ[environ_size - 1] = nullptr;
+
+ free_environment_variable_if_needed(name);
+ return 0;
+}
+
+int clearenv()
+{
+ size_t environ_size = 0;
+ for (; environ[environ_size]; ++environ_size) {
+ environ[environ_size] = NULL;
+ }
+ *environ = NULL;
+ return 0;
+}
+
+int setenv(const char* name, const char* value, int overwrite)
+{
+ if (!overwrite && getenv(name))
+ return 0;
+ auto length = strlen(name) + strlen(value) + 2;
+ auto* var = (char*)malloc(length);
+ snprintf(var, length, "%s=%s", name, value);
+ s_malloced_environment_variables.set(var);
+ return putenv(var);
+}
+
+int putenv(char* new_var)
+{
+ char* new_eq = strchr(new_var, '=');
+
+ if (!new_eq)
+ return unsetenv(new_var);
+
+ auto new_var_len = new_eq - new_var;
+ int environ_size = 0;
+ for (; environ[environ_size]; ++environ_size) {
+ char* old_var = environ[environ_size];
+ char* old_eq = strchr(old_var, '=');
+ ASSERT(old_eq);
+ auto old_var_len = old_eq - old_var;
+
+ if (new_var_len != old_var_len)
+ continue; // can't match
+
+ if (strncmp(new_var, old_var, new_var_len) == 0) {
+ free_environment_variable_if_needed(old_var);
+ environ[environ_size] = new_var;
+ return 0;
+ }
+ }
+
+ // At this point, we need to append the new var.
+ // 2 here: one for the new var, one for the sentinel value.
+ char** new_environ = (char**)malloc((environ_size + 2) * sizeof(char*));
+ if (new_environ == nullptr) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ for (int i = 0; environ[i]; ++i) {
+ new_environ[i] = environ[i];
+ }
+
+ new_environ[environ_size] = new_var;
+ new_environ[environ_size + 1] = nullptr;
+
+ // swap new and old
+ // note that the initial environ is not heap allocated!
+ extern bool __environ_is_malloced;
+ if (__environ_is_malloced)
+ free(environ);
+ __environ_is_malloced = true;
+ environ = new_environ;
+ return 0;
+}
+
+double strtod(const char* str, char** endptr)
+{
+ // Parse spaces, sign, and base
+ char* parse_ptr = const_cast<char*>(str);
+ strtons(parse_ptr, &parse_ptr);
+ const Sign sign = strtosign(parse_ptr, &parse_ptr);
+
+ // Parse inf/nan, if applicable.
+ if (is_either(parse_ptr, 0, 'i', 'I')) {
+ if (is_either(parse_ptr, 1, 'n', 'N')) {
+ if (is_either(parse_ptr, 2, 'f', 'F')) {
+ parse_ptr += 3;
+ if (is_either(parse_ptr, 0, 'i', 'I')) {
+ if (is_either(parse_ptr, 1, 'n', 'N')) {
+ if (is_either(parse_ptr, 2, 'i', 'I')) {
+ if (is_either(parse_ptr, 3, 't', 'T')) {
+ if (is_either(parse_ptr, 4, 'y', 'Y')) {
+ parse_ptr += 5;
+ }
+ }
+ }
+ }
+ }
+ if (endptr)
+ *endptr = parse_ptr;
+ // Don't set errno to ERANGE here:
+ // The caller may want to distinguish between "input is
+ // literal infinity" and "input is not literal infinity
+ // but did not fit into double".
+ if (sign != Sign::Negative) {
+ return __builtin_huge_val();
+ } else {
+ return -__builtin_huge_val();
+ }
+ }
+ }
+ }
+ if (is_either(parse_ptr, 0, 'n', 'N')) {
+ if (is_either(parse_ptr, 1, 'a', 'A')) {
+ if (is_either(parse_ptr, 2, 'n', 'N')) {
+ if (endptr)
+ *endptr = parse_ptr + 3;
+ errno = ERANGE;
+ if (sign != Sign::Negative) {
+ return __builtin_nan("");
+ } else {
+ return -__builtin_nan("");
+ }
+ }
+ }
+ }
+
+ // Parse base
+ char exponent_lower;
+ char exponent_upper;
+ int base = 10;
+ if (*parse_ptr == '0') {
+ const char base_ch = *(parse_ptr + 1);
+ if (base_ch == 'x' || base_ch == 'X') {
+ base = 16;
+ parse_ptr += 2;
+ }
+ }
+
+ if (base == 10) {
+ exponent_lower = 'e';
+ exponent_upper = 'E';
+ } else {
+ exponent_lower = 'p';
+ exponent_upper = 'P';
+ }
+
+ // Parse "digits", possibly keeping track of the exponent offset.
+ // We parse the most significant digits and the position in the
+ // base-`base` representation separately. This allows us to handle
+ // numbers like `0.0000000000000000000000000000000000001234` or
+ // `1234567890123456789012345678901234567890` with ease.
+ LongLongParser digits { sign, base };
+ bool digits_usable = false;
+ bool should_continue = true;
+ bool digits_overflow = false;
+ bool after_decimal = false;
+ int exponent = 0;
+ do {
+ if (!after_decimal && *parse_ptr == '.') {
+ after_decimal = true;
+ parse_ptr += 1;
+ continue;
+ }
+
+ bool is_a_digit;
+ if (digits_overflow) {
+ is_a_digit = digits.parse_digit(*parse_ptr) != -1;
+ } else {
+ DigitConsumeDecision decision = digits.consume(*parse_ptr);
+ switch (decision) {
+ case DigitConsumeDecision::Consumed:
+ is_a_digit = true;
+ // The very first actual digit must pass here:
+ digits_usable = true;
+ break;
+ case DigitConsumeDecision::PosOverflow:
+ case DigitConsumeDecision::NegOverflow:
+ is_a_digit = true;
+ digits_overflow = true;
+ break;
+ case DigitConsumeDecision::Invalid:
+ is_a_digit = false;
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ if (is_a_digit) {
+ exponent -= after_decimal ? 1 : 0;
+ exponent += digits_overflow ? 1 : 0;
+ }
+
+ should_continue = is_a_digit;
+ parse_ptr += should_continue;
+ } while (should_continue);
+
+ if (!digits_usable) {
+ // No actual number value available.
+ if (endptr)
+ *endptr = const_cast<char*>(str);
+ return 0.0;
+ }
+
+ // Parse exponent.
+ // We already know the next character is not a digit in the current base,
+ // nor a valid decimal point. Check whether it's an exponent sign.
+ if (*parse_ptr == exponent_lower || *parse_ptr == exponent_upper) {
+ // Need to keep the old parse_ptr around, in case of rollback.
+ char* old_parse_ptr = parse_ptr;
+ parse_ptr += 1;
+
+ // Can't use atol or strtol here: Must accept excessive exponents,
+ // even exponents >64 bits.
+ Sign exponent_sign = strtosign(parse_ptr, &parse_ptr);
+ IntParser exponent_parser { exponent_sign, base };
+ bool exponent_usable = false;
+ bool exponent_overflow = false;
+ should_continue = true;
+ do {
+ bool is_a_digit;
+ if (exponent_overflow) {
+ is_a_digit = exponent_parser.parse_digit(*parse_ptr) != -1;
+ } else {
+ DigitConsumeDecision decision = exponent_parser.consume(*parse_ptr);
+ switch (decision) {
+ case DigitConsumeDecision::Consumed:
+ is_a_digit = true;
+ // The very first actual digit must pass here:
+ exponent_usable = true;
+ break;
+ case DigitConsumeDecision::PosOverflow:
+ case DigitConsumeDecision::NegOverflow:
+ is_a_digit = true;
+ exponent_overflow = true;
+ break;
+ case DigitConsumeDecision::Invalid:
+ is_a_digit = false;
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ should_continue = is_a_digit;
+ parse_ptr += should_continue;
+ } while (should_continue);
+
+ if (!exponent_usable) {
+ parse_ptr = old_parse_ptr;
+ } else if (exponent_overflow) {
+ // Technically this is wrong. If someone gives us 5GB of digits,
+ // and then an exponent of -5_000_000_000, the resulting exponent
+ // should be around 0.
+ // However, I think it's safe to assume that we never have to deal
+ // with that many digits anyway.
+ if (sign != Sign::Negative) {
+ exponent = INT_MIN;
+ } else {
+ exponent = INT_MAX;
+ }
+ } else {
+ // Literal exponent is usable and fits in an int.
+ // However, `exponent + exponent_parser.number()` might overflow an int.
+ // This would result in the wrong sign of the exponent!
+ long long new_exponent = static_cast<long long>(exponent) + static_cast<long long>(exponent_parser.number());
+ if (new_exponent < INT_MIN) {
+ exponent = INT_MIN;
+ } else if (new_exponent > INT_MAX) {
+ exponent = INT_MAX;
+ } else {
+ exponent = static_cast<int>(new_exponent);
+ }
+ }
+ }
+
+ // Parsing finished. now we only have to compute the result.
+ if (endptr)
+ *endptr = const_cast<char*>(parse_ptr);
+
+ // If `digits` is zero, we don't even have to look at `exponent`.
+ if (digits.number() == 0) {
+ if (sign != Sign::Negative) {
+ return 0.0;
+ } else {
+ return -0.0;
+ }
+ }
+
+ // Deal with extreme exponents.
+ // The smallest normal is 2^-1022.
+ // The smallest denormal is 2^-1074.
+ // The largest number in `digits` is 2^63 - 1.
+ // Therefore, if "base^exponent" is smaller than 2^-(1074+63), the result is 0.0 anyway.
+ // This threshold is roughly 5.3566 * 10^-343.
+ // So if the resulting exponent is -344 or lower (closer to -inf),
+ // the result is 0.0 anyway.
+ // We only need to avoid false positives, so we can ignore base 16.
+ if (exponent <= -344) {
+ errno = ERANGE;
+ // Definitely can't be represented more precisely.
+ // I lied, sometimes the result is +0.0, and sometimes -0.0.
+ if (sign != Sign::Negative) {
+ return 0.0;
+ } else {
+ return -0.0;
+ }
+ }
+ // The largest normal is 2^+1024-eps.
+ // The smallest number in `digits` is 1.
+ // Therefore, if "base^exponent" is 2^+1024, the result is INF anyway.
+ // This threshold is roughly 1.7977 * 10^-308.
+ // So if the resulting exponent is +309 or higher,
+ // the result is INF anyway.
+ // We only need to avoid false positives, so we can ignore base 16.
+ if (exponent >= 309) {
+ errno = ERANGE;
+ // Definitely can't be represented more precisely.
+ // I lied, sometimes the result is +INF, and sometimes -INF.
+ if (sign != Sign::Negative) {
+ return __builtin_huge_val();
+ } else {
+ return -__builtin_huge_val();
+ }
+ }
+
+ // TODO: If `exponent` is large, this could be made faster.
+ double value = digits.number();
+ if (exponent < 0) {
+ exponent = -exponent;
+ for (int i = 0; i < exponent; ++i) {
+ value /= base;
+ }
+ if (value == -0.0 || value == +0.0) {
+ errno = ERANGE;
+ }
+ } else if (exponent > 0) {
+ for (int i = 0; i < exponent; ++i) {
+ value *= base;
+ }
+ if (value == -__builtin_huge_val() || value == +__builtin_huge_val()) {
+ errno = ERANGE;
+ }
+ }
+
+ return value;
+}
+
+long double strtold(const char* str, char** endptr)
+{
+ assert(sizeof(double) == sizeof(long double));
+ return strtod(str, endptr);
+}
+
+float strtof(const char* str, char** endptr)
+{
+ return strtod(str, endptr);
+}
+
+double atof(const char* str)
+{
+ return strtod(str, nullptr);
+}
+
+int atoi(const char* str)
+{
+ long value = strtol(str, nullptr, 10);
+ if (value > INT_MAX) {
+ return INT_MAX;
+ }
+ return value;
+}
+
+long atol(const char* str)
+{
+ return strtol(str, nullptr, 10);
+}
+
+long long atoll(const char* str)
+{
+ return strtoll(str, nullptr, 10);
+}
+
+static char ptsname_buf[32];
+char* ptsname(int fd)
+{
+ if (ptsname_r(fd, ptsname_buf, sizeof(ptsname_buf)) < 0)
+ return nullptr;
+ return ptsname_buf;
+}
+
+int ptsname_r(int fd, char* buffer, size_t size)
+{
+ int rc = syscall(SC_ptsname, fd, buffer, size);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+static unsigned long s_next_rand = 1;
+
+int rand()
+{
+ s_next_rand = s_next_rand * 1103515245 + 12345;
+ return ((unsigned)(s_next_rand / ((RAND_MAX + 1) * 2)) % (RAND_MAX + 1));
+}
+
+void srand(unsigned seed)
+{
+ s_next_rand = seed;
+}
+
+int abs(int i)
+{
+ return i < 0 ? -i : i;
+}
+
+long int random()
+{
+ return rand();
+}
+
+void srandom(unsigned seed)
+{
+ srand(seed);
+}
+
+int system(const char* command)
+{
+ if (!command)
+ return 1;
+
+ pid_t child;
+ const char* argv[] = { "sh", "-c", command, nullptr };
+ if ((errno = posix_spawn(&child, "/bin/sh", nullptr, nullptr, const_cast<char**>(argv), environ)))
+ return -1;
+ int wstatus;
+ waitpid(child, &wstatus, 0);
+ return WEXITSTATUS(wstatus);
+}
+
+char* mktemp(char* pattern)
+{
+ if (__generate_unique_filename(pattern) < 0)
+ pattern[0] = '\0';
+
+ return pattern;
+}
+
+int mkstemp(char* pattern)
+{
+ char* path = mktemp(pattern);
+
+ int fd = open(path, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); // I'm using the flags I saw glibc using.
+ if (fd >= 0)
+ return fd;
+
+ return -1;
+}
+
+char* mkdtemp(char* pattern)
+{
+ if (__generate_unique_filename(pattern) < 0)
+ return nullptr;
+
+ if (mkdir(pattern, 0700) < 0)
+ return nullptr;
+
+ return pattern;
+}
+
+void* bsearch(const void* key, const void* base, size_t nmemb, size_t size, int (*compar)(const void*, const void*))
+{
+ char* start = static_cast<char*>(const_cast<void*>(base));
+ while (nmemb > 0) {
+ char* middle_memb = start + (nmemb / 2) * size;
+ int comparison = compar(key, middle_memb);
+ if (comparison == 0)
+ return middle_memb;
+ else if (comparison > 0) {
+ start = middle_memb + size;
+ --nmemb;
+ }
+ nmemb /= 2;
+ }
+
+ return nullptr;
+}
+
+div_t div(int numerator, int denominator)
+{
+ div_t result;
+ result.quot = numerator / denominator;
+ result.rem = numerator % denominator;
+
+ if (numerator >= 0 && result.rem < 0) {
+ result.quot++;
+ result.rem -= denominator;
+ }
+ return result;
+}
+
+ldiv_t ldiv(long numerator, long denominator)
+{
+ ldiv_t result;
+ result.quot = numerator / denominator;
+ result.rem = numerator % denominator;
+
+ if (numerator >= 0 && result.rem < 0) {
+ result.quot++;
+ result.rem -= denominator;
+ }
+ return result;
+}
+
+size_t mbstowcs(wchar_t*, const char*, size_t)
+{
+ ASSERT_NOT_REACHED();
+}
+
+int mbtowc(wchar_t* wch, const char* data, [[maybe_unused]] size_t data_size)
+{
+ // FIXME: This needs a real implementation.
+ if (wch && data) {
+ *wch = *data;
+ return 1;
+ }
+
+ if (!wch && data) {
+ return 1;
+ }
+
+ return 0;
+}
+
+int wctomb(char*, wchar_t)
+{
+ ASSERT_NOT_REACHED();
+}
+
+size_t wcstombs(char* dest, const wchar_t* src, size_t max)
+{
+ char* originalDest = dest;
+ while ((size_t)(dest - originalDest) < max) {
+ StringView v { (const char*)src, sizeof(wchar_t) };
+
+ // FIXME: dependent on locale, for now utf-8 is supported.
+ Utf8View utf8 { v };
+ if (*utf8.begin() == '\0') {
+ *dest = '\0';
+ return (size_t)(dest - originalDest); // Exclude null character in returned size
+ }
+
+ for (auto byte : utf8) {
+ if (byte != '\0')
+ *dest++ = byte;
+ }
+ ++src;
+ }
+ return max;
+}
+
+long strtol(const char* str, char** endptr, int base)
+{
+ long long value = strtoll(str, endptr, base);
+ if (value < LONG_MIN) {
+ errno = ERANGE;
+ return LONG_MIN;
+ } else if (value > LONG_MAX) {
+ errno = ERANGE;
+ return LONG_MAX;
+ }
+ return value;
+}
+
+unsigned long strtoul(const char* str, char** endptr, int base)
+{
+ unsigned long long value = strtoull(str, endptr, base);
+ if (value > ULONG_MAX) {
+ errno = ERANGE;
+ return ULONG_MAX;
+ }
+ return value;
+}
+
+long long strtoll(const char* str, char** endptr, int base)
+{
+ // Parse spaces and sign
+ char* parse_ptr = const_cast<char*>(str);
+ strtons(parse_ptr, &parse_ptr);
+ const Sign sign = strtosign(parse_ptr, &parse_ptr);
+
+ // Parse base
+ if (base == 0) {
+ if (*parse_ptr == '0') {
+ if (tolower(*(parse_ptr + 1)) == 'x') {
+ base = 16;
+ parse_ptr += 2;
+ } else {
+ base = 8;
+ }
+ } else {
+ base = 10;
+ }
+ }
+
+ // Parse actual digits.
+ LongLongParser digits { sign, base };
+ bool digits_usable = false;
+ bool should_continue = true;
+ bool overflow = false;
+ do {
+ bool is_a_digit;
+ if (overflow) {
+ is_a_digit = digits.parse_digit(*parse_ptr) >= 0;
+ } else {
+ DigitConsumeDecision decision = digits.consume(*parse_ptr);
+ switch (decision) {
+ case DigitConsumeDecision::Consumed:
+ is_a_digit = true;
+ // The very first actual digit must pass here:
+ digits_usable = true;
+ break;
+ case DigitConsumeDecision::PosOverflow:
+ case DigitConsumeDecision::NegOverflow:
+ is_a_digit = true;
+ overflow = true;
+ break;
+ case DigitConsumeDecision::Invalid:
+ is_a_digit = false;
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ should_continue = is_a_digit;
+ parse_ptr += should_continue;
+ } while (should_continue);
+
+ if (!digits_usable) {
+ // No actual number value available.
+ if (endptr)
+ *endptr = const_cast<char*>(str);
+ return 0;
+ }
+
+ if (endptr)
+ *endptr = parse_ptr;
+
+ if (overflow) {
+ errno = ERANGE;
+ if (sign != Sign::Negative) {
+ return LONG_LONG_MAX;
+ } else {
+ return LONG_LONG_MIN;
+ }
+ }
+
+ return digits.number();
+}
+
+unsigned long long strtoull(const char* str, char** endptr, int base)
+{
+ // Parse spaces and sign
+ char* parse_ptr = const_cast<char*>(str);
+ strtons(parse_ptr, &parse_ptr);
+
+ // Parse base
+ if (base == 0) {
+ if (*parse_ptr == '0') {
+ if (tolower(*(parse_ptr + 1)) == 'x') {
+ base = 16;
+ parse_ptr += 2;
+ } else {
+ base = 8;
+ }
+ } else {
+ base = 10;
+ }
+ }
+
+ // Parse actual digits.
+ ULongLongParser digits { Sign::Positive, base };
+ bool digits_usable = false;
+ bool should_continue = true;
+ bool overflow = false;
+ do {
+ bool is_a_digit;
+ if (overflow) {
+ is_a_digit = digits.parse_digit(*parse_ptr) >= 0;
+ } else {
+ DigitConsumeDecision decision = digits.consume(*parse_ptr);
+ switch (decision) {
+ case DigitConsumeDecision::Consumed:
+ is_a_digit = true;
+ // The very first actual digit must pass here:
+ digits_usable = true;
+ break;
+ case DigitConsumeDecision::PosOverflow:
+ case DigitConsumeDecision::NegOverflow:
+ is_a_digit = true;
+ overflow = true;
+ break;
+ case DigitConsumeDecision::Invalid:
+ is_a_digit = false;
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ should_continue = is_a_digit;
+ parse_ptr += should_continue;
+ } while (should_continue);
+
+ if (!digits_usable) {
+ // No actual number value available.
+ if (endptr)
+ *endptr = const_cast<char*>(str);
+ return 0;
+ }
+
+ if (endptr)
+ *endptr = parse_ptr;
+
+ if (overflow) {
+ errno = ERANGE;
+ return LONG_LONG_MAX;
+ }
+
+ return digits.number();
+}
+
+// Serenity's PRNG is not cryptographically secure. Do not rely on this for
+// any real crypto! These functions (for now) are for compatibility.
+// TODO: In the future, rand can be made deterministic and this not.
+uint32_t arc4random(void)
+{
+ char buf[4];
+ syscall(SC_getrandom, buf, 4, 0);
+ return *(uint32_t*)buf;
+}
+
+void arc4random_buf(void* buffer, size_t buffer_size)
+{
+ // arc4random_buf should never fail, but user supplied buffers could fail.
+ // However, if the user passes a garbage buffer, that's on them.
+ syscall(SC_getrandom, buffer, buffer_size, 0);
+}
+
+uint32_t arc4random_uniform(uint32_t max_bounds)
+{
+ // XXX: Should actually apply special rules for uniformity; avoid what is
+ // called "modulo bias".
+ return arc4random() % max_bounds;
+}
+
+char* realpath(const char* pathname, char* buffer)
+{
+ if (!pathname) {
+ errno = EFAULT;
+ return nullptr;
+ }
+ size_t size = PATH_MAX;
+ if (buffer == nullptr)
+ buffer = (char*)malloc(size);
+ Syscall::SC_realpath_params params { { pathname, strlen(pathname) }, { buffer, size } };
+ int rc = syscall(SC_realpath, &params);
+ if (rc < 0) {
+ errno = -rc;
+ return nullptr;
+ }
+ errno = 0;
+ return buffer;
+}
+
+int posix_openpt(int flags)
+{
+ if (flags & ~(O_RDWR | O_NOCTTY | O_CLOEXEC)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return open("/dev/ptmx", flags);
+}
+
+int grantpt([[maybe_unused]] int fd)
+{
+ return 0;
+}
+
+int unlockpt([[maybe_unused]] int fd)
+{
+ return 0;
+}
+}
diff --git a/Userland/Libraries/LibC/stdlib.h b/Userland/Libraries/LibC/stdlib.h
new file mode 100644
index 0000000000..735e62e36a
--- /dev/null
+++ b/Userland/Libraries/LibC/stdlib.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__attribute__((warn_unused_result)) int __generate_unique_filename(char* pattern);
+
+__BEGIN_DECLS
+
+#define EXIT_SUCCESS 0
+#define EXIT_FAILURE 1
+#define MB_CUR_MAX 1
+
+__attribute__((malloc)) __attribute__((alloc_size(1))) void* malloc(size_t);
+__attribute__((malloc)) __attribute__((alloc_size(1, 2))) void* calloc(size_t nmemb, size_t);
+size_t malloc_size(void*);
+void serenity_dump_malloc_stats(void);
+void free(void*);
+__attribute__((alloc_size(2))) void* realloc(void* ptr, size_t);
+char* getenv(const char* name);
+int putenv(char*);
+int unsetenv(const char*);
+int clearenv(void);
+int setenv(const char* name, const char* value, int overwrite);
+int atoi(const char*);
+long atol(const char*);
+long long atoll(const char*);
+double strtod(const char*, char** endptr);
+long double strtold(const char*, char** endptr);
+float strtof(const char*, char** endptr);
+long strtol(const char*, char** endptr, int base);
+long long strtoll(const char*, char** endptr, int base);
+unsigned long long strtoull(const char*, char** endptr, int base);
+unsigned long strtoul(const char*, char** endptr, int base);
+void qsort(void* base, size_t nmemb, size_t size, int (*compar)(const void*, const void*));
+void qsort_r(void* base, size_t nmemb, size_t size, int (*compar)(const void*, const void*, void*), void* arg);
+int atexit(void (*function)());
+__attribute__((noreturn)) void exit(int status);
+__attribute__((noreturn)) void abort();
+char* ptsname(int fd);
+int ptsname_r(int fd, char* buffer, size_t);
+int abs(int);
+long labs(long);
+double atof(const char*);
+int system(const char* command);
+char* mktemp(char*);
+int mkstemp(char*);
+char* mkdtemp(char*);
+void* bsearch(const void* key, const void* base, size_t nmemb, size_t size, int (*compar)(const void*, const void*));
+size_t mbstowcs(wchar_t*, const char*, size_t);
+int mbtowc(wchar_t*, const char*, size_t);
+int wctomb(char*, wchar_t);
+size_t wcstombs(char*, const wchar_t*, size_t);
+char* realpath(const char* pathname, char* buffer);
+
+#define RAND_MAX 32767
+int rand();
+void srand(unsigned seed);
+
+long int random();
+void srandom(unsigned seed);
+
+uint32_t arc4random(void);
+void arc4random_buf(void*, size_t);
+uint32_t arc4random_uniform(uint32_t);
+
+typedef struct {
+ int quot;
+ int rem;
+} div_t;
+div_t div(int, int);
+typedef struct {
+ long quot;
+ long rem;
+} ldiv_t;
+ldiv_t ldiv(long, long);
+
+int posix_openpt(int flags);
+int grantpt(int fd);
+int unlockpt(int fd);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/string.cpp b/Userland/Libraries/LibC/string.cpp
new file mode 100644
index 0000000000..86a72b72a9
--- /dev/null
+++ b/Userland/Libraries/LibC/string.cpp
@@ -0,0 +1,486 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/MemMem.h>
+#include <AK/Platform.h>
+#include <AK/StdLibExtras.h>
+#include <AK/Types.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+extern "C" {
+
+size_t strspn(const char* s, const char* accept)
+{
+ const char* p = s;
+cont:
+ char ch = *p++;
+ char ac;
+ for (const char* ap = accept; (ac = *ap++) != '\0';) {
+ if (ac == ch)
+ goto cont;
+ }
+ return p - 1 - s;
+}
+
+size_t strcspn(const char* s, const char* reject)
+{
+ for (auto* p = s;;) {
+ char c = *p++;
+ auto* rp = reject;
+ char rc;
+ do {
+ if ((rc = *rp++) == c)
+ return p - 1 - s;
+ } while (rc);
+ }
+}
+
+size_t strlen(const char* str)
+{
+ size_t len = 0;
+ while (*(str++))
+ ++len;
+ return len;
+}
+
+size_t strnlen(const char* str, size_t maxlen)
+{
+ size_t len = 0;
+ for (; len < maxlen && *str; str++)
+ len++;
+ return len;
+}
+
+char* strdup(const char* str)
+{
+ size_t len = strlen(str);
+ char* new_str = (char*)malloc(len + 1);
+ memcpy(new_str, str, len);
+ new_str[len] = '\0';
+ return new_str;
+}
+
+char* strndup(const char* str, size_t maxlen)
+{
+ size_t len = strnlen(str, maxlen);
+ char* new_str = (char*)malloc(len + 1);
+ memcpy(new_str, str, len);
+ new_str[len] = 0;
+ return new_str;
+}
+
+int strcmp(const char* s1, const char* s2)
+{
+ while (*s1 == *s2++)
+ if (*s1++ == 0)
+ return 0;
+ return *(const unsigned char*)s1 - *(const unsigned char*)--s2;
+}
+
+int strncmp(const char* s1, const char* s2, size_t n)
+{
+ if (!n)
+ return 0;
+ do {
+ if (*s1 != *s2++)
+ return *(const unsigned char*)s1 - *(const unsigned char*)--s2;
+ if (*s1++ == 0)
+ break;
+ } while (--n);
+ return 0;
+}
+
+int memcmp(const void* v1, const void* v2, size_t n)
+{
+ auto* s1 = (const uint8_t*)v1;
+ auto* s2 = (const uint8_t*)v2;
+ while (n-- > 0) {
+ if (*s1++ != *s2++)
+ return s1[-1] < s2[-1] ? -1 : 1;
+ }
+ return 0;
+}
+
+#if ARCH(I386)
+void* memcpy(void* dest_ptr, const void* src_ptr, size_t n)
+{
+ void* original_dest = dest_ptr;
+ asm volatile(
+ "rep movsb"
+ : "+D"(dest_ptr), "+S"(src_ptr), "+c"(n)::"memory");
+ return original_dest;
+}
+
+void* memset(void* dest_ptr, int c, size_t n)
+{
+ void* original_dest = dest_ptr;
+ asm volatile(
+ "rep stosb\n"
+ : "=D"(dest_ptr), "=c"(n)
+ : "0"(dest_ptr), "1"(n), "a"(c)
+ : "memory");
+ return original_dest;
+}
+#else
+void* memcpy(void* dest_ptr, const void* src_ptr, size_t n)
+{
+ auto* dest = (u8*)dest_ptr;
+ auto* src = (const u8*)src_ptr;
+ for (size_t i = 0; i < n; ++i)
+ dest[i] = src[i];
+ return dest_ptr;
+}
+
+void* memset(void* dest_ptr, int c, size_t n)
+{
+ auto* dest = (u8*)dest_ptr;
+ for (size_t i = 0; i < n; ++i)
+ dest[i] = (u8)c;
+ return dest_ptr;
+}
+#endif
+
+void* memmove(void* dest, const void* src, size_t n)
+{
+ if (dest < src)
+ return memcpy(dest, src, n);
+
+ u8* pd = (u8*)dest;
+ const u8* ps = (const u8*)src;
+ for (pd += n, ps += n; n--;)
+ *--pd = *--ps;
+ return dest;
+}
+
+const void* memmem(const void* haystack, size_t haystack_length, const void* needle, size_t needle_length)
+{
+ return AK::memmem(haystack, haystack_length, needle, needle_length);
+}
+
+char* strcpy(char* dest, const char* src)
+{
+ char* originalDest = dest;
+ while ((*dest++ = *src++) != '\0')
+ ;
+ return originalDest;
+}
+
+char* strncpy(char* dest, const char* src, size_t n)
+{
+ size_t i;
+ for (i = 0; i < n && src[i] != '\0'; ++i)
+ dest[i] = src[i];
+ for (; i < n; ++i)
+ dest[i] = '\0';
+ return dest;
+}
+
+size_t strlcpy(char* dest, const char* src, size_t n)
+{
+ size_t i;
+ // Would like to test i < n - 1 here, but n might be 0.
+ for (i = 0; i + 1 < n && src[i] != '\0'; ++i)
+ dest[i] = src[i];
+ if (n)
+ dest[i] = '\0';
+ for (; src[i] != '\0'; ++i)
+ ; // Determine the length of src, don't copy.
+ return i;
+}
+
+char* strchr(const char* str, int c)
+{
+ char ch = c;
+ for (;; ++str) {
+ if (*str == ch)
+ return const_cast<char*>(str);
+ if (!*str)
+ return nullptr;
+ }
+}
+
+char* strchrnul(const char* str, int c)
+{
+ char ch = c;
+ for (;; ++str) {
+ if (*str == ch || !*str)
+ return const_cast<char*>(str);
+ }
+}
+
+void* memchr(const void* ptr, int c, size_t size)
+{
+ char ch = c;
+ auto* cptr = (const char*)ptr;
+ for (size_t i = 0; i < size; ++i) {
+ if (cptr[i] == ch)
+ return const_cast<char*>(cptr + i);
+ }
+ return nullptr;
+}
+
+char* strrchr(const char* str, int ch)
+{
+ char* last = nullptr;
+ char c;
+ for (; (c = *str); ++str) {
+ if (c == ch)
+ last = const_cast<char*>(str);
+ }
+ return last;
+}
+
+char* strcat(char* dest, const char* src)
+{
+ size_t dest_length = strlen(dest);
+ size_t i;
+ for (i = 0; src[i] != '\0'; i++)
+ dest[dest_length + i] = src[i];
+ dest[dest_length + i] = '\0';
+ return dest;
+}
+
+char* strncat(char* dest, const char* src, size_t n)
+{
+ size_t dest_length = strlen(dest);
+ size_t i;
+ for (i = 0; i < n && src[i] != '\0'; i++)
+ dest[dest_length + i] = src[i];
+ dest[dest_length + i] = '\0';
+ return dest;
+}
+
+const char* const sys_errlist[] = {
+ "Success (not an error)",
+ "Operation not permitted",
+ "No such file or directory",
+ "No such process",
+ "Interrupted syscall",
+ "I/O error",
+ "No such device or address",
+ "Argument list too long",
+ "Exec format error",
+ "Bad fd number",
+ "No child processes",
+ "Try again",
+ "Out of memory",
+ "Permission denied",
+ "Bad address",
+ "Block device required",
+ "Device or resource busy",
+ "File already exists",
+ "Cross-device link",
+ "No such device",
+ "Not a directory",
+ "Is a directory",
+ "Invalid argument",
+ "File table overflow",
+ "Too many open files",
+ "Not a TTY",
+ "Text file busy",
+ "File too large",
+ "No space left on device",
+ "Illegal seek",
+ "Read-only filesystem",
+ "Too many links",
+ "Broken pipe",
+ "Range error",
+ "Name too long",
+ "Too many symlinks",
+ "Overflow",
+ "Operation not supported",
+ "No such syscall",
+ "Not implemented",
+ "Address family not supported",
+ "Not a socket",
+ "Address in use",
+ "Failed without setting an error code (bug!)",
+ "Directory not empty",
+ "Math argument out of domain",
+ "Connection refused",
+ "Address not available",
+ "Already connected",
+ "Connection aborted",
+ "Connection already in progress",
+ "Connection reset",
+ "Destination address required",
+ "Host unreachable",
+ "Illegal byte sequence",
+ "Message size",
+ "Network down",
+ "Network unreachable",
+ "Network reset",
+ "No buffer space",
+ "No lock available",
+ "No message",
+ "No protocol option",
+ "Not connected",
+ "Operation would block",
+ "Protocol not supported",
+ "Resource deadlock would occur",
+ "Timed out",
+ "Wrong protocol type",
+ "Operation in progress",
+ "No such thread",
+ "Protocol error",
+ "Not supported",
+ "Protocol family not supported",
+ "Cannot make directory a subdirectory of itself",
+ "The highest errno +1 :^)",
+};
+
+int sys_nerr = EMAXERRNO;
+
+char* strerror(int errnum)
+{
+ if (errnum < 0 || errnum >= EMAXERRNO) {
+ printf("strerror() missing string for errnum=%d\n", errnum);
+ return const_cast<char*>("Unknown error");
+ }
+ return const_cast<char*>(sys_errlist[errnum]);
+}
+
+char* strsignal(int signum)
+{
+ if (signum >= NSIG) {
+ printf("strsignal() missing string for signum=%d\n", signum);
+ return const_cast<char*>("Unknown signal");
+ }
+ return const_cast<char*>(sys_siglist[signum]);
+}
+
+char* strstr(const char* haystack, const char* needle)
+{
+ char nch;
+ char hch;
+
+ if ((nch = *needle++) != 0) {
+ size_t len = strlen(needle);
+ do {
+ do {
+ if ((hch = *haystack++) == 0)
+ return nullptr;
+ } while (hch != nch);
+ } while (strncmp(haystack, needle, len) != 0);
+ --haystack;
+ }
+ return const_cast<char*>(haystack);
+}
+
+char* strpbrk(const char* s, const char* accept)
+{
+ while (*s)
+ if (strchr(accept, *s++))
+ return const_cast<char*>(--s);
+ return nullptr;
+}
+
+char* strtok_r(char* str, const char* delim, char** saved_str)
+{
+ if (!str) {
+ if (!saved_str)
+ return nullptr;
+ str = *saved_str;
+ }
+
+ size_t token_start = 0;
+ size_t token_end = 0;
+ size_t str_len = strlen(str);
+ size_t delim_len = strlen(delim);
+
+ for (size_t i = 0; i < str_len; ++i) {
+ bool is_proper_delim = false;
+
+ for (size_t j = 0; j < delim_len; ++j) {
+ if (str[i] == delim[j]) {
+ // Skip beginning delimiters
+ if (token_end - token_start == 0) {
+ ++token_start;
+ break;
+ }
+
+ is_proper_delim = true;
+ }
+ }
+
+ ++token_end;
+ if (is_proper_delim && token_end > 0) {
+ --token_end;
+ break;
+ }
+ }
+
+ if (str[token_start] == '\0')
+ return nullptr;
+
+ if (token_end == 0) {
+ *saved_str = nullptr;
+ return &str[token_start];
+ }
+
+ if (str[token_end] == '\0')
+ *saved_str = &str[token_end];
+ else
+ *saved_str = &str[token_end + 1];
+
+ str[token_end] = '\0';
+ return &str[token_start];
+}
+
+char* strtok(char* str, const char* delim)
+{
+ static char* saved_str;
+ return strtok_r(str, delim, &saved_str);
+}
+
+int strcoll(const char* s1, const char* s2)
+{
+ return strcmp(s1, s2);
+}
+
+size_t strxfrm(char* dest, const char* src, size_t n)
+{
+ size_t i;
+ for (i = 0; i < n && src[i] != '\0'; ++i)
+ dest[i] = src[i];
+ for (; i < n; ++i)
+ dest[i] = '\0';
+ return i;
+}
+
+void explicit_bzero(void* ptr, size_t size)
+{
+ memset(ptr, 0, size);
+ asm volatile("" ::
+ : "memory");
+}
+}
diff --git a/Userland/Libraries/LibC/string.h b/Userland/Libraries/LibC/string.h
new file mode 100644
index 0000000000..40a2aabe7d
--- /dev/null
+++ b/Userland/Libraries/LibC/string.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+size_t strlen(const char*);
+size_t strnlen(const char*, size_t maxlen);
+
+int strcmp(const char*, const char*);
+int strncmp(const char*, const char*, size_t);
+
+int memcmp(const void*, const void*, size_t);
+void* memcpy(void*, const void*, size_t);
+void* memmove(void*, const void*, size_t);
+void* memchr(const void*, int c, size_t);
+const void* memmem(const void* haystack, size_t, const void* needle, size_t);
+
+void* memset(void*, int, size_t);
+void explicit_bzero(void*, size_t) __attribute__((nonnull(1)));
+
+__attribute__((malloc)) char* strdup(const char*);
+__attribute__((malloc)) char* strndup(const char*, size_t);
+
+__attribute__((deprecated("use strlcpy or String::copy_characters_to_buffer"))) char* strcpy(char* dest, const char* src);
+__attribute__((deprecated("use strlcpy or String::copy_characters_to_buffer"))) char* strncpy(char* dest, const char* src, size_t);
+__attribute__((warn_unused_result)) size_t strlcpy(char* dest, const char* src, size_t);
+
+char* strchr(const char*, int c);
+char* strchrnul(const char*, int c);
+char* strstr(const char* haystack, const char* needle);
+char* strrchr(const char*, int c);
+
+__attribute__((deprecated("use strncat"))) char* strcat(char* dest, const char* src);
+char* strncat(char* dest, const char* src, size_t);
+
+size_t strspn(const char*, const char* accept);
+size_t strcspn(const char*, const char* reject);
+char* strerror(int errnum);
+char* strsignal(int signum);
+char* strpbrk(const char*, const char* accept);
+char* strtok_r(char* str, const char* delim, char** saved_str);
+char* strtok(char* str, const char* delim);
+int strcoll(const char* s1, const char* s2);
+size_t strxfrm(char* dest, const char* src, size_t n);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/strings.cpp b/Userland/Libraries/LibC/strings.cpp
new file mode 100644
index 0000000000..b5f6579f89
--- /dev/null
+++ b/Userland/Libraries/LibC/strings.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+#include <strings.h>
+
+extern "C" {
+
+void bzero(void* dest, size_t n)
+{
+ memset(dest, 0, n);
+}
+
+void bcopy(const void* src, void* dest, size_t n)
+{
+ memmove(dest, src, n);
+}
+
+static char foldcase(char ch)
+{
+ if (isalpha(ch))
+ return tolower(ch);
+ return ch;
+}
+
+int strcasecmp(const char* s1, const char* s2)
+{
+ for (; foldcase(*s1) == foldcase(*s2); ++s1, ++s2) {
+ if (*s1 == 0)
+ return 0;
+ }
+ return foldcase(*(const unsigned char*)s1) < foldcase(*(const unsigned char*)s2) ? -1 : 1;
+}
+
+int strncasecmp(const char* s1, const char* s2, size_t n)
+{
+ if (!n)
+ return 0;
+ do {
+ if (foldcase(*s1) != foldcase(*s2++))
+ return foldcase(*(const unsigned char*)s1) - foldcase(*(const unsigned char*)--s2);
+ if (*s1++ == 0)
+ break;
+ } while (--n);
+ return 0;
+}
+}
diff --git a/Userland/Libraries/LibC/strings.h b/Userland/Libraries/LibC/strings.h
new file mode 100644
index 0000000000..b157e30489
--- /dev/null
+++ b/Userland/Libraries/LibC/strings.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+int strcasecmp(const char*, const char*);
+int strncasecmp(const char*, const char*, size_t);
+void bzero(void*, size_t);
+void bcopy(const void*, void*, size_t);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/sys/arch/i386/regs.h b/Userland/Libraries/LibC/sys/arch/i386/regs.h
new file mode 100644
index 0000000000..d15d0fdd86
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/arch/i386/regs.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+
+struct [[gnu::packed]] PtraceRegisters {
+ u32 eax;
+ u32 ecx;
+ u32 edx;
+ u32 ebx;
+ u32 esp;
+ u32 ebp;
+ u32 esi;
+ u32 edi;
+ u32 eip;
+ u32 eflags;
+ u32 cs;
+ u32 ss;
+ u32 ds;
+ u32 es;
+ u32 fs;
+ u32 gs;
+};
diff --git a/Userland/Libraries/LibC/sys/cdefs.h b/Userland/Libraries/LibC/sys/cdefs.h
new file mode 100644
index 0000000000..4b7edc0cae
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/cdefs.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#define _POSIX_VERSION 200809L
+
+#ifndef ALWAYS_INLINE
+# define ALWAYS_INLINE inline __attribute__((always_inline))
+#endif
+
+#ifdef __cplusplus
+# ifndef __BEGIN_DECLS
+# define __BEGIN_DECLS extern "C" {
+# define __END_DECLS }
+# endif
+#else
+# ifndef __BEGIN_DECLS
+# define __BEGIN_DECLS
+# define __END_DECLS
+# endif
+#endif
+
+#undef __P
+#define __P(a) a
+
+#define offsetof(type, member) __builtin_offsetof(type, member)
+
+#ifdef __cplusplus
+//extern "C" int main(int, char**);
+#endif
diff --git a/Userland/Libraries/LibC/sys/file.h b/Userland/Libraries/LibC/sys/file.h
new file mode 100644
index 0000000000..2bf8abd099
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/file.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
diff --git a/Userland/Libraries/LibC/sys/internals.h b/Userland/Libraries/LibC/sys/internals.h
new file mode 100644
index 0000000000..c23785a02e
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/internals.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+typedef void (*AtExitFunction)(void*);
+
+extern void __libc_init();
+extern void __malloc_init();
+extern void __stdio_init();
+extern void _init();
+extern bool __environ_is_malloced;
+extern bool __stdio_is_initialized;
+
+int __cxa_atexit(AtExitFunction exit_function, void* parameter, void* dso_handle);
+void __cxa_finalize(void* dso_handle);
+[[noreturn]] void __cxa_pure_virtual() __attribute__((weak));
+[[noreturn]] void __stack_chk_fail();
+[[noreturn]] void __stack_chk_fail_local();
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/sys/ioctl.h b/Userland/Libraries/LibC/sys/ioctl.h
new file mode 100644
index 0000000000..e29ea315ad
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/ioctl.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/ioctl_numbers.h>
+
+__BEGIN_DECLS
+
+int ioctl(int fd, unsigned request, ...);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/sys/ioctl_numbers.h b/Userland/Libraries/LibC/sys/ioctl_numbers.h
new file mode 100644
index 0000000000..2492349060
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/ioctl_numbers.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+struct winsize {
+ unsigned short ws_row;
+ unsigned short ws_col;
+ unsigned short ws_xpixel;
+ unsigned short ws_ypixel;
+};
+
+struct FBResolution {
+ unsigned pitch;
+ unsigned width;
+ unsigned height;
+};
+
+__END_DECLS
+
+enum IOCtlNumber {
+ TIOCGPGRP,
+ TIOCSPGRP,
+ TCGETS,
+ TCSETS,
+ TCSETSW,
+ TCSETSF,
+ TCFLSH,
+ TIOCGWINSZ,
+ TIOCSCTTY,
+ TIOCNOTTY,
+ TIOCSWINSZ,
+ FB_IOCTL_GET_SIZE_IN_BYTES,
+ FB_IOCTL_GET_RESOLUTION,
+ FB_IOCTL_SET_RESOLUTION,
+ FB_IOCTL_GET_BUFFER,
+ FB_IOCTL_SET_BUFFER,
+ SIOCSIFADDR,
+ SIOCGIFADDR,
+ SIOCGIFHWADDR,
+ SIOCSIFNETMASK,
+ SIOCADDRT,
+ SIOCDELRT
+};
+
+#define TIOCGPGRP TIOCGPGRP
+#define TIOCSPGRP TIOCSPGRP
+#define TCGETS TCGETS
+#define TCSETS TCSETS
+#define TCSETSW TCSETSW
+#define TCSETSF TCSETSF
+#define TCFLSH TCFLSH
+#define TIOCGWINSZ TIOCGWINSZ
+#define TIOCSCTTY TIOCSCTTY
+#define TIOCNOTTY TIOCNOTTY
+#define TIOCSWINSZ TIOCSWINSZ
+#define FB_IOCTL_GET_SIZE_IN_BYTES FB_IOCTL_GET_SIZE_IN_BYTES
+#define FB_IOCTL_GET_RESOLUTION FB_IOCTL_GET_RESOLUTION
+#define FB_IOCTL_SET_RESOLUTION FB_IOCTL_SET_RESOLUTION
+#define FB_IOCTL_GET_BUFFER FB_IOCTL_GET_BUFFER
+#define FB_IOCTL_SET_BUFFER FB_IOCTL_SET_BUFFER
+#define SIOCSIFADDR SIOCSIFADDR
+#define SIOCGIFADDR SIOCGIFADDR
+#define SIOCGIFHWADDR SIOCGIFHWADDR
+#define SIOCSIFNETMASK SIOCSIFNETMASK
+#define SIOCADDRT SIOCADDRT
+#define SIOCDELRT SIOCDELRT
diff --git a/Userland/Libraries/LibC/sys/mman.h b/Userland/Libraries/LibC/sys/mman.h
new file mode 100644
index 0000000000..4068ce8dd6
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/mman.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <mman.h>
diff --git a/Userland/Libraries/LibC/sys/param.h b/Userland/Libraries/LibC/sys/param.h
new file mode 100644
index 0000000000..5258ac11ca
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/param.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <endian.h>
+#include <limits.h>
diff --git a/Userland/Libraries/LibC/sys/prctl.cpp b/Userland/Libraries/LibC/sys/prctl.cpp
new file mode 100644
index 0000000000..d7aa35486b
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/prctl.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+
+extern "C" {
+
+int prctl(int option, uintptr_t arg1, uintptr_t arg2)
+{
+ int rc = syscall(SC_prctl, option, arg1, arg2);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+}
diff --git a/Userland/Libraries/LibC/sys/prctl.h b/Userland/Libraries/LibC/sys/prctl.h
new file mode 100644
index 0000000000..ea6b8ed47d
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/prctl.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/prctl_numbers.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+int prctl(int option, uintptr_t arg1, uintptr_t arg2);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/sys/prctl_numbers.h b/Userland/Libraries/LibC/sys/prctl_numbers.h
new file mode 100644
index 0000000000..36ecc75bb6
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/prctl_numbers.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#define PR_SET_DUMPABLE 1
+#define PR_GET_DUMPABLE 2
diff --git a/Userland/Libraries/LibC/sys/ptrace.cpp b/Userland/Libraries/LibC/sys/ptrace.cpp
new file mode 100644
index 0000000000..9e7eb60c2a
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/ptrace.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LogStream.h>
+#include <Kernel/API/Syscall.h>
+#include <errno.h>
+#include <sys/ptrace.h>
+
+extern "C" {
+
+int ptrace(int request, pid_t tid, void* addr, int data)
+{
+
+ // PT_PEEK needs special handling since the syscall wrapper
+ // returns the peeked value as an int, which can be negative because of the cast.
+ // When using PT_PEEK, the user can check if an error occurred
+ // by looking at errno rather than the return value.
+
+ u32 out_data;
+ Syscall::SC_ptrace_peek_params peek_params;
+ if (request == PT_PEEK) {
+ peek_params.address = reinterpret_cast<u32*>(addr);
+ peek_params.out_data = &out_data;
+ addr = &peek_params;
+ }
+
+ Syscall::SC_ptrace_params params {
+ request,
+ tid,
+ reinterpret_cast<u8*>(addr),
+ data
+ };
+ int rc = syscall(SC_ptrace, &params);
+
+ if (request == PT_PEEK) {
+ if (rc < 0) {
+ errno = -rc;
+ return -1;
+ }
+ errno = 0;
+ return static_cast<int>(out_data);
+ }
+
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+}
diff --git a/Userland/Libraries/LibC/sys/ptrace.h b/Userland/Libraries/LibC/sys/ptrace.h
new file mode 100644
index 0000000000..edb45aeec9
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/ptrace.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+#define PT_TRACE_ME 1
+#define PT_ATTACH 2
+#define PT_CONTINUE 3
+#define PT_SYSCALL 4
+#define PT_GETREGS 5
+#define PT_DETACH 6
+#define PT_PEEK 7
+#define PT_POKE 8
+#define PT_SETREGS 9
+
+// FIXME: PID/TID ISSUE
+// Affects the entirety of LibDebug and Userland/strace.cpp.
+// See also Kernel/Ptrace.cpp
+int ptrace(int request, pid_t tid, void* addr, int data);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/sys/resource.h b/Userland/Libraries/LibC/sys/resource.h
new file mode 100644
index 0000000000..82f58f0902
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/resource.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/time.h>
+
+__BEGIN_DECLS
+
+struct rusage {
+ struct timeval ru_utime;
+ struct timeval ru_stime;
+ long ru_maxrss;
+ long ru_ixrss;
+ long ru_idrss;
+ long ru_isrss;
+ long ru_minflt;
+ long ru_majflt;
+ long ru_nswap;
+ long ru_inblock;
+ long ru_oublock;
+ long ru_msgsnd;
+ long ru_msgrcv;
+ long ru_nsignals;
+ long ru_nvcsw;
+ long ru_nivcsw;
+};
+
+#define RUSAGE_SELF 1
+#define RUSAGE_CHILDREN 2
+
+int getrusage(int who, struct rusage* usage);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/sys/select.cpp b/Userland/Libraries/LibC/sys/select.cpp
new file mode 100644
index 0000000000..85c5b03d43
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/select.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/select.h>
+#include <sys/time.h>
+
+extern "C" {
+
+int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, timeval* timeout_tv)
+{
+ timespec* timeout_ts = nullptr;
+ timespec timeout;
+ if (timeout_tv) {
+ timeout_ts = &timeout;
+ TIMEVAL_TO_TIMESPEC(timeout_tv, timeout_ts);
+ }
+ return pselect(nfds, readfds, writefds, exceptfds, timeout_ts, nullptr);
+}
+
+int pselect(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const timespec* timeout, const sigset_t* sigmask)
+{
+ Syscall::SC_select_params params { nfds, readfds, writefds, exceptfds, timeout, sigmask };
+ int rc = syscall(SC_select, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+}
diff --git a/Userland/Libraries/LibC/sys/select.h b/Userland/Libraries/LibC/sys/select.h
new file mode 100644
index 0000000000..9d451eb8ed
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/select.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <fd_set.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
+int pselect(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timespec* timeout, const sigset_t* sigmask);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/sys/socket.cpp b/Userland/Libraries/LibC/sys/socket.cpp
new file mode 100644
index 0000000000..95d2a015a7
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/socket.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <Kernel/API/Syscall.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+extern "C" {
+
+int socket(int domain, int type, int protocol)
+{
+ int rc = syscall(SC_socket, domain, type, protocol);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int bind(int sockfd, const sockaddr* addr, socklen_t addrlen)
+{
+ int rc = syscall(SC_bind, sockfd, addr, addrlen);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int listen(int sockfd, int backlog)
+{
+ int rc = syscall(SC_listen, sockfd, backlog);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int accept(int sockfd, sockaddr* addr, socklen_t* addrlen)
+{
+ int rc = syscall(SC_accept, sockfd, addr, addrlen);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int connect(int sockfd, const sockaddr* addr, socklen_t addrlen)
+{
+ int rc = syscall(SC_connect, sockfd, addr, addrlen);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int shutdown(int sockfd, int how)
+{
+ int rc = syscall(SC_shutdown, sockfd, how);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+ssize_t sendmsg(int sockfd, const struct msghdr* msg, int flags)
+{
+ int rc = syscall(SC_sendmsg, sockfd, msg, flags);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+ssize_t sendto(int sockfd, const void* data, size_t data_length, int flags, const struct sockaddr* addr, socklen_t addr_length)
+{
+ iovec iov = { const_cast<void*>(data), data_length };
+ msghdr msg = { const_cast<struct sockaddr*>(addr), addr_length, &iov, 1, nullptr, 0, 0 };
+ return sendmsg(sockfd, &msg, flags);
+}
+
+ssize_t send(int sockfd, const void* data, size_t data_length, int flags)
+{
+ return sendto(sockfd, data, data_length, flags, nullptr, 0);
+}
+
+ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags)
+{
+ int rc = syscall(SC_recvmsg, sockfd, msg, flags);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+ssize_t recvfrom(int sockfd, void* buffer, size_t buffer_length, int flags, struct sockaddr* addr, socklen_t* addr_length)
+{
+ if (!addr_length && addr) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ sockaddr_storage internal_addr;
+ iovec iov = { buffer, buffer_length };
+ msghdr msg = { addr ? &internal_addr : nullptr, addr ? (socklen_t)sizeof(internal_addr) : 0, &iov, 1, nullptr, 0, 0 };
+ ssize_t rc = recvmsg(sockfd, &msg, flags);
+ if (rc >= 0 && addr) {
+ memcpy(addr, &internal_addr, min(*addr_length, msg.msg_namelen));
+ *addr_length = msg.msg_namelen;
+ }
+ return rc;
+}
+
+ssize_t recv(int sockfd, void* buffer, size_t buffer_length, int flags)
+{
+ return recvfrom(sockfd, buffer, buffer_length, flags, nullptr, nullptr);
+}
+
+int getsockopt(int sockfd, int level, int option, void* value, socklen_t* value_size)
+{
+ Syscall::SC_getsockopt_params params { sockfd, level, option, value, value_size };
+ int rc = syscall(SC_getsockopt, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int setsockopt(int sockfd, int level, int option, const void* value, socklen_t value_size)
+{
+ Syscall::SC_setsockopt_params params { sockfd, level, option, value, value_size };
+ int rc = syscall(SC_setsockopt, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int getsockname(int sockfd, struct sockaddr* addr, socklen_t* addrlen)
+{
+ Syscall::SC_getsockname_params params { sockfd, addr, addrlen };
+ int rc = syscall(SC_getsockname, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int getpeername(int sockfd, struct sockaddr* addr, socklen_t* addrlen)
+{
+ Syscall::SC_getpeername_params params { sockfd, addr, addrlen };
+ int rc = syscall(SC_getpeername, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int sendfd(int sockfd, int fd)
+{
+ int rc = syscall(SC_sendfd, sockfd, fd);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int recvfd(int sockfd)
+{
+ int rc = syscall(SC_recvfd, sockfd);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+}
diff --git a/Userland/Libraries/LibC/sys/socket.h b/Userland/Libraries/LibC/sys/socket.h
new file mode 100644
index 0000000000..c5a5943041
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/socket.h
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <bits/stdint.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+__BEGIN_DECLS
+
+#define AF_MASK 0xff
+#define AF_UNSPEC 0
+#define AF_LOCAL 1
+#define AF_UNIX AF_LOCAL
+#define AF_INET 2
+#define AF_MAX 3
+#define PF_LOCAL AF_LOCAL
+#define PF_UNIX PF_LOCAL
+#define PF_INET AF_INET
+#define PF_UNSPEC AF_UNSPEC
+#define PF_MAX AF_MAX
+
+#define SOCK_TYPE_MASK 0xff
+#define SOCK_STREAM 1
+#define SOCK_DGRAM 2
+#define SOCK_RAW 3
+#define SOCK_NONBLOCK 04000
+#define SOCK_CLOEXEC 02000000
+
+#define SHUT_RD 1
+#define SHUT_WR 2
+#define SHUT_RDWR 3
+
+#define IPPROTO_IP 0
+#define IPPROTO_ICMP 1
+#define IPPROTO_TCP 6
+#define IPPROTO_UDP 17
+
+#define MSG_TRUNC 0x1
+#define MSG_CTRUNC 0x2
+#define MSG_DONTWAIT 0x40
+
+typedef uint16_t sa_family_t;
+
+struct cmsghdr {
+ socklen_t cmsg_len;
+ int cmsg_level;
+ int cmsg_type;
+};
+
+struct msghdr {
+ void* msg_name;
+ socklen_t msg_namelen;
+ struct iovec* msg_iov;
+ int msg_iovlen;
+ void* msg_control;
+ socklen_t msg_controllen;
+ int msg_flags;
+};
+
+struct sockaddr {
+ sa_family_t sa_family;
+ char sa_data[14];
+};
+
+struct ucred {
+ pid_t pid;
+ uid_t uid;
+ gid_t gid;
+};
+
+#define SOL_SOCKET 1
+#define SOMAXCONN 128
+
+enum {
+ SO_RCVTIMEO,
+ SO_SNDTIMEO,
+ SO_TYPE,
+ SO_ERROR,
+ SO_PEERCRED,
+ SO_REUSEADDR,
+ SO_BINDTODEVICE,
+ SO_KEEPALIVE,
+ SO_TIMESTAMP,
+ SO_BROADCAST,
+};
+#define SO_RCVTIMEO SO_RCVTIMEO
+#define SO_SNDTIMEO SO_SNDTIMEO
+#define SO_TYPE SO_TYPE
+#define SO_ERROR SO_ERROR
+#define SO_PEERCRED SO_PEERCRED
+#define SO_REUSEADDR SO_REUSEADDR
+#define SO_BINDTODEVICE SO_BINDTODEVICE
+#define SO_KEEPALIVE SO_KEEPALIVE
+#define SO_TIMESTAMP SO_TIMESTAMP
+#define SO_BROADCAST SO_BROADCAST
+
+enum {
+ SCM_TIMESTAMP,
+ SCM_RIGHTS,
+};
+#define SCM_TIMESTAMP SCM_TIMESTAMP
+#define SCM_RIGHTS SCM_RIGHTS
+
+struct sockaddr_storage {
+ sa_family_t ss_family;
+ union {
+ char data[sizeof(struct sockaddr_un)];
+ void* alignment;
+ };
+};
+
+int socket(int domain, int type, int protocol);
+int bind(int sockfd, const struct sockaddr* addr, socklen_t);
+int listen(int sockfd, int backlog);
+int accept(int sockfd, struct sockaddr*, socklen_t*);
+int connect(int sockfd, const struct sockaddr*, socklen_t);
+int shutdown(int sockfd, int how);
+ssize_t send(int sockfd, const void*, size_t, int flags);
+ssize_t sendmsg(int sockfd, const struct msghdr*, int flags);
+ssize_t sendto(int sockfd, const void*, size_t, int flags, const struct sockaddr*, socklen_t);
+ssize_t recv(int sockfd, void*, size_t, int flags);
+ssize_t recvmsg(int sockfd, struct msghdr*, int flags);
+ssize_t recvfrom(int sockfd, void*, size_t, int flags, struct sockaddr*, socklen_t*);
+int getsockopt(int sockfd, int level, int option, void*, socklen_t*);
+int setsockopt(int sockfd, int level, int option, const void*, socklen_t);
+int getsockname(int sockfd, struct sockaddr*, socklen_t*);
+int getpeername(int sockfd, struct sockaddr*, socklen_t*);
+int sendfd(int sockfd, int fd);
+int recvfd(int sockfd);
+
+// These three are non-POSIX, but common:
+#define CMSG_ALIGN(x) (((x) + sizeof(void*) - 1) & ~(sizeof(void*) - 1))
+#define CMSG_SPACE(x) (CMSG_ALIGN(sizeof(struct cmsghdr)) + CMSG_ALIGN(x))
+#define CMSG_LEN(x) (CMSG_ALIGN(sizeof(struct cmsghdr)) + (x))
+
+static inline struct cmsghdr* CMSG_FIRSTHDR(struct msghdr* msg)
+{
+ if (msg->msg_controllen < sizeof(struct cmsghdr))
+ return 0;
+ return (struct cmsghdr*)msg->msg_control;
+}
+
+static inline struct cmsghdr* CMSG_NXTHDR(struct msghdr* msg, struct cmsghdr* cmsg)
+{
+ struct cmsghdr* next = (struct cmsghdr*)((char*)cmsg + CMSG_ALIGN(cmsg->cmsg_len));
+ unsigned offset = (char*)next - (char*)msg->msg_control;
+ if (msg->msg_controllen < offset + sizeof(struct cmsghdr))
+ return NULL;
+ return next;
+}
+
+static inline void* CMSG_DATA(struct cmsghdr* cmsg)
+{
+ return (void*)(cmsg + 1);
+}
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/sys/stat.h b/Userland/Libraries/LibC/sys/stat.h
new file mode 100644
index 0000000000..ed6d84458d
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/stat.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <fcntl.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
+#define S_ISCHR(m) (((m)&S_IFMT) == S_IFCHR)
+#define S_ISBLK(m) (((m)&S_IFMT) == S_IFBLK)
+#define S_ISREG(m) (((m)&S_IFMT) == S_IFREG)
+#define S_ISFIFO(m) (((m)&S_IFMT) == S_IFIFO)
+#define S_ISLNK(m) (((m)&S_IFMT) == S_IFLNK)
+#define S_ISSOCK(m) (((m)&S_IFMT) == S_IFSOCK)
+
+struct stat {
+ dev_t st_dev; /* ID of device containing file */
+ ino_t st_ino; /* inode number */
+ mode_t st_mode; /* protection */
+ nlink_t st_nlink; /* number of hard links */
+ uid_t st_uid; /* user ID of owner */
+ gid_t st_gid; /* group ID of owner */
+ dev_t st_rdev; /* device ID (if special file) */
+ off_t st_size; /* total size, in bytes */
+ blksize_t st_blksize; /* blocksize for file system I/O */
+ blkcnt_t st_blocks; /* number of 512B blocks allocated */
+ time_t st_atime; /* time of last access */
+ time_t st_mtime; /* time of last modification */
+ time_t st_ctime; /* time of last status change */
+};
+
+mode_t umask(mode_t);
+int chmod(const char* pathname, mode_t);
+int fchmod(int fd, mode_t);
+int mkdir(const char* pathname, mode_t);
+int mkfifo(const char* pathname, mode_t);
+int fstat(int fd, struct stat* statbuf);
+int lstat(const char* path, struct stat* statbuf);
+int stat(const char* path, struct stat* statbuf);
+
+inline dev_t makedev(unsigned int major, unsigned int minor) { return (minor & 0xffu) | (major << 8u) | ((minor & ~0xffu) << 12u); }
+inline unsigned int major(dev_t dev) { return (dev & 0xfff00u) >> 8u; }
+inline unsigned int minor(dev_t dev) { return (dev & 0xffu) | ((dev >> 12u) & 0xfff00u); }
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/sys/sysmacros.h b/Userland/Libraries/LibC/sys/sysmacros.h
new file mode 100644
index 0000000000..2bf8abd099
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/sysmacros.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
diff --git a/Userland/Libraries/LibC/sys/time.h b/Userland/Libraries/LibC/sys/time.h
new file mode 100644
index 0000000000..11faa3bdd4
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/time.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <time.h>
+
+__BEGIN_DECLS
+
+struct timeval {
+ time_t tv_sec;
+ suseconds_t tv_usec;
+};
+
+struct timezone {
+ int tz_minuteswest;
+ int tz_dsttime;
+};
+
+int adjtime(const struct timeval* delta, struct timeval* old_delta);
+int gettimeofday(struct timeval* __restrict__, void* __restrict__) __attribute__((nonnull(1)));
+int settimeofday(struct timeval* __restrict__, void* __restrict__) __attribute__((nonnull(1)));
+
+static inline void timeradd(const struct timeval* a, const struct timeval* b, struct timeval* out)
+{
+ out->tv_sec = a->tv_sec + b->tv_sec;
+ out->tv_usec = a->tv_usec + b->tv_usec;
+ if (out->tv_usec >= 1000 * 1000) {
+ out->tv_sec++;
+ out->tv_usec -= 1000 * 1000;
+ }
+}
+
+static inline void timersub(const struct timeval* a, const struct timeval* b, struct timeval* out)
+{
+ out->tv_sec = a->tv_sec - b->tv_sec;
+ out->tv_usec = a->tv_usec - b->tv_usec;
+ if (out->tv_usec < 0) {
+ out->tv_sec--;
+ out->tv_usec += 1000 * 1000;
+ }
+}
+
+static inline void timerclear(struct timeval* out)
+{
+ out->tv_sec = out->tv_usec = 0;
+}
+
+static inline int timerisset(const struct timeval* tv)
+{
+ return tv->tv_sec || tv->tv_usec;
+}
+
+#define timeradd timeradd
+#define timersub timersub
+#define timerclear timerclear
+#define timerisset timerisset
+#define timercmp(tvp, uvp, cmp) \
+ (((tvp)->tv_sec == (uvp)->tv_sec) ? ((tvp)->tv_usec cmp(uvp)->tv_usec) : ((tvp)->tv_sec cmp(uvp)->tv_sec))
+
+static inline void timespecadd(const struct timespec* a, const struct timespec* b, struct timespec* out)
+{
+ out->tv_sec = a->tv_sec + b->tv_sec;
+ out->tv_nsec = a->tv_nsec + b->tv_nsec;
+ if (out->tv_nsec >= 1000 * 1000 * 1000) {
+ out->tv_sec++;
+ out->tv_nsec -= 1000 * 1000 * 1000;
+ }
+}
+
+static inline void timespecsub(const struct timespec* a, const struct timespec* b, struct timespec* out)
+{
+ out->tv_sec = a->tv_sec - b->tv_sec;
+ out->tv_nsec = a->tv_nsec - b->tv_nsec;
+ if (out->tv_nsec < 0) {
+ out->tv_sec--;
+ out->tv_nsec += 1000 * 1000 * 1000;
+ }
+}
+
+static inline void timespecclear(struct timespec* out)
+{
+ out->tv_sec = out->tv_nsec = 0;
+}
+
+static inline int timespecisset(const struct timespec* ts)
+{
+ return ts->tv_sec || ts->tv_nsec;
+}
+
+static inline void TIMEVAL_TO_TIMESPEC(const struct timeval* tv, struct timespec* ts)
+{
+ ts->tv_sec = tv->tv_sec;
+ ts->tv_nsec = tv->tv_usec * 1000;
+}
+
+static inline void TIMESPEC_TO_TIMEVAL(struct timeval* tv, const struct timespec* ts)
+{
+ tv->tv_sec = ts->tv_sec;
+ tv->tv_usec = ts->tv_nsec / 1000;
+}
+
+#define timespecadd timespecadd
+#define timespecsub timespecsub
+#define timespecclear timespecclear
+#define timespecisset timespecisset
+#define timespeccmp(ts, us, cmp) \
+ (((ts)->tv_sec == (us)->tv_sec) ? ((ts)->vf_nsec cmp(us)->tv_nsec) : ((ts)->tv_sec cmp(us)->tv_sec))
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/sys/times.h b/Userland/Libraries/LibC/sys/times.h
new file mode 100644
index 0000000000..3b677d18dc
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/times.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+struct tms {
+ clock_t tms_utime;
+ clock_t tms_stime;
+ clock_t tms_cutime;
+ clock_t tms_cstime;
+};
+
+clock_t times(struct tms*);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/sys/types.h b/Userland/Libraries/LibC/sys/types.h
new file mode 100644
index 0000000000..6de6b1feb1
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/types.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <bits/stdint.h>
+#include <stddef.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+/* There is no __SSIZE_TYPE__ but we can trick the preprocessor into defining it for us anyway! */
+#define unsigned signed
+typedef __SIZE_TYPE__ ssize_t;
+#undef unsigned
+
+typedef unsigned char u_char;
+typedef unsigned short u_short;
+typedef unsigned int u_int;
+typedef unsigned long u_long;
+
+typedef uint32_t uid_t;
+typedef uint32_t gid_t;
+
+typedef int __pid_t;
+#define pid_t __pid_t
+
+typedef char* caddr_t;
+
+typedef int id_t;
+
+typedef __WINT_TYPE__ wint_t;
+
+typedef uint32_t ino_t;
+typedef ssize_t off_t;
+
+typedef uint32_t dev_t;
+typedef uint16_t mode_t;
+typedef uint32_t nlink_t;
+typedef uint32_t blksize_t;
+typedef uint32_t blkcnt_t;
+typedef int64_t time_t;
+typedef uint32_t useconds_t;
+typedef int32_t suseconds_t;
+typedef uint32_t clock_t;
+
+#define __socklen_t_defined
+#define __socklen_t uint32_t
+typedef __socklen_t socklen_t;
+
+struct utimbuf {
+ time_t actime;
+ time_t modtime;
+};
+
+typedef int pthread_t;
+typedef int pthread_key_t;
+typedef int32_t pthread_once_t;
+
+typedef struct __pthread_mutex_t {
+ uint32_t lock;
+ pthread_t owner;
+ int level;
+ int type;
+} pthread_mutex_t;
+
+typedef void* pthread_attr_t;
+typedef struct __pthread_mutexattr_t {
+ int type;
+} pthread_mutexattr_t;
+
+typedef struct __pthread_cond_t {
+ int32_t value;
+ uint32_t previous;
+ int clockid; // clockid_t
+} pthread_cond_t;
+
+typedef void* pthread_rwlock_t;
+typedef void* pthread_rwlockatrr_t;
+typedef void* pthread_spinlock_t;
+typedef struct __pthread_condattr_t {
+ int clockid; // clockid_t
+} pthread_condattr_t;
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/sys/uio.cpp b/Userland/Libraries/LibC/sys/uio.cpp
new file mode 100644
index 0000000000..4aad828c65
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/uio.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <errno.h>
+#include <sys/uio.h>
+
+extern "C" {
+
+ssize_t writev(int fd, const struct iovec* iov, int iov_count)
+{
+ int rc = syscall(SC_writev, fd, iov, iov_count);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+}
diff --git a/Userland/Libraries/LibC/sys/uio.h b/Userland/Libraries/LibC/sys/uio.h
new file mode 100644
index 0000000000..be310b38f1
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/uio.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+struct iovec {
+ void* iov_base;
+ size_t iov_len;
+};
+
+ssize_t writev(int fd, const struct iovec*, int iov_count);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/sys/un.h b/Userland/Libraries/LibC/sys/un.h
new file mode 100644
index 0000000000..e0df306153
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/un.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+#define UNIX_PATH_MAX 108
+struct sockaddr_un {
+ uint16_t sun_family;
+ char sun_path[UNIX_PATH_MAX];
+};
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/sys/utsname.h b/Userland/Libraries/LibC/sys/utsname.h
new file mode 100644
index 0000000000..1637a6c990
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/utsname.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+#define UTSNAME_ENTRY_LEN 65
+
+__BEGIN_DECLS
+
+struct utsname {
+ char sysname[UTSNAME_ENTRY_LEN];
+ char nodename[UTSNAME_ENTRY_LEN];
+ char release[UTSNAME_ENTRY_LEN];
+ char version[UTSNAME_ENTRY_LEN];
+ char machine[UTSNAME_ENTRY_LEN];
+};
+
+int uname(struct utsname*);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/sys/wait.cpp b/Userland/Libraries/LibC/sys/wait.cpp
new file mode 100644
index 0000000000..9969a8eec2
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/wait.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <assert.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+extern "C" {
+
+pid_t wait(int* wstatus)
+{
+ return waitpid(-1, wstatus, 0);
+}
+
+pid_t waitpid(pid_t waitee, int* wstatus, int options)
+{
+ siginfo_t siginfo;
+ idtype_t idtype;
+ id_t id;
+
+ if (waitee < -1) {
+ idtype = P_PGID;
+ id = -waitee;
+ } else if (waitee == -1) {
+ idtype = P_ALL;
+ id = 0;
+ } else if (waitee == 0) {
+ idtype = P_PGID;
+ id = getgid();
+ } else {
+ idtype = P_PID;
+ id = waitee;
+ }
+
+ // To be able to detect if a child was found when WNOHANG is set,
+ // we need to clear si_pid, which will only be set if it was found.
+ siginfo.si_pid = 0;
+ int rc = waitid(idtype, id, &siginfo, options | WEXITED);
+
+ if (rc < 0)
+ return rc;
+
+ if (wstatus) {
+ if ((options & WNOHANG) && siginfo.si_pid == 0) {
+ // No child in a waitable state was found. All other fields
+ // in siginfo are undefined
+ *wstatus = 0;
+ return 0;
+ }
+
+ switch (siginfo.si_code) {
+ case CLD_EXITED:
+ *wstatus = siginfo.si_status << 8;
+ break;
+ case CLD_KILLED:
+ *wstatus = siginfo.si_status;
+ break;
+ case CLD_STOPPED:
+ *wstatus = siginfo.si_status << 8 | 0x7f;
+ break;
+ case CLD_CONTINUED:
+ *wstatus = 0;
+ return 0; // return 0 if running
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ return siginfo.si_pid;
+}
+
+int waitid(idtype_t idtype, id_t id, siginfo_t* infop, int options)
+{
+ Syscall::SC_waitid_params params { idtype, id, infop, options };
+ int rc = syscall(SC_waitid, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+}
diff --git a/Userland/Libraries/LibC/sys/wait.h b/Userland/Libraries/LibC/sys/wait.h
new file mode 100644
index 0000000000..825453292a
--- /dev/null
+++ b/Userland/Libraries/LibC/sys/wait.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <signal.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+#define WEXITSTATUS(status) (((status)&0xff00) >> 8)
+#define WSTOPSIG(status) WEXITSTATUS(status)
+#define WTERMSIG(status) ((status)&0x7f)
+#define WIFEXITED(status) (WTERMSIG(status) == 0)
+#define WIFSTOPPED(status) (((status)&0xff) == 0x7f)
+#define WIFSIGNALED(status) (((char)(((status)&0x7f) + 1) >> 1) > 0)
+
+#define WNOHANG 1
+#define WUNTRACED 2
+#define WSTOPPED WUNTRACED
+#define WEXITED 4
+#define WCONTINUED 8
+#define WNOWAIT 0x1000000
+
+typedef enum {
+ P_ALL = 1,
+ P_PID,
+ P_PGID
+} idtype_t;
+
+pid_t waitpid(pid_t, int* wstatus, int options);
+pid_t wait(int* wstatus);
+int waitid(idtype_t idtype, id_t id, siginfo_t* infop, int options);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/syslog.cpp b/Userland/Libraries/LibC/syslog.cpp
new file mode 100644
index 0000000000..650347da96
--- /dev/null
+++ b/Userland/Libraries/LibC/syslog.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Has to be defined before including due to legacy Unices
+#define SYSLOG_NAMES 1
+
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+// This implementation doesn't talk to a syslog server. Any options related to
+// that are no-ops.
+
+extern "C" {
+
+// For implementation simplicity, we actually only use the re-entrant version
+// of each function, and the version that isn't just redirects with a static
+// struct to share.
+static struct syslog_data global_log_data = {
+ .ident = nullptr,
+ .logopt = 0,
+ .facility = LOG_USER,
+ .maskpri = LOG_UPTO(LOG_DEBUG)
+};
+
+// Used when ident is null, since syslog traditionally prints the program's
+// own name; the process name will always be the same unless we exec.
+static char program_name_buffer[256];
+static bool program_name_set = false;
+
+// Convenience function for initialization and checking what string to use
+// for the program name.
+static const char* get_syslog_ident(struct syslog_data* data)
+{
+ if (!program_name_set && data->ident == nullptr)
+ program_name_set = get_process_name(program_name_buffer, sizeof(program_name_buffer)) >= 0;
+
+ if (data->ident != nullptr)
+ return data->ident;
+ else if (program_name_set)
+ return program_name_buffer;
+
+ ASSERT_NOT_REACHED();
+}
+
+void openlog_r(const char* ident, int logopt, int facility, struct syslog_data* data)
+{
+ data->ident = ident;
+ data->logopt = logopt;
+ data->facility = facility;
+ // default value
+ data->maskpri = LOG_UPTO(LOG_DEBUG);
+ // would be where we connect to a daemon
+}
+
+void openlog(const char* ident, int logopt, int facility)
+{
+ openlog_r(ident, logopt, facility, &global_log_data);
+}
+
+void closelog_r(struct syslog_data* data)
+{
+ // would be where we disconnect from a daemon
+ // restore defaults
+ data->ident = nullptr;
+ data->logopt = 0;
+ data->facility = LOG_USER;
+ data->maskpri = LOG_UPTO(LOG_DEBUG);
+}
+
+void closelog(void)
+{
+ closelog_r(&global_log_data);
+}
+
+int setlogmask_r(int maskpri, struct syslog_data* data)
+{
+ // Remember, this takes the input of LOG_MASK/LOG_UPTO
+ int old_maskpri = data->maskpri;
+ data->maskpri = maskpri;
+ return old_maskpri;
+}
+
+int setlogmask(int maskpri)
+{
+ return setlogmask_r(maskpri, &global_log_data);
+}
+
+void syslog_r(int priority, struct syslog_data* data, const char* message, ...)
+{
+ va_list ap;
+ va_start(ap, message);
+ vsyslog_r(priority, data, message, ap);
+ va_end(ap);
+}
+
+void syslog(int priority, const char* message, ...)
+{
+ va_list ap;
+ va_start(ap, message);
+ vsyslog_r(priority, &global_log_data, message, ap);
+ va_end(ap);
+}
+
+void vsyslog_r(int priority, struct syslog_data* data, const char* message, va_list args)
+{
+ StringBuilder combined;
+
+ int real_priority = LOG_PRI(priority);
+ // Lots of parens, but it just extracts the priority from combo and masks.
+ if (!(data->maskpri & LOG_MASK(real_priority)))
+ return;
+
+ // Some metadata would be consumed by a syslog daemon, if we had one.
+ if (data->logopt & LOG_PID)
+ combined.appendf("%s[%d]: ", get_syslog_ident(data), getpid());
+ else
+ combined.appendf("%s: ", get_syslog_ident(data));
+
+ combined.appendvf(message, args);
+ String combined_string = combined.build();
+
+ if (data->logopt & LOG_CONS)
+ dbgputstr(combined_string.characters(), combined_string.length());
+ if (data->logopt & LOG_PERROR)
+ fputs(combined_string.characters(), stderr);
+}
+
+void vsyslog(int priority, const char* message, va_list args)
+{
+ vsyslog_r(priority, &global_log_data, message, args);
+}
+}
diff --git a/Userland/Libraries/LibC/syslog.h b/Userland/Libraries/LibC/syslog.h
new file mode 100644
index 0000000000..eedebdc277
--- /dev/null
+++ b/Userland/Libraries/LibC/syslog.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <stdarg.h>
+
+__BEGIN_DECLS
+
+struct syslog_data {
+ const char* ident;
+ int logopt;
+ int facility;
+ int maskpri;
+};
+
+/* The severity of the message. This is ordered. */
+#define LOG_EMERG 0
+#define LOG_ALERT 1
+#define LOG_CRIT 2
+#define LOG_ERR 3
+#define LOG_WARNING 4
+#define LOG_NOTICE 5
+#define LOG_INFO 6
+#define LOG_DEBUG 7
+
+/* Macros for masking out the priority of a combined priority */
+#define LOG_PRIMASK (7)
+#define LOG_PRI(priority) ((priority)&LOG_PRIMASK)
+
+/*
+ * Many of these facilities don't really make sense anymore, but we keep them
+ * for compatibility purposes.
+ */
+#define LOG_KERN (0 << 3)
+#define LOG_USER (1 << 3)
+#define LOG_MAIL (2 << 3)
+#define LOG_DAEMON (3 << 3)
+#define LOG_AUTH (4 << 3)
+#define LOG_SYSLOG (5 << 3)
+#define LOG_LPR (6 << 3)
+#define LOG_NEWS (7 << 3)
+#define LOG_UUCP (8 << 3)
+#define LOG_CRON (9 << 3)
+#define LOG_AUTHPRIV (10 << 3)
+#define LOG_FTP (11 << 3)
+/* glibc and OpenBSD reserve 12..15 for future system usage, we will too */
+#define LOG_LOCAL0 (16 << 3)
+#define LOG_LOCAL1 (17 << 3)
+#define LOG_LOCAL2 (18 << 3)
+#define LOG_LOCAL3 (19 << 3)
+#define LOG_LOCAL4 (20 << 3)
+#define LOG_LOCAL5 (21 << 3)
+#define LOG_LOCAL6 (22 << 3)
+#define LOG_LOCAL7 (23 << 3)
+
+#define LOG_NFACILITIES 24
+
+/* Macros to get the facility from a combined priority. */
+#define LOG_FACMASK (~7)
+#define LOG_FAC(priority) (((priority)&LOG_FACMASK) >> 3)
+
+/* For masking logs, we use these macros with just the priority. */
+#define LOG_MASK(priority) (1 << (priority))
+#define LOG_UPTO(priority) (LOG_MASK(priority) + (LOG_MASK(priority) - 1))
+
+/* Macro to make a combined priority. */
+#define LOG_MAKEPRI(facility, priority) ((facility) | (priority))
+
+/* Include a PID with the message. */
+#define LOG_PID (1 << 0)
+/* Log on the console. */
+#define LOG_CONS (1 << 1)
+/* Open the syslogd connection at the first call. (not implemented, default) */
+#define LOG_ODELAY (1 << 2)
+/* Open the syslogd connection immediately. (not implemented) */
+#define LOG_NDELAY (1 << 3)
+/* Log to stderr as well. */
+#define LOG_PERROR (1 << 4)
+
+/* This is useful to have, but has to be stored weirdly for compatibility. */
+#ifdef SYSLOG_NAMES
+/* Used for marking the fallback; some applications check for these defines. */
+# define INTERNAL_NOPRI 0x10
+# define INTERNAL_MARK LOG_MAKEPRI(LOG_NFACILITIES << 3, 0)
+
+typedef struct _code {
+ /*
+ * Most Unices define this as char*, but in C++, we have to define it as a
+ * const char* if we want to use string constants.
+ */
+ const char* c_name;
+ int c_val;
+} CODE;
+
+/*
+ * The names we use are the same as what glibc and OpenBSD use. We omit
+ * deprecated values in the hope that no one uses them. Sorted, as well.
+ */
+
+CODE prioritynames[] = {
+ { "alert", LOG_ALERT },
+ { "crit", LOG_CRIT },
+ { "debug", LOG_DEBUG },
+ { "emerg", LOG_EMERG },
+ { "err", LOG_ERR },
+ { "info", LOG_INFO },
+ /* Fallback */
+ { "none", INTERNAL_NOPRI },
+ { "notice", LOG_NOTICE },
+ { "warning", LOG_WARNING },
+ { NULL, -1 },
+};
+
+CODE facilitynames[] = {
+ { "auth", LOG_AUTH },
+ { "authpriv", LOG_AUTHPRIV },
+ { "cron", LOG_CRON },
+ { "daemon", LOG_DAEMON },
+ { "ftp", LOG_FTP },
+ { "kern", LOG_KERN },
+ { "local0", LOG_LOCAL0 },
+ { "local1", LOG_LOCAL1 },
+ { "local2", LOG_LOCAL2 },
+ { "local3", LOG_LOCAL3 },
+ { "local4", LOG_LOCAL4 },
+ { "local5", LOG_LOCAL5 },
+ { "local6", LOG_LOCAL6 },
+ { "local7", LOG_LOCAL7 },
+ { "lpr", LOG_LPR },
+ { "mail", LOG_MAIL },
+ /* Fallback */
+ { "mark", INTERNAL_MARK },
+ { "news", LOG_NEWS },
+ { "syslog", LOG_SYSLOG },
+ { "user", LOG_USER },
+ { "uucp", LOG_UUCP },
+ { NULL, -1 },
+};
+#endif
+
+/* The re-entrant versions are an OpenBSD extension we also implement. */
+void syslog(int, const char*, ...);
+void syslog_r(int, struct syslog_data*, const char*, ...);
+void vsyslog(int, const char* message, va_list);
+void vsyslog_r(int, struct syslog_data* data, const char* message, va_list);
+void openlog(const char*, int, int);
+void openlog_r(const char*, int, int, struct syslog_data*);
+void closelog(void);
+void closelog_r(struct syslog_data*);
+int setlogmask(int);
+int setlogmask_r(int, struct syslog_data*);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/termcap.cpp b/Userland/Libraries/LibC/termcap.cpp
new file mode 100644
index 0000000000..1dcc73df68
--- /dev/null
+++ b/Userland/Libraries/LibC/termcap.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/HashMap.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <termcap.h>
+
+//#define TERMCAP_DEBUG
+
+extern "C" {
+
+char PC;
+char* UP;
+char* BC;
+
+int tgetent([[maybe_unused]] char* bp, [[maybe_unused]] const char* name)
+{
+#ifdef TERMCAP_DEBUG
+ fprintf(stderr, "tgetent: bp=%p, name='%s'\n", bp, name);
+#endif
+ PC = '\0';
+ BC = const_cast<char*>("\033[D");
+ UP = const_cast<char*>("\033[A");
+ return 1;
+}
+
+static HashMap<String, const char*>* caps = nullptr;
+
+static void ensure_caps()
+{
+ if (caps)
+ return;
+ caps = new HashMap<String, const char*>;
+ caps->set("DC", "\033[%p1%dP");
+ caps->set("IC", "\033[%p1%d@");
+ caps->set("ce", "\033[K");
+ caps->set("cl", "\033[H\033[J");
+ caps->set("cr", "\015");
+ caps->set("dc", "\033[P");
+ caps->set("ei", "");
+ caps->set("ic", "");
+ caps->set("im", "");
+ caps->set("kd", "\033[B");
+ caps->set("kl", "\033[D");
+ caps->set("kr", "\033[C");
+ caps->set("ku", "\033[A");
+ caps->set("ks", "");
+ caps->set("ke", "");
+ caps->set("le", "\033[D");
+ caps->set("mm", "");
+ caps->set("mo", "");
+ caps->set("pc", "");
+ caps->set("up", "\033[A");
+ caps->set("vb", "");
+ caps->set("am", "");
+ caps->set("@7", "");
+ caps->set("kH", "");
+ caps->set("kI", "\033[L");
+ caps->set("kh", "\033[H");
+ caps->set("vs", "");
+ caps->set("ve", "");
+ caps->set("E3", "");
+ caps->set("kD", "");
+ caps->set("nd", "\033[C");
+
+ caps->set("co", "80");
+ caps->set("li", "25");
+}
+
+// Unfortunately, tgetstr() doesn't accept a size argument for the buffer
+// pointed to by area, so we have to use bare strcpy().
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+char* tgetstr(const char* id, char** area)
+{
+ ensure_caps();
+#ifdef TERMCAP_DEBUG
+ fprintf(stderr, "tgetstr: id='%s'\n", id);
+#endif
+ auto it = caps->find(id);
+ if (it != caps->end()) {
+ char* ret = *area;
+ const char* val = (*it).value;
+ strcpy(*area, val);
+ *area += strlen(val) + 1;
+ return ret;
+ }
+ fprintf(stderr, "tgetstr: missing cap id='%s'\n", id);
+ return nullptr;
+}
+
+#pragma GCC diagnostic pop
+
+int tgetflag([[maybe_unused]] const char* id)
+{
+#ifdef TERMCAP_DEBUG
+ fprintf(stderr, "tgetflag: '%s'\n", id);
+#endif
+ auto it = caps->find(id);
+ if (it != caps->end())
+ return 1;
+ return 0;
+}
+
+int tgetnum(const char* id)
+{
+#ifdef TERMCAP_DEBUG
+ fprintf(stderr, "tgetnum: '%s'\n", id);
+#endif
+ auto it = caps->find(id);
+ if (it != caps->end())
+ return atoi((*it).value);
+ ASSERT_NOT_REACHED();
+}
+
+static Vector<char> s_tgoto_buffer;
+char* tgoto([[maybe_unused]] const char* cap, [[maybe_unused]] int col, [[maybe_unused]] int row)
+{
+ auto cap_str = String(cap);
+ cap_str.replace("%p1%d", String::format("%d", col));
+ cap_str.replace("%p2%d", String::format("%d", row));
+
+ s_tgoto_buffer.clear_with_capacity();
+ s_tgoto_buffer.ensure_capacity(cap_str.length());
+ (void)cap_str.copy_characters_to_buffer(s_tgoto_buffer.data(), cap_str.length());
+ return s_tgoto_buffer.data();
+}
+
+int tputs(const char* str, [[maybe_unused]] int affcnt, int (*putc)(int))
+{
+ size_t len = strlen(str);
+ for (size_t i = 0; i < len; ++i)
+ putc(str[i]);
+ return 0;
+}
+}
diff --git a/Userland/Libraries/LibC/termcap.h b/Userland/Libraries/LibC/termcap.h
new file mode 100644
index 0000000000..b018cf14cb
--- /dev/null
+++ b/Userland/Libraries/LibC/termcap.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+extern char PC;
+extern char* UP;
+extern char* BC;
+
+int tgetent(char* bp, const char* name);
+int tgetflag(const char* id);
+int tgetnum(const char* id);
+char* tgetstr(const char* id, char** area);
+char* tgoto(const char* cap, int col, int row);
+int tputs(const char* str, int affcnt, int (*putc)(int));
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/termios.cpp b/Userland/Libraries/LibC/termios.cpp
new file mode 100644
index 0000000000..1037b0ab61
--- /dev/null
+++ b/Userland/Libraries/LibC/termios.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <assert.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+
+extern "C" {
+
+int tcgetattr(int fd, struct termios* t)
+{
+ return ioctl(fd, TCGETS, t);
+}
+
+int tcsetattr(int fd, int optional_actions, const struct termios* t)
+{
+ switch (optional_actions) {
+ case TCSANOW:
+ return ioctl(fd, TCSETS, t);
+ case TCSADRAIN:
+ return ioctl(fd, TCSETSW, t);
+ case TCSAFLUSH:
+ return ioctl(fd, TCSETSF, t);
+ }
+ errno = EINVAL;
+ return -1;
+}
+
+int tcflow([[maybe_unused]] int fd, [[maybe_unused]] int action)
+{
+ errno = EINVAL;
+ return -1;
+}
+
+int tcflush(int fd, int queue_selector)
+{
+ return ioctl(fd, TCFLSH, queue_selector);
+}
+
+speed_t cfgetispeed(const struct termios* tp)
+{
+ return tp->c_ispeed;
+}
+
+speed_t cfgetospeed(const struct termios* tp)
+{
+ return tp->c_ospeed;
+}
+
+static int baud_rate_from_speed(speed_t speed)
+{
+ int rate = -EINVAL;
+ switch (speed) {
+ case B0:
+ rate = 0;
+ break;
+ case B50:
+ rate = 50;
+ break;
+ case B75:
+ rate = 75;
+ break;
+ case B110:
+ rate = 110;
+ break;
+ case B134:
+ rate = 134;
+ break;
+ case B150:
+ rate = 150;
+ break;
+ case B200:
+ rate = 200;
+ break;
+ case B300:
+ rate = 300;
+ break;
+ case B600:
+ rate = 600;
+ break;
+ case B1200:
+ rate = 1200;
+ break;
+ case B1800:
+ rate = 1800;
+ break;
+ case B2400:
+ rate = 2400;
+ break;
+ case B4800:
+ rate = 4800;
+ break;
+ case B9600:
+ rate = 9600;
+ break;
+ case B19200:
+ rate = 19200;
+ break;
+ case B38400:
+ rate = 38400;
+ break;
+ }
+
+ return rate;
+}
+
+int cfsetispeed(struct termios* tp, speed_t speed)
+{
+ auto ispeed = baud_rate_from_speed(speed);
+ if (ispeed > 0) {
+ tp->c_ispeed = ispeed;
+ }
+ __RETURN_WITH_ERRNO(ispeed, 0, -1);
+}
+
+int cfsetospeed(struct termios* tp, speed_t speed)
+{
+ auto ospeed = baud_rate_from_speed(speed);
+ if (ospeed > 0) {
+ tp->c_ispeed = ospeed;
+ }
+ __RETURN_WITH_ERRNO(ospeed, 0, -1);
+}
+}
diff --git a/Userland/Libraries/LibC/termios.h b/Userland/Libraries/LibC/termios.h
new file mode 100644
index 0000000000..028ddc06a5
--- /dev/null
+++ b/Userland/Libraries/LibC/termios.h
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+#define NCCS 32
+
+typedef uint32_t tcflag_t;
+typedef uint8_t cc_t;
+typedef uint32_t speed_t;
+
+struct termios {
+ tcflag_t c_iflag;
+ tcflag_t c_oflag;
+ tcflag_t c_cflag;
+ tcflag_t c_lflag;
+ cc_t c_cc[NCCS];
+ speed_t c_ispeed;
+ speed_t c_ospeed;
+};
+
+int tcgetattr(int fd, struct termios*);
+int tcsetattr(int fd, int optional_actions, const struct termios*);
+int tcflow(int fd, int action);
+int tcflush(int fd, int queue_selector);
+
+speed_t cfgetispeed(const struct termios*);
+speed_t cfgetospeed(const struct termios*);
+int cfsetispeed(struct termios*, speed_t);
+int cfsetospeed(struct termios*, speed_t);
+
+/* c_cc characters */
+#define VINTR 0
+#define VQUIT 1
+#define VERASE 2
+#define VKILL 3
+#define VEOF 4
+#define VTIME 5
+#define VMIN 6
+#define VSWTC 7
+#define VSTART 8
+#define VSTOP 9
+#define VSUSP 10
+#define VEOL 11
+#define VREPRINT 12
+#define VDISCARD 13
+#define VWERASE 14
+#define VLNEXT 15
+#define VEOL2 16
+
+/* c_iflag bits */
+#define IGNBRK 0000001
+#define BRKINT 0000002
+#define IGNPAR 0000004
+#define PARMRK 0000010
+#define INPCK 0000020
+#define ISTRIP 0000040
+#define INLCR 0000100
+#define IGNCR 0000200
+#define ICRNL 0000400
+#define IUCLC 0001000
+#define IXON 0002000
+#define IXANY 0004000
+#define IXOFF 0010000
+#define IMAXBEL 0020000
+#define IUTF8 0040000
+
+/* c_oflag bits */
+#define OPOST 0000001
+#define OLCUC 0000002
+#define ONLCR 0000004
+#define OCRNL 0000010
+#define ONOCR 0000020
+#define ONLRET 0000040
+#define OFILL 0000100
+#define OFDEL 0000200
+#if defined __USE_MISC || defined __USE_XOPEN
+# define NLDLY 0000400
+# define NL0 0000000
+# define NL1 0000400
+# define CRDLY 0003000
+# define CR0 0000000
+# define CR1 0001000
+# define CR2 0002000
+# define CR3 0003000
+# define TABDLY 0014000
+# define TAB0 0000000
+# define TAB1 0004000
+# define TAB2 0010000
+# define TAB3 0014000
+# define BSDLY 0020000
+# define BS0 0000000
+# define BS1 0020000
+# define FFDLY 0100000
+# define FF0 0000000
+# define FF1 0100000
+#endif
+
+#define VTDLY 0040000
+#define VT0 0000000
+#define VT1 0040000
+
+#ifdef __USE_MISC
+# define XTABS 0014000
+#endif
+
+/* c_cflag bit meaning */
+#ifdef __USE_MISC
+# define CBAUD 0010017
+#endif
+#define B0 0000000 /* hang up */
+#define B50 0000001
+#define B75 0000002
+#define B110 0000003
+#define B134 0000004
+#define B150 0000005
+#define B200 0000006
+#define B300 0000007
+#define B600 0000010
+#define B1200 0000011
+#define B1800 0000012
+#define B2400 0000013
+#define B4800 0000014
+#define B9600 0000015
+#define B19200 0000016
+#define B38400 0000017
+#ifdef __USE_MISC
+# define EXTA B19200
+# define EXTB B38400
+#endif
+#define CSIZE 0000060
+#define CS5 0000000
+#define CS6 0000020
+#define CS7 0000040
+#define CS8 0000060
+#define CSTOPB 0000100
+#define CREAD 0000200
+#define PARENB 0000400
+#define PARODD 0001000
+#define HUPCL 0002000
+#define CLOCAL 0004000
+#ifdef __USE_MISC
+# define CBAUDEX 0010000
+#endif
+#define B57600 0010001
+#define B115200 0010002
+#define B230400 0010003
+#define B460800 0010004
+#define B500000 0010005
+#define B576000 0010006
+#define B921600 0010007
+#define B1000000 0010010
+#define B1152000 0010011
+#define B1500000 0010012
+#define B2000000 0010013
+#define B2500000 0010014
+#define B3000000 0010015
+#define B3500000 0010016
+#define B4000000 0010017
+#define __MAX_BAUD B4000000
+#ifdef __USE_MISC
+# define CIBAUD 002003600000 /* input baud rate (not used) */
+# define CMSPAR 010000000000 /* mark or space (stick) parity */
+# define CRTSCTS 020000000000 /* flow control */
+#endif
+
+/* c_lflag bits */
+#define ISIG 0000001
+#define ICANON 0000002
+#if defined __USE_MISC || (defined __USE_XOPEN && !defined __USE_XOPEN2K)
+# define XCASE 0000004
+#endif
+#define ECHO 0000010
+#define ECHOE 0000020
+#define ECHOK 0000040
+#define ECHONL 0000100
+#define NOFLSH 0000200
+#define TOSTOP 0000400
+#ifdef __USE_MISC
+# define ECHOCTL 0001000
+# define ECHOPRT 0002000
+# define ECHOKE 0004000
+# define FLUSHO 0010000
+# define PENDIN 0040000
+#endif
+#define IEXTEN 0100000
+#ifdef __USE_MISC
+# define EXTPROC 0200000
+#endif
+
+/* tcflow() and TCXONC use these */
+#define TCOOFF 0
+#define TCOON 1
+#define TCIOFF 2
+#define TCION 3
+
+/* tcflush() and TCFLSH use these */
+#define TCIFLUSH 0
+#define TCOFLUSH 1
+#define TCIOFLUSH 2
+
+/* tcsetattr uses these */
+#define TCSANOW 0
+#define TCSADRAIN 1
+#define TCSAFLUSH 2
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/time.cpp b/Userland/Libraries/LibC/time.cpp
new file mode 100644
index 0000000000..73f1a4234d
--- /dev/null
+++ b/Userland/Libraries/LibC/time.cpp
@@ -0,0 +1,376 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/Time.h>
+#include <Kernel/API/Syscall.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/times.h>
+#include <time.h>
+
+extern "C" {
+
+time_t time(time_t* tloc)
+{
+ struct timeval tv;
+ struct timezone tz;
+ if (gettimeofday(&tv, &tz) < 0)
+ return (time_t)-1;
+ if (tloc)
+ *tloc = tv.tv_sec;
+ return tv.tv_sec;
+}
+
+int adjtime(const struct timeval* delta, struct timeval* old_delta)
+{
+ int rc = syscall(SC_adjtime, delta, old_delta);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int gettimeofday(struct timeval* __restrict__ tv, void* __restrict__)
+{
+ int rc = syscall(SC_gettimeofday, tv);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int settimeofday(struct timeval* __restrict__ tv, void* __restrict__)
+{
+ timespec ts;
+ TIMEVAL_TO_TIMESPEC(tv, &ts);
+ return clock_settime(CLOCK_REALTIME, &ts);
+}
+
+char* ctime(const time_t* t)
+{
+ return asctime(localtime(t));
+}
+
+static const int __seconds_per_day = 60 * 60 * 24;
+
+static void time_to_tm(struct tm* tm, time_t t)
+{
+ int year = 1970;
+ for (; t >= days_in_year(year) * __seconds_per_day; ++year)
+ t -= days_in_year(year) * __seconds_per_day;
+ for (; t < 0; --year)
+ t += days_in_year(year - 1) * __seconds_per_day;
+ tm->tm_year = year - 1900;
+
+ ASSERT(t >= 0);
+ int days = t / __seconds_per_day;
+ tm->tm_yday = days;
+ int remaining = t % __seconds_per_day;
+ tm->tm_sec = remaining % 60;
+ remaining /= 60;
+ tm->tm_min = remaining % 60;
+ tm->tm_hour = remaining / 60;
+
+ int month;
+ for (month = 1; month < 12 && days >= days_in_month(year, month); ++month)
+ days -= days_in_month(year, month);
+
+ tm->tm_mday = days + 1;
+ tm->tm_wday = day_of_week(year, month, tm->tm_mday);
+ tm->tm_mon = month - 1;
+}
+
+static time_t tm_to_time(struct tm* tm, long timezone_adjust_seconds)
+{
+ // "The original values of the tm_wday and tm_yday components of the structure are ignored,
+ // and the original values of the other components are not restricted to the ranges described in <time.h>.
+ // [...]
+ // Upon successful completion, the values of the tm_wday and tm_yday components of the structure shall be set appropriately,
+ // and the other components are set to represent the specified time since the Epoch,
+ // but with their values forced to the ranges indicated in the <time.h> entry;
+ // the final value of tm_mday shall not be set until tm_mon and tm_year are determined."
+
+ // FIXME: Handle tm_isdst eventually.
+
+ tm->tm_year += tm->tm_mon / 12;
+ tm->tm_mon %= 12;
+ if (tm->tm_mon < 0) {
+ tm->tm_year--;
+ tm->tm_mon += 12;
+ }
+
+ tm->tm_yday = day_of_year(1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday);
+ time_t days_since_epoch = years_to_days_since_epoch(1900 + tm->tm_year) + tm->tm_yday;
+ auto timestamp = ((days_since_epoch * 24 + tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec + timezone_adjust_seconds;
+ time_to_tm(tm, timestamp);
+ return timestamp;
+}
+
+time_t mktime(struct tm* tm)
+{
+ return tm_to_time(tm, timezone);
+}
+
+struct tm* localtime(const time_t* t)
+{
+ static struct tm tm_buf;
+ return localtime_r(t, &tm_buf);
+}
+
+struct tm* localtime_r(const time_t* t, struct tm* tm)
+{
+ if (!t)
+ return nullptr;
+ time_to_tm(tm, (*t) - timezone);
+ return tm;
+}
+
+time_t timegm(struct tm* tm)
+{
+ return tm_to_time(tm, 0);
+}
+
+struct tm* gmtime(const time_t* t)
+{
+ static struct tm tm_buf;
+ return gmtime_r(t, &tm_buf);
+}
+
+struct tm* gmtime_r(const time_t* t, struct tm* tm)
+{
+ if (!t)
+ return nullptr;
+ time_to_tm(tm, *t);
+ return tm;
+}
+
+char* asctime(const struct tm* tm)
+{
+ static char buffer[69];
+ strftime(buffer, sizeof buffer, "%a %b %e %T %Y", tm);
+ return buffer;
+}
+
+//FIXME: Some formats are not supported.
+size_t strftime(char* destination, size_t max_size, const char* format, const struct tm* tm)
+{
+ const char wday_short_names[7][4] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+ };
+ const char wday_long_names[7][10] = {
+ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
+ };
+ const char mon_short_names[12][4] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+ const char mon_long_names[12][10] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+ };
+
+ StringBuilder builder { max_size };
+
+ const int format_len = strlen(format);
+ for (int i = 0; i < format_len; ++i) {
+ if (format[i] != '%') {
+ builder.append(format[i]);
+ } else {
+ if (++i >= format_len)
+ return 0;
+
+ switch (format[i]) {
+ case 'a':
+ builder.append(wday_short_names[tm->tm_wday]);
+ break;
+ case 'A':
+ builder.append(wday_long_names[tm->tm_wday]);
+ break;
+ case 'b':
+ builder.append(mon_short_names[tm->tm_mon]);
+ break;
+ case 'B':
+ builder.append(mon_long_names[tm->tm_mon]);
+ break;
+ case 'C':
+ builder.appendf("%02d", (tm->tm_year + 1900) / 100);
+ break;
+ case 'd':
+ builder.appendf("%02d", tm->tm_mday);
+ break;
+ case 'D':
+ builder.appendf("%02d/%02d/%02d", tm->tm_mon + 1, tm->tm_mday, (tm->tm_year + 1900) % 100);
+ break;
+ case 'e':
+ builder.appendf("%2d", tm->tm_mday);
+ break;
+ case 'h':
+ builder.append(mon_short_names[tm->tm_mon]);
+ break;
+ case 'H':
+ builder.appendf("%02d", tm->tm_hour);
+ break;
+ case 'I':
+ builder.appendf("%02d", tm->tm_hour % 12);
+ break;
+ case 'j':
+ builder.appendf("%03d", tm->tm_yday + 1);
+ break;
+ case 'm':
+ builder.appendf("%02d", tm->tm_mon + 1);
+ break;
+ case 'M':
+ builder.appendf("%02d", tm->tm_min);
+ break;
+ case 'n':
+ builder.append('\n');
+ break;
+ case 'p':
+ builder.append(tm->tm_hour < 12 ? "a.m." : "p.m.");
+ break;
+ case 'r':
+ builder.appendf("%02d:%02d:%02d %s", tm->tm_hour % 12, tm->tm_min, tm->tm_sec, tm->tm_hour < 12 ? "a.m." : "p.m.");
+ break;
+ case 'R':
+ builder.appendf("%02d:%02d", tm->tm_hour, tm->tm_min);
+ break;
+ case 'S':
+ builder.appendf("%02d", tm->tm_sec);
+ break;
+ case 't':
+ builder.append('\t');
+ break;
+ case 'T':
+ builder.appendf("%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
+ break;
+ case 'u':
+ builder.appendf("%d", tm->tm_wday ? tm->tm_wday : 7);
+ break;
+ case 'U': {
+ const int wday_of_year_beginning = (tm->tm_wday + 6 * tm->tm_yday) % 7;
+ const int week_number = (tm->tm_yday + wday_of_year_beginning) / 7;
+ builder.appendf("%02d", week_number);
+ break;
+ }
+ case 'V': {
+ const int wday_of_year_beginning = (tm->tm_wday + 6 + 6 * tm->tm_yday) % 7;
+ int week_number = (tm->tm_yday + wday_of_year_beginning) / 7 + 1;
+ if (wday_of_year_beginning > 3) {
+ if (tm->tm_yday >= 7 - wday_of_year_beginning)
+ --week_number;
+ else {
+ const int days_of_last_year = days_in_year(tm->tm_year + 1900 - 1);
+ const int wday_of_last_year_beginning = (wday_of_year_beginning + 6 * days_of_last_year) % 7;
+ week_number = (days_of_last_year + wday_of_last_year_beginning) / 7 + 1;
+ if (wday_of_last_year_beginning > 3)
+ --week_number;
+ }
+ }
+ builder.appendf("%02d", week_number);
+ break;
+ }
+ case 'w':
+ builder.appendf("%d", tm->tm_wday);
+ break;
+ case 'W': {
+ const int wday_of_year_beginning = (tm->tm_wday + 6 + 6 * tm->tm_yday) % 7;
+ const int week_number = (tm->tm_yday + wday_of_year_beginning) / 7;
+ builder.appendf("%02d", week_number);
+ break;
+ }
+ case 'y':
+ builder.appendf("%02d", (tm->tm_year + 1900) % 100);
+ break;
+ case 'Y':
+ builder.appendf("%d", tm->tm_year + 1900);
+ break;
+ case '%':
+ builder.append('%');
+ break;
+ default:
+ return 0;
+ }
+ }
+ if (builder.length() + 1 > max_size)
+ return 0;
+ }
+
+ auto str = builder.build();
+ bool fits = str.copy_characters_to_buffer(destination, max_size);
+ return fits ? str.length() : 0;
+}
+
+long timezone = 0;
+long altzone;
+char* tzname[2];
+int daylight;
+
+void tzset()
+{
+ //FIXME: Here we prepend we are in UTC+0.
+ timezone = 0;
+}
+
+clock_t clock()
+{
+ struct tms tms;
+ times(&tms);
+ return tms.tms_utime + tms.tms_stime;
+}
+
+int clock_gettime(clockid_t clock_id, struct timespec* ts)
+{
+ int rc = syscall(SC_clock_gettime, clock_id, ts);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int clock_settime(clockid_t clock_id, struct timespec* ts)
+{
+ int rc = syscall(SC_clock_settime, clock_id, ts);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec* requested_sleep, struct timespec* remaining_sleep)
+{
+ Syscall::SC_clock_nanosleep_params params { clock_id, flags, requested_sleep, remaining_sleep };
+ int rc = syscall(SC_clock_nanosleep, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int nanosleep(const struct timespec* requested_sleep, struct timespec* remaining_sleep)
+{
+ return clock_nanosleep(CLOCK_REALTIME, 0, requested_sleep, remaining_sleep);
+}
+
+int clock_getres(clockid_t, struct timespec*)
+{
+ ASSERT_NOT_REACHED();
+}
+
+double difftime(time_t t1, time_t t0)
+{
+ return (double)(t1 - t0);
+}
+}
diff --git a/Userland/Libraries/LibC/time.h b/Userland/Libraries/LibC/time.h
new file mode 100644
index 0000000000..74cf7f8de6
--- /dev/null
+++ b/Userland/Libraries/LibC/time.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+struct tm {
+ int tm_sec; /* Seconds (0-60) */
+ int tm_min; /* Minutes (0-59) */
+ int tm_hour; /* Hours (0-23) */
+ int tm_mday; /* Day of the month (1-31) */
+ int tm_mon; /* Month (0-11) */
+ int tm_year; /* Year - 1900 */
+ int tm_wday; /* Day of the week (0-6, Sunday = 0) */
+ int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
+ int tm_isdst; /* Daylight saving time */
+};
+
+extern long timezone; /* The difference in seconds between UTC and local time */
+extern long altzone;
+extern char* tzname[2];
+extern int daylight;
+
+typedef uint32_t clock_t;
+typedef int64_t time_t;
+
+struct tm* localtime(const time_t*);
+struct tm* gmtime(const time_t*);
+time_t mktime(struct tm*);
+time_t timegm(struct tm*);
+time_t time(time_t*);
+char* ctime(const time_t*);
+void tzset();
+char* asctime(const struct tm*);
+
+#define CLOCKS_PER_SEC 1000
+clock_t clock();
+
+struct timespec {
+ time_t tv_sec;
+ long tv_nsec;
+};
+
+typedef int clockid_t;
+
+#define CLOCK_REALTIME 0
+#define CLOCK_MONOTONIC 1
+#define CLOCK_MONOTONIC_RAW 4
+#define CLOCK_REALTIME_COARSE 5
+#define CLOCK_MONOTONIC_COARSE 6
+#define TIMER_ABSTIME 99
+
+int clock_gettime(clockid_t, struct timespec*);
+int clock_settime(clockid_t, struct timespec*);
+int clock_nanosleep(clockid_t, int flags, const struct timespec* requested_sleep, struct timespec* remaining_sleep);
+int clock_getres(clockid_t, struct timespec* result);
+int nanosleep(const struct timespec* requested_sleep, struct timespec* remaining_sleep);
+struct tm* gmtime_r(const time_t* timep, struct tm* result);
+struct tm* localtime_r(const time_t* timep, struct tm* result);
+
+double difftime(time_t, time_t);
+size_t strftime(char* s, size_t max, const char* format, const struct tm*) __attribute__((format(strftime, 3, 0)));
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/times.cpp b/Userland/Libraries/LibC/times.cpp
new file mode 100644
index 0000000000..012dfc7d70
--- /dev/null
+++ b/Userland/Libraries/LibC/times.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <errno.h>
+#include <sys/times.h>
+
+clock_t times(struct tms* buf)
+{
+ int rc = syscall(SC_times, buf);
+ __RETURN_WITH_ERRNO(rc, rc, (clock_t)-1);
+}
diff --git a/Userland/Libraries/LibC/ulimit.cpp b/Userland/Libraries/LibC/ulimit.cpp
new file mode 100644
index 0000000000..1ab9b7dc3a
--- /dev/null
+++ b/Userland/Libraries/LibC/ulimit.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LogStream.h>
+#include <assert.h>
+#include <sys/resource.h>
+#include <ulimit.h>
+
+extern "C" {
+
+long ulimit([[maybe_unused]] int cmd, [[maybe_unused]] long newlimit)
+{
+ ASSERT_NOT_REACHED();
+ return -1;
+}
+
+int getrusage([[maybe_unused]] int who, [[maybe_unused]] struct rusage* usage)
+{
+ dbgln("LibC: getrusage is not implemented");
+ return -1;
+}
+}
diff --git a/Userland/Libraries/LibC/ulimit.h b/Userland/Libraries/LibC/ulimit.h
new file mode 100644
index 0000000000..dd48401c6e
--- /dev/null
+++ b/Userland/Libraries/LibC/ulimit.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+long ulimit(int cmd, long newlimit);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/unistd.cpp b/Userland/Libraries/LibC/unistd.cpp
new file mode 100644
index 0000000000..b35f011289
--- /dev/null
+++ b/Userland/Libraries/LibC/unistd.cpp
@@ -0,0 +1,725 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ScopedValueRollback.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <Kernel/API/Syscall.h>
+#include <alloca.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+
+extern "C" {
+
+#ifdef NO_TLS
+static int s_cached_tid = 0;
+#else
+static __thread int s_cached_tid = 0;
+#endif
+
+static int s_cached_pid = 0;
+
+int chown(const char* pathname, uid_t uid, gid_t gid)
+{
+ if (!pathname) {
+ errno = EFAULT;
+ return -1;
+ }
+ Syscall::SC_chown_params params { { pathname, strlen(pathname) }, uid, gid };
+ int rc = syscall(SC_chown, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int fchown(int fd, uid_t uid, gid_t gid)
+{
+ int rc = syscall(SC_fchown, fd, uid, gid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+pid_t fork()
+{
+ int rc = syscall(SC_fork);
+ if (rc == 0) {
+ s_cached_tid = 0;
+ s_cached_pid = 0;
+ }
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int execv(const char* path, char* const argv[])
+{
+ return execve(path, argv, environ);
+}
+
+int execve(const char* filename, char* const argv[], char* const envp[])
+{
+ size_t arg_count = 0;
+ for (size_t i = 0; argv[i]; ++i)
+ ++arg_count;
+
+ size_t env_count = 0;
+ for (size_t i = 0; envp[i]; ++i)
+ ++env_count;
+
+ auto copy_strings = [&](auto& vec, size_t count, auto& output) {
+ output.length = count;
+ for (size_t i = 0; vec[i]; ++i) {
+ output.strings[i].characters = vec[i];
+ output.strings[i].length = strlen(vec[i]);
+ }
+ };
+
+ Syscall::SC_execve_params params;
+ params.arguments.strings = (Syscall::StringArgument*)alloca(arg_count * sizeof(Syscall::StringArgument));
+ params.environment.strings = (Syscall::StringArgument*)alloca(env_count * sizeof(Syscall::StringArgument));
+
+ params.path = { filename, strlen(filename) };
+ copy_strings(argv, arg_count, params.arguments);
+ copy_strings(envp, env_count, params.environment);
+
+ int rc = syscall(SC_execve, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int execvpe(const char* filename, char* const argv[], char* const envp[])
+{
+ if (strchr(filename, '/'))
+ return execve(filename, argv, envp);
+
+ ScopedValueRollback errno_rollback(errno);
+ String path = getenv("PATH");
+ if (path.is_empty())
+ path = "/bin:/usr/bin";
+ auto parts = path.split(':');
+ for (auto& part : parts) {
+ auto candidate = String::format("%s/%s", part.characters(), filename);
+ int rc = execve(candidate.characters(), argv, envp);
+ if (rc < 0 && errno != ENOENT) {
+ errno_rollback.set_override_rollback_value(errno);
+ dbgln("execvpe() failed on attempt ({}) with {}", candidate, strerror(errno));
+ return rc;
+ }
+ }
+ errno_rollback.set_override_rollback_value(ENOENT);
+ dbgln("execvpe() leaving :(");
+ return -1;
+}
+
+int execvp(const char* filename, char* const argv[])
+{
+ int rc = execvpe(filename, argv, environ);
+ int saved_errno = errno;
+ dbgln("execvp() about to return {} with errno={}", rc, saved_errno);
+ errno = saved_errno;
+ return rc;
+}
+
+int execl(const char* filename, const char* arg0, ...)
+{
+ Vector<const char*, 16> args;
+ args.append(arg0);
+
+ va_list ap;
+ va_start(ap, arg0);
+ for (;;) {
+ const char* arg = va_arg(ap, const char*);
+ if (!arg)
+ break;
+ args.append(arg);
+ }
+ va_end(ap);
+ args.append(nullptr);
+ return execve(filename, const_cast<char* const*>(args.data()), environ);
+}
+
+int execlp(const char* filename, const char* arg0, ...)
+{
+ Vector<const char*, 16> args;
+ args.append(arg0);
+
+ va_list ap;
+ va_start(ap, arg0);
+ for (;;) {
+ const char* arg = va_arg(ap, const char*);
+ if (!arg)
+ break;
+ args.append(arg);
+ }
+ va_end(ap);
+ args.append(nullptr);
+ return execvpe(filename, const_cast<char* const*>(args.data()), environ);
+}
+
+uid_t geteuid()
+{
+ return syscall(SC_geteuid);
+}
+
+gid_t getegid()
+{
+ return syscall(SC_getegid);
+}
+
+uid_t getuid()
+{
+ return syscall(SC_getuid);
+}
+
+gid_t getgid()
+{
+ return syscall(SC_getgid);
+}
+
+pid_t getpid()
+{
+ int cached_pid = s_cached_pid;
+ if (!cached_pid) {
+ cached_pid = syscall(SC_getpid);
+ s_cached_pid = cached_pid;
+ }
+ return cached_pid;
+}
+
+pid_t getppid()
+{
+ return syscall(SC_getppid);
+}
+
+int getresuid(uid_t* ruid, uid_t* euid, uid_t* suid)
+{
+ return syscall(SC_getresuid, ruid, euid, suid);
+}
+
+int getresgid(gid_t* rgid, gid_t* egid, gid_t* sgid)
+{
+ return syscall(SC_getresgid, rgid, egid, sgid);
+}
+
+pid_t getsid(pid_t pid)
+{
+ int rc = syscall(SC_getsid, pid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+pid_t setsid()
+{
+ int rc = syscall(SC_setsid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+pid_t tcgetpgrp(int fd)
+{
+ return ioctl(fd, TIOCGPGRP);
+}
+
+int tcsetpgrp(int fd, pid_t pgid)
+{
+ return ioctl(fd, TIOCSPGRP, pgid);
+}
+
+int setpgid(pid_t pid, pid_t pgid)
+{
+ int rc = syscall(SC_setpgid, pid, pgid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+pid_t getpgid(pid_t pid)
+{
+ int rc = syscall(SC_getpgid, pid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+pid_t getpgrp()
+{
+ int rc = syscall(SC_getpgrp);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+ssize_t read(int fd, void* buf, size_t count)
+{
+ int rc = syscall(SC_read, fd, buf, count);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+ssize_t write(int fd, const void* buf, size_t count)
+{
+ int rc = syscall(SC_write, fd, buf, count);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int ttyname_r(int fd, char* buffer, size_t size)
+{
+ int rc = syscall(SC_ttyname, fd, buffer, size);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+static char ttyname_buf[32];
+char* ttyname(int fd)
+{
+ if (ttyname_r(fd, ttyname_buf, sizeof(ttyname_buf)) < 0)
+ return nullptr;
+ return ttyname_buf;
+}
+
+int close(int fd)
+{
+ int rc = syscall(SC_close, fd);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int chdir(const char* path)
+{
+ if (!path) {
+ errno = EFAULT;
+ return -1;
+ }
+ int rc = syscall(SC_chdir, path, strlen(path));
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int fchdir(int fd)
+{
+ int rc = syscall(SC_fchdir, fd);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+char* getcwd(char* buffer, size_t size)
+{
+ if (!buffer) {
+ size = size ? size : PATH_MAX;
+ buffer = (char*)malloc(size);
+ }
+ int rc = syscall(SC_getcwd, buffer, size);
+ __RETURN_WITH_ERRNO(rc, buffer, nullptr);
+}
+
+char* getwd(char* buf)
+{
+ auto* p = getcwd(buf, PATH_MAX);
+ return p;
+}
+
+int sleep(unsigned seconds)
+{
+ struct timespec ts = { seconds, 0 };
+ if (clock_nanosleep(CLOCK_MONOTONIC_COARSE, 0, &ts, nullptr) < 0)
+ return ts.tv_sec;
+ return 0;
+}
+
+int usleep(useconds_t usec)
+{
+ struct timespec ts = { (long)(usec / 1000000), (long)(usec % 1000000) * 1000 };
+ return clock_nanosleep(CLOCK_MONOTONIC_COARSE, 0, &ts, nullptr);
+}
+
+int gethostname(char* buffer, size_t size)
+{
+ int rc = syscall(SC_gethostname, buffer, size);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int sethostname(const char* hostname, ssize_t size)
+{
+ int rc = syscall(SC_sethostname, hostname, size);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+ssize_t readlink(const char* path, char* buffer, size_t size)
+{
+ Syscall::SC_readlink_params params { { path, strlen(path) }, { buffer, size } };
+ int rc = syscall(SC_readlink, &params);
+ // Return the number of bytes placed in the buffer, not the full path size.
+ __RETURN_WITH_ERRNO(rc, min((size_t)rc, size), -1);
+}
+
+off_t lseek(int fd, off_t offset, int whence)
+{
+ int rc = syscall(SC_lseek, fd, offset, whence);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int link(const char* old_path, const char* new_path)
+{
+ if (!old_path || !new_path) {
+ errno = EFAULT;
+ return -1;
+ }
+ Syscall::SC_link_params params { { old_path, strlen(old_path) }, { new_path, strlen(new_path) } };
+ int rc = syscall(SC_link, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int unlink(const char* pathname)
+{
+ int rc = syscall(SC_unlink, pathname, strlen(pathname));
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int symlink(const char* target, const char* linkpath)
+{
+ if (!target || !linkpath) {
+ errno = EFAULT;
+ return -1;
+ }
+ Syscall::SC_symlink_params params { { target, strlen(target) }, { linkpath, strlen(linkpath) } };
+ int rc = syscall(SC_symlink, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int rmdir(const char* pathname)
+{
+ if (!pathname) {
+ errno = EFAULT;
+ return -1;
+ }
+ int rc = syscall(SC_rmdir, pathname, strlen(pathname));
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int isatty(int fd)
+{
+ return fcntl(fd, F_ISTTY);
+}
+
+int dup(int old_fd)
+{
+ return fcntl(old_fd, F_DUPFD, 0);
+}
+
+int dup2(int old_fd, int new_fd)
+{
+ int rc = syscall(SC_dup2, old_fd, new_fd);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int setgroups(size_t size, const gid_t* list)
+{
+ int rc = syscall(SC_setgroups, size, list);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int getgroups(int size, gid_t list[])
+{
+ int rc = syscall(SC_getgroups, size, list);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int pipe(int pipefd[2])
+{
+ return pipe2(pipefd, 0);
+}
+
+int pipe2(int pipefd[2], int flags)
+{
+ int rc = syscall(SC_pipe, pipefd, flags);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+unsigned int alarm(unsigned int seconds)
+{
+ return syscall(SC_alarm, seconds);
+}
+
+int seteuid(uid_t euid)
+{
+ int rc = syscall(SC_seteuid, euid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int setegid(gid_t egid)
+{
+ int rc = syscall(SC_setegid, egid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int setuid(uid_t uid)
+{
+ int rc = syscall(SC_setuid, uid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int setgid(gid_t gid)
+{
+ int rc = syscall(SC_setgid, gid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int setresuid(uid_t ruid, uid_t euid, uid_t suid)
+{
+ int rc = syscall(SC_setresuid, ruid, euid, suid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int setresgid(gid_t rgid, gid_t egid, gid_t sgid)
+{
+ int rc = syscall(SC_setresgid, rgid, egid, sgid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int access(const char* pathname, int mode)
+{
+ if (!pathname) {
+ errno = EFAULT;
+ return -1;
+ }
+ int rc = syscall(SC_access, pathname, strlen(pathname), mode);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int mknod(const char* pathname, mode_t mode, dev_t dev)
+{
+ if (!pathname) {
+ errno = EFAULT;
+ return -1;
+ }
+ Syscall::SC_mknod_params params { { pathname, strlen(pathname) }, mode, dev };
+ int rc = syscall(SC_mknod, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+long fpathconf([[maybe_unused]] int fd, [[maybe_unused]] int name)
+{
+ switch (name) {
+ case _PC_PATH_MAX:
+ return PATH_MAX;
+ case _PC_VDISABLE:
+ return _POSIX_VDISABLE;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+long pathconf([[maybe_unused]] const char* path, int name)
+{
+ switch (name) {
+ case _PC_PATH_MAX:
+ return PATH_MAX;
+ case _PC_PIPE_BUF:
+ return PIPE_BUF;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+void _exit(int status)
+{
+ syscall(SC_exit, status);
+ ASSERT_NOT_REACHED();
+}
+
+void sync()
+{
+ syscall(SC_sync);
+}
+
+static String getlogin_buffer;
+
+char* getlogin()
+{
+ if (getlogin_buffer.is_null()) {
+ if (auto* passwd = getpwuid(getuid())) {
+ getlogin_buffer = String(passwd->pw_name);
+ }
+ endpwent();
+ }
+ return const_cast<char*>(getlogin_buffer.characters());
+}
+
+int ftruncate(int fd, off_t length)
+{
+ int rc = syscall(SC_ftruncate, fd, length);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int truncate(const char* path, off_t length)
+{
+ int fd = open(path, O_RDWR | O_CREAT, 0666);
+ if (fd < 0)
+ return fd;
+ int rc = ftruncate(fd, length);
+ int saved_errno = errno;
+ if (int close_rc = close(fd); close_rc < 0)
+ return close_rc;
+ errno = saved_errno;
+ return rc;
+}
+
+int gettid()
+{
+ int cached_tid = s_cached_tid;
+ if (!cached_tid) {
+ cached_tid = syscall(SC_gettid);
+ s_cached_tid = cached_tid;
+ }
+ return cached_tid;
+}
+
+int donate(int tid)
+{
+ int rc = syscall(SC_donate, tid);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+void sysbeep()
+{
+ syscall(SC_beep);
+}
+
+int fsync([[maybe_unused]] int fd)
+{
+ dbgprintf("FIXME: Implement fsync()\n");
+ return 0;
+}
+
+int halt()
+{
+ int rc = syscall(SC_halt);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int reboot()
+{
+ int rc = syscall(SC_reboot);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int mount(int source_fd, const char* target, const char* fs_type, int flags)
+{
+ if (!target || !fs_type) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ Syscall::SC_mount_params params {
+ source_fd,
+ { target, strlen(target) },
+ { fs_type, strlen(fs_type) },
+ flags
+ };
+ int rc = syscall(SC_mount, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int umount(const char* mountpoint)
+{
+ int rc = syscall(SC_umount, mountpoint, strlen(mountpoint));
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+void dump_backtrace()
+{
+ syscall(SC_dump_backtrace);
+}
+
+int get_process_name(char* buffer, int buffer_size)
+{
+ int rc = syscall(SC_get_process_name, buffer, buffer_size);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int set_process_name(const char* name, size_t name_length)
+{
+ int rc = syscall(SC_set_process_name, name, name_length);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int chroot(const char* path)
+{
+ return chroot_with_mount_flags(path, -1);
+}
+
+int chroot_with_mount_flags(const char* path, int mount_flags)
+{
+ if (!path) {
+ errno = EFAULT;
+ return -1;
+ }
+ int rc = syscall(SC_chroot, path, strlen(path), mount_flags);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int pledge(const char* promises, const char* execpromises)
+{
+ Syscall::SC_pledge_params params {
+ { promises, promises ? strlen(promises) : 0 },
+ { execpromises, execpromises ? strlen(execpromises) : 0 }
+ };
+ int rc = syscall(SC_pledge, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int unveil(const char* path, const char* permissions)
+{
+ Syscall::SC_unveil_params params {
+ { path, path ? strlen(path) : 0 },
+ { permissions, permissions ? strlen(permissions) : 0 }
+ };
+ int rc = syscall(SC_unveil, &params);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+ssize_t pread(int fd, void* buf, size_t count, off_t offset)
+{
+ // FIXME: This is not thread safe and should be implemented in the kernel instead.
+ off_t old_offset = lseek(fd, 0, SEEK_CUR);
+ lseek(fd, offset, SEEK_SET);
+ ssize_t nread = read(fd, buf, count);
+ lseek(fd, old_offset, SEEK_SET);
+ return nread;
+}
+
+char* getpass(const char* prompt)
+{
+ dbgln("FIXME: getpass('{}')", prompt);
+ ASSERT_NOT_REACHED();
+}
+
+long sysconf(int name)
+{
+ int rc = syscall(SC_sysconf, name);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
+int getpagesize()
+{
+ return PAGE_SIZE;
+}
+}
diff --git a/Userland/Libraries/LibC/unistd.h b/Userland/Libraries/LibC/unistd.h
new file mode 100644
index 0000000000..483a690011
--- /dev/null
+++ b/Userland/Libraries/LibC/unistd.h
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* standard symbolic constants and types
+ *
+ * values from POSIX standard unix specification
+ *
+ * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/unistd.h.html
+ */
+
+#pragma once
+
+#include <errno.h>
+#include <limits.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+#define HZ 1000
+#define STDIN_FILENO 0
+#define STDOUT_FILENO 1
+#define STDERR_FILENO 2
+
+/* lseek whence values */
+#ifndef _STDIO_H /* also defined in stdio.h */
+# define SEEK_SET 0 /* from beginning of file. */
+# define SEEK_CUR 1 /* from current position in file. */
+# define SEEK_END 2 /* from the end of the file. */
+#endif
+
+extern char** environ;
+
+int get_process_name(char* buffer, int buffer_size);
+int set_process_name(const char* name, size_t name_length);
+void dump_backtrace();
+int fsync(int fd);
+void sysbeep();
+int gettid();
+int donate(int tid);
+int getpagesize();
+pid_t fork();
+int execv(const char* path, char* const argv[]);
+int execve(const char* filename, char* const argv[], char* const envp[]);
+int execvpe(const char* filename, char* const argv[], char* const envp[]);
+int execvp(const char* filename, char* const argv[]);
+int execl(const char* filename, const char* arg, ...);
+int execlp(const char* filename, const char* arg, ...);
+int chroot(const char* path);
+int chroot_with_mount_flags(const char* path, int mount_flags);
+void sync();
+__attribute__((noreturn)) void _exit(int status);
+pid_t getsid(pid_t);
+pid_t setsid();
+int setpgid(pid_t pid, pid_t pgid);
+pid_t getpgid(pid_t);
+pid_t getpgrp();
+uid_t geteuid();
+gid_t getegid();
+uid_t getuid();
+gid_t getgid();
+pid_t getpid();
+pid_t getppid();
+int getresuid(uid_t*, uid_t*, uid_t*);
+int getresgid(gid_t*, gid_t*, gid_t*);
+int getgroups(int size, gid_t list[]);
+int setgroups(size_t, const gid_t*);
+int seteuid(uid_t);
+int setegid(gid_t);
+int setuid(uid_t);
+int setgid(gid_t);
+int setresuid(uid_t, uid_t, uid_t);
+int setresgid(gid_t, gid_t, gid_t);
+pid_t tcgetpgrp(int fd);
+int tcsetpgrp(int fd, pid_t pgid);
+ssize_t read(int fd, void* buf, size_t count);
+ssize_t pread(int fd, void* buf, size_t count, off_t);
+ssize_t write(int fd, const void* buf, size_t count);
+int close(int fd);
+int chdir(const char* path);
+int fchdir(int fd);
+char* getcwd(char* buffer, size_t size);
+char* getwd(char* buffer);
+int sleep(unsigned seconds);
+int usleep(useconds_t);
+int gethostname(char*, size_t);
+int sethostname(const char*, ssize_t);
+ssize_t readlink(const char* path, char* buffer, size_t);
+char* ttyname(int fd);
+int ttyname_r(int fd, char* buffer, size_t);
+off_t lseek(int fd, off_t, int whence);
+int link(const char* oldpath, const char* newpath);
+int unlink(const char* pathname);
+int symlink(const char* target, const char* linkpath);
+int rmdir(const char* pathname);
+int dup(int old_fd);
+int dup2(int old_fd, int new_fd);
+int pipe(int pipefd[2]);
+int pipe2(int pipefd[2], int flags);
+unsigned int alarm(unsigned int seconds);
+int access(const char* pathname, int mode);
+int isatty(int fd);
+int mknod(const char* pathname, mode_t, dev_t);
+long fpathconf(int fd, int name);
+long pathconf(const char* path, int name);
+char* getlogin();
+int chown(const char* pathname, uid_t, gid_t);
+int fchown(int fd, uid_t, gid_t);
+int ftruncate(int fd, off_t length);
+int truncate(const char* path, off_t length);
+int halt();
+int reboot();
+int mount(int source_fd, const char* target, const char* fs_type, int flags);
+int umount(const char* mountpoint);
+int pledge(const char* promises, const char* execpromises);
+int unveil(const char* path, const char* permissions);
+char* getpass(const char* prompt);
+
+enum {
+ _PC_NAME_MAX,
+ _PC_PATH_MAX,
+ _PC_PIPE_BUF,
+ _PC_VDISABLE
+};
+
+#define HOST_NAME_MAX 64
+
+#define R_OK 4
+#define W_OK 2
+#define X_OK 1
+#define F_OK 0
+
+#define MS_NODEV (1 << 0)
+#define MS_NOEXEC (1 << 1)
+#define MS_NOSUID (1 << 2)
+#define MS_BIND (1 << 3)
+#define MS_RDONLY (1 << 4)
+#define MS_REMOUNT (1 << 5)
+
+#define _POSIX_SAVED_IDS
+
+/*
+ * We aren't fully compliant (don't support policies, and don't have a wide
+ * range of values), but we do have process priorities.
+ */
+#define _POSIX_PRIORITY_SCHEDULING
+#define _POSIX_VDISABLE '\0'
+
+enum {
+ _SC_NPROCESSORS_CONF,
+ _SC_NPROCESSORS_ONLN,
+ _SC_PAGESIZE,
+ _SC_OPEN_MAX,
+};
+
+#define _SC_NPROCESSORS_CONF _SC_NPROCESSORS_CONF
+#define _SC_NPROCESSORS_ONLN _SC_NPROCESSORS_ONLN
+#define _SC_PAGESIZE _SC_PAGESIZE
+#define _SC_OPEN_MAX _SC_OPEN_MAX
+
+long sysconf(int name);
+
+struct crypt_data {
+ int initialized;
+ char result[65];
+};
+
+char* crypt(const char* key, const char* salt);
+char* crypt_r(const char* key, const char* salt, struct crypt_data* data);
+
+// If opterr is set (the default), print error messages to stderr.
+extern int opterr;
+// On errors, optopt is set to the erroneous *character*.
+extern int optopt;
+// Index of the next argument to process upon a getopt*() call.
+extern int optind;
+// If set, reset the internal state kept by getopt*(). You may also want to set
+// optind to 1 in that case. Alternatively, setting optind to 0 is treated like
+// doing both of the above.
+extern int optreset;
+// After parsing an option that accept an argument, set to point to the argument
+// value.
+extern char* optarg;
+
+int getopt(int argc, char** argv, const char* short_options);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/utime.cpp b/Userland/Libraries/LibC/utime.cpp
new file mode 100644
index 0000000000..501d7f06c2
--- /dev/null
+++ b/Userland/Libraries/LibC/utime.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <errno.h>
+#include <string.h>
+#include <utime.h>
+
+extern "C" {
+
+int utime(const char* pathname, const struct utimbuf* buf)
+{
+ if (!pathname) {
+ errno = EFAULT;
+ return -1;
+ }
+ int rc = syscall(SC_utime, pathname, strlen(pathname), buf);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+}
diff --git a/Userland/Libraries/LibC/utime.h b/Userland/Libraries/LibC/utime.h
new file mode 100644
index 0000000000..073caa3655
--- /dev/null
+++ b/Userland/Libraries/LibC/utime.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+int utime(const char* pathname, const struct utimbuf*);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/utmp.h b/Userland/Libraries/LibC/utmp.h
new file mode 100644
index 0000000000..0b0ca85946
--- /dev/null
+++ b/Userland/Libraries/LibC/utmp.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/time.h>
+
+__BEGIN_DECLS
+
+struct exit_status { /* Type for ut_exit, below */
+ short int e_termination; /* Process termination status */
+ short int e_exit; /* Process exit status */
+};
+
+#define USER_PROCESS 7
+#define DEAD_PROCESS 8
+
+#define UT_NAMESIZE 32
+#define UT_LINESIZE 32
+#define UT_HOSTSIZE 256
+
+struct utmp {
+ short ut_type; /* Type of record */
+ pid_t ut_pid; /* PID of login process */
+ char ut_line[UT_LINESIZE]; /* Device name of tty - "/dev/" */
+ char ut_id[4]; /* Terminal name suffix,
+ or inittab(5) ID */
+ char ut_user[UT_NAMESIZE]; /* Username */
+ char ut_host[UT_HOSTSIZE]; /* Hostname for remote login, or
+ kernel version for run-level
+ messages */
+ struct exit_status ut_exit; /* Exit status of a process
+ marked as DEAD_PROCESS; not
+ used by Linux init (1 */
+
+ long ut_session; /* Session ID */
+ struct timeval ut_tv; /* Time entry was made */
+
+ int32_t ut_addr_v6[4]; /* Internet address of remote
+ host; IPv4 address uses
+ just ut_addr_v6[0] */
+
+ char __unused[20]; /* Reserved for future use */
+};
+
+/* Backward compatibility hacks */
+#define ut_name ut_user
+#ifndef _NO_UT_TIME
+# define ut_time ut_tv.tv_sec
+#endif
+#define ut_xtime ut_tv.tv_sec
+#define ut_addr ut_addr_v6[0]
+
+__END_DECLS
diff --git a/Userland/Libraries/LibC/utsname.cpp b/Userland/Libraries/LibC/utsname.cpp
new file mode 100644
index 0000000000..23d10ee7e9
--- /dev/null
+++ b/Userland/Libraries/LibC/utsname.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <Kernel/API/Syscall.h>
+#include <errno.h>
+#include <sys/utsname.h>
+
+extern "C" {
+
+int uname(struct utsname* buf)
+{
+ int rc = syscall(SC_uname, buf);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+}
diff --git a/Userland/Libraries/LibC/wchar.cpp b/Userland/Libraries/LibC/wchar.cpp
new file mode 100644
index 0000000000..54cfdacda4
--- /dev/null
+++ b/Userland/Libraries/LibC/wchar.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <wchar.h>
+
+extern "C" {
+
+size_t wcslen(const wchar_t* str)
+{
+ size_t len = 0;
+ while (*(str++))
+ ++len;
+ return len;
+}
+
+wchar_t* wcscpy(wchar_t* dest, const wchar_t* src)
+{
+ wchar_t* originalDest = dest;
+ while ((*dest++ = *src++) != '\0')
+ ;
+ return originalDest;
+}
+
+wchar_t* wcsncpy(wchar_t* dest, const wchar_t* src, size_t num)
+{
+ wchar_t* originalDest = dest;
+ while (((*dest++ = *src++) != '\0') && ((size_t)(dest - originalDest) < num))
+ ;
+ return originalDest;
+}
+
+int wcscmp(const wchar_t* s1, const wchar_t* s2)
+{
+ while (*s1 == *s2++)
+ if (*s1++ == 0)
+ return 0;
+ return *(const wchar_t*)s1 - *(const wchar_t*)--s2;
+}
+
+wchar_t* wcschr(const wchar_t* str, int c)
+{
+ wchar_t ch = c;
+ for (;; ++str) {
+ if (*str == ch)
+ return const_cast<wchar_t*>(str);
+ if (!*str)
+ return nullptr;
+ }
+}
+
+const wchar_t* wcsrchr(const wchar_t* str, wchar_t wc)
+{
+ wchar_t* last = nullptr;
+ wchar_t c;
+ for (; (c = *str); ++str) {
+ if (c == wc)
+ last = const_cast<wchar_t*>(str);
+ }
+ return last;
+}
+
+wchar_t* wcscat(wchar_t* dest, const wchar_t* src)
+{
+ size_t dest_length = wcslen(dest);
+ size_t i;
+ for (i = 0; src[i] != '\0'; i++)
+ dest[dest_length + i] = src[i];
+ dest[dest_length + i] = '\0';
+ return dest;
+}
+
+wchar_t* wcstok(wchar_t* str, const wchar_t* delim, wchar_t** ptr)
+{
+ wchar_t* used_str = str;
+ if (!used_str) {
+ used_str = *ptr;
+ }
+
+ size_t token_start = 0;
+ size_t token_end = 0;
+ size_t str_len = wcslen(used_str);
+ size_t delim_len = wcslen(delim);
+
+ for (size_t i = 0; i < str_len; ++i) {
+ bool is_proper_delim = false;
+
+ for (size_t j = 0; j < delim_len; ++j) {
+ if (used_str[i] == delim[j]) {
+ // Skip beginning delimiters
+ if (token_end - token_start == 0) {
+ ++token_start;
+ break;
+ }
+
+ is_proper_delim = true;
+ }
+ }
+
+ ++token_end;
+ if (is_proper_delim && token_end > 0) {
+ --token_end;
+ break;
+ }
+ }
+
+ if (used_str[token_start] == '\0')
+ return nullptr;
+
+ if (token_end == 0) {
+ return &used_str[token_start];
+ }
+
+ used_str[token_end] = '\0';
+ return &used_str[token_start];
+}
+
+wchar_t* wcsncat(wchar_t* dest, const wchar_t* src, size_t n)
+{
+ size_t dest_length = wcslen(dest);
+ size_t i;
+ for (i = 0; i < n && src[i] != '\0'; i++)
+ dest[dest_length + i] = src[i];
+ dest[dest_length + i] = '\0';
+ return dest;
+}
+}
diff --git a/Userland/Libraries/LibC/wchar.h b/Userland/Libraries/LibC/wchar.h
new file mode 100644
index 0000000000..c6ab5b2965
--- /dev/null
+++ b/Userland/Libraries/LibC/wchar.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+#ifndef WEOF
+# define WEOF (0xffffffffu)
+#endif
+
+size_t wcslen(const wchar_t*);
+wchar_t* wcscpy(wchar_t*, const wchar_t*);
+wchar_t* wcsncpy(wchar_t*, const wchar_t*, size_t);
+int wcscmp(const wchar_t*, const wchar_t*);
+wchar_t* wcschr(const wchar_t*, int);
+const wchar_t* wcsrchr(const wchar_t*, wchar_t);
+wchar_t* wcscat(wchar_t*, const wchar_t*);
+wchar_t* wcstok(wchar_t*, const wchar_t*, wchar_t**);
+wchar_t* wcsncat(wchar_t*, const wchar_t*, size_t);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibChess/CMakeLists.txt b/Userland/Libraries/LibChess/CMakeLists.txt
new file mode 100644
index 0000000000..07c6a5aa47
--- /dev/null
+++ b/Userland/Libraries/LibChess/CMakeLists.txt
@@ -0,0 +1,8 @@
+set(SOURCES
+ Chess.cpp
+ UCICommand.cpp
+ UCIEndpoint.cpp
+)
+
+serenity_lib(LibChess chess)
+target_link_libraries(LibChess LibC LibCore)
diff --git a/Userland/Libraries/LibChess/Chess.cpp b/Userland/Libraries/LibChess/Chess.cpp
new file mode 100644
index 0000000000..fae0c1e513
--- /dev/null
+++ b/Userland/Libraries/LibChess/Chess.cpp
@@ -0,0 +1,970 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/LogStream.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/Vector.h>
+#include <LibChess/Chess.h>
+#include <stdlib.h>
+
+namespace Chess {
+
+String char_for_piece(Chess::Type type)
+{
+ switch (type) {
+ case Type::Knight:
+ return "N";
+ case Type::Bishop:
+ return "B";
+ case Type::Rook:
+ return "R";
+ case Type::Queen:
+ return "Q";
+ case Type::King:
+ return "K";
+ case Type::Pawn:
+ default:
+ return "";
+ }
+}
+
+Chess::Type piece_for_char_promotion(const StringView& str)
+{
+ String string = String(str).to_lowercase();
+ if (string == "")
+ return Type::None;
+ if (string == "n")
+ return Type::Knight;
+ if (string == "b")
+ return Type::Bishop;
+ if (string == "r")
+ return Type::Rook;
+ if (string == "q")
+ return Type::Queen;
+ if (string == "k")
+ return Type::King;
+
+ return Type::None;
+}
+
+Color opposing_color(Color color)
+{
+ return (color == Color::White) ? Color::Black : Color::White;
+}
+
+Square::Square(const StringView& name)
+{
+ ASSERT(name.length() == 2);
+ char filec = name[0];
+ char rankc = name[1];
+
+ if (filec >= 'a' && filec <= 'h') {
+ file = filec - 'a';
+ } else if (filec >= 'A' && filec <= 'H') {
+ file = filec - 'A';
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+
+ if (rankc >= '1' && rankc <= '8') {
+ rank = rankc - '1';
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+}
+
+String Square::to_algebraic() const
+{
+ StringBuilder builder;
+ builder.append(file + 'a');
+ builder.append(rank + '1');
+ return builder.build();
+}
+
+Move::Move(const StringView& long_algebraic)
+ : from(long_algebraic.substring_view(0, 2))
+ , to(long_algebraic.substring_view(2, 2))
+ , promote_to(piece_for_char_promotion((long_algebraic.length() >= 5) ? long_algebraic.substring_view(4, 1) : ""))
+{
+}
+
+String Move::to_long_algebraic() const
+{
+ StringBuilder builder;
+ builder.append(from.to_algebraic());
+ builder.append(to.to_algebraic());
+ builder.append(char_for_piece(promote_to).to_lowercase());
+ return builder.build();
+}
+
+Move Move::from_algebraic(const StringView& algebraic, const Color turn, const Board& board)
+{
+ String move_string = algebraic;
+ Move move({ 50, 50 }, { 50, 50 });
+
+ if (move_string.contains("-")) {
+ move.from = Square(turn == Color::White ? 0 : 7, 4);
+ move.to = Square(turn == Color::White ? 0 : 7, move_string == "O-O" ? 6 : 2);
+ move.promote_to = Type::None;
+ move.piece = { turn, Type::King };
+
+ return move;
+ }
+
+ if (algebraic.contains("#")) {
+ move.is_mate = true;
+ move_string = move_string.substring(0, move_string.length() - 1);
+ } else if (algebraic.contains("+")) {
+ move.is_check = true;
+ move_string = move_string.substring(0, move_string.length() - 1);
+ }
+
+ if (algebraic.contains("=")) {
+ move.promote_to = piece_for_char_promotion(move_string.split('=').at(1).substring(0, 1));
+ move_string = move_string.split('=').at(0);
+ }
+
+ move.to = Square(move_string.substring(move_string.length() - 2, 2));
+ move_string = move_string.substring(0, move_string.length() - 2);
+
+ if (move_string.contains("x")) {
+ move.is_capture = true;
+ move_string = move_string.substring(0, move_string.length() - 1);
+ }
+
+ if (move_string.is_empty() || move_string.characters()[0] >= 'a') {
+ move.piece = Piece(turn, Type::Pawn);
+ } else {
+ move.piece = Piece(turn, piece_for_char_promotion(move_string.substring(0, 1)));
+ move_string = move_string.substring(1, move_string.length() - 1);
+ }
+
+ Square::for_each([&](const Square& square) {
+ if (!move_string.is_empty()) {
+ if (board.get_piece(square).type == move.piece.type && board.is_legal(Move(square, move.to), turn)) {
+ if (move_string.length() >= 2) {
+ if (square == Square(move_string.substring(0, 2))) {
+ move.from = square;
+ return IterationDecision::Break;
+ }
+ } else if (move_string.characters()[0] <= 57) {
+ if (square.rank == (unsigned)(move_string.characters()[0] - '0')) {
+ move.from = square;
+ return IterationDecision::Break;
+ }
+ } else {
+ if (square.file == (unsigned)(move_string.characters()[0] - 'a')) {
+ move.from = square;
+ return IterationDecision::Break;
+ }
+ }
+ }
+ return IterationDecision::Continue;
+ } else {
+ if (board.get_piece(square).type == move.piece.type && board.is_legal(Move(square, move.to), turn)) {
+ move.from = square;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ }
+ });
+
+ return move;
+}
+
+String Move::to_algebraic() const
+{
+ if (piece.type == Type::King && from.file == 4) {
+ if (to.file == 2)
+ return "O-O-O";
+ if (to.file == 6)
+ return "O-O";
+ }
+
+ StringBuilder builder;
+
+ builder.append(char_for_piece(piece.type));
+
+ if (is_ambiguous) {
+ if (from.file != ambiguous.file)
+ builder.append(from.to_algebraic().substring(0, 1));
+ else if (from.rank != ambiguous.rank)
+ builder.append(from.to_algebraic().substring(1, 1));
+ else
+ builder.append(from.to_algebraic());
+ }
+
+ if (is_capture) {
+ if (piece.type == Type::Pawn)
+ builder.append(from.to_algebraic().substring(0, 1));
+ builder.append("x");
+ }
+
+ builder.append(to.to_algebraic());
+
+ if (promote_to != Type::None) {
+ builder.append("=");
+ builder.append(char_for_piece(promote_to));
+ }
+
+ if (is_mate)
+ builder.append("#");
+ else if (is_check)
+ builder.append("+");
+
+ return builder.build();
+}
+
+Board::Board()
+{
+ // Fill empty spaces.
+ for (unsigned rank = 2; rank < 6; ++rank) {
+ for (unsigned file = 0; file < 8; ++file) {
+ set_piece({ rank, file }, EmptyPiece);
+ }
+ }
+
+ // Fill white pawns.
+ for (unsigned file = 0; file < 8; ++file) {
+ set_piece({ 1, file }, { Color::White, Type::Pawn });
+ }
+
+ // Fill black pawns.
+ for (unsigned file = 0; file < 8; ++file) {
+ set_piece({ 6, file }, { Color::Black, Type::Pawn });
+ }
+
+ // Fill while pieces.
+ set_piece(Square("a1"), { Color::White, Type::Rook });
+ set_piece(Square("b1"), { Color::White, Type::Knight });
+ set_piece(Square("c1"), { Color::White, Type::Bishop });
+ set_piece(Square("d1"), { Color::White, Type::Queen });
+ set_piece(Square("e1"), { Color::White, Type::King });
+ set_piece(Square("f1"), { Color::White, Type::Bishop });
+ set_piece(Square("g1"), { Color::White, Type::Knight });
+ set_piece(Square("h1"), { Color::White, Type::Rook });
+
+ // Fill black pieces.
+ set_piece(Square("a8"), { Color::Black, Type::Rook });
+ set_piece(Square("b8"), { Color::Black, Type::Knight });
+ set_piece(Square("c8"), { Color::Black, Type::Bishop });
+ set_piece(Square("d8"), { Color::Black, Type::Queen });
+ set_piece(Square("e8"), { Color::Black, Type::King });
+ set_piece(Square("f8"), { Color::Black, Type::Bishop });
+ set_piece(Square("g8"), { Color::Black, Type::Knight });
+ set_piece(Square("h8"), { Color::Black, Type::Rook });
+}
+
+String Board::to_fen() const
+{
+ StringBuilder builder;
+
+ // 1. Piece placement
+ int empty = 0;
+ for (unsigned rank = 0; rank < 8; rank++) {
+ for (unsigned file = 0; file < 8; file++) {
+ const Piece p(get_piece({ 7 - rank, file }));
+ if (p.type == Type::None) {
+ empty++;
+ continue;
+ }
+ if (empty > 0) {
+ builder.append(String::number(empty));
+ empty = 0;
+ }
+ String piece = char_for_piece(p.type);
+ if (piece == "")
+ piece = "P";
+
+ builder.append(p.color == Color::Black ? piece.to_lowercase() : piece);
+ }
+ if (empty > 0) {
+ builder.append(String::number(empty));
+ empty = 0;
+ }
+ if (rank < 7)
+ builder.append("/");
+ }
+
+ // 2. Active color
+ ASSERT(m_turn != Color::None);
+ builder.append(m_turn == Color::White ? " w " : " b ");
+
+ // 3. Castling availability
+ builder.append(m_white_can_castle_kingside ? "K" : "");
+ builder.append(m_white_can_castle_queenside ? "Q" : "");
+ builder.append(m_black_can_castle_kingside ? "k" : "");
+ builder.append(m_black_can_castle_queenside ? "q" : "");
+ builder.append(" ");
+
+ // 4. En passant target square
+ if (!m_last_move.has_value())
+ builder.append("-");
+ else if (m_last_move.value().piece.type == Type::Pawn) {
+ if (m_last_move.value().from.rank == 1 && m_last_move.value().to.rank == 3)
+ builder.append(Square(m_last_move.value().to.rank - 1, m_last_move.value().to.file).to_algebraic());
+ else if (m_last_move.value().from.rank == 6 && m_last_move.value().to.rank == 4)
+ builder.append(Square(m_last_move.value().to.rank + 1, m_last_move.value().to.file).to_algebraic());
+ else
+ builder.append("-");
+ } else {
+ builder.append("-");
+ }
+ builder.append(" ");
+
+ // 5. Halfmove clock
+ builder.append(String::number(min(m_moves_since_capture, m_moves_since_pawn_advance)));
+ builder.append(" ");
+
+ // 6. Fullmove number
+ builder.append(String::number(1 + m_moves.size() / 2));
+
+ return builder.to_string();
+}
+
+Piece Board::get_piece(const Square& square) const
+{
+ ASSERT(square.rank < 8);
+ ASSERT(square.file < 8);
+ return m_board[square.rank][square.file];
+}
+
+Piece Board::set_piece(const Square& square, const Piece& piece)
+{
+ ASSERT(square.rank < 8);
+ ASSERT(square.file < 8);
+ return m_board[square.rank][square.file] = piece;
+}
+
+bool Board::is_legal_promotion(const Move& move, Color color) const
+{
+ auto piece = get_piece(move.from);
+
+ if (move.promote_to == Type::Pawn || move.promote_to == Type::King) {
+ // attempted promotion to invalid piece
+ return false;
+ }
+
+ if (piece.type != Type::Pawn && move.promote_to != Type::None) {
+ // attempted promotion from invalid piece
+ return false;
+ }
+
+ unsigned promotion_rank = (color == Color::White) ? 7 : 0;
+
+ if (move.to.rank != promotion_rank && move.promote_to != Type::None) {
+ // attempted promotion from invalid rank
+ return false;
+ }
+
+ if (piece.type == Type::Pawn && move.to.rank == promotion_rank && move.promote_to == Type::None) {
+ // attempted move to promotion rank without promoting
+ return false;
+ }
+
+ return true;
+}
+
+bool Board::is_legal(const Move& move, Color color) const
+{
+ if (color == Color::None)
+ color = turn();
+
+ if (!is_legal_no_check(move, color))
+ return false;
+
+ if (!is_legal_promotion(move, color))
+ return false;
+
+ Board clone = *this;
+ clone.apply_illegal_move(move, color);
+ if (clone.in_check(color))
+ return false;
+
+ // Don't allow castling through check or out of check.
+ Vector<Square> check_squares;
+ if (color == Color::White && move.from == Square("e1") && get_piece(Square("e1")) == Piece(Color::White, Type::King)) {
+ if (move.to == Square("a1") || move.to == Square("c1")) {
+ check_squares = { Square("e1"), Square("d1"), Square("c1") };
+ } else if (move.to == Square("h1") || move.to == Square("g1")) {
+ check_squares = { Square("e1"), Square("f1"), Square("g1") };
+ }
+ } else if (color == Color::Black && move.from == Square("e8") && get_piece(Square("e8")) == Piece(Color::Black, Type::King)) {
+ if (move.to == Square("a8") || move.to == Square("c8")) {
+ check_squares = { Square("e8"), Square("d8"), Square("c8") };
+ } else if (move.to == Square("h8") || move.to == Square("g8")) {
+ check_squares = { Square("e8"), Square("f8"), Square("g8") };
+ }
+ }
+ for (auto& square : check_squares) {
+ Board clone = *this;
+ clone.set_piece(move.from, EmptyPiece);
+ clone.set_piece(square, { color, Type::King });
+ if (clone.in_check(color))
+ return false;
+ }
+
+ return true;
+}
+
+bool Board::is_legal_no_check(const Move& move, Color color) const
+{
+ auto piece = get_piece(move.from);
+
+ if (piece.color != color)
+ // attempted move of opponent's piece
+ return false;
+
+ if (move.to.rank > 7 || move.to.file > 7)
+ // attempted move outside of board
+ return false;
+
+ if (piece.type == Type::Pawn) {
+ int dir = (color == Color::White) ? +1 : -1;
+ unsigned start_rank = (color == Color::White) ? 1 : 6;
+
+ if (move.from.rank == start_rank && move.to.rank == move.from.rank + (2 * dir) && move.to.file == move.from.file
+ && get_piece(move.to).type == Type::None && get_piece({ move.from.rank + dir, move.from.file }).type == Type::None) {
+ // 2 square pawn move from initial position.
+ return true;
+ }
+
+ if (move.to.rank != move.from.rank + dir)
+ // attempted backwards or sideways move
+ return false;
+
+ if (move.to.file == move.from.file && get_piece(move.to).type == Type::None) {
+ // Regular pawn move.
+ return true;
+ }
+
+ if (move.to.file == move.from.file + 1 || move.to.file == move.from.file - 1) {
+ unsigned other_start_rank = (color == Color::White) ? 6 : 1;
+ unsigned en_passant_rank = (color == Color::White) ? 4 : 3;
+ Move en_passant_last_move = { { other_start_rank, move.to.file }, { en_passant_rank, move.to.file } };
+ if (get_piece(move.to).color == opposing_color(color)) {
+ // Pawn capture.
+ return true;
+ }
+ if (m_last_move.has_value() && move.from.rank == en_passant_rank && m_last_move.value() == en_passant_last_move
+ && get_piece(en_passant_last_move.to) == Piece(opposing_color(color), Type::Pawn)) {
+ // En passant.
+ return true;
+ }
+ }
+
+ return false;
+ } else if (piece.type == Type::Knight) {
+ int rank_delta = abs(move.to.rank - move.from.rank);
+ int file_delta = abs(move.to.file - move.from.file);
+ if (get_piece(move.to).color != color && max(rank_delta, file_delta) == 2 && min(rank_delta, file_delta) == 1) {
+ return true;
+ }
+ } else if (piece.type == Type::Bishop) {
+ int rank_delta = move.to.rank - move.from.rank;
+ int file_delta = move.to.file - move.from.file;
+ if (abs(rank_delta) == abs(file_delta)) {
+ int dr = rank_delta / abs(rank_delta);
+ int df = file_delta / abs(file_delta);
+ for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
+ if (get_piece(sq).type != Type::None && sq != move.from) {
+ return false;
+ }
+ }
+
+ if (get_piece(move.to).color != color) {
+ return true;
+ }
+ }
+ } else if (piece.type == Type::Rook) {
+ int rank_delta = move.to.rank - move.from.rank;
+ int file_delta = move.to.file - move.from.file;
+ if (rank_delta == 0 || file_delta == 0) {
+ int dr = (rank_delta) ? rank_delta / abs(rank_delta) : 0;
+ int df = (file_delta) ? file_delta / abs(file_delta) : 0;
+ for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
+ if (get_piece(sq).type != Type::None && sq != move.from) {
+ return false;
+ }
+ }
+
+ if (get_piece(move.to).color != color) {
+ return true;
+ }
+ }
+ } else if (piece.type == Type::Queen) {
+ int rank_delta = move.to.rank - move.from.rank;
+ int file_delta = move.to.file - move.from.file;
+ if (abs(rank_delta) == abs(file_delta) || rank_delta == 0 || file_delta == 0) {
+ int dr = (rank_delta) ? rank_delta / abs(rank_delta) : 0;
+ int df = (file_delta) ? file_delta / abs(file_delta) : 0;
+ for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
+ if (get_piece(sq).type != Type::None && sq != move.from) {
+ return false;
+ }
+ }
+
+ if (get_piece(move.to).color != color) {
+ return true;
+ }
+ }
+ } else if (piece.type == Type::King) {
+ int rank_delta = move.to.rank - move.from.rank;
+ int file_delta = move.to.file - move.from.file;
+ if (abs(rank_delta) <= 1 && abs(file_delta) <= 1) {
+ if (get_piece(move.to).color != color) {
+ return true;
+ }
+ }
+
+ if (color == Color::White) {
+ if ((move.to == Square("a1") || move.to == Square("c1")) && m_white_can_castle_queenside && get_piece(Square("b1")).type == Type::None && get_piece(Square("c1")).type == Type::None && get_piece(Square("d1")).type == Type::None) {
+
+ return true;
+ } else if ((move.to == Square("h1") || move.to == Square("g1")) && m_white_can_castle_kingside && get_piece(Square("f1")).type == Type::None && get_piece(Square("g1")).type == Type::None) {
+
+ return true;
+ }
+ } else {
+ if ((move.to == Square("a8") || move.to == Square("c8")) && m_black_can_castle_queenside && get_piece(Square("b8")).type == Type::None && get_piece(Square("c8")).type == Type::None && get_piece(Square("d8")).type == Type::None) {
+
+ return true;
+ } else if ((move.to == Square("h8") || move.to == Square("g8")) && m_black_can_castle_kingside && get_piece(Square("f8")).type == Type::None && get_piece(Square("g8")).type == Type::None) {
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool Board::in_check(Color color) const
+{
+ Square king_square = { 50, 50 };
+ Square::for_each([&](const Square& square) {
+ if (get_piece(square) == Piece(color, Type::King)) {
+ king_square = square;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+
+ bool check = false;
+ Square::for_each([&](const Square& square) {
+ if (is_legal_no_check({ square, king_square }, opposing_color(color))) {
+ check = true;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+
+ return check;
+}
+
+bool Board::apply_move(const Move& move, Color color)
+{
+ if (color == Color::None)
+ color = turn();
+
+ if (!is_legal(move, color))
+ return false;
+
+ const_cast<Move&>(move).piece = get_piece(move.from);
+
+ return apply_illegal_move(move, color);
+}
+
+bool Board::apply_illegal_move(const Move& move, Color color)
+{
+ Board clone = *this;
+ clone.m_previous_states = {};
+ clone.m_moves = {};
+ auto state_count = 0;
+ if (m_previous_states.contains(clone))
+ state_count = m_previous_states.get(clone).value();
+
+ m_previous_states.set(clone, state_count + 1);
+ m_moves.append(move);
+
+ m_turn = opposing_color(color);
+
+ m_last_move = move;
+ m_moves_since_capture++;
+ m_moves_since_pawn_advance++;
+
+ if (move.from == Square("a1") || move.to == Square("a1") || move.from == Square("e1"))
+ m_white_can_castle_queenside = false;
+ if (move.from == Square("h1") || move.to == Square("h1") || move.from == Square("e1"))
+ m_white_can_castle_kingside = false;
+ if (move.from == Square("a8") || move.to == Square("a8") || move.from == Square("e8"))
+ m_black_can_castle_queenside = false;
+ if (move.from == Square("h8") || move.to == Square("h8") || move.from == Square("e8"))
+ m_black_can_castle_kingside = false;
+
+ if (color == Color::White && move.from == Square("e1") && get_piece(Square("e1")) == Piece(Color::White, Type::King)) {
+ if (move.to == Square("a1") || move.to == Square("c1")) {
+ set_piece(Square("e1"), EmptyPiece);
+ set_piece(Square("a1"), EmptyPiece);
+ set_piece(Square("c1"), { Color::White, Type::King });
+ set_piece(Square("d1"), { Color::White, Type::Rook });
+ return true;
+ } else if (move.to == Square("h1") || move.to == Square("g1")) {
+ set_piece(Square("e1"), EmptyPiece);
+ set_piece(Square("h1"), EmptyPiece);
+ set_piece(Square("g1"), { Color::White, Type::King });
+ set_piece(Square("f1"), { Color::White, Type::Rook });
+ return true;
+ }
+ } else if (color == Color::Black && move.from == Square("e8") && get_piece(Square("e8")) == Piece(Color::Black, Type::King)) {
+ if (move.to == Square("a8") || move.to == Square("c8")) {
+ set_piece(Square("e8"), EmptyPiece);
+ set_piece(Square("a8"), EmptyPiece);
+ set_piece(Square("c8"), { Color::Black, Type::King });
+ set_piece(Square("d8"), { Color::Black, Type::Rook });
+ return true;
+ } else if (move.to == Square("h8") || move.to == Square("g8")) {
+ set_piece(Square("e8"), EmptyPiece);
+ set_piece(Square("h8"), EmptyPiece);
+ set_piece(Square("g8"), { Color::Black, Type::King });
+ set_piece(Square("f8"), { Color::Black, Type::Rook });
+ return true;
+ }
+ }
+
+ if (move.piece.type == Type::Pawn)
+ m_moves_since_pawn_advance = 0;
+
+ if (get_piece(move.to).color != Color::None) {
+ const_cast<Move&>(move).is_capture = true;
+ m_moves_since_capture = 0;
+ }
+
+ if (get_piece(move.from).type == Type::Pawn && ((color == Color::Black && move.to.rank == 0) || (color == Color::White && move.to.rank == 7))) {
+ // Pawn Promotion
+ set_piece(move.to, { color, move.promote_to });
+ set_piece(move.from, EmptyPiece);
+
+ if (in_check(m_turn))
+ const_cast<Move&>(move).is_check = true;
+
+ return true;
+ }
+
+ if (get_piece(move.from).type == Type::Pawn && move.from.file != move.to.file && get_piece(move.to).type == Type::None) {
+ // En passant.
+ if (color == Color::White) {
+ set_piece({ move.to.rank - 1, move.to.file }, EmptyPiece);
+ } else {
+ set_piece({ move.to.rank + 1, move.to.file }, EmptyPiece);
+ }
+ const_cast<Move&>(move).is_capture = true;
+ m_moves_since_capture = 0;
+ }
+
+ Square::for_each([&](Square sq) {
+ // Ambiguous Move
+ if (sq != move.from && get_piece(sq).type == move.piece.type && get_piece(sq).color == move.piece.color) {
+ if (is_legal(Move(sq, move.to), get_piece(sq).color)) {
+ m_moves.last().is_ambiguous = true;
+ m_moves.last().ambiguous = sq;
+
+ return IterationDecision::Break;
+ }
+ }
+ return IterationDecision::Continue;
+ });
+
+ set_piece(move.to, get_piece(move.from));
+ set_piece(move.from, EmptyPiece);
+
+ if (in_check(m_turn))
+ const_cast<Move&>(move).is_check = true;
+
+ return true;
+}
+
+Move Board::random_move(Color color) const
+{
+ if (color == Color::None)
+ color = turn();
+
+ Move move = { { 50, 50 }, { 50, 50 } };
+ int probability = 1;
+ generate_moves([&](Move m) {
+ if (rand() % probability == 0)
+ move = m;
+ ++probability;
+ return IterationDecision::Continue;
+ });
+
+ return move;
+}
+
+Board::Result Board::game_result() const
+{
+ if (m_resigned != Color::None)
+ return (m_resigned == Color::White) ? Result::WhiteResign : Result::BlackResign;
+
+ bool sufficient_material = false;
+ bool no_more_pieces_allowed = false;
+ Optional<Square> bishop;
+ Square::for_each([&](Square sq) {
+ if (get_piece(sq).type == Type::Queen || get_piece(sq).type == Type::Rook || get_piece(sq).type == Type::Pawn) {
+ sufficient_material = true;
+ return IterationDecision::Break;
+ }
+
+ if (get_piece(sq).type != Type::None && get_piece(sq).type != Type::King && no_more_pieces_allowed) {
+ sufficient_material = true;
+ return IterationDecision::Break;
+ }
+
+ if (get_piece(sq).type == Type::Knight)
+ no_more_pieces_allowed = true;
+
+ if (get_piece(sq).type == Type::Bishop) {
+ if (bishop.has_value()) {
+ if (get_piece(sq).color == get_piece(bishop.value()).color) {
+ sufficient_material = true;
+ return IterationDecision::Break;
+ } else if (sq.is_light() != bishop.value().is_light()) {
+ sufficient_material = true;
+ return IterationDecision::Break;
+ }
+ no_more_pieces_allowed = true;
+ } else {
+ bishop = sq;
+ }
+ }
+
+ return IterationDecision::Continue;
+ });
+
+ if (!sufficient_material)
+ return Result::InsufficientMaterial;
+
+ bool are_legal_moves = false;
+ generate_moves([&]([[maybe_unused]] Move m) {
+ are_legal_moves = true;
+ return IterationDecision::Break;
+ });
+
+ if (are_legal_moves) {
+ if (m_moves_since_capture >= 75 * 2)
+ return Result::SeventyFiveMoveRule;
+ if (m_moves_since_capture == 50 * 2)
+ return Result::FiftyMoveRule;
+
+ auto repeats = m_previous_states.get(*this);
+ if (repeats.has_value()) {
+ if (repeats.value() == 3)
+ return Result::ThreeFoldRepetition;
+ if (repeats.value() >= 5)
+ return Result::FiveFoldRepetition;
+ }
+
+ return Result::NotFinished;
+ }
+
+ if (in_check(turn())) {
+ const_cast<Vector<Move>&>(m_moves).last().is_mate = true;
+ return Result::CheckMate;
+ }
+
+ return Result::StaleMate;
+}
+
+Color Board::game_winner() const
+{
+ if (game_result() == Result::CheckMate)
+ return opposing_color(turn());
+
+ return Color::None;
+}
+
+int Board::game_score() const
+{
+ switch (game_winner()) {
+ case Color::White:
+ return +1;
+ case Color::Black:
+ return -1;
+ case Color::None:
+ return 0;
+ }
+ return 0;
+}
+
+bool Board::game_finished() const
+{
+ return game_result() != Result::NotFinished;
+}
+
+int Board::material_imbalance() const
+{
+ int imbalance = 0;
+ Square::for_each([&](Square square) {
+ int value = 0;
+ switch (get_piece(square).type) {
+ case Type::Pawn:
+ value = 1;
+ break;
+ case Type::Knight:
+ case Type::Bishop:
+ value = 3;
+ break;
+ case Type::Rook:
+ value = 5;
+ break;
+ case Type::Queen:
+ value = 9;
+ break;
+ default:
+ break;
+ }
+
+ if (get_piece(square).color == Color::White) {
+ imbalance += value;
+ } else {
+ imbalance -= value;
+ }
+ return IterationDecision::Continue;
+ });
+ return imbalance;
+}
+
+bool Board::is_promotion_move(const Move& move, Color color) const
+{
+ if (color == Color::None)
+ color = turn();
+
+ unsigned promotion_rank = (color == Color::White) ? 7 : 0;
+ if (move.to.rank != promotion_rank)
+ return false;
+
+ if (get_piece(move.from).type != Type::Pawn)
+ return false;
+
+ Move queen_move = move;
+ queen_move.promote_to = Type::Queen;
+ if (!is_legal(queen_move, color))
+ return false;
+
+ return true;
+}
+
+bool Board::operator==(const Board& other) const
+{
+ bool equal_squares = true;
+ Square::for_each([&](Square sq) {
+ if (get_piece(sq) != other.get_piece(sq)) {
+ equal_squares = false;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+ if (!equal_squares)
+ return false;
+
+ if (m_white_can_castle_queenside != other.m_white_can_castle_queenside)
+ return false;
+ if (m_white_can_castle_kingside != other.m_white_can_castle_kingside)
+ return false;
+ if (m_black_can_castle_queenside != other.m_black_can_castle_queenside)
+ return false;
+ if (m_black_can_castle_kingside != other.m_black_can_castle_kingside)
+ return false;
+
+ return turn() == other.turn();
+}
+
+void Board::set_resigned(Chess::Color c)
+{
+ m_resigned = c;
+}
+
+String Board::result_to_string(Result result, Color turn)
+{
+ switch (result) {
+ case Result::CheckMate:
+ ASSERT(turn != Chess::Color::None);
+ return turn == Chess::Color::White ? "Black wins by Checkmate" : "White wins by Checkmate";
+ case Result::WhiteResign:
+ return "Black wins by Resignation";
+ case Result::BlackResign:
+ return "White wins by Resignation";
+ case Result::StaleMate:
+ return "Draw by Stalemate";
+ case Chess::Board::Result::FiftyMoveRule:
+ return "Draw by 50 move rule";
+ case Chess::Board::Result::SeventyFiveMoveRule:
+ return "Draw by 75 move rule";
+ case Chess::Board::Result::ThreeFoldRepetition:
+ return "Draw by threefold repetition";
+ case Chess::Board::Result::FiveFoldRepetition:
+ return "Draw by fivefold repetition";
+ case Chess::Board::Result::InsufficientMaterial:
+ return "Draw by insufficient material";
+ case Chess::Board::Result::NotFinished:
+ return "Game not finished";
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+String Board::result_to_points(Result result, Color turn)
+{
+ switch (result) {
+ case Result::CheckMate:
+ ASSERT(turn != Chess::Color::None);
+ return turn == Chess::Color::White ? "0-1" : "1-0";
+ case Result::WhiteResign:
+ return "0-1";
+ case Result::BlackResign:
+ return "1-0";
+ case Result::StaleMate:
+ return "1/2-1/2";
+ case Chess::Board::Result::FiftyMoveRule:
+ return "1/2-1/2";
+ case Chess::Board::Result::SeventyFiveMoveRule:
+ return "1/2-1/2";
+ case Chess::Board::Result::ThreeFoldRepetition:
+ return "1/2-1/2";
+ case Chess::Board::Result::FiveFoldRepetition:
+ return "1/2-1/2";
+ case Chess::Board::Result::InsufficientMaterial:
+ return "1/2-1/2";
+ case Chess::Board::Result::NotFinished:
+ return "*";
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibChess/Chess.h b/Userland/Libraries/LibChess/Chess.h
new file mode 100644
index 0000000000..798a6cc716
--- /dev/null
+++ b/Userland/Libraries/LibChess/Chess.h
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/IterationDecision.h>
+#include <AK/Optional.h>
+#include <AK/StringView.h>
+#include <AK/Traits.h>
+#include <AK/Vector.h>
+
+namespace Chess {
+
+enum class Type {
+ Pawn,
+ Knight,
+ Bishop,
+ Rook,
+ Queen,
+ King,
+ None,
+};
+
+String char_for_piece(Type type);
+Chess::Type piece_for_char_promotion(const StringView& str);
+
+enum class Color {
+ White,
+ Black,
+ None,
+};
+
+Color opposing_color(Color color);
+
+struct Piece {
+ constexpr Piece()
+ : color(Color::None)
+ , type(Type::None)
+ {
+ }
+ constexpr Piece(Color c, Type t)
+ : color(c)
+ , type(t)
+ {
+ }
+ Color color : 4;
+ Type type : 4;
+ bool operator==(const Piece& other) const { return color == other.color && type == other.type; }
+};
+
+constexpr Piece EmptyPiece = { Color::None, Type::None };
+
+struct Square {
+ unsigned rank; // zero indexed;
+ unsigned file;
+ Square(const StringView& name);
+ Square(const unsigned& rank, const unsigned& file)
+ : rank(rank)
+ , file(file)
+ {
+ }
+ bool operator==(const Square& other) const { return rank == other.rank && file == other.file; }
+
+ template<typename Callback>
+ static void for_each(Callback callback)
+ {
+ for (int rank = 0; rank < 8; ++rank) {
+ for (int file = 0; file < 8; ++file) {
+ if (callback(Square(rank, file)) == IterationDecision::Break)
+ return;
+ }
+ }
+ }
+
+ bool in_bounds() const { return rank < 8 && file < 8; }
+ bool is_light() const { return (rank % 2) != (file % 2); }
+ String to_algebraic() const;
+};
+
+class Board;
+
+struct Move {
+ Square from;
+ Square to;
+ Type promote_to;
+ Piece piece;
+ bool is_check = false;
+ bool is_mate = false;
+ bool is_capture = false;
+ bool is_ambiguous = false;
+ Square ambiguous { 50, 50 };
+ Move(const StringView& long_algebraic);
+ Move(const Square& from, const Square& to, const Type& promote_to = Type::None)
+ : from(from)
+ , to(to)
+ , promote_to(promote_to)
+ {
+ }
+ bool operator==(const Move& other) const { return from == other.from && to == other.to && promote_to == other.promote_to; }
+
+ static Move from_algebraic(const StringView& algebraic, const Color turn, const Board& board);
+ String to_long_algebraic() const;
+ String to_algebraic() const;
+};
+
+class Board {
+public:
+ Board();
+
+ Piece get_piece(const Square&) const;
+ Piece set_piece(const Square&, const Piece&);
+
+ bool is_legal(const Move&, Color color = Color::None) const;
+ bool in_check(Color color) const;
+
+ bool is_promotion_move(const Move&, Color color = Color::None) const;
+
+ bool apply_move(const Move&, Color color = Color::None);
+ const Optional<Move>& last_move() const { return m_last_move; }
+
+ String to_fen() const;
+
+ enum class Result {
+ CheckMate,
+ StaleMate,
+ WhiteResign,
+ BlackResign,
+ FiftyMoveRule,
+ SeventyFiveMoveRule,
+ ThreeFoldRepetition,
+ FiveFoldRepetition,
+ InsufficientMaterial,
+ NotFinished,
+ };
+
+ static String result_to_string(Result, Color turn);
+ static String result_to_points(Result, Color turn);
+
+ template<typename Callback>
+ void generate_moves(Callback callback, Color color = Color::None) const;
+ Move random_move(Color color = Color::None) const;
+ Result game_result() const;
+ Color game_winner() const;
+ int game_score() const;
+ bool game_finished() const;
+ void set_resigned(Color);
+ int material_imbalance() const;
+
+ Color turn() const { return m_turn; }
+ const Vector<Move>& moves() const { return m_moves; }
+
+ bool operator==(const Board& other) const;
+
+private:
+ bool is_legal_no_check(const Move&, Color color) const;
+ bool is_legal_promotion(const Move&, Color color) const;
+ bool apply_illegal_move(const Move&, Color color);
+
+ Piece m_board[8][8];
+ Color m_turn { Color::White };
+ Color m_resigned { Color::None };
+ Optional<Move> m_last_move;
+ int m_moves_since_capture { 0 };
+ int m_moves_since_pawn_advance { 0 };
+
+ bool m_white_can_castle_kingside { true };
+ bool m_white_can_castle_queenside { true };
+ bool m_black_can_castle_kingside { true };
+ bool m_black_can_castle_queenside { true };
+
+ HashMap<Board, int> m_previous_states;
+ Vector<Move> m_moves;
+ friend struct AK::Traits<Board>;
+};
+
+template<typename Callback>
+void Board::generate_moves(Callback callback, Color color) const
+{
+ if (color == Color::None)
+ color = turn();
+
+ auto try_move = [&](Move m) {
+ if (is_legal(m, color)) {
+ if (callback(m) == IterationDecision::Break)
+ return false;
+ }
+ return true;
+ };
+
+ Square::for_each([&](Square sq) {
+ auto piece = get_piece(sq);
+ if (piece.color != color)
+ return IterationDecision::Continue;
+
+ bool keep_going = true;
+ if (piece.type == Type::Pawn) {
+ for (auto& piece : Vector({ Type::None, Type::Knight, Type::Bishop, Type::Rook, Type::Queen })) {
+ keep_going = try_move({ sq, { sq.rank + 1, sq.file }, piece })
+ && try_move({ sq, { sq.rank + 2, sq.file }, piece })
+ && try_move({ sq, { sq.rank - 1, sq.file }, piece })
+ && try_move({ sq, { sq.rank - 2, sq.file }, piece })
+ && try_move({ sq, { sq.rank + 1, sq.file + 1 }, piece })
+ && try_move({ sq, { sq.rank + 1, sq.file - 1 }, piece })
+ && try_move({ sq, { sq.rank - 1, sq.file + 1 }, piece })
+ && try_move({ sq, { sq.rank - 1, sq.file - 1 }, piece });
+ }
+ } else if (piece.type == Type::Knight) {
+ keep_going = try_move({ sq, { sq.rank + 2, sq.file + 1 } })
+ && try_move({ sq, { sq.rank + 2, sq.file - 1 } })
+ && try_move({ sq, { sq.rank + 1, sq.file + 2 } })
+ && try_move({ sq, { sq.rank + 1, sq.file - 2 } })
+ && try_move({ sq, { sq.rank - 2, sq.file + 1 } })
+ && try_move({ sq, { sq.rank - 2, sq.file - 1 } })
+ && try_move({ sq, { sq.rank - 1, sq.file + 2 } })
+ && try_move({ sq, { sq.rank - 1, sq.file - 2 } });
+ } else if (piece.type == Type::Bishop) {
+ for (int dr = -1; dr <= 1; dr += 2) {
+ for (int df = -1; df <= 1; df += 2) {
+ for (Square to = sq; to.in_bounds(); to = { to.rank + dr, to.file + df }) {
+ if (!try_move({ sq, to }))
+ return IterationDecision::Break;
+ }
+ }
+ }
+ } else if (piece.type == Type::Rook) {
+ for (int dr = -1; dr <= 1; dr++) {
+ for (int df = -1; df <= 1; df++) {
+ if ((dr == 0) != (df == 0)) {
+ for (Square to = sq; to.in_bounds(); to = { to.rank + dr, to.file + df }) {
+ if (!try_move({ sq, to }))
+ return IterationDecision::Break;
+ }
+ }
+ }
+ }
+ } else if (piece.type == Type::Queen) {
+ for (int dr = -1; dr <= 1; dr++) {
+ for (int df = -1; df <= 1; df++) {
+ if (dr != 0 || df != 0) {
+ for (Square to = sq; to.in_bounds(); to = { to.rank + dr, to.file + df }) {
+ if (!try_move({ sq, to }))
+ return IterationDecision::Break;
+ }
+ }
+ }
+ }
+ } else if (piece.type == Type::King) {
+ for (int dr = -1; dr <= 1; dr++) {
+ for (int df = -1; df <= 1; df++) {
+ if (!try_move({ sq, { sq.rank + dr, sq.file + df } }))
+ return IterationDecision::Break;
+ }
+ }
+
+ // Castling moves.
+ if (sq == Square("e1")) {
+ keep_going = try_move({ sq, Square("c1") }) && try_move({ sq, Square("g1") });
+ } else if (sq == Square("e8")) {
+ keep_going = try_move({ sq, Square("c8") }) && try_move({ sq, Square("g8") });
+ }
+ }
+
+ if (keep_going) {
+ return IterationDecision::Continue;
+ } else {
+ return IterationDecision::Break;
+ }
+ });
+}
+
+}
+
+template<>
+struct AK::Traits<Chess::Piece> : public GenericTraits<Chess::Piece> {
+ static unsigned hash(Chess::Piece piece)
+ {
+ return pair_int_hash(static_cast<u32>(piece.color), static_cast<u32>(piece.type));
+ }
+};
+
+template<>
+struct AK::Traits<Chess::Board> : public GenericTraits<Chess::Board> {
+ static unsigned hash(Chess::Board chess)
+ {
+ unsigned hash = 0;
+ hash = pair_int_hash(hash, static_cast<u32>(chess.m_white_can_castle_queenside));
+ hash = pair_int_hash(hash, static_cast<u32>(chess.m_white_can_castle_kingside));
+ hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_queenside));
+ hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_kingside));
+
+ hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_kingside));
+
+ Chess::Square::for_each([&](Chess::Square sq) {
+ hash = pair_int_hash(hash, Traits<Chess::Piece>::hash(chess.get_piece(sq)));
+ return IterationDecision::Continue;
+ });
+
+ return hash;
+ }
+};
diff --git a/Userland/Libraries/LibChess/UCICommand.cpp b/Userland/Libraries/LibChess/UCICommand.cpp
new file mode 100644
index 0000000000..3676c52efe
--- /dev/null
+++ b/Userland/Libraries/LibChess/UCICommand.cpp
@@ -0,0 +1,332 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "UCICommand.h"
+#include <AK/StringBuilder.h>
+
+namespace Chess::UCI {
+
+UCICommand UCICommand::from_string(const StringView& command)
+{
+ auto tokens = command.split_view(' ');
+ ASSERT(tokens[0] == "uci");
+ ASSERT(tokens.size() == 1);
+ return UCICommand();
+}
+
+String UCICommand::to_string() const
+{
+ return "uci\n";
+}
+
+DebugCommand DebugCommand::from_string(const StringView& command)
+{
+ auto tokens = command.split_view(' ');
+ ASSERT(tokens[0] == "debug");
+ ASSERT(tokens.size() == 2);
+ if (tokens[1] == "on")
+ return DebugCommand(Flag::On);
+ if (tokens[1] == "off")
+ return DebugCommand(Flag::On);
+
+ ASSERT_NOT_REACHED();
+}
+
+String DebugCommand::to_string() const
+{
+ if (flag() == Flag::On) {
+ return "debug on\n";
+ } else {
+ return "debug off\n";
+ }
+}
+
+IsReadyCommand IsReadyCommand::from_string(const StringView& command)
+{
+ auto tokens = command.split_view(' ');
+ ASSERT(tokens[0] == "isready");
+ ASSERT(tokens.size() == 1);
+ return IsReadyCommand();
+}
+
+String IsReadyCommand::to_string() const
+{
+ return "isready\n";
+}
+
+SetOptionCommand SetOptionCommand::from_string(const StringView& command)
+{
+ auto tokens = command.split_view(' ');
+ ASSERT(tokens[0] == "setoption");
+ ASSERT(tokens[1] == "name");
+ if (tokens.size() == 3) {
+ return SetOptionCommand(tokens[1]);
+ } else if (tokens.size() == 4) {
+ ASSERT(tokens[2] == "value");
+ return SetOptionCommand(tokens[1], tokens[3]);
+ }
+ ASSERT_NOT_REACHED();
+}
+
+String SetOptionCommand::to_string() const
+{
+ StringBuilder builder;
+ builder.append("setoption name ");
+ builder.append(name());
+ if (value().has_value()) {
+ builder.append(" value ");
+ builder.append(value().value());
+ }
+ builder.append('\n');
+ return builder.build();
+}
+
+PositionCommand PositionCommand::from_string(const StringView& command)
+{
+ auto tokens = command.split_view(' ');
+ ASSERT(tokens.size() >= 3);
+ ASSERT(tokens[0] == "position");
+ ASSERT(tokens[2] == "moves");
+
+ Optional<String> fen;
+ if (tokens[1] != "startpos")
+ fen = tokens[1];
+
+ Vector<Move> moves;
+ for (size_t i = 3; i < tokens.size(); ++i) {
+ moves.append(Move(tokens[i]));
+ }
+ return PositionCommand(fen, moves);
+}
+
+String PositionCommand::to_string() const
+{
+ StringBuilder builder;
+ builder.append("position ");
+ if (fen().has_value()) {
+ builder.append(fen().value());
+ } else {
+ builder.append("startpos ");
+ }
+ builder.append("moves");
+ for (auto& move : moves()) {
+ builder.append(' ');
+ builder.append(move.to_long_algebraic());
+ }
+ builder.append('\n');
+ return builder.build();
+}
+
+GoCommand GoCommand::from_string(const StringView& command)
+{
+ auto tokens = command.split_view(' ');
+ ASSERT(tokens[0] == "go");
+
+ GoCommand go_command;
+ for (size_t i = 1; i < tokens.size(); ++i) {
+ if (tokens[i] == "searchmoves") {
+ ASSERT_NOT_REACHED();
+ } else if (tokens[i] == "ponder") {
+ go_command.ponder = true;
+ } else if (tokens[i] == "wtime") {
+ ASSERT(i++ < tokens.size());
+ go_command.wtime = tokens[i].to_int().value();
+ } else if (tokens[i] == "btime") {
+ ASSERT(i++ < tokens.size());
+ go_command.btime = tokens[i].to_int().value();
+ } else if (tokens[i] == "winc") {
+ ASSERT(i++ < tokens.size());
+ go_command.winc = tokens[i].to_int().value();
+ } else if (tokens[i] == "binc") {
+ ASSERT(i++ < tokens.size());
+ go_command.binc = tokens[i].to_int().value();
+ } else if (tokens[i] == "movestogo") {
+ ASSERT(i++ < tokens.size());
+ go_command.movestogo = tokens[i].to_int().value();
+ } else if (tokens[i] == "depth") {
+ ASSERT(i++ < tokens.size());
+ go_command.depth = tokens[i].to_int().value();
+ } else if (tokens[i] == "nodes") {
+ ASSERT(i++ < tokens.size());
+ go_command.nodes = tokens[i].to_int().value();
+ } else if (tokens[i] == "mate") {
+ ASSERT(i++ < tokens.size());
+ go_command.mate = tokens[i].to_int().value();
+ } else if (tokens[i] == "movetime") {
+ ASSERT(i++ < tokens.size());
+ go_command.movetime = tokens[i].to_int().value();
+ } else if (tokens[i] == "infinite") {
+ go_command.infinite = true;
+ }
+ }
+
+ return go_command;
+}
+
+String GoCommand::to_string() const
+{
+ StringBuilder builder;
+ builder.append("go");
+
+ if (searchmoves.has_value()) {
+ builder.append(" searchmoves");
+ for (auto& move : searchmoves.value()) {
+ builder.append(' ');
+ builder.append(move.to_long_algebraic());
+ }
+ }
+
+ if (ponder)
+ builder.append(" ponder");
+ if (wtime.has_value())
+ builder.appendff(" wtime {}", wtime.value());
+ if (btime.has_value())
+ builder.appendff(" btime {}", btime.value());
+ if (winc.has_value())
+ builder.appendff(" winc {}", winc.value());
+ if (binc.has_value())
+ builder.appendff(" binc {}", binc.value());
+ if (movestogo.has_value())
+ builder.appendff(" movestogo {}", movestogo.value());
+ if (depth.has_value())
+ builder.appendff(" depth {}", depth.value());
+ if (nodes.has_value())
+ builder.appendff(" nodes {}", nodes.value());
+ if (mate.has_value())
+ builder.appendff(" mate {}", mate.value());
+ if (movetime.has_value())
+ builder.appendff(" movetime {}", movetime.value());
+ if (infinite)
+ builder.append(" infinite");
+
+ builder.append('\n');
+ return builder.build();
+}
+
+StopCommand StopCommand::from_string(const StringView& command)
+{
+ auto tokens = command.split_view(' ');
+ ASSERT(tokens[0] == "stop");
+ ASSERT(tokens.size() == 1);
+ return StopCommand();
+}
+
+String StopCommand::to_string() const
+{
+ return "stop\n";
+}
+
+IdCommand IdCommand::from_string(const StringView& command)
+{
+ auto tokens = command.split_view(' ');
+ ASSERT(tokens[0] == "id");
+ StringBuilder value;
+ for (size_t i = 2; i < tokens.size(); ++i) {
+ if (i != 2)
+ value.append(' ');
+
+ value.append(tokens[i]);
+ }
+
+ if (tokens[1] == "name") {
+ return IdCommand(Type::Name, value.build());
+ } else if (tokens[1] == "author") {
+ return IdCommand(Type::Author, value.build());
+ }
+ ASSERT_NOT_REACHED();
+}
+
+String IdCommand::to_string() const
+{
+ StringBuilder builder;
+ builder.append("id ");
+ if (field_type() == Type::Name) {
+ builder.append("name ");
+ } else {
+ builder.append("author ");
+ }
+ builder.append(value());
+ builder.append('\n');
+ return builder.build();
+}
+
+UCIOkCommand UCIOkCommand::from_string(const StringView& command)
+{
+ auto tokens = command.split_view(' ');
+ ASSERT(tokens[0] == "uciok");
+ ASSERT(tokens.size() == 1);
+ return UCIOkCommand();
+}
+
+String UCIOkCommand::to_string() const
+{
+ return "uciok\n";
+}
+
+ReadyOkCommand ReadyOkCommand::from_string(const StringView& command)
+{
+ auto tokens = command.split_view(' ');
+ ASSERT(tokens[0] == "readyok");
+ ASSERT(tokens.size() == 1);
+ return ReadyOkCommand();
+}
+
+String ReadyOkCommand::to_string() const
+{
+ return "readyok\n";
+}
+
+BestMoveCommand BestMoveCommand::from_string(const StringView& command)
+{
+ auto tokens = command.split_view(' ');
+ ASSERT(tokens[0] == "bestmove");
+ ASSERT(tokens.size() == 2);
+ return BestMoveCommand(Move(tokens[1]));
+}
+
+String BestMoveCommand::to_string() const
+{
+ StringBuilder builder;
+ builder.append("bestmove ");
+ builder.append(move().to_long_algebraic());
+ builder.append('\n');
+ return builder.build();
+}
+
+InfoCommand InfoCommand::from_string([[maybe_unused]] const StringView& command)
+{
+ // FIXME: Implement this.
+ ASSERT_NOT_REACHED();
+}
+
+String InfoCommand::to_string() const
+{
+ // FIXME: Implement this.
+ ASSERT_NOT_REACHED();
+ return "info";
+}
+
+}
diff --git a/Userland/Libraries/LibChess/UCICommand.h b/Userland/Libraries/LibChess/UCICommand.h
new file mode 100644
index 0000000000..cd79751d98
--- /dev/null
+++ b/Userland/Libraries/LibChess/UCICommand.h
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Optional.h>
+#include <AK/String.h>
+#include <LibChess/Chess.h>
+#include <LibCore/Event.h>
+
+namespace Chess::UCI {
+
+class Command : public Core::Event {
+public:
+ enum Type {
+ // GUI to engine commands.
+ UCI = 12000,
+ Debug,
+ IsReady,
+ SetOption,
+ Register,
+ UCINewGame,
+ Position,
+ Go,
+ Stop,
+ PonderHit,
+ Quit,
+ // Engine to GUI commands.
+ Id,
+ UCIOk,
+ ReadyOk,
+ BestMove,
+ CopyProtection,
+ Registration,
+ Info,
+ Option,
+ };
+
+ explicit Command(Type type)
+ : Core::Event(type)
+ {
+ }
+
+ virtual String to_string() const = 0;
+
+ virtual ~Command() { }
+};
+
+class UCICommand : public Command {
+public:
+ explicit UCICommand()
+ : Command(Command::Type::UCI)
+ {
+ }
+
+ static UCICommand from_string(const StringView& command);
+
+ virtual String to_string() const;
+};
+
+class DebugCommand : public Command {
+public:
+ enum class Flag {
+ On,
+ Off
+ };
+
+ explicit DebugCommand(Flag flag)
+ : Command(Command::Type::Debug)
+ , m_flag(flag)
+ {
+ }
+
+ static DebugCommand from_string(const StringView& command);
+
+ virtual String to_string() const;
+
+ Flag flag() const { return m_flag; }
+
+private:
+ Flag m_flag;
+};
+
+class IsReadyCommand : public Command {
+public:
+ explicit IsReadyCommand()
+ : Command(Command::Type::IsReady)
+ {
+ }
+
+ static IsReadyCommand from_string(const StringView& command);
+
+ virtual String to_string() const;
+};
+
+class SetOptionCommand : public Command {
+public:
+ explicit SetOptionCommand(const StringView& name, Optional<String> value = {})
+ : Command(Command::Type::SetOption)
+ , m_name(name)
+ , m_value(value)
+ {
+ }
+
+ static SetOptionCommand from_string(const StringView& command);
+
+ virtual String to_string() const;
+
+ const String& name() const { return m_name; }
+ const Optional<String>& value() const { return m_value; }
+
+private:
+ String m_name;
+ Optional<String> m_value;
+};
+
+class PositionCommand : public Command {
+public:
+ explicit PositionCommand(const Optional<String>& fen, const Vector<Chess::Move>& moves)
+ : Command(Command::Type::Position)
+ , m_fen(fen)
+ , m_moves(moves)
+ {
+ }
+
+ static PositionCommand from_string(const StringView& command);
+
+ virtual String to_string() const;
+
+ const Optional<String>& fen() const { return m_fen; }
+ const Vector<Chess::Move>& moves() const { return m_moves; }
+
+private:
+ Optional<String> m_fen;
+ Vector<Chess::Move> m_moves;
+};
+
+class GoCommand : public Command {
+public:
+ explicit GoCommand()
+ : Command(Command::Type::Go)
+ {
+ }
+
+ static GoCommand from_string(const StringView& command);
+
+ virtual String to_string() const;
+
+ Optional<Vector<Chess::Move>> searchmoves;
+ bool ponder { false };
+ Optional<int> wtime;
+ Optional<int> btime;
+ Optional<int> winc;
+ Optional<int> binc;
+ Optional<int> movestogo;
+ Optional<int> depth;
+ Optional<int> nodes;
+ Optional<int> mate;
+ Optional<int> movetime;
+ bool infinite { false };
+};
+
+class StopCommand : public Command {
+public:
+ explicit StopCommand()
+ : Command(Command::Type::Stop)
+ {
+ }
+
+ static StopCommand from_string(const StringView& command);
+
+ virtual String to_string() const;
+};
+
+class IdCommand : public Command {
+public:
+ enum class Type {
+ Name,
+ Author,
+ };
+
+ explicit IdCommand(Type field_type, const StringView& value)
+ : Command(Command::Type::Id)
+ , m_field_type(field_type)
+ , m_value(value)
+ {
+ }
+
+ static IdCommand from_string(const StringView& command);
+
+ virtual String to_string() const;
+
+ Type field_type() const { return m_field_type; }
+ const String& value() const { return m_value; }
+
+private:
+ Type m_field_type;
+ String m_value;
+};
+
+class UCIOkCommand : public Command {
+public:
+ explicit UCIOkCommand()
+ : Command(Command::Type::UCIOk)
+ {
+ }
+
+ static UCIOkCommand from_string(const StringView& command);
+
+ virtual String to_string() const;
+};
+
+class ReadyOkCommand : public Command {
+public:
+ explicit ReadyOkCommand()
+ : Command(Command::Type::ReadyOk)
+ {
+ }
+
+ static ReadyOkCommand from_string(const StringView& command);
+
+ virtual String to_string() const;
+};
+
+class BestMoveCommand : public Command {
+public:
+ explicit BestMoveCommand(const Chess::Move& move)
+ : Command(Command::Type::BestMove)
+ , m_move(move)
+ {
+ }
+
+ static BestMoveCommand from_string(const StringView& command);
+
+ virtual String to_string() const;
+
+ Chess::Move move() const { return m_move; }
+
+private:
+ Chess::Move m_move;
+};
+
+class InfoCommand : public Command {
+public:
+ explicit InfoCommand()
+ : Command(Command::Type::BestMove)
+ {
+ }
+
+ static InfoCommand from_string(const StringView& command);
+
+ virtual String to_string() const;
+
+ Optional<int> depth;
+ Optional<int> seldepth;
+ Optional<int> time;
+ Optional<int> nodes;
+ Optional<Vector<Chess::Move>> pv;
+ // FIXME: Add multipv.
+ Optional<int> score_cp;
+ Optional<int> score_mate;
+ // FIXME: Add score bounds.
+ Optional<Chess::Move> currmove;
+ Optional<int> currmove_number;
+ // FIXME: Add additional fields.
+};
+
+}
diff --git a/Userland/Libraries/LibChess/UCIEndpoint.cpp b/Userland/Libraries/LibChess/UCIEndpoint.cpp
new file mode 100644
index 0000000000..1b7fc07f54
--- /dev/null
+++ b/Userland/Libraries/LibChess/UCIEndpoint.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "UCIEndpoint.h"
+#include <AK/ByteBuffer.h>
+#include <AK/String.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/File.h>
+
+// #define UCI_DEBUG
+
+namespace Chess::UCI {
+
+Endpoint::Endpoint(NonnullRefPtr<Core::IODevice> in, NonnullRefPtr<Core::IODevice> out)
+ : m_in(in)
+ , m_out(out)
+ , m_in_notifier(Core::Notifier::construct(in->fd(), Core::Notifier::Read))
+{
+ set_in_notifier();
+}
+
+void Endpoint::send_command(const Command& command)
+{
+#ifdef UCI_DEBUG
+ dbgln("{} Sent UCI Command: {}", class_name(), String(command.to_string().characters(), Chomp));
+#endif
+ m_out->write(command.to_string());
+}
+
+void Endpoint::event(Core::Event& event)
+{
+ switch (event.type()) {
+ case Command::Type::UCI:
+ return handle_uci();
+ case Command::Type::Debug:
+ return handle_debug(static_cast<const DebugCommand&>(event));
+ case Command::Type::IsReady:
+ return handle_uci();
+ case Command::Type::SetOption:
+ return handle_setoption(static_cast<const SetOptionCommand&>(event));
+ case Command::Type::Position:
+ return handle_position(static_cast<const PositionCommand&>(event));
+ case Command::Type::Go:
+ return handle_go(static_cast<const GoCommand&>(event));
+ case Command::Type::Stop:
+ return handle_stop();
+ case Command::Type::Id:
+ return handle_id(static_cast<const IdCommand&>(event));
+ case Command::Type::UCIOk:
+ return handle_uciok();
+ case Command::Type::ReadyOk:
+ return handle_readyok();
+ case Command::Type::BestMove:
+ return handle_bestmove(static_cast<const BestMoveCommand&>(event));
+ case Command::Type::Info:
+ return handle_info(static_cast<const InfoCommand&>(event));
+ default:
+ break;
+ }
+}
+
+void Endpoint::set_in_notifier()
+{
+ m_in_notifier = Core::Notifier::construct(m_in->fd(), Core::Notifier::Read);
+ m_in_notifier->on_ready_to_read = [this] {
+ while (m_in->can_read_line())
+ Core::EventLoop::current().post_event(*this, read_command());
+ };
+}
+
+NonnullOwnPtr<Command> Endpoint::read_command()
+{
+ String line(ReadonlyBytes(m_in->read_line(4096).bytes()), Chomp);
+
+#ifdef UCI_DEBUG
+ dbgln("{} Received UCI Command: {}", class_name(), line);
+#endif
+
+ if (line == "uci") {
+ return make<UCICommand>(UCICommand::from_string(line));
+ } else if (line.starts_with("debug")) {
+ return make<DebugCommand>(DebugCommand::from_string(line));
+ } else if (line.starts_with("isready")) {
+ return make<IsReadyCommand>(IsReadyCommand::from_string(line));
+ } else if (line.starts_with("setoption")) {
+ return make<SetOptionCommand>(SetOptionCommand::from_string(line));
+ } else if (line.starts_with("position")) {
+ return make<PositionCommand>(PositionCommand::from_string(line));
+ } else if (line.starts_with("go")) {
+ return make<GoCommand>(GoCommand::from_string(line));
+ } else if (line.starts_with("stop")) {
+ return make<StopCommand>(StopCommand::from_string(line));
+ } else if (line.starts_with("id")) {
+ return make<IdCommand>(IdCommand::from_string(line));
+ } else if (line.starts_with("uciok")) {
+ return make<UCIOkCommand>(UCIOkCommand::from_string(line));
+ } else if (line.starts_with("readyok")) {
+ return make<ReadyOkCommand>(ReadyOkCommand::from_string(line));
+ } else if (line.starts_with("bestmove")) {
+ return make<BestMoveCommand>(BestMoveCommand::from_string(line));
+ } else if (line.starts_with("info")) {
+ return make<InfoCommand>(InfoCommand::from_string(line));
+ }
+
+ dbgln("command line: {}", line);
+ ASSERT_NOT_REACHED();
+}
+
+};
diff --git a/Userland/Libraries/LibChess/UCIEndpoint.h b/Userland/Libraries/LibChess/UCIEndpoint.h
new file mode 100644
index 0000000000..0913c12b35
--- /dev/null
+++ b/Userland/Libraries/LibChess/UCIEndpoint.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibChess/UCICommand.h>
+#include <LibCore/IODevice.h>
+#include <LibCore/Notifier.h>
+#include <LibCore/Object.h>
+
+namespace Chess::UCI {
+
+class Endpoint : public Core::Object {
+ C_OBJECT(Endpoint)
+public:
+ virtual ~Endpoint() override { }
+
+ Endpoint() { }
+ Endpoint(NonnullRefPtr<Core::IODevice> in, NonnullRefPtr<Core::IODevice> out);
+
+ virtual void handle_uci() { }
+ virtual void handle_debug(const DebugCommand&) { }
+ virtual void handle_isready() { }
+ virtual void handle_setoption(const SetOptionCommand&) { }
+ virtual void handle_position(const PositionCommand&) { }
+ virtual void handle_go(const GoCommand&) { }
+ virtual void handle_stop() { }
+ virtual void handle_id(const IdCommand&) { }
+ virtual void handle_uciok() { }
+ virtual void handle_readyok() { }
+ virtual void handle_bestmove(const BestMoveCommand&) { }
+ virtual void handle_info(const InfoCommand&) { }
+
+ void send_command(const Command&);
+
+ virtual void event(Core::Event&);
+
+ Core::IODevice& in() { return *m_in; }
+ Core::IODevice& out() { return *m_out; }
+
+ void set_in(RefPtr<Core::IODevice> in)
+ {
+ m_in = in;
+ set_in_notifier();
+ }
+ void set_out(RefPtr<Core::IODevice> out) { m_out = out; }
+
+private:
+ void set_in_notifier();
+ NonnullOwnPtr<Command> read_command();
+
+ RefPtr<Core::IODevice> m_in;
+ RefPtr<Core::IODevice> m_out;
+ RefPtr<Core::Notifier> m_in_notifier;
+};
+
+}
diff --git a/Userland/Libraries/LibCompress/CMakeLists.txt b/Userland/Libraries/LibCompress/CMakeLists.txt
new file mode 100644
index 0000000000..f76ba8d5b5
--- /dev/null
+++ b/Userland/Libraries/LibCompress/CMakeLists.txt
@@ -0,0 +1,8 @@
+set(SOURCES
+ Deflate.cpp
+ Zlib.cpp
+ Gzip.cpp
+)
+
+serenity_lib(LibCompress compression)
+target_link_libraries(LibCompress LibC LibCrypto)
diff --git a/Userland/Libraries/LibCompress/Deflate.cpp b/Userland/Libraries/LibCompress/Deflate.cpp
new file mode 100644
index 0000000000..0f67b7035e
--- /dev/null
+++ b/Userland/Libraries/LibCompress/Deflate.cpp
@@ -0,0 +1,452 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Array.h>
+#include <AK/Assertions.h>
+#include <AK/BinarySearch.h>
+#include <AK/LogStream.h>
+#include <AK/MemoryStream.h>
+
+#include <LibCompress/Deflate.h>
+
+namespace Compress {
+
+const CanonicalCode& CanonicalCode::fixed_literal_codes()
+{
+ static CanonicalCode code;
+ static bool initialized = false;
+
+ if (initialized)
+ return code;
+
+ Array<u8, 288> data;
+ data.span().slice(0, 144 - 0).fill(8);
+ data.span().slice(144, 256 - 144).fill(9);
+ data.span().slice(256, 280 - 256).fill(7);
+ data.span().slice(280, 288 - 280).fill(8);
+
+ code = CanonicalCode::from_bytes(data).value();
+ initialized = true;
+
+ return code;
+}
+
+const CanonicalCode& CanonicalCode::fixed_distance_codes()
+{
+ static CanonicalCode code;
+ static bool initialized = false;
+
+ if (initialized)
+ return code;
+
+ Array<u8, 32> data;
+ data.span().fill(5);
+
+ code = CanonicalCode::from_bytes(data).value();
+ initialized = true;
+
+ return code;
+}
+
+Optional<CanonicalCode> CanonicalCode::from_bytes(ReadonlyBytes bytes)
+{
+ // FIXME: I can't quite follow the algorithm here, but it seems to work.
+
+ CanonicalCode code;
+
+ auto next_code = 0;
+ for (size_t code_length = 1; code_length <= 15; ++code_length) {
+ next_code <<= 1;
+ auto start_bit = 1 << code_length;
+
+ for (size_t symbol = 0; symbol < bytes.size(); ++symbol) {
+ if (bytes[symbol] != code_length)
+ continue;
+
+ if (next_code > start_bit)
+ return {};
+
+ code.m_symbol_codes.append(start_bit | next_code);
+ code.m_symbol_values.append(symbol);
+
+ next_code++;
+ }
+ }
+
+ if (next_code != (1 << 15)) {
+ return {};
+ }
+
+ return code;
+}
+
+u32 CanonicalCode::read_symbol(InputBitStream& stream) const
+{
+ u32 code_bits = 1;
+
+ for (;;) {
+ code_bits = code_bits << 1 | stream.read_bits(1);
+ ASSERT(code_bits < (1 << 16));
+
+ // FIXME: This is very inefficent and could greatly be improved by implementing this
+ // algorithm: https://www.hanshq.net/zip.html#huffdec
+ size_t index;
+ if (AK::binary_search(m_symbol_codes.span(), code_bits, &index))
+ return m_symbol_values[index];
+ }
+}
+
+DeflateDecompressor::CompressedBlock::CompressedBlock(DeflateDecompressor& decompressor, CanonicalCode literal_codes, Optional<CanonicalCode> distance_codes)
+ : m_decompressor(decompressor)
+ , m_literal_codes(literal_codes)
+ , m_distance_codes(distance_codes)
+{
+}
+
+bool DeflateDecompressor::CompressedBlock::try_read_more()
+{
+ if (m_eof == true)
+ return false;
+
+ const auto symbol = m_literal_codes.read_symbol(m_decompressor.m_input_stream);
+
+ if (symbol < 256) {
+ m_decompressor.m_output_stream << static_cast<u8>(symbol);
+ return true;
+ } else if (symbol == 256) {
+ m_eof = true;
+ return false;
+ } else {
+ if (!m_distance_codes.has_value()) {
+ m_decompressor.set_fatal_error();
+ return false;
+ }
+
+ const auto length = m_decompressor.decode_length(symbol);
+ const auto distance = m_decompressor.decode_distance(m_distance_codes.value().read_symbol(m_decompressor.m_input_stream));
+
+ for (size_t idx = 0; idx < length; ++idx) {
+ u8 byte = 0;
+ m_decompressor.m_output_stream.read({ &byte, sizeof(byte) }, distance);
+ m_decompressor.m_output_stream << byte;
+ }
+
+ return true;
+ }
+}
+
+DeflateDecompressor::UncompressedBlock::UncompressedBlock(DeflateDecompressor& decompressor, size_t length)
+ : m_decompressor(decompressor)
+ , m_bytes_remaining(length)
+{
+}
+
+bool DeflateDecompressor::UncompressedBlock::try_read_more()
+{
+ if (m_bytes_remaining == 0)
+ return false;
+
+ const auto nread = min(m_bytes_remaining, m_decompressor.m_output_stream.remaining_contigous_space());
+ m_bytes_remaining -= nread;
+
+ m_decompressor.m_input_stream >> m_decompressor.m_output_stream.reserve_contigous_space(nread);
+
+ return true;
+}
+
+DeflateDecompressor::DeflateDecompressor(InputStream& stream)
+ : m_input_stream(stream)
+{
+}
+
+DeflateDecompressor::~DeflateDecompressor()
+{
+ if (m_state == State::ReadingCompressedBlock)
+ m_compressed_block.~CompressedBlock();
+ if (m_state == State::ReadingUncompressedBlock)
+ m_uncompressed_block.~UncompressedBlock();
+}
+
+size_t DeflateDecompressor::read(Bytes bytes)
+{
+ if (has_any_error())
+ return 0;
+
+ if (m_state == State::Idle) {
+ if (m_read_final_bock)
+ return 0;
+
+ m_read_final_bock = m_input_stream.read_bit();
+ const auto block_type = m_input_stream.read_bits(2);
+
+ if (block_type == 0b00) {
+ m_input_stream.align_to_byte_boundary();
+
+ LittleEndian<u16> length, negated_length;
+ m_input_stream >> length >> negated_length;
+
+ if ((length ^ 0xffff) != negated_length) {
+ set_fatal_error();
+ return 0;
+ }
+
+ m_state = State::ReadingUncompressedBlock;
+ new (&m_uncompressed_block) UncompressedBlock(*this, length);
+
+ return read(bytes);
+ }
+
+ if (block_type == 0b01) {
+ m_state = State::ReadingCompressedBlock;
+ new (&m_compressed_block) CompressedBlock(*this, CanonicalCode::fixed_literal_codes(), CanonicalCode::fixed_distance_codes());
+
+ return read(bytes);
+ }
+
+ if (block_type == 0b10) {
+ CanonicalCode literal_codes;
+ Optional<CanonicalCode> distance_codes;
+ decode_codes(literal_codes, distance_codes);
+
+ m_state = State::ReadingCompressedBlock;
+ new (&m_compressed_block) CompressedBlock(*this, literal_codes, distance_codes);
+
+ return read(bytes);
+ }
+
+ set_fatal_error();
+ return 0;
+ }
+
+ if (m_state == State::ReadingCompressedBlock) {
+ auto nread = m_output_stream.read(bytes);
+
+ while (nread < bytes.size() && m_compressed_block.try_read_more()) {
+ nread += m_output_stream.read(bytes.slice(nread));
+ }
+
+ if (nread == bytes.size())
+ return nread;
+
+ m_compressed_block.~CompressedBlock();
+ m_state = State::Idle;
+
+ return nread + read(bytes.slice(nread));
+ }
+
+ if (m_state == State::ReadingUncompressedBlock) {
+ auto nread = m_output_stream.read(bytes);
+
+ while (nread < bytes.size() && m_uncompressed_block.try_read_more()) {
+ nread += m_output_stream.read(bytes.slice(nread));
+ }
+
+ if (nread == bytes.size())
+ return nread;
+
+ m_uncompressed_block.~UncompressedBlock();
+ m_state = State::Idle;
+
+ return nread + read(bytes.slice(nread));
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+bool DeflateDecompressor::read_or_error(Bytes bytes)
+{
+ if (read(bytes) < bytes.size()) {
+ set_fatal_error();
+ return false;
+ }
+
+ return true;
+}
+
+bool DeflateDecompressor::discard_or_error(size_t count)
+{
+ u8 buffer[4096];
+
+ size_t ndiscarded = 0;
+ while (ndiscarded < count) {
+ if (unreliable_eof()) {
+ set_fatal_error();
+ return false;
+ }
+
+ ndiscarded += read({ buffer, min<size_t>(count - ndiscarded, 4096) });
+ }
+
+ return true;
+}
+
+bool DeflateDecompressor::unreliable_eof() const { return m_state == State::Idle && m_read_final_bock; }
+
+Optional<ByteBuffer> DeflateDecompressor::decompress_all(ReadonlyBytes bytes)
+{
+ InputMemoryStream memory_stream { bytes };
+ DeflateDecompressor deflate_stream { memory_stream };
+ DuplexMemoryStream output_stream;
+
+ u8 buffer[4096];
+ while (!deflate_stream.has_any_error() && !deflate_stream.unreliable_eof()) {
+ const auto nread = deflate_stream.read({ buffer, sizeof(buffer) });
+ output_stream.write_or_error({ buffer, nread });
+ }
+
+ if (deflate_stream.handle_any_error())
+ return {};
+
+ return output_stream.copy_into_contiguous_buffer();
+}
+
+u32 DeflateDecompressor::decode_length(u32 symbol)
+{
+ // FIXME: I can't quite follow the algorithm here, but it seems to work.
+
+ if (symbol <= 264)
+ return symbol - 254;
+
+ if (symbol <= 284) {
+ auto extra_bits = (symbol - 261) / 4;
+ return (((symbol - 265) % 4 + 4) << extra_bits) + 3 + m_input_stream.read_bits(extra_bits);
+ }
+
+ if (symbol == 285)
+ return 258;
+
+ ASSERT_NOT_REACHED();
+}
+
+u32 DeflateDecompressor::decode_distance(u32 symbol)
+{
+ // FIXME: I can't quite follow the algorithm here, but it seems to work.
+
+ if (symbol <= 3)
+ return symbol + 1;
+
+ if (symbol <= 29) {
+ auto extra_bits = (symbol / 2) - 1;
+ return ((symbol % 2 + 2) << extra_bits) + 1 + m_input_stream.read_bits(extra_bits);
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+void DeflateDecompressor::decode_codes(CanonicalCode& literal_code, Optional<CanonicalCode>& distance_code)
+{
+ auto literal_code_count = m_input_stream.read_bits(5) + 257;
+ auto distance_code_count = m_input_stream.read_bits(5) + 1;
+ auto code_length_count = m_input_stream.read_bits(4) + 4;
+
+ // First we have to extract the code lengths of the code that was used to encode the code lengths of
+ // the code that was used to encode the block.
+
+ u8 code_lengths_code_lengths[19] = { 0 };
+ for (size_t i = 0; i < code_length_count; ++i) {
+ static const size_t indices[] { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+ code_lengths_code_lengths[indices[i]] = m_input_stream.read_bits(3);
+ }
+
+ // Now we can extract the code that was used to encode the code lengths of the code that was used to
+ // encode the block.
+
+ auto code_length_code_result = CanonicalCode::from_bytes({ code_lengths_code_lengths, sizeof(code_lengths_code_lengths) });
+ if (!code_length_code_result.has_value()) {
+ set_fatal_error();
+ return;
+ }
+ const auto code_length_code = code_length_code_result.value();
+
+ // Next we extract the code lengths of the code that was used to encode the block.
+
+ Vector<u8> code_lengths;
+ while (code_lengths.size() < literal_code_count + distance_code_count) {
+ auto symbol = code_length_code.read_symbol(m_input_stream);
+
+ if (symbol <= 15) {
+ code_lengths.append(static_cast<u8>(symbol));
+ continue;
+ } else if (symbol == 17) {
+ auto nrepeat = 3 + m_input_stream.read_bits(3);
+ for (size_t j = 0; j < nrepeat; ++j)
+ code_lengths.append(0);
+ continue;
+ } else if (symbol == 18) {
+ auto nrepeat = 11 + m_input_stream.read_bits(7);
+ for (size_t j = 0; j < nrepeat; ++j)
+ code_lengths.append(0);
+ continue;
+ } else {
+ ASSERT(symbol == 16);
+
+ if (code_lengths.is_empty()) {
+ set_fatal_error();
+ return;
+ }
+
+ auto nrepeat = 3 + m_input_stream.read_bits(2);
+ for (size_t j = 0; j < nrepeat; ++j)
+ code_lengths.append(code_lengths.last());
+ }
+ }
+
+ if (code_lengths.size() != literal_code_count + distance_code_count) {
+ set_fatal_error();
+ return;
+ }
+
+ // Now we extract the code that was used to encode literals and lengths in the block.
+
+ auto literal_code_result = CanonicalCode::from_bytes(code_lengths.span().trim(literal_code_count));
+ if (!literal_code_result.has_value()) {
+ set_fatal_error();
+ return;
+ }
+ literal_code = literal_code_result.value();
+
+ // Now we extract the code that was used to encode distances in the block.
+
+ if (distance_code_count == 1) {
+ auto length = code_lengths[literal_code_count];
+
+ if (length == 0) {
+ return;
+ } else if (length != 1) {
+ set_fatal_error();
+ return;
+ }
+ }
+
+ auto distance_code_result = CanonicalCode::from_bytes(code_lengths.span().slice(literal_code_count));
+ if (!distance_code_result.has_value()) {
+ set_fatal_error();
+ return;
+ }
+ distance_code = distance_code_result.value();
+}
+
+}
diff --git a/Userland/Libraries/LibCompress/Deflate.h b/Userland/Libraries/LibCompress/Deflate.h
new file mode 100644
index 0000000000..c069ba6951
--- /dev/null
+++ b/Userland/Libraries/LibCompress/Deflate.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/BitStream.h>
+#include <AK/ByteBuffer.h>
+#include <AK/CircularDuplexStream.h>
+#include <AK/Endian.h>
+#include <AK/Vector.h>
+
+namespace Compress {
+
+class CanonicalCode {
+public:
+ CanonicalCode() = default;
+ u32 read_symbol(InputBitStream&) const;
+
+ static const CanonicalCode& fixed_literal_codes();
+ static const CanonicalCode& fixed_distance_codes();
+
+ static Optional<CanonicalCode> from_bytes(ReadonlyBytes);
+
+private:
+ Vector<u32> m_symbol_codes;
+ Vector<u32> m_symbol_values;
+};
+
+class DeflateDecompressor final : public InputStream {
+private:
+ class CompressedBlock {
+ public:
+ CompressedBlock(DeflateDecompressor&, CanonicalCode literal_codes, Optional<CanonicalCode> distance_codes);
+
+ bool try_read_more();
+
+ private:
+ bool m_eof { false };
+
+ DeflateDecompressor& m_decompressor;
+ CanonicalCode m_literal_codes;
+ Optional<CanonicalCode> m_distance_codes;
+ };
+
+ class UncompressedBlock {
+ public:
+ UncompressedBlock(DeflateDecompressor&, size_t);
+
+ bool try_read_more();
+
+ private:
+ DeflateDecompressor& m_decompressor;
+ size_t m_bytes_remaining;
+ };
+
+ enum class State {
+ Idle,
+ ReadingCompressedBlock,
+ ReadingUncompressedBlock
+ };
+
+public:
+ friend CompressedBlock;
+ friend UncompressedBlock;
+
+ DeflateDecompressor(InputStream&);
+ ~DeflateDecompressor();
+
+ size_t read(Bytes) override;
+ bool read_or_error(Bytes) override;
+ bool discard_or_error(size_t) override;
+
+ bool unreliable_eof() const override;
+
+ static Optional<ByteBuffer> decompress_all(ReadonlyBytes);
+
+private:
+ u32 decode_length(u32);
+ u32 decode_distance(u32);
+ void decode_codes(CanonicalCode& literal_code, Optional<CanonicalCode>& distance_code);
+
+ bool m_read_final_bock { false };
+
+ State m_state { State::Idle };
+ union {
+ CompressedBlock m_compressed_block;
+ UncompressedBlock m_uncompressed_block;
+ };
+
+ InputBitStream m_input_stream;
+ CircularDuplexStream<32 * 1024> m_output_stream;
+};
+
+}
diff --git a/Userland/Libraries/LibCompress/Gzip.cpp b/Userland/Libraries/LibCompress/Gzip.cpp
new file mode 100644
index 0000000000..cbedc5843b
--- /dev/null
+++ b/Userland/Libraries/LibCompress/Gzip.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCompress/Gzip.h>
+
+#include <AK/MemoryStream.h>
+#include <AK/String.h>
+
+namespace Compress {
+
+bool GzipDecompressor::BlockHeader::valid_magic_number() const
+{
+ return identification_1 == 0x1f && identification_2 == 0x8b;
+}
+
+bool GzipDecompressor::BlockHeader::supported_by_implementation() const
+{
+ if (compression_method != 0x08) {
+ // RFC 1952 does not define any compression methods other than deflate.
+ return false;
+ }
+
+ if (flags > Flags::MAX) {
+ // RFC 1952 does not define any more flags.
+ return false;
+ }
+
+ if (flags & Flags::FHCRC) {
+ TODO();
+ }
+
+ return true;
+}
+
+GzipDecompressor::GzipDecompressor(InputStream& stream)
+ : m_input_stream(stream)
+{
+}
+
+GzipDecompressor::~GzipDecompressor()
+{
+ m_current_member.clear();
+}
+
+// FIXME: Again, there are surely a ton of bugs because the code doesn't check for read errors.
+size_t GzipDecompressor::read(Bytes bytes)
+{
+ if (has_any_error() || m_eof)
+ return 0;
+
+ if (m_current_member.has_value()) {
+ size_t nread = current_member().m_stream.read(bytes);
+ current_member().m_checksum.update(bytes.trim(nread));
+ current_member().m_nread += nread;
+
+ if (nread < bytes.size()) {
+ LittleEndian<u32> crc32, input_size;
+ m_input_stream >> crc32 >> input_size;
+
+ if (crc32 != current_member().m_checksum.digest()) {
+ // FIXME: Somehow the checksum is incorrect?
+
+ set_fatal_error();
+ return 0;
+ }
+
+ if (input_size != current_member().m_nread) {
+ set_fatal_error();
+ return 0;
+ }
+
+ m_current_member.clear();
+
+ return nread + read(bytes.slice(nread));
+ }
+
+ return nread;
+ } else {
+ BlockHeader header;
+ m_input_stream >> Bytes { &header, sizeof(header) };
+
+ if (m_input_stream.handle_any_error()) {
+ m_eof = true;
+ return 0;
+ }
+
+ if (!header.valid_magic_number() || !header.supported_by_implementation()) {
+ set_fatal_error();
+ return 0;
+ }
+
+ if (header.flags & Flags::FEXTRA) {
+ LittleEndian<u16> subfield_id, length;
+ m_input_stream >> subfield_id >> length;
+ m_input_stream.discard_or_error(length);
+ }
+
+ if (header.flags & Flags::FNAME) {
+ String original_filename;
+ m_input_stream >> original_filename;
+ }
+
+ if (header.flags & Flags::FCOMMENT) {
+ String comment;
+ m_input_stream >> comment;
+ }
+
+ m_current_member.emplace(header, m_input_stream);
+ return read(bytes);
+ }
+}
+
+bool GzipDecompressor::read_or_error(Bytes bytes)
+{
+ if (read(bytes) < bytes.size()) {
+ set_fatal_error();
+ return false;
+ }
+
+ return true;
+}
+
+bool GzipDecompressor::discard_or_error(size_t count)
+{
+ u8 buffer[4096];
+
+ size_t ndiscarded = 0;
+ while (ndiscarded < count) {
+ if (unreliable_eof()) {
+ set_fatal_error();
+ return false;
+ }
+
+ ndiscarded += read({ buffer, min<size_t>(count - ndiscarded, sizeof(buffer)) });
+ }
+
+ return true;
+}
+
+Optional<ByteBuffer> GzipDecompressor::decompress_all(ReadonlyBytes bytes)
+{
+ InputMemoryStream memory_stream { bytes };
+ GzipDecompressor gzip_stream { memory_stream };
+ DuplexMemoryStream output_stream;
+
+ u8 buffer[4096];
+ while (!gzip_stream.has_any_error() && !gzip_stream.unreliable_eof()) {
+ const auto nread = gzip_stream.read({ buffer, sizeof(buffer) });
+ output_stream.write_or_error({ buffer, nread });
+ }
+
+ if (gzip_stream.handle_any_error())
+ return {};
+
+ return output_stream.copy_into_contiguous_buffer();
+}
+
+bool GzipDecompressor::unreliable_eof() const { return m_eof; }
+
+}
diff --git a/Userland/Libraries/LibCompress/Gzip.h b/Userland/Libraries/LibCompress/Gzip.h
new file mode 100644
index 0000000000..fc8b92f38f
--- /dev/null
+++ b/Userland/Libraries/LibCompress/Gzip.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibCompress/Deflate.h>
+#include <LibCrypto/Checksum/CRC32.h>
+
+namespace Compress {
+
+class GzipDecompressor final : public InputStream {
+public:
+ GzipDecompressor(InputStream&);
+ ~GzipDecompressor();
+
+ size_t read(Bytes) override;
+ bool read_or_error(Bytes) override;
+ bool discard_or_error(size_t) override;
+
+ bool unreliable_eof() const override;
+
+ static Optional<ByteBuffer> decompress_all(ReadonlyBytes);
+
+private:
+ struct [[gnu::packed]] BlockHeader {
+ u8 identification_1;
+ u8 identification_2;
+ u8 compression_method;
+ u8 flags;
+ LittleEndian<u32> modification_time;
+ u8 extra_flags;
+ u8 operating_system;
+
+ bool valid_magic_number() const;
+ bool supported_by_implementation() const;
+ };
+
+ struct Flags {
+ static constexpr u8 FTEXT = 1 << 0;
+ static constexpr u8 FHCRC = 1 << 1;
+ static constexpr u8 FEXTRA = 1 << 2;
+ static constexpr u8 FNAME = 1 << 3;
+ static constexpr u8 FCOMMENT = 1 << 4;
+
+ static constexpr u8 MAX = FTEXT | FHCRC | FEXTRA | FNAME | FCOMMENT;
+ };
+
+ class Member {
+ public:
+ Member(BlockHeader header, InputStream& stream)
+ : m_header(header)
+ , m_stream(stream)
+ {
+ }
+
+ BlockHeader m_header;
+ DeflateDecompressor m_stream;
+ Crypto::Checksum::CRC32 m_checksum;
+ size_t m_nread { 0 };
+ };
+
+ const Member& current_member() const { return m_current_member.value(); }
+ Member& current_member() { return m_current_member.value(); }
+
+ InputStream& m_input_stream;
+ Optional<Member> m_current_member;
+
+ bool m_eof { false };
+};
+
+}
diff --git a/Userland/Libraries/LibCompress/Zlib.cpp b/Userland/Libraries/LibCompress/Zlib.cpp
new file mode 100644
index 0000000000..dc2d112ad6
--- /dev/null
+++ b/Userland/Libraries/LibCompress/Zlib.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/Span.h>
+#include <AK/Types.h>
+#include <AK/Vector.h>
+#include <LibCompress/Deflate.h>
+#include <LibCompress/Zlib.h>
+
+namespace Compress {
+
+Zlib::Zlib(ReadonlyBytes data)
+{
+ m_input_data = data;
+
+ u8 compression_info = data.at(0);
+ u8 flags = data.at(1);
+
+ m_compression_method = compression_info & 0xF;
+ m_compression_info = (compression_info >> 4) & 0xF;
+ m_check_bits = flags & 0xF;
+ m_has_dictionary = (flags >> 5) & 0x1;
+ m_compression_level = (flags >> 6) & 0x3;
+ m_checksum = 0;
+
+ ASSERT(m_compression_method == 8);
+ ASSERT(m_compression_info == 7);
+ ASSERT(!m_has_dictionary);
+ ASSERT((compression_info * 256 + flags) % 31 == 0);
+
+ m_data_bytes = data.slice(2, data.size() - 2 - 4);
+}
+
+Optional<ByteBuffer> Zlib::decompress()
+{
+ return DeflateDecompressor::decompress_all(m_data_bytes);
+}
+
+Optional<ByteBuffer> Zlib::decompress_all(ReadonlyBytes bytes)
+{
+ Zlib zlib { bytes };
+ return zlib.decompress();
+}
+
+u32 Zlib::checksum()
+{
+ if (!m_checksum) {
+ auto bytes = m_input_data.slice(m_input_data.size() - 4, 4);
+ m_checksum = bytes.at(0) << 24 | bytes.at(1) << 16 | bytes.at(2) << 8 || bytes.at(3);
+ }
+
+ return m_checksum;
+}
+
+}
diff --git a/Userland/Libraries/LibCompress/Zlib.h b/Userland/Libraries/LibCompress/Zlib.h
new file mode 100644
index 0000000000..aa2a912095
--- /dev/null
+++ b/Userland/Libraries/LibCompress/Zlib.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/Span.h>
+#include <AK/Types.h>
+
+namespace Compress {
+
+class Zlib {
+public:
+ Zlib(ReadonlyBytes data);
+
+ Optional<ByteBuffer> decompress();
+ u32 checksum();
+
+ static Optional<ByteBuffer> decompress_all(ReadonlyBytes);
+
+private:
+ u8 m_compression_method;
+ u8 m_compression_info;
+ u8 m_check_bits;
+ u8 m_has_dictionary;
+ u8 m_compression_level;
+
+ u32 m_checksum;
+ ReadonlyBytes m_input_data;
+ ReadonlyBytes m_data_bytes;
+};
+
+}
diff --git a/Userland/Libraries/LibCore/Account.cpp b/Userland/Libraries/LibCore/Account.cpp
new file mode 100644
index 0000000000..7e4d40669a
--- /dev/null
+++ b/Userland/Libraries/LibCore/Account.cpp
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Base64.h>
+#include <AK/Random.h>
+#include <LibCore/Account.h>
+#include <LibCore/File.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+namespace Core {
+
+static String get_salt()
+{
+ char random_data[12];
+ AK::fill_with_random(random_data, sizeof(random_data));
+
+ StringBuilder builder;
+ builder.append("$5$");
+ builder.append(encode_base64(ReadonlyBytes(random_data, sizeof(random_data))));
+
+ return builder.build();
+}
+
+static Vector<gid_t> get_gids(const StringView& username)
+{
+ Vector<gid_t> extra_gids;
+ for (auto* group = getgrent(); group; group = getgrent()) {
+ for (size_t i = 0; group->gr_mem[i]; ++i) {
+ if (username == group->gr_mem[i]) {
+ extra_gids.append(group->gr_gid);
+ break;
+ }
+ }
+ }
+ endgrent();
+ return extra_gids;
+}
+
+Result<Account, String> Account::from_passwd(const passwd& pwd, Core::Account::OpenPasswdFile open_passwd_file, Core::Account::OpenShadowFile open_shadow_file)
+{
+ RefPtr<Core::File> passwd_file;
+ if (open_passwd_file != Core::Account::OpenPasswdFile::No) {
+ auto open_mode = open_passwd_file == Core::Account::OpenPasswdFile::ReadOnly
+ ? Core::File::OpenMode::ReadOnly
+ : Core::File::OpenMode::ReadWrite;
+ auto file_or_error = Core::File::open("/etc/passwd", open_mode);
+ if (file_or_error.is_error())
+ return file_or_error.error();
+ passwd_file = file_or_error.value();
+ }
+
+ RefPtr<Core::File> shadow_file;
+ if (open_shadow_file != Core::Account::OpenShadowFile::No) {
+ auto open_mode = open_shadow_file == Core::Account::OpenShadowFile::ReadOnly
+ ? Core::File::OpenMode::ReadOnly
+ : Core::File::OpenMode::ReadWrite;
+ auto file_or_error = Core::File::open("/etc/shadow", open_mode);
+ if (file_or_error.is_error())
+ return file_or_error.error();
+ shadow_file = file_or_error.value();
+ }
+
+ Account account(pwd, get_gids(pwd.pw_name), move(passwd_file), move(shadow_file));
+ endpwent();
+ return account;
+}
+
+Result<Account, String> Account::from_name(const char* username, Core::Account::OpenPasswdFile open_passwd_file, Core::Account::OpenShadowFile open_shadow_file)
+{
+ struct passwd* pwd = nullptr;
+ errno = 0;
+ pwd = getpwnam(username);
+ if (!pwd) {
+ if (errno == 0)
+ return String("No such user");
+
+ return String(strerror(errno));
+ }
+ return from_passwd(*pwd, open_passwd_file, open_shadow_file);
+}
+
+Result<Account, String> Account::from_uid(uid_t uid, Core::Account::OpenPasswdFile open_passwd_file, Core::Account::OpenShadowFile open_shadow_file)
+{
+ struct passwd* pwd = nullptr;
+ errno = 0;
+ pwd = getpwuid(uid);
+ if (!pwd) {
+ if (errno == 0)
+ return String("No such user");
+
+ return String(strerror(errno));
+ }
+ return from_passwd(*pwd, open_passwd_file, open_shadow_file);
+}
+
+bool Account::authenticate(const char* password) const
+{
+ // An empty passwd field indicates that no password is required to log in.
+ if (m_password_hash.is_empty())
+ return true;
+
+ // FIXME: Use crypt_r if it can be built in lagom.
+ char* hash = crypt(password, m_password_hash.characters());
+ return hash != nullptr && strcmp(hash, m_password_hash.characters()) == 0;
+}
+
+bool Account::login() const
+{
+ if (setgroups(m_extra_gids.size(), m_extra_gids.data()) < 0)
+ return false;
+
+ if (setgid(m_gid) < 0)
+ return false;
+
+ if (setuid(m_uid) < 0)
+ return false;
+
+ return true;
+}
+
+void Account::set_password(const char* password)
+{
+ m_password_hash = crypt(password, get_salt().characters());
+}
+
+void Account::set_password_enabled(bool enabled)
+{
+ if (enabled && m_password_hash != "" && m_password_hash[0] == '!') {
+ m_password_hash = m_password_hash.substring(1, m_password_hash.length() - 1);
+ } else if (!enabled && (m_password_hash == "" || m_password_hash[0] != '!')) {
+ StringBuilder builder;
+ builder.append('!');
+ builder.append(m_password_hash);
+ m_password_hash = builder.build();
+ }
+}
+
+void Account::delete_password()
+{
+ m_password_hash = "";
+}
+
+Account::Account(const passwd& pwd, Vector<gid_t> extra_gids, RefPtr<Core::File> passwd_file, RefPtr<Core::File> shadow_file)
+ : m_passwd_file(move(passwd_file))
+ , m_shadow_file(move(shadow_file))
+ , m_username(pwd.pw_name)
+ , m_uid(pwd.pw_uid)
+ , m_gid(pwd.pw_gid)
+ , m_gecos(pwd.pw_gecos)
+ , m_home_directory(pwd.pw_dir)
+ , m_shell(pwd.pw_shell)
+ , m_extra_gids(extra_gids)
+{
+ if (m_shadow_file) {
+ load_shadow_file();
+ }
+}
+
+String Account::generate_passwd_file() const
+{
+ StringBuilder builder;
+
+ setpwent();
+
+ struct passwd* p;
+ errno = 0;
+ while ((p = getpwent())) {
+ if (p->pw_uid == m_uid) {
+ builder.appendff("{}:!:{}:{}:{}:{}:{}\n",
+ m_username,
+ m_uid, m_gid,
+ m_gecos,
+ m_home_directory,
+ m_shell);
+
+ } else {
+ builder.appendff("{}:!:{}:{}:{}:{}:{}\n",
+ p->pw_name, p->pw_uid,
+ p->pw_gid, p->pw_gecos, p->pw_dir,
+ p->pw_shell);
+ }
+ }
+ endpwent();
+
+ if (errno) {
+ dbgln("errno was non-zero after generating new passwd file.");
+ return {};
+ }
+
+ return builder.to_string();
+}
+
+void Account::load_shadow_file()
+{
+ ASSERT(m_shadow_file);
+ ASSERT(m_shadow_file->is_open());
+
+ if (!m_shadow_file->seek(0)) {
+ ASSERT_NOT_REACHED();
+ }
+
+ Vector<ShadowEntry> entries;
+
+ for (;;) {
+ auto line = m_shadow_file->read_line();
+ if (line.is_null())
+ break;
+ auto parts = line.split(':');
+ if (parts.size() != 2) {
+ dbgln("Malformed shadow entry, ignoring.");
+ continue;
+ }
+ const auto& username = parts[0];
+ const auto& password_hash = parts[1];
+ entries.append({ username, password_hash });
+
+ if (username == m_username) {
+ m_password_hash = password_hash;
+ }
+ }
+
+ m_shadow_entries = move(entries);
+}
+
+String Account::generate_shadow_file() const
+{
+ StringBuilder builder;
+ bool updated_entry_in_place = false;
+ for (auto& entry : m_shadow_entries) {
+ if (entry.username == m_username) {
+ updated_entry_in_place = true;
+ builder.appendff("{}:{}\n", m_username, m_password_hash);
+ } else {
+ builder.appendff("{}:{}\n", entry.username, entry.password_hash);
+ }
+ }
+ if (!updated_entry_in_place)
+ builder.appendff("{}:{}\n", m_username, m_password_hash);
+ return builder.to_string();
+}
+
+bool Account::sync()
+{
+ ASSERT(m_passwd_file);
+ ASSERT(m_passwd_file->mode() == Core::File::OpenMode::ReadWrite);
+ ASSERT(m_shadow_file);
+ ASSERT(m_shadow_file->mode() == Core::File::OpenMode::ReadWrite);
+
+ // FIXME: Maybe reorganize this to create temporary files and finish it completely before renaming them to /etc/{passwd,shadow}
+ // If truncation succeeds but write fails, we'll have an empty file :(
+
+ auto new_passwd_file = generate_passwd_file();
+ auto new_shadow_file = generate_shadow_file();
+
+ if (new_passwd_file.is_null() || new_shadow_file.is_null()) {
+ ASSERT_NOT_REACHED();
+ }
+
+ if (!m_passwd_file->seek(0) || !m_shadow_file->seek(0)) {
+ ASSERT_NOT_REACHED();
+ }
+
+ if (!m_passwd_file->truncate(0)) {
+ dbgln("Truncating passwd file failed.");
+ return false;
+ }
+
+ if (!m_passwd_file->write(new_passwd_file)) {
+ // FIXME: Improve Core::File::write() error reporting.
+ dbgln("Writing to passwd file failed.");
+ return false;
+ }
+
+ if (!m_shadow_file->truncate(0)) {
+ dbgln("Truncating shadow file failed.");
+ return false;
+ }
+
+ if (!m_shadow_file->write(new_shadow_file)) {
+ // FIXME: Improve Core::File::write() error reporting.
+ dbgln("Writing to shadow file failed.");
+ return false;
+ }
+
+ return true;
+ // FIXME: Sync extra groups.
+}
+
+}
diff --git a/Userland/Libraries/LibCore/Account.h b/Userland/Libraries/LibCore/Account.h
new file mode 100644
index 0000000000..fdd7d9bea9
--- /dev/null
+++ b/Userland/Libraries/LibCore/Account.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Result.h>
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <AK/Vector.h>
+#include <LibCore/File.h>
+#include <pwd.h>
+#include <sys/types.h>
+
+namespace Core {
+
+class Account {
+public:
+ enum class OpenPasswdFile {
+ No,
+ ReadOnly,
+ ReadWrite,
+ };
+
+ enum class OpenShadowFile {
+ No,
+ ReadOnly,
+ ReadWrite,
+ };
+
+ static Result<Account, String> from_name(const char* username, OpenPasswdFile = OpenPasswdFile::No, OpenShadowFile = OpenShadowFile::No);
+ static Result<Account, String> from_uid(uid_t uid, OpenPasswdFile = OpenPasswdFile::No, OpenShadowFile = OpenShadowFile::No);
+
+ bool authenticate(const char* password) const;
+ bool login() const;
+
+ String username() const { return m_username; }
+ String password_hash() const { return m_password_hash; }
+
+ // Setters only affect in-memory copy of password.
+ // You must call sync to apply changes.
+ void set_password(const char* password);
+ void set_password_enabled(bool enabled);
+ void delete_password();
+ bool has_password() const { return !m_password_hash.is_empty(); }
+
+ uid_t uid() const { return m_uid; }
+ gid_t gid() const { return m_gid; }
+ const String& gecos() const { return m_gecos; }
+ const String& home_directory() const { return m_home_directory; }
+ const String& shell() const { return m_shell; }
+ const Vector<gid_t>& extra_gids() const { return m_extra_gids; }
+
+ bool sync();
+
+private:
+ static Result<Account, String> from_passwd(const passwd&, OpenPasswdFile, OpenShadowFile);
+
+ Account(const passwd& pwd, Vector<gid_t> extra_gids, RefPtr<Core::File> passwd_file, RefPtr<Core::File> shadow_file);
+ void load_shadow_file();
+
+ String generate_passwd_file() const;
+ String generate_shadow_file() const;
+
+ RefPtr<Core::File> m_passwd_file;
+ RefPtr<Core::File> m_shadow_file;
+
+ String m_username;
+
+ // Contents of passwd field in passwd entry.
+ // Can be empty, "x", or contain a leading '!'
+ String m_password_hash;
+ uid_t m_uid { 0 };
+ gid_t m_gid { 0 };
+ String m_gecos;
+ String m_home_directory;
+ String m_shell;
+ Vector<gid_t> m_extra_gids;
+
+ struct ShadowEntry {
+ String username;
+ String password_hash;
+ };
+ Vector<ShadowEntry> m_shadow_entries;
+};
+
+}
diff --git a/Userland/Libraries/LibCore/ArgsParser.cpp b/Userland/Libraries/LibCore/ArgsParser.cpp
new file mode 100644
index 0000000000..121272e38b
--- /dev/null
+++ b/Userland/Libraries/LibCore/ArgsParser.cpp
@@ -0,0 +1,392 @@
+/*
+ * Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Format.h>
+#include <AK/StringBuilder.h>
+#include <LibCore/ArgsParser.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+static constexpr bool isnan(double __x) { return __builtin_isnan(__x); }
+
+static Optional<double> convert_to_double(const char* s)
+{
+ char* p;
+ double v = strtod(s, &p);
+ if (isnan(v) || p == s)
+ return {};
+ return v;
+}
+
+namespace Core {
+
+ArgsParser::ArgsParser()
+{
+ add_option(m_show_help, "Display this message", "help", 0);
+}
+
+bool ArgsParser::parse(int argc, char** argv, bool exit_on_failure)
+{
+ auto print_usage_and_exit = [this, argv, exit_on_failure] {
+ print_usage(stderr, argv[0]);
+ if (exit_on_failure)
+ exit(1);
+ };
+
+ Vector<option> long_options;
+ StringBuilder short_options_builder;
+
+ int index_of_found_long_option = -1;
+
+ // Tell getopt() to reset its internal state, and start scanning from optind = 1.
+ // We could also set optreset = 1, but the host platform may not support that.
+ optind = 0;
+
+ for (size_t i = 0; i < m_options.size(); i++) {
+ auto& opt = m_options[i];
+ if (opt.long_name) {
+ option long_opt {
+ opt.long_name,
+ opt.requires_argument ? required_argument : no_argument,
+ &index_of_found_long_option,
+ static_cast<int>(i)
+ };
+ long_options.append(long_opt);
+ }
+ if (opt.short_name) {
+ short_options_builder.append(opt.short_name);
+ if (opt.requires_argument)
+ short_options_builder.append(':');
+ }
+ }
+ long_options.append({ 0, 0, 0, 0 });
+
+ String short_options = short_options_builder.build();
+
+ while (true) {
+ int c = getopt_long(argc, argv, short_options.characters(), long_options.data(), nullptr);
+ if (c == -1) {
+ // We have reached the end.
+ break;
+ } else if (c == '?') {
+ // There was an error, and getopt() has already
+ // printed its error message.
+ print_usage_and_exit();
+ return false;
+ }
+
+ // Let's see what option we just found.
+ Option* found_option = nullptr;
+ if (c == 0) {
+ // It was a long option.
+ ASSERT(index_of_found_long_option >= 0);
+ found_option = &m_options[index_of_found_long_option];
+ index_of_found_long_option = -1;
+ } else {
+ // It was a short option, look it up.
+ auto it = m_options.find_if([c](auto& opt) { return c == opt.short_name; });
+ ASSERT(!it.is_end());
+ found_option = &*it;
+ }
+ ASSERT(found_option);
+
+ const char* arg = found_option->requires_argument ? optarg : nullptr;
+ if (!found_option->accept_value(arg)) {
+ warnln("\033[31mInvalid value for option \033[1m{}\033[22m, dude\033[0m", found_option->name_for_display());
+ print_usage_and_exit();
+ return false;
+ }
+ }
+
+ // We're done processing options, now let's parse positional arguments.
+
+ int values_left = argc - optind;
+ int num_values_for_arg[m_positional_args.size()];
+ int total_values_required = 0;
+ for (size_t i = 0; i < m_positional_args.size(); i++) {
+ auto& arg = m_positional_args[i];
+ num_values_for_arg[i] = arg.min_values;
+ total_values_required += arg.min_values;
+ }
+
+ if (total_values_required > values_left) {
+ print_usage_and_exit();
+ return false;
+ }
+ int extra_values_to_distribute = values_left - total_values_required;
+
+ for (size_t i = 0; i < m_positional_args.size(); i++) {
+ auto& arg = m_positional_args[i];
+ int extra_values_to_this_arg = min(arg.max_values - arg.min_values, extra_values_to_distribute);
+ num_values_for_arg[i] += extra_values_to_this_arg;
+ extra_values_to_distribute -= extra_values_to_this_arg;
+ if (extra_values_to_distribute == 0)
+ break;
+ }
+
+ if (extra_values_to_distribute > 0) {
+ // We still have too many values :(
+ print_usage_and_exit();
+ return false;
+ }
+
+ for (size_t i = 0; i < m_positional_args.size(); i++) {
+ auto& arg = m_positional_args[i];
+ for (int j = 0; j < num_values_for_arg[i]; j++) {
+ const char* value = argv[optind++];
+ if (!arg.accept_value(value)) {
+ warnln("Invalid value for argument {}", arg.name);
+ print_usage_and_exit();
+ return false;
+ }
+ }
+ }
+
+ // We're done parsing! :)
+ // Now let's show help if requested.
+ if (m_show_help) {
+ print_usage(stdout, argv[0]);
+ if (exit_on_failure)
+ exit(0);
+ return false;
+ }
+
+ return true;
+}
+
+void ArgsParser::print_usage(FILE* file, const char* argv0)
+{
+ out(file, "Usage:\n\t\033[1m{}\033[0m", argv0);
+
+ for (auto& opt : m_options) {
+ if (opt.long_name && !strcmp(opt.long_name, "help"))
+ continue;
+ if (opt.requires_argument)
+ out(file, " [{} {}]", opt.name_for_display(), opt.value_name);
+ else
+ out(file, " [{}]", opt.name_for_display());
+ }
+ for (auto& arg : m_positional_args) {
+ bool required = arg.min_values > 0;
+ bool repeated = arg.max_values > 1;
+
+ if (required && repeated)
+ out(file, " <{}...>", arg.name);
+ else if (required && !repeated)
+ out(file, " <{}>", arg.name);
+ else if (!required && repeated)
+ out(file, " [{}...]", arg.name);
+ else if (!required && !repeated)
+ out(file, " [{}]", arg.name);
+ }
+ outln(file);
+
+ if (m_general_help != nullptr && m_general_help[0] != '\0') {
+ outln(file, "\nDescription:");
+ outln(file, m_general_help);
+ }
+
+ if (!m_options.is_empty())
+ outln(file, "\nOptions:");
+ for (auto& opt : m_options) {
+ auto print_argument = [&]() {
+ if (opt.value_name) {
+ if (opt.requires_argument)
+ out(file, " {}", opt.value_name);
+ else
+ out(file, " [{}]", opt.value_name);
+ }
+ };
+ out(file, "\t");
+ if (opt.short_name) {
+ out(file, "\033[1m-{}\033[0m", opt.short_name);
+ print_argument();
+ }
+ if (opt.short_name && opt.long_name)
+ out(file, ", ");
+ if (opt.long_name) {
+ out(file, "\033[1m--{}\033[0m", opt.long_name);
+ print_argument();
+ }
+
+ if (opt.help_string)
+ out(file, "\t{}", opt.help_string);
+ outln(file);
+ }
+
+ if (!m_positional_args.is_empty())
+ outln(file, "\nArguments:");
+
+ for (auto& arg : m_positional_args) {
+ out(file, "\t\033[1m{}\033[0m", arg.name);
+ if (arg.help_string)
+ out(file, "\t{}", arg.help_string);
+ outln(file);
+ }
+}
+
+void ArgsParser::add_option(Option&& option)
+{
+ m_options.append(move(option));
+}
+
+void ArgsParser::add_option(bool& value, const char* help_string, const char* long_name, char short_name)
+{
+ Option option {
+ false,
+ help_string,
+ long_name,
+ short_name,
+ nullptr,
+ [&value](const char* s) {
+ ASSERT(s == nullptr);
+ value = true;
+ return true;
+ }
+ };
+ add_option(move(option));
+}
+
+void ArgsParser::add_option(const char*& value, const char* help_string, const char* long_name, char short_name, const char* value_name)
+{
+ Option option {
+ true,
+ help_string,
+ long_name,
+ short_name,
+ value_name,
+ [&value](const char* s) {
+ value = s;
+ return true;
+ }
+ };
+ add_option(move(option));
+}
+
+void ArgsParser::add_option(int& value, const char* help_string, const char* long_name, char short_name, const char* value_name)
+{
+ Option option {
+ true,
+ help_string,
+ long_name,
+ short_name,
+ value_name,
+ [&value](const char* s) {
+ auto opt = StringView(s).to_int();
+ value = opt.value_or(0);
+ return opt.has_value();
+ }
+ };
+ add_option(move(option));
+}
+
+void ArgsParser::add_option(double& value, const char* help_string, const char* long_name, char short_name, const char* value_name)
+{
+ Option option {
+ true,
+ help_string,
+ long_name,
+ short_name,
+ value_name,
+ [&value](const char* s) {
+ auto opt = convert_to_double(s);
+ value = opt.value_or(0.0);
+ return opt.has_value();
+ }
+ };
+ add_option(move(option));
+}
+
+void ArgsParser::add_positional_argument(Arg&& arg)
+{
+ m_positional_args.append(move(arg));
+}
+
+void ArgsParser::add_positional_argument(const char*& value, const char* help_string, const char* name, Required required)
+{
+ Arg arg {
+ help_string,
+ name,
+ required == Required::Yes ? 1 : 0,
+ 1,
+ [&value](const char* s) {
+ value = s;
+ return true;
+ }
+ };
+ add_positional_argument(move(arg));
+}
+
+void ArgsParser::add_positional_argument(int& value, const char* help_string, const char* name, Required required)
+{
+ Arg arg {
+ help_string,
+ name,
+ required == Required::Yes ? 1 : 0,
+ 1,
+ [&value](const char* s) {
+ auto opt = StringView(s).to_int();
+ value = opt.value_or(0);
+ return opt.has_value();
+ }
+ };
+ add_positional_argument(move(arg));
+}
+
+void ArgsParser::add_positional_argument(double& value, const char* help_string, const char* name, Required required)
+{
+ Arg arg {
+ help_string,
+ name,
+ required == Required::Yes ? 1 : 0,
+ 1,
+ [&value](const char* s) {
+ auto opt = convert_to_double(s);
+ value = opt.value_or(0.0);
+ return opt.has_value();
+ }
+ };
+ add_positional_argument(move(arg));
+}
+
+void ArgsParser::add_positional_argument(Vector<const char*>& values, const char* help_string, const char* name, Required required)
+{
+ Arg arg {
+ help_string,
+ name,
+ required == Required::Yes ? 1 : 0,
+ INT_MAX,
+ [&values](const char* s) {
+ values.append(s);
+ return true;
+ }
+ };
+ add_positional_argument(move(arg));
+}
+
+}
diff --git a/Userland/Libraries/LibCore/ArgsParser.h b/Userland/Libraries/LibCore/ArgsParser.h
new file mode 100644
index 0000000000..2f7b923a9d
--- /dev/null
+++ b/Userland/Libraries/LibCore/ArgsParser.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <stdio.h>
+
+namespace Core {
+
+class ArgsParser {
+public:
+ ArgsParser();
+
+ enum class Required {
+ Yes,
+ No
+ };
+
+ struct Option {
+ bool requires_argument { true };
+ const char* help_string { nullptr };
+ const char* long_name { nullptr };
+ char short_name { 0 };
+ const char* value_name { nullptr };
+ Function<bool(const char*)> accept_value;
+
+ String name_for_display() const
+ {
+ if (long_name)
+ return String::format("--%s", long_name);
+ return String::format("-%c", short_name);
+ }
+ };
+
+ struct Arg {
+ const char* help_string { nullptr };
+ const char* name { nullptr };
+ int min_values { 0 };
+ int max_values { 1 };
+ Function<bool(const char*)> accept_value;
+ };
+
+ bool parse(int argc, char** argv, bool exit_on_failure = true);
+ // *Without* trailing newline!
+ void set_general_help(const char* help_string) { m_general_help = help_string; };
+ void print_usage(FILE*, const char* argv0);
+
+ void add_option(Option&&);
+ void add_option(bool& value, const char* help_string, const char* long_name, char short_name);
+ void add_option(const char*& value, const char* help_string, const char* long_name, char short_name, const char* value_name);
+ void add_option(int& value, const char* help_string, const char* long_name, char short_name, const char* value_name);
+ void add_option(double& value, const char* help_string, const char* long_name, char short_name, const char* value_name);
+
+ void add_positional_argument(Arg&&);
+ void add_positional_argument(const char*& value, const char* help_string, const char* name, Required required = Required::Yes);
+ void add_positional_argument(int& value, const char* help_string, const char* name, Required required = Required::Yes);
+ void add_positional_argument(double& value, const char* help_string, const char* name, Required required = Required::Yes);
+ void add_positional_argument(Vector<const char*>& value, const char* help_string, const char* name, Required required = Required::Yes);
+
+private:
+ Vector<Option> m_options;
+ Vector<Arg> m_positional_args;
+
+ bool m_show_help { false };
+ const char* m_general_help { nullptr };
+};
+
+}
diff --git a/Userland/Libraries/LibCore/CMakeLists.txt b/Userland/Libraries/LibCore/CMakeLists.txt
new file mode 100644
index 0000000000..de16c64990
--- /dev/null
+++ b/Userland/Libraries/LibCore/CMakeLists.txt
@@ -0,0 +1,37 @@
+set(SOURCES
+ Account.cpp
+ ArgsParser.cpp
+ ConfigFile.cpp
+ Command.cpp
+ DateTime.cpp
+ DirectoryWatcher.cpp
+ DirIterator.cpp
+ ElapsedTimer.cpp
+ Event.cpp
+ EventLoop.cpp
+ File.cpp
+ GetPassword.cpp
+ Gzip.cpp
+ IODevice.cpp
+ LocalServer.cpp
+ LocalSocket.cpp
+ MimeData.cpp
+ NetworkJob.cpp
+ NetworkResponse.cpp
+ Notifier.cpp
+ Object.cpp
+ ProcessStatisticsReader.cpp
+ Property.cpp
+ puff.cpp
+ SocketAddress.cpp
+ Socket.cpp
+ StandardPaths.cpp
+ TCPServer.cpp
+ TCPSocket.cpp
+ Timer.cpp
+ UDPServer.cpp
+ UDPSocket.cpp
+)
+
+serenity_lib(LibCore core)
+target_link_libraries(LibCore LibC LibCrypt)
diff --git a/Userland/Libraries/LibCore/Command.cpp b/Userland/Libraries/LibCore/Command.cpp
new file mode 100644
index 0000000000..e4fb717473
--- /dev/null
+++ b/Userland/Libraries/LibCore/Command.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Command.h"
+#include <AK/ByteBuffer.h>
+#include <AK/LogStream.h>
+#include <AK/ScopeGuard.h>
+#include <LibCore/File.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+// #define DBG_FAILED_COMMANDS
+
+namespace Core {
+
+// Only supported in serenity mode because we use `posix_spawn_file_actions_addchdir`
+#ifdef __serenity__
+
+String command(const String& command_string, Optional<LexicalPath> chdir)
+{
+ auto parts = command_string.split(' ');
+ if (parts.is_empty())
+ return {};
+ auto program = parts[0];
+ parts.remove(0);
+ return command(program, parts, chdir);
+}
+
+String command(const String& program, const Vector<String>& arguments, Optional<LexicalPath> chdir)
+{
+ int stdout_pipe[2] = {};
+ int stderr_pipe[2] = {};
+ if (pipe2(stdout_pipe, O_CLOEXEC)) {
+ perror("pipe2");
+ ASSERT_NOT_REACHED();
+ }
+ if (pipe2(stderr_pipe, O_CLOEXEC)) {
+ perror("pipe2");
+ ASSERT_NOT_REACHED();
+ }
+
+ auto close_pipes = ScopeGuard([stderr_pipe, stdout_pipe] {
+ // The write-ends of these pipes are closed manually
+ close(stdout_pipe[0]);
+ close(stderr_pipe[0]);
+ });
+
+ Vector<const char*> parts = { program.characters() };
+ for (const auto& part : arguments) {
+ parts.append(part.characters());
+ }
+ parts.append(nullptr);
+
+ const char** argv = parts.data();
+
+ posix_spawn_file_actions_t action;
+ posix_spawn_file_actions_init(&action);
+ if (chdir.has_value()) {
+ posix_spawn_file_actions_addchdir(&action, chdir.value().string().characters());
+ }
+ posix_spawn_file_actions_adddup2(&action, stdout_pipe[1], STDOUT_FILENO);
+ posix_spawn_file_actions_adddup2(&action, stderr_pipe[1], STDERR_FILENO);
+
+ pid_t pid;
+ if ((errno = posix_spawnp(&pid, program.characters(), &action, nullptr, const_cast<char**>(argv), environ))) {
+ perror("posix_spawn");
+ ASSERT_NOT_REACHED();
+ }
+ int wstatus;
+ waitpid(pid, &wstatus, 0);
+ posix_spawn_file_actions_destroy(&action);
+
+ // close the write-ends so reading wouldn't block
+ close(stdout_pipe[1]);
+ close(stderr_pipe[1]);
+
+ auto read_all_from_pipe = [](int pipe[2]) {
+ auto result_file = Core::File::construct();
+ if (!result_file->open(pipe[0], Core::IODevice::ReadOnly, Core::File::ShouldCloseFileDescriptor::Yes)) {
+ perror("open");
+ ASSERT_NOT_REACHED();
+ }
+ return String::copy(result_file->read_all());
+ };
+
+ if (WEXITSTATUS(wstatus) != 0) {
+# ifdef DBG_FAILED_COMMANDS
+ dbgln("command failed. stderr: {}", read_all_from_pipe(stderr_pipe));
+# endif
+ return {};
+ }
+
+ auto result = read_all_from_pipe(stdout_pipe);
+ if (result.is_null())
+ return "";
+ return result;
+}
+
+#endif
+
+}
diff --git a/Userland/Libraries/LibCore/Command.h b/Userland/Libraries/LibCore/Command.h
new file mode 100644
index 0000000000..725c6b55a2
--- /dev/null
+++ b/Userland/Libraries/LibCore/Command.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/LexicalPath.h>
+#include <AK/Optional.h>
+#include <AK/String.h>
+#include <spawn.h>
+
+namespace Core {
+
+// If the executed command fails, the returned String will be in the null state.
+String command(const String& program, const Vector<String>& arguments, Optional<LexicalPath> chdir);
+String command(const String& command_string, Optional<LexicalPath> chdir);
+
+}
diff --git a/Userland/Libraries/LibCore/ConfigFile.cpp b/Userland/Libraries/LibCore/ConfigFile.cpp
new file mode 100644
index 0000000000..cb7e68576c
--- /dev/null
+++ b/Userland/Libraries/LibCore/ConfigFile.cpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ByteBuffer.h>
+#include <AK/StringBuilder.h>
+#include <LibCore/ConfigFile.h>
+#include <LibCore/File.h>
+#include <LibCore/StandardPaths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <unistd.h>
+
+namespace Core {
+
+NonnullRefPtr<ConfigFile> ConfigFile::get_for_lib(const String& lib_name)
+{
+ String directory = StandardPaths::config_directory();
+ auto path = String::formatted("{}/lib/{}.ini", directory, lib_name);
+
+ return adopt(*new ConfigFile(path));
+}
+
+NonnullRefPtr<ConfigFile> ConfigFile::get_for_app(const String& app_name)
+{
+ String directory = StandardPaths::config_directory();
+ auto path = String::formatted("{}/{}.ini", directory, app_name);
+ return adopt(*new ConfigFile(path));
+}
+
+NonnullRefPtr<ConfigFile> ConfigFile::get_for_system(const String& app_name)
+{
+ auto path = String::formatted("/etc/{}.ini", app_name);
+ return adopt(*new ConfigFile(path));
+}
+
+NonnullRefPtr<ConfigFile> ConfigFile::open(const String& path)
+{
+ return adopt(*new ConfigFile(path));
+}
+
+ConfigFile::ConfigFile(const String& file_name)
+ : m_file_name(file_name)
+{
+ reparse();
+}
+
+ConfigFile::~ConfigFile()
+{
+ sync();
+}
+
+void ConfigFile::reparse()
+{
+ m_groups.clear();
+
+ auto file = File::construct(m_file_name);
+ if (!file->open(IODevice::OpenMode::ReadOnly))
+ return;
+
+ HashMap<String, String>* current_group = nullptr;
+
+ while (file->can_read_line()) {
+ auto line = file->read_line();
+ auto* cp = line.characters();
+
+ while (*cp && (*cp == ' ' || *cp == '\t' || *cp == '\n'))
+ ++cp;
+
+ switch (*cp) {
+ case '\0': // EOL...
+ case '#': // Comment, skip entire line.
+ case ';': // -||-
+ continue;
+ case '[': { // Start of new group.
+ StringBuilder builder;
+ ++cp; // Skip the '['
+ while (*cp && (*cp != ']'))
+ builder.append(*(cp++));
+ current_group = &m_groups.ensure(builder.to_string());
+ break;
+ }
+ default: { // Start of key{
+ StringBuilder key_builder;
+ StringBuilder value_builder;
+ while (*cp && (*cp != '='))
+ key_builder.append(*(cp++));
+ ++cp; // Skip the '='
+ while (*cp && (*cp != '\n'))
+ value_builder.append(*(cp++));
+ if (!current_group) {
+ // We're not in a group yet, create one with the name ""...
+ current_group = &m_groups.ensure("");
+ }
+ current_group->set(key_builder.to_string(), value_builder.to_string());
+ }
+ }
+ }
+}
+
+String ConfigFile::read_entry(const String& group, const String& key, const String& default_value) const
+{
+ if (!has_key(group, key)) {
+ return default_value;
+ }
+ auto it = m_groups.find(group);
+ auto jt = it->value.find(key);
+ return jt->value;
+}
+
+int ConfigFile::read_num_entry(const String& group, const String& key, int default_value) const
+{
+ if (!has_key(group, key)) {
+ return default_value;
+ }
+
+ return read_entry(group, key).to_int().value_or(default_value);
+}
+
+bool ConfigFile::read_bool_entry(const String& group, const String& key, bool default_value) const
+{
+ auto value = read_entry(group, key, default_value ? "1" : "0");
+ if (value == "1" || value.to_lowercase() == "true")
+ return 1;
+ return 0;
+}
+
+void ConfigFile::write_entry(const String& group, const String& key, const String& value)
+{
+ m_groups.ensure(group).ensure(key) = value;
+ m_dirty = true;
+}
+
+void ConfigFile::write_num_entry(const String& group, const String& key, int value)
+{
+ write_entry(group, key, String::number(value));
+}
+void ConfigFile::write_bool_entry(const String& group, const String& key, bool value)
+{
+ write_entry(group, key, value ? "1" : "0");
+}
+void ConfigFile::write_color_entry(const String& group, const String& key, Color value)
+{
+ write_entry(group, key, String::formatted("{},{},{},{}", value.red(), value.green(), value.blue(), value.alpha()));
+}
+
+bool ConfigFile::sync()
+{
+ if (!m_dirty)
+ return true;
+
+ FILE* fp = fopen(m_file_name.characters(), "wb");
+ if (!fp)
+ return false;
+
+ for (auto& it : m_groups) {
+ outln(fp, "[{}]", it.key);
+ for (auto& jt : it.value)
+ outln(fp, "{}={}", jt.key, jt.value);
+ outln(fp);
+ }
+
+ fclose(fp);
+
+ m_dirty = false;
+ return true;
+}
+
+void ConfigFile::dump() const
+{
+ for (auto& it : m_groups) {
+ outln("[{}]", it.key);
+ for (auto& jt : it.value)
+ outln("{}={}", jt.key, jt.value);
+ outln();
+ }
+}
+
+Vector<String> ConfigFile::groups() const
+{
+ return m_groups.keys();
+}
+
+Vector<String> ConfigFile::keys(const String& group) const
+{
+ auto it = m_groups.find(group);
+ if (it == m_groups.end())
+ return {};
+ return it->value.keys();
+}
+
+bool ConfigFile::has_key(const String& group, const String& key) const
+{
+ auto it = m_groups.find(group);
+ if (it == m_groups.end())
+ return {};
+ return it->value.contains(key);
+}
+
+bool ConfigFile::has_group(const String& group) const
+{
+ return m_groups.contains(group);
+}
+
+void ConfigFile::remove_group(const String& group)
+{
+ m_groups.remove(group);
+ m_dirty = true;
+}
+
+void ConfigFile::remove_entry(const String& group, const String& key)
+{
+ auto it = m_groups.find(group);
+ if (it == m_groups.end())
+ return;
+ it->value.remove(key);
+ m_dirty = true;
+}
+
+}
diff --git a/Userland/Libraries/LibCore/ConfigFile.h b/Userland/Libraries/LibCore/ConfigFile.h
new file mode 100644
index 0000000000..24865a0e48
--- /dev/null
+++ b/Userland/Libraries/LibCore/ConfigFile.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <LibGfx/Color.h>
+
+namespace Core {
+
+class ConfigFile : public RefCounted<ConfigFile> {
+public:
+ static NonnullRefPtr<ConfigFile> get_for_lib(const String& lib_name);
+ static NonnullRefPtr<ConfigFile> get_for_app(const String& app_name);
+ static NonnullRefPtr<ConfigFile> get_for_system(const String& app_name);
+ static NonnullRefPtr<ConfigFile> open(const String& path);
+ ~ConfigFile();
+
+ bool has_group(const String&) const;
+ bool has_key(const String& group, const String& key) const;
+
+ Vector<String> groups() const;
+ Vector<String> keys(const String& group) const;
+
+ String read_entry(const String& group, const String& key, const String& default_value = String()) const;
+ int read_num_entry(const String& group, const String& key, int default_value = 0) const;
+ bool read_bool_entry(const String& group, const String& key, bool default_value = false) const;
+
+ void write_entry(const String& group, const String& key, const String& value);
+ void write_num_entry(const String& group, const String& key, int value);
+ void write_bool_entry(const String& group, const String& key, bool value);
+ void write_color_entry(const String& group, const String& key, Color value);
+
+ void dump() const;
+
+ bool is_dirty() const { return m_dirty; }
+
+ bool sync();
+
+ void remove_group(const String& group);
+ void remove_entry(const String& group, const String& key);
+
+ String file_name() const { return m_file_name; }
+
+private:
+ explicit ConfigFile(const String& file_name);
+
+ void reparse();
+
+ String m_file_name;
+ HashMap<String, HashMap<String, String>> m_groups;
+ bool m_dirty { false };
+};
+
+}
diff --git a/Userland/Libraries/LibCore/DateTime.cpp b/Userland/Libraries/LibCore/DateTime.cpp
new file mode 100644
index 0000000000..804abac3de
--- /dev/null
+++ b/Userland/Libraries/LibCore/DateTime.cpp
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <AK/Time.h>
+#include <LibCore/DateTime.h>
+#include <sys/time.h>
+#include <time.h>
+
+namespace Core {
+
+DateTime DateTime::now()
+{
+ return from_timestamp(time(nullptr));
+}
+
+DateTime DateTime::create(unsigned year, unsigned month, unsigned day, unsigned hour, unsigned minute, unsigned second)
+{
+ DateTime dt;
+ dt.set_time(year, month, day, hour, minute, second);
+ return dt;
+}
+
+DateTime DateTime::from_timestamp(time_t timestamp)
+{
+ struct tm tm;
+ localtime_r(&timestamp, &tm);
+ DateTime dt;
+ dt.m_year = tm.tm_year + 1900;
+ dt.m_month = tm.tm_mon + 1;
+ dt.m_day = tm.tm_mday;
+ dt.m_hour = tm.tm_hour;
+ dt.m_minute = tm.tm_min;
+ dt.m_second = tm.tm_sec;
+ dt.m_timestamp = timestamp;
+ return dt;
+}
+
+unsigned DateTime::weekday() const
+{
+ return ::day_of_week(m_year, m_month, m_day);
+}
+
+unsigned DateTime::days_in_month() const
+{
+ return ::days_in_month(m_year, m_month);
+}
+
+unsigned DateTime::day_of_year() const
+{
+ return ::day_of_year(m_year, m_month, m_day);
+}
+
+bool DateTime::is_leap_year() const
+{
+ return ::is_leap_year(m_year);
+}
+
+void DateTime::set_time(unsigned year, unsigned month, unsigned day, unsigned hour, unsigned minute, unsigned second)
+{
+ struct tm tm = {};
+ tm.tm_sec = (int)second;
+ tm.tm_min = (int)minute;
+ tm.tm_hour = (int)hour;
+ tm.tm_mday = (int)day;
+ tm.tm_mon = (int)month - 1;
+ tm.tm_year = (int)year - 1900;
+ tm.tm_isdst = -1;
+ // mktime() doesn't read tm.tm_wday and tm.tm_yday, no need to fill them in.
+
+ m_timestamp = mktime(&tm);
+
+ // mktime() normalizes the components to the right ranges (Jan 32 -> Feb 1 etc), so read fields back out from tm.
+ m_year = tm.tm_year + 1900;
+ m_month = tm.tm_mon + 1;
+ m_day = tm.tm_mday;
+ m_hour = tm.tm_hour;
+ m_minute = tm.tm_min;
+ m_second = tm.tm_sec;
+}
+
+String DateTime::to_string(const String& format) const
+{
+
+ const char wday_short_names[7][4] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+ };
+ const char wday_long_names[7][10] = {
+ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
+ };
+ const char mon_short_names[12][4] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+ const char mon_long_names[12][10] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+ };
+
+ struct tm tm;
+ localtime_r(&m_timestamp, &tm);
+ StringBuilder builder;
+ const int format_len = format.length();
+
+ for (int i = 0; i < format_len; ++i) {
+ if (format[i] != '%') {
+ builder.append(format[i]);
+ } else {
+ if (++i == format_len)
+ return String();
+
+ switch (format[i]) {
+ case 'a':
+ builder.append(wday_short_names[tm.tm_wday]);
+ break;
+ case 'A':
+ builder.append(wday_long_names[tm.tm_wday]);
+ break;
+ case 'b':
+ builder.append(mon_short_names[tm.tm_mon]);
+ break;
+ case 'B':
+ builder.append(mon_long_names[tm.tm_mon]);
+ break;
+ case 'C':
+ builder.appendf("%02d", (tm.tm_year + 1900) / 100);
+ break;
+ case 'd':
+ builder.appendf("%02d", tm.tm_mday);
+ break;
+ case 'D':
+ builder.appendf("%02d/%02d/%02d", tm.tm_mon + 1, tm.tm_mday, (tm.tm_year + 1900) % 100);
+ break;
+ case 'e':
+ builder.appendf("%2d", tm.tm_mday);
+ break;
+ case 'h':
+ builder.append(mon_short_names[tm.tm_mon]);
+ break;
+ case 'H':
+ builder.appendf("%02d", tm.tm_hour);
+ break;
+ case 'I':
+ builder.appendf("%02d", tm.tm_hour % 12);
+ break;
+ case 'j':
+ builder.appendf("%03d", tm.tm_yday + 1);
+ break;
+ case 'm':
+ builder.appendf("%02d", tm.tm_mon + 1);
+ break;
+ case 'M':
+ builder.appendf("%02d", tm.tm_min);
+ break;
+ case 'n':
+ builder.append('\n');
+ break;
+ case 'p':
+ builder.append(tm.tm_hour < 12 ? "a.m." : "p.m.");
+ break;
+ case 'r':
+ builder.appendf("%02d:%02d:%02d %s", tm.tm_hour % 12, tm.tm_min, tm.tm_sec, tm.tm_hour < 12 ? "a.m." : "p.m.");
+ break;
+ case 'R':
+ builder.appendf("%02d:%02d", tm.tm_hour, tm.tm_min);
+ break;
+ case 'S':
+ builder.appendf("%02d", tm.tm_sec);
+ break;
+ case 't':
+ builder.append('\t');
+ break;
+ case 'T':
+ builder.appendf("%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec);
+ break;
+ case 'u':
+ builder.appendf("%d", tm.tm_wday ? tm.tm_wday : 7);
+ break;
+ case 'U': {
+ const int wday_of_year_beginning = (tm.tm_wday + 6 * tm.tm_yday) % 7;
+ const int week_number = (tm.tm_yday + wday_of_year_beginning) / 7;
+ builder.appendf("%02d", week_number);
+ break;
+ }
+ case 'V': {
+ const int wday_of_year_beginning = (tm.tm_wday + 6 + 6 * tm.tm_yday) % 7;
+ int week_number = (tm.tm_yday + wday_of_year_beginning) / 7 + 1;
+ if (wday_of_year_beginning > 3) {
+ if (tm.tm_yday >= 7 - wday_of_year_beginning)
+ --week_number;
+ else {
+ const int days_of_last_year = days_in_year(tm.tm_year + 1900 - 1);
+ const int wday_of_last_year_beginning = (wday_of_year_beginning + 6 * days_of_last_year) % 7;
+ week_number = (days_of_last_year + wday_of_last_year_beginning) / 7 + 1;
+ if (wday_of_last_year_beginning > 3)
+ --week_number;
+ }
+ }
+ builder.appendf("%02d", week_number);
+ break;
+ }
+ case 'w':
+ builder.appendf("%d", tm.tm_wday);
+ break;
+ case 'W': {
+ const int wday_of_year_beginning = (tm.tm_wday + 6 + 6 * tm.tm_yday) % 7;
+ const int week_number = (tm.tm_yday + wday_of_year_beginning) / 7;
+ builder.appendf("%02d", week_number);
+ break;
+ }
+ case 'y':
+ builder.appendf("%02d", (tm.tm_year + 1900) % 100);
+ break;
+ case 'Y':
+ builder.appendf("%d", tm.tm_year + 1900);
+ break;
+ case '%':
+ builder.append('%');
+ break;
+ default:
+ return String();
+ }
+ }
+ }
+
+ return builder.build();
+}
+
+bool DateTime::is_before(const String& other) const
+{
+ auto now_string = String::formatted("{:04}{:02}{:02}{:02}{:02}{:02}Z", year(), month(), weekday(), hour(), minute(), second());
+ return __builtin_strcasecmp(now_string.characters(), other.characters()) < 0;
+}
+
+const LogStream& operator<<(const LogStream& stream, const DateTime& value)
+{
+ return stream << value.to_string();
+}
+
+}
diff --git a/Userland/Libraries/LibCore/DateTime.h b/Userland/Libraries/LibCore/DateTime.h
new file mode 100644
index 0000000000..15515cc0fd
--- /dev/null
+++ b/Userland/Libraries/LibCore/DateTime.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <time.h>
+
+namespace Core {
+
+// Represents a time in local time.
+class DateTime {
+public:
+ time_t timestamp() const { return m_timestamp; }
+
+ unsigned year() const { return m_year; }
+ unsigned month() const { return m_month; }
+ unsigned day() const { return m_day; }
+
+ unsigned hour() const { return m_hour; }
+ unsigned minute() const { return m_minute; }
+ unsigned second() const { return m_second; }
+ unsigned weekday() const;
+ unsigned days_in_month() const;
+ unsigned day_of_year() const;
+ bool is_leap_year() const;
+
+ void set_time(unsigned year, unsigned month = 1, unsigned day = 0, unsigned hour = 0, unsigned minute = 0, unsigned second = 0);
+ String to_string(const String& format = "%Y-%m-%d %H:%M:%S") const;
+
+ static DateTime create(unsigned year, unsigned month = 1, unsigned day = 0, unsigned hour = 0, unsigned minute = 0, unsigned second = 0);
+ static DateTime now();
+ static DateTime from_timestamp(time_t);
+
+ // FIXME: This should be replaced with a proper comparison
+ // operator when we get the equivalent of strptime
+ bool is_before(const String&) const;
+
+private:
+ time_t m_timestamp { 0 };
+ unsigned m_year { 0 };
+ unsigned m_month { 0 };
+ unsigned m_day { 0 };
+ unsigned m_hour { 0 };
+ unsigned m_minute { 0 };
+ unsigned m_second { 0 };
+};
+
+const LogStream& operator<<(const LogStream&, const DateTime&);
+
+}
diff --git a/Userland/Libraries/LibCore/DirIterator.cpp b/Userland/Libraries/LibCore/DirIterator.cpp
new file mode 100644
index 0000000000..3dbeecf24f
--- /dev/null
+++ b/Userland/Libraries/LibCore/DirIterator.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Vector.h>
+#include <LibCore/DirIterator.h>
+#include <errno.h>
+
+namespace Core {
+
+DirIterator::DirIterator(const StringView& path, Flags flags)
+ : m_path(path)
+ , m_flags(flags)
+{
+ m_dir = opendir(path.to_string().characters());
+ if (!m_dir) {
+ m_error = errno;
+ }
+}
+
+DirIterator::~DirIterator()
+{
+ if (m_dir) {
+ closedir(m_dir);
+ m_dir = nullptr;
+ }
+}
+
+bool DirIterator::advance_next()
+{
+ if (!m_dir)
+ return false;
+
+ while (true) {
+ errno = 0;
+ auto* de = readdir(m_dir);
+ if (!de) {
+ m_error = errno;
+ m_next = String();
+ return false;
+ }
+
+ m_next = de->d_name;
+ if (m_next.is_null())
+ return false;
+
+ if (m_flags & Flags::SkipDots && m_next.starts_with('.'))
+ continue;
+
+ if (m_flags & Flags::SkipParentAndBaseDir && (m_next == "." || m_next == ".."))
+ continue;
+
+ return !m_next.is_empty();
+ }
+}
+
+bool DirIterator::has_next()
+{
+ if (!m_next.is_null())
+ return true;
+
+ return advance_next();
+}
+
+String DirIterator::next_path()
+{
+ if (m_next.is_null())
+ advance_next();
+
+ auto tmp = m_next;
+ m_next = String();
+ return tmp;
+}
+
+String DirIterator::next_full_path()
+{
+ return String::formatted("{}/{}", m_path, next_path());
+}
+
+String find_executable_in_path(String filename)
+{
+ if (filename.starts_with('/')) {
+ if (access(filename.characters(), X_OK) == 0)
+ return filename;
+
+ return {};
+ }
+
+ for (auto directory : String { getenv("PATH") }.split(':')) {
+ auto fullpath = String::formatted("{}/{}", directory, filename);
+
+ if (access(fullpath.characters(), X_OK) == 0)
+ return fullpath;
+ }
+
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibCore/DirIterator.h b/Userland/Libraries/LibCore/DirIterator.h
new file mode 100644
index 0000000000..7f423b4f17
--- /dev/null
+++ b/Userland/Libraries/LibCore/DirIterator.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <dirent.h>
+#include <string.h>
+
+namespace Core {
+
+class DirIterator {
+public:
+ enum Flags {
+ NoFlags = 0x0,
+ SkipDots = 0x1,
+ SkipParentAndBaseDir = 0x2,
+ };
+
+ DirIterator(const StringView& path, Flags = Flags::NoFlags);
+ ~DirIterator();
+
+ bool has_error() const { return m_error != 0; }
+ int error() const { return m_error; }
+ const char* error_string() const { return strerror(m_error); }
+ bool has_next();
+ String next_path();
+ String next_full_path();
+
+private:
+ DIR* m_dir = nullptr;
+ int m_error = 0;
+ String m_next;
+ String m_path;
+ int m_flags;
+
+ bool advance_next();
+};
+
+String find_executable_in_path(String filename);
+
+}
diff --git a/Userland/Libraries/LibCore/DirectoryWatcher.cpp b/Userland/Libraries/LibCore/DirectoryWatcher.cpp
new file mode 100644
index 0000000000..a363c2461d
--- /dev/null
+++ b/Userland/Libraries/LibCore/DirectoryWatcher.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DirectoryWatcher.h"
+#include <AK/LexicalPath.h>
+#include <AK/Optional.h>
+#include <LibCore/DirIterator.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+namespace Core {
+
+// Only supported in serenity mode because we use `watch_file`
+#ifdef __serenity__
+
+DirectoryWatcher::DirectoryWatcher(const String& path)
+ : m_path(path)
+{
+ m_watcher_fd = watch_file(path.characters(), path.length());
+ ASSERT(m_watcher_fd != -1);
+}
+
+DirectoryWatcher::~DirectoryWatcher()
+{
+ close(m_watcher_fd);
+}
+
+Optional<DirectoryWatcher::Event> DirectoryWatcher::wait_for_event()
+{
+ InodeWatcherEvent event {};
+ int rc = read(m_watcher_fd, &event, sizeof(event));
+ if (rc <= 0)
+ return {};
+
+ Event result;
+ if (event.type == InodeWatcherEvent::Type::ChildAdded)
+ result.type = Event::Type::ChildAdded;
+ else if (event.type == InodeWatcherEvent::Type::ChildRemoved)
+ result.type = Event::Type::ChildRemoved;
+ else
+ return {};
+
+ auto child_path = get_child_with_inode_index(event.inode_index);
+ if (!LexicalPath(child_path).is_valid())
+ return {};
+
+ result.child_path = child_path;
+ return result;
+}
+
+String DirectoryWatcher::get_child_with_inode_index(unsigned child_inode_index) const
+{
+ DirIterator iterator(m_path, Core::DirIterator::SkipDots);
+ if (iterator.has_error()) {
+ return {};
+ }
+
+ while (iterator.has_next()) {
+ auto child_full_path = String::formatted("{}/{}", m_path, iterator.next_path());
+ struct stat st;
+
+ if (lstat(child_full_path.characters(), &st)) {
+ return {};
+ }
+
+ if (st.st_ino == child_inode_index) {
+ return child_full_path;
+ }
+ }
+ return {};
+}
+
+#endif
+
+}
diff --git a/Userland/Libraries/LibCore/DirectoryWatcher.h b/Userland/Libraries/LibCore/DirectoryWatcher.h
new file mode 100644
index 0000000000..63be2c1aa7
--- /dev/null
+++ b/Userland/Libraries/LibCore/DirectoryWatcher.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/Noncopyable.h>
+#include <AK/String.h>
+#include <Kernel/API/InodeWatcherEvent.h>
+
+namespace Core {
+
+class DirectoryWatcher {
+ AK_MAKE_NONCOPYABLE(DirectoryWatcher);
+
+public:
+ explicit DirectoryWatcher(const String& path);
+ ~DirectoryWatcher();
+
+ struct Event {
+ enum class Type {
+ ChildAdded,
+ ChildRemoved,
+ };
+ Type type;
+ String child_path;
+ };
+
+ Optional<Event> wait_for_event();
+
+private:
+ String get_child_with_inode_index(unsigned) const;
+
+ String m_path;
+ int m_watcher_fd { -1 };
+};
+
+}
diff --git a/Userland/Libraries/LibCore/ElapsedTimer.cpp b/Userland/Libraries/LibCore/ElapsedTimer.cpp
new file mode 100644
index 0000000000..9d0fc95378
--- /dev/null
+++ b/Userland/Libraries/LibCore/ElapsedTimer.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/Time.h>
+#include <LibCore/ElapsedTimer.h>
+#include <sys/time.h>
+#include <time.h>
+
+namespace Core {
+
+void ElapsedTimer::start()
+{
+ m_valid = true;
+ timespec now_spec;
+ clock_gettime(m_precise ? CLOCK_MONOTONIC : CLOCK_MONOTONIC_COARSE, &now_spec);
+ m_origin_time.tv_sec = now_spec.tv_sec;
+ m_origin_time.tv_usec = now_spec.tv_nsec / 1000;
+}
+
+int ElapsedTimer::elapsed() const
+{
+ ASSERT(is_valid());
+ struct timeval now;
+ timespec now_spec;
+ clock_gettime(m_precise ? CLOCK_MONOTONIC : CLOCK_MONOTONIC_COARSE, &now_spec);
+ now.tv_sec = now_spec.tv_sec;
+ now.tv_usec = now_spec.tv_nsec / 1000;
+ struct timeval diff;
+ timeval_sub(now, m_origin_time, diff);
+ return diff.tv_sec * 1000 + diff.tv_usec / 1000;
+}
+
+}
diff --git a/Userland/Libraries/LibCore/ElapsedTimer.h b/Userland/Libraries/LibCore/ElapsedTimer.h
new file mode 100644
index 0000000000..224eb139e3
--- /dev/null
+++ b/Userland/Libraries/LibCore/ElapsedTimer.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/time.h>
+
+namespace Core {
+
+class ElapsedTimer {
+public:
+ ElapsedTimer(bool precise = false)
+ : m_precise(precise)
+ {
+ }
+
+ bool is_valid() const { return m_valid; }
+ void start();
+ int elapsed() const;
+
+ const struct timeval& origin_time() const { return m_origin_time; }
+
+private:
+ bool m_precise { false };
+ bool m_valid { false };
+ struct timeval m_origin_time {
+ 0, 0
+ };
+};
+
+}
diff --git a/Userland/Libraries/LibCore/Event.cpp b/Userland/Libraries/LibCore/Event.cpp
new file mode 100644
index 0000000000..fac44e5537
--- /dev/null
+++ b/Userland/Libraries/LibCore/Event.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/WeakPtr.h>
+#include <LibCore/Event.h>
+#include <LibCore/Object.h>
+
+namespace Core {
+
+ChildEvent::ChildEvent(Type type, Object& child, Object* insertion_before_child)
+ : Core::Event(type)
+ , m_child(child.make_weak_ptr())
+ , m_insertion_before_child(AK::try_make_weak_ptr(insertion_before_child))
+{
+}
+
+ChildEvent::~ChildEvent()
+{
+}
+
+Object* ChildEvent::child()
+{
+ if (auto ref = m_child.strong_ref())
+ return ref.ptr();
+ return nullptr;
+}
+
+const Object* ChildEvent::child() const
+{
+ if (auto ref = m_child.strong_ref())
+ return ref.ptr();
+ return nullptr;
+}
+
+Object* ChildEvent::insertion_before_child()
+{
+ if (auto ref = m_insertion_before_child.strong_ref())
+ return ref.ptr();
+ return nullptr;
+}
+
+const Object* ChildEvent::insertion_before_child() const
+{
+ if (auto ref = m_insertion_before_child.strong_ref())
+ return ref.ptr();
+ return nullptr;
+}
+
+}
diff --git a/Userland/Libraries/LibCore/Event.h b/Userland/Libraries/LibCore/Event.h
new file mode 100644
index 0000000000..46dc3dc375
--- /dev/null
+++ b/Userland/Libraries/LibCore/Event.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <AK/WeakPtr.h>
+#include <LibCore/Forward.h>
+
+namespace Core {
+
+class Event {
+public:
+ enum Type {
+ Invalid = 0,
+ Quit,
+ Timer,
+ NotifierRead,
+ NotifierWrite,
+ DeferredInvoke,
+ ChildAdded,
+ ChildRemoved,
+ Custom,
+ };
+
+ Event() { }
+ explicit Event(unsigned type)
+ : m_type(type)
+ {
+ }
+ virtual ~Event() { }
+
+ unsigned type() const { return m_type; }
+
+ bool is_accepted() const { return m_accepted; }
+ void accept() { m_accepted = true; }
+ void ignore() { m_accepted = false; }
+
+private:
+ unsigned m_type { Type::Invalid };
+ bool m_accepted { true };
+};
+
+class DeferredInvocationEvent : public Event {
+ friend class EventLoop;
+
+public:
+ DeferredInvocationEvent(Function<void(Object&)> invokee)
+ : Event(Event::Type::DeferredInvoke)
+ , m_invokee(move(invokee))
+ {
+ }
+
+private:
+ Function<void(Object&)> m_invokee;
+};
+
+class TimerEvent final : public Event {
+public:
+ explicit TimerEvent(int timer_id)
+ : Event(Event::Timer)
+ , m_timer_id(timer_id)
+ {
+ }
+ ~TimerEvent() { }
+
+ int timer_id() const { return m_timer_id; }
+
+private:
+ int m_timer_id;
+};
+
+class NotifierReadEvent final : public Event {
+public:
+ explicit NotifierReadEvent(int fd)
+ : Event(Event::NotifierRead)
+ , m_fd(fd)
+ {
+ }
+ ~NotifierReadEvent() { }
+
+ int fd() const { return m_fd; }
+
+private:
+ int m_fd;
+};
+
+class NotifierWriteEvent final : public Event {
+public:
+ explicit NotifierWriteEvent(int fd)
+ : Event(Event::NotifierWrite)
+ , m_fd(fd)
+ {
+ }
+ ~NotifierWriteEvent() { }
+
+ int fd() const { return m_fd; }
+
+private:
+ int m_fd;
+};
+
+class ChildEvent final : public Event {
+public:
+ ChildEvent(Type, Object& child, Object* insertion_before_child = nullptr);
+ ~ChildEvent();
+
+ Object* child();
+ const Object* child() const;
+
+ Object* insertion_before_child();
+ const Object* insertion_before_child() const;
+
+private:
+ WeakPtr<Object> m_child;
+ WeakPtr<Object> m_insertion_before_child;
+};
+
+class CustomEvent : public Event {
+public:
+ CustomEvent(int custom_type)
+ : Event(Event::Type::Custom)
+ , m_custom_type(custom_type)
+ {
+ }
+ ~CustomEvent() { }
+
+ int custom_type() const { return m_custom_type; }
+
+private:
+ int m_custom_type { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibCore/EventLoop.cpp b/Userland/Libraries/LibCore/EventLoop.cpp
new file mode 100644
index 0000000000..c13cc986ea
--- /dev/null
+++ b/Userland/Libraries/LibCore/EventLoop.cpp
@@ -0,0 +1,835 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Badge.h>
+#include <AK/ByteBuffer.h>
+#include <AK/IDAllocator.h>
+#include <AK/JsonObject.h>
+#include <AK/JsonValue.h>
+#include <AK/NeverDestroyed.h>
+#include <AK/Singleton.h>
+#include <AK/TemporaryChange.h>
+#include <AK/Time.h>
+#include <LibCore/Event.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/LocalServer.h>
+#include <LibCore/LocalSocket.h>
+#include <LibCore/Notifier.h>
+#include <LibCore/Object.h>
+#include <LibCore/SyscallUtils.h>
+#include <LibThread/Lock.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+
+//#define EVENTLOOP_DEBUG
+//#define DEFERRED_INVOKE_DEBUG
+
+namespace Core {
+
+class RPCClient;
+
+struct EventLoopTimer {
+ int timer_id { 0 };
+ int interval { 0 };
+ timeval fire_time { 0, 0 };
+ bool should_reload { false };
+ TimerShouldFireWhenNotVisible fire_when_not_visible { TimerShouldFireWhenNotVisible::No };
+ WeakPtr<Object> owner;
+
+ void reload(const timeval& now);
+ bool has_expired(const timeval& now) const;
+};
+
+struct EventLoop::Private {
+ LibThread::Lock lock;
+};
+
+static EventLoop* s_main_event_loop;
+static Vector<EventLoop*>* s_event_loop_stack;
+static NeverDestroyed<IDAllocator> s_id_allocator;
+static HashMap<int, NonnullOwnPtr<EventLoopTimer>>* s_timers;
+static HashTable<Notifier*>* s_notifiers;
+int EventLoop::s_wake_pipe_fds[2];
+static RefPtr<LocalServer> s_rpc_server;
+HashMap<int, RefPtr<RPCClient>> s_rpc_clients;
+
+class SignalHandlers : public RefCounted<SignalHandlers> {
+ AK_MAKE_NONCOPYABLE(SignalHandlers);
+ AK_MAKE_NONMOVABLE(SignalHandlers);
+
+public:
+ SignalHandlers(int signo, void (*handle_signal)(int));
+ ~SignalHandlers();
+
+ void dispatch();
+ int add(Function<void(int)>&& handler);
+ bool remove(int handler_id);
+
+ bool is_empty() const
+ {
+ if (m_calling_handlers) {
+ for (auto& handler : m_handlers_pending) {
+ if (handler.value)
+ return false; // an add is pending
+ }
+ }
+ return m_handlers.is_empty();
+ }
+
+ bool have(int handler_id) const
+ {
+ if (m_calling_handlers) {
+ auto it = m_handlers_pending.find(handler_id);
+ if (it != m_handlers_pending.end()) {
+ if (!it->value)
+ return false; // a deletion is pending
+ }
+ }
+ return m_handlers.contains(handler_id);
+ }
+
+ int m_signo;
+ void (*m_original_handler)(int); // TODO: can't use sighandler_t?
+ HashMap<int, Function<void(int)>> m_handlers;
+ HashMap<int, Function<void(int)>> m_handlers_pending;
+ bool m_calling_handlers { false };
+};
+
+struct SignalHandlersInfo {
+ HashMap<int, NonnullRefPtr<SignalHandlers>> signal_handlers;
+ int next_signal_id { 0 };
+};
+
+template<bool create_if_null = true>
+inline SignalHandlersInfo* signals_info()
+{
+ static SignalHandlersInfo* s_signals;
+ return AK::Singleton<SignalHandlersInfo>::get(s_signals);
+}
+
+pid_t EventLoop::s_pid;
+
+class RPCClient : public Object {
+ C_OBJECT(RPCClient)
+public:
+ explicit RPCClient(RefPtr<LocalSocket> socket)
+ : m_socket(move(socket))
+ , m_client_id(s_id_allocator->allocate())
+ {
+ s_rpc_clients.set(m_client_id, this);
+ add_child(*m_socket);
+ m_socket->on_ready_to_read = [this] {
+ u32 length;
+ int nread = m_socket->read((u8*)&length, sizeof(length));
+ if (nread == 0) {
+#ifdef EVENTLOOP_DEBUG
+ dbgln("RPC client disconnected");
+#endif
+ shutdown();
+ return;
+ }
+ ASSERT(nread == sizeof(length));
+ auto request = m_socket->read(length);
+
+ auto request_json = JsonValue::from_string(request);
+ if (!request_json.has_value() || !request_json.value().is_object()) {
+ dbgln("RPC client sent invalid request");
+ shutdown();
+ return;
+ }
+
+ handle_request(request_json.value().as_object());
+ };
+ }
+ virtual ~RPCClient() override
+ {
+ if (auto inspected_object = m_inspected_object.strong_ref())
+ inspected_object->decrement_inspector_count({});
+ }
+
+ void send_response(const JsonObject& response)
+ {
+ auto serialized = response.to_string();
+ u32 length = serialized.length();
+ m_socket->write((const u8*)&length, sizeof(length));
+ m_socket->write(serialized);
+ }
+
+ void handle_request(const JsonObject& request)
+ {
+ auto type = request.get("type").as_string_or({});
+
+ if (type.is_null()) {
+ dbgln("RPC client sent request without type field");
+ return;
+ }
+
+ if (type == "Identify") {
+ JsonObject response;
+ response.set("type", type);
+ response.set("pid", getpid());
+#ifdef __serenity__
+ char buffer[1024];
+ if (get_process_name(buffer, sizeof(buffer)) >= 0) {
+ response.set("process_name", buffer);
+ } else {
+ response.set("process_name", JsonValue());
+ }
+#endif
+ send_response(response);
+ return;
+ }
+
+ if (type == "GetAllObjects") {
+ JsonObject response;
+ response.set("type", type);
+ JsonArray objects;
+ for (auto& object : Object::all_objects()) {
+ JsonObject json_object;
+ object.save_to(json_object);
+ objects.append(move(json_object));
+ }
+ response.set("objects", move(objects));
+ send_response(response);
+ return;
+ }
+
+ if (type == "SetInspectedObject") {
+ auto address = request.get("address").to_number<FlatPtr>();
+ for (auto& object : Object::all_objects()) {
+ if ((FlatPtr)&object == address) {
+ if (auto inspected_object = m_inspected_object.strong_ref())
+ inspected_object->decrement_inspector_count({});
+ m_inspected_object = object;
+ object.increment_inspector_count({});
+ break;
+ }
+ }
+ return;
+ }
+
+ if (type == "SetProperty") {
+ auto address = request.get("address").to_number<FlatPtr>();
+ for (auto& object : Object::all_objects()) {
+ if ((FlatPtr)&object == address) {
+ bool success = object.set_property(request.get("name").to_string(), request.get("value"));
+ JsonObject response;
+ response.set("type", "SetProperty");
+ response.set("success", success);
+ send_response(response);
+ break;
+ }
+ }
+ return;
+ }
+
+ if (type == "Disconnect") {
+ shutdown();
+ return;
+ }
+ }
+
+ void shutdown()
+ {
+ s_rpc_clients.remove(m_client_id);
+ s_id_allocator->deallocate(m_client_id);
+ }
+
+private:
+ RefPtr<LocalSocket> m_socket;
+ WeakPtr<Object> m_inspected_object;
+ int m_client_id { -1 };
+};
+
+EventLoop::EventLoop()
+ : m_private(make<Private>())
+{
+ if (!s_event_loop_stack) {
+ s_event_loop_stack = new Vector<EventLoop*>;
+ s_timers = new HashMap<int, NonnullOwnPtr<EventLoopTimer>>;
+ s_notifiers = new HashTable<Notifier*>;
+ }
+
+ if (!s_main_event_loop) {
+ s_main_event_loop = this;
+ s_pid = getpid();
+#if defined(SOCK_NONBLOCK)
+ int rc = pipe2(s_wake_pipe_fds, O_CLOEXEC);
+#else
+ int rc = pipe(s_wake_pipe_fds);
+ fcntl(s_wake_pipe_fds[0], F_SETFD, FD_CLOEXEC);
+ fcntl(s_wake_pipe_fds[1], F_SETFD, FD_CLOEXEC);
+
+#endif
+ ASSERT(rc == 0);
+ s_event_loop_stack->append(this);
+
+ if (!s_rpc_server) {
+ if (!start_rpc_server())
+ dbgln("Core::EventLoop: Failed to start an RPC server");
+ }
+ }
+
+#ifdef EVENTLOOP_DEBUG
+ dbgln("{} Core::EventLoop constructed :)", getpid());
+#endif
+}
+
+EventLoop::~EventLoop()
+{
+}
+
+bool EventLoop::start_rpc_server()
+{
+ s_rpc_server = LocalServer::construct();
+ s_rpc_server->set_name("Core::EventLoop_RPC_server");
+ s_rpc_server->on_ready_to_accept = [&] {
+ RPCClient::construct(s_rpc_server->accept());
+ };
+ return s_rpc_server->listen(String::formatted("/tmp/rpc/{}", getpid()));
+}
+
+EventLoop& EventLoop::main()
+{
+ ASSERT(s_main_event_loop);
+ return *s_main_event_loop;
+}
+
+EventLoop& EventLoop::current()
+{
+ EventLoop* event_loop = s_event_loop_stack->last();
+ ASSERT(event_loop != nullptr);
+ return *event_loop;
+}
+
+void EventLoop::quit(int code)
+{
+#ifdef EVENTLOOP_DEBUG
+ dbgln("Core::EventLoop::quit({})", code);
+#endif
+ m_exit_requested = true;
+ m_exit_code = code;
+}
+
+void EventLoop::unquit()
+{
+#ifdef EVENTLOOP_DEBUG
+ dbgln("Core::EventLoop::unquit()");
+#endif
+ m_exit_requested = false;
+ m_exit_code = 0;
+}
+
+struct EventLoopPusher {
+public:
+ EventLoopPusher(EventLoop& event_loop)
+ : m_event_loop(event_loop)
+ {
+ if (&m_event_loop != s_main_event_loop) {
+ m_event_loop.take_pending_events_from(EventLoop::current());
+ s_event_loop_stack->append(&event_loop);
+ }
+ }
+ ~EventLoopPusher()
+ {
+ if (&m_event_loop != s_main_event_loop) {
+ s_event_loop_stack->take_last();
+ EventLoop::current().take_pending_events_from(m_event_loop);
+ }
+ }
+
+private:
+ EventLoop& m_event_loop;
+};
+
+int EventLoop::exec()
+{
+ EventLoopPusher pusher(*this);
+ for (;;) {
+ if (m_exit_requested)
+ return m_exit_code;
+ pump();
+ }
+ ASSERT_NOT_REACHED();
+}
+
+void EventLoop::pump(WaitMode mode)
+{
+ wait_for_event(mode);
+
+ decltype(m_queued_events) events;
+ {
+ LOCKER(m_private->lock);
+ events = move(m_queued_events);
+ }
+
+ for (size_t i = 0; i < events.size(); ++i) {
+ auto& queued_event = events.at(i);
+ auto receiver = queued_event.receiver.strong_ref();
+ auto& event = *queued_event.event;
+#ifdef EVENTLOOP_DEBUG
+ if (receiver)
+ dbgln("Core::EventLoop: {} event {}", *receiver, event.type());
+#endif
+ if (!receiver) {
+ switch (event.type()) {
+ case Event::Quit:
+ ASSERT_NOT_REACHED();
+ return;
+ default:
+#ifdef EVENTLOOP_DEBUG
+ dbgln("Event type {} with no receiver :(", event.type());
+#endif
+ break;
+ }
+ } else if (event.type() == Event::Type::DeferredInvoke) {
+#ifdef DEFERRED_INVOKE_DEBUG
+ dbgln("DeferredInvoke: receiver = {}", *receiver);
+#endif
+ static_cast<DeferredInvocationEvent&>(event).m_invokee(*receiver);
+ } else {
+ NonnullRefPtr<Object> protector(*receiver);
+ receiver->dispatch_event(event);
+ }
+
+ if (m_exit_requested) {
+ LOCKER(m_private->lock);
+#ifdef EVENTLOOP_DEBUG
+ dbgln("Core::EventLoop: Exit requested. Rejigging {} events.", events.size() - i);
+#endif
+ decltype(m_queued_events) new_event_queue;
+ new_event_queue.ensure_capacity(m_queued_events.size() + events.size());
+ for (++i; i < events.size(); ++i)
+ new_event_queue.unchecked_append(move(events[i]));
+ new_event_queue.append(move(m_queued_events));
+ m_queued_events = move(new_event_queue);
+ return;
+ }
+ }
+}
+
+void EventLoop::post_event(Object& receiver, NonnullOwnPtr<Event>&& event)
+{
+ LOCKER(m_private->lock);
+#ifdef EVENTLOOP_DEBUG
+ dbgln("Core::EventLoop::post_event: ({}) << receivier={}, event={}", m_queued_events.size(), receiver, event);
+#endif
+ m_queued_events.empend(receiver, move(event));
+}
+
+SignalHandlers::SignalHandlers(int signo, void (*handle_signal)(int))
+ : m_signo(signo)
+ , m_original_handler(signal(signo, handle_signal))
+{
+#ifdef EVENTLOOP_DEBUG
+ dbgln("Core::EventLoop: Registered handler for signal {}", m_signo);
+#endif
+}
+
+SignalHandlers::~SignalHandlers()
+{
+#ifdef EVENTLOOP_DEBUG
+ dbgln("Core::EventLoop: Unregistering handler for signal {}", m_signo);
+#endif
+ signal(m_signo, m_original_handler);
+}
+
+void SignalHandlers::dispatch()
+{
+ TemporaryChange change(m_calling_handlers, true);
+ for (auto& handler : m_handlers)
+ handler.value(m_signo);
+ if (!m_handlers_pending.is_empty()) {
+ // Apply pending adds/removes
+ for (auto& handler : m_handlers_pending) {
+ if (handler.value) {
+ auto result = m_handlers.set(handler.key, move(handler.value));
+ ASSERT(result == AK::HashSetResult::InsertedNewEntry);
+ } else {
+ m_handlers.remove(handler.key);
+ }
+ }
+ m_handlers_pending.clear();
+ }
+}
+
+int SignalHandlers::add(Function<void(int)>&& handler)
+{
+ int id = ++signals_info()->next_signal_id; // TODO: worry about wrapping and duplicates?
+ if (m_calling_handlers)
+ m_handlers_pending.set(id, move(handler));
+ else
+ m_handlers.set(id, move(handler));
+ return id;
+}
+
+bool SignalHandlers::remove(int handler_id)
+{
+ ASSERT(handler_id != 0);
+ if (m_calling_handlers) {
+ auto it = m_handlers.find(handler_id);
+ if (it != m_handlers.end()) {
+ // Mark pending remove
+ m_handlers_pending.set(handler_id, {});
+ return true;
+ }
+ it = m_handlers_pending.find(handler_id);
+ if (it != m_handlers_pending.end()) {
+ if (!it->value)
+ return false; // already was marked as deleted
+ it->value = nullptr;
+ return true;
+ }
+ return false;
+ }
+ return m_handlers.remove(handler_id);
+}
+
+void EventLoop::dispatch_signal(int signo)
+{
+ auto& info = *signals_info();
+ auto handlers = info.signal_handlers.find(signo);
+ if (handlers != info.signal_handlers.end()) {
+ // Make sure we bump the ref count while dispatching the handlers!
+ // This allows a handler to unregister/register while the handlers
+ // are being called!
+ auto handler = handlers->value;
+#ifdef EVENTLOOP_DEBUG
+ dbgln("Core::EventLoop: dispatching signal {}", signo);
+#endif
+ handler->dispatch();
+ }
+}
+
+void EventLoop::handle_signal(int signo)
+{
+ ASSERT(signo != 0);
+ // We MUST check if the current pid still matches, because there
+ // is a window between fork() and exec() where a signal delivered
+ // to our fork could be inadvertedly routed to the parent process!
+ if (getpid() == s_pid) {
+ int nwritten = write(s_wake_pipe_fds[1], &signo, sizeof(signo));
+ if (nwritten < 0) {
+ perror("EventLoop::register_signal: write");
+ ASSERT_NOT_REACHED();
+ }
+ } else {
+ // We're a fork who received a signal, reset s_pid
+ s_pid = 0;
+ }
+}
+
+int EventLoop::register_signal(int signo, Function<void(int)> handler)
+{
+ ASSERT(signo != 0);
+ auto& info = *signals_info();
+ auto handlers = info.signal_handlers.find(signo);
+ if (handlers == info.signal_handlers.end()) {
+ auto signal_handlers = adopt(*new SignalHandlers(signo, EventLoop::handle_signal));
+ auto handler_id = signal_handlers->add(move(handler));
+ info.signal_handlers.set(signo, move(signal_handlers));
+ return handler_id;
+ } else {
+ return handlers->value->add(move(handler));
+ }
+}
+
+void EventLoop::unregister_signal(int handler_id)
+{
+ ASSERT(handler_id != 0);
+ int remove_signo = 0;
+ auto& info = *signals_info();
+ for (auto& h : info.signal_handlers) {
+ auto& handlers = *h.value;
+ if (handlers.remove(handler_id)) {
+ if (handlers.is_empty())
+ remove_signo = handlers.m_signo;
+ break;
+ }
+ }
+ if (remove_signo != 0)
+ info.signal_handlers.remove(remove_signo);
+}
+
+void EventLoop::notify_forked(ForkEvent event)
+{
+ switch (event) {
+ case ForkEvent::Child:
+ s_main_event_loop = nullptr;
+ s_event_loop_stack->clear();
+ s_timers->clear();
+ s_notifiers->clear();
+ if (auto* info = signals_info<false>()) {
+ info->signal_handlers.clear();
+ info->next_signal_id = 0;
+ }
+ s_pid = 0;
+ s_rpc_server = nullptr;
+ s_rpc_clients.clear();
+ return;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+void EventLoop::wait_for_event(WaitMode mode)
+{
+ fd_set rfds;
+ fd_set wfds;
+retry:
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+
+ int max_fd = 0;
+ auto add_fd_to_set = [&max_fd](int fd, fd_set& set) {
+ FD_SET(fd, &set);
+ if (fd > max_fd)
+ max_fd = fd;
+ };
+
+ int max_fd_added = -1;
+ add_fd_to_set(s_wake_pipe_fds[0], rfds);
+ max_fd = max(max_fd, max_fd_added);
+ for (auto& notifier : *s_notifiers) {
+ if (notifier->event_mask() & Notifier::Read)
+ add_fd_to_set(notifier->fd(), rfds);
+ if (notifier->event_mask() & Notifier::Write)
+ add_fd_to_set(notifier->fd(), wfds);
+ if (notifier->event_mask() & Notifier::Exceptional)
+ ASSERT_NOT_REACHED();
+ }
+
+ bool queued_events_is_empty;
+ {
+ LOCKER(m_private->lock);
+ queued_events_is_empty = m_queued_events.is_empty();
+ }
+
+ timeval now;
+ struct timeval timeout = { 0, 0 };
+ bool should_wait_forever = false;
+ if (mode == WaitMode::WaitForEvents && queued_events_is_empty) {
+ auto next_timer_expiration = get_next_timer_expiration();
+ if (next_timer_expiration.has_value()) {
+ timespec now_spec;
+ clock_gettime(CLOCK_MONOTONIC_COARSE, &now_spec);
+ now.tv_sec = now_spec.tv_sec;
+ now.tv_usec = now_spec.tv_nsec / 1000;
+ timeval_sub(next_timer_expiration.value(), now, timeout);
+ if (timeout.tv_sec < 0 || (timeout.tv_sec == 0 && timeout.tv_usec < 0)) {
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ }
+ } else {
+ should_wait_forever = true;
+ }
+ }
+
+try_select_again:
+ int marked_fd_count = select(max_fd + 1, &rfds, &wfds, nullptr, should_wait_forever ? nullptr : &timeout);
+ if (marked_fd_count < 0) {
+ int saved_errno = errno;
+ if (saved_errno == EINTR) {
+ if (m_exit_requested)
+ return;
+ goto try_select_again;
+ }
+#ifdef EVENTLOOP_DEBUG
+ dbgln("Core::EventLoop::wait_for_event: {} ({}: {})", marked_fd_count, saved_errno, strerror(saved_errno));
+#endif
+ // Blow up, similar to Core::safe_syscall.
+ ASSERT_NOT_REACHED();
+ }
+ if (FD_ISSET(s_wake_pipe_fds[0], &rfds)) {
+ int wake_events[8];
+ auto nread = read(s_wake_pipe_fds[0], wake_events, sizeof(wake_events));
+ if (nread < 0) {
+ perror("read from wake pipe");
+ ASSERT_NOT_REACHED();
+ }
+ ASSERT(nread > 0);
+ bool wake_requested = false;
+ int event_count = nread / sizeof(wake_events[0]);
+ for (int i = 0; i < event_count; i++) {
+ if (wake_events[i] != 0)
+ dispatch_signal(wake_events[i]);
+ else
+ wake_requested = true;
+ }
+
+ if (!wake_requested && nread == sizeof(wake_events))
+ goto retry;
+ }
+
+ if (!s_timers->is_empty()) {
+ timespec now_spec;
+ clock_gettime(CLOCK_MONOTONIC_COARSE, &now_spec);
+ now.tv_sec = now_spec.tv_sec;
+ now.tv_usec = now_spec.tv_nsec / 1000;
+ }
+
+ for (auto& it : *s_timers) {
+ auto& timer = *it.value;
+ if (!timer.has_expired(now))
+ continue;
+ auto owner = timer.owner.strong_ref();
+ if (timer.fire_when_not_visible == TimerShouldFireWhenNotVisible::No
+ && owner && !owner->is_visible_for_timer_purposes()) {
+ continue;
+ }
+#ifdef EVENTLOOP_DEBUG
+ dbgln("Core::EventLoop: Timer {} has expired, sending Core::TimerEvent to {}", timer.timer_id, *owner);
+#endif
+ if (owner)
+ post_event(*owner, make<TimerEvent>(timer.timer_id));
+ if (timer.should_reload) {
+ timer.reload(now);
+ } else {
+ // FIXME: Support removing expired timers that don't want to reload.
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ if (!marked_fd_count)
+ return;
+
+ for (auto& notifier : *s_notifiers) {
+ if (FD_ISSET(notifier->fd(), &rfds)) {
+ if (notifier->event_mask() & Notifier::Event::Read)
+ post_event(*notifier, make<NotifierReadEvent>(notifier->fd()));
+ }
+ if (FD_ISSET(notifier->fd(), &wfds)) {
+ if (notifier->event_mask() & Notifier::Event::Write)
+ post_event(*notifier, make<NotifierWriteEvent>(notifier->fd()));
+ }
+ }
+}
+
+bool EventLoopTimer::has_expired(const timeval& now) const
+{
+ return now.tv_sec > fire_time.tv_sec || (now.tv_sec == fire_time.tv_sec && now.tv_usec >= fire_time.tv_usec);
+}
+
+void EventLoopTimer::reload(const timeval& now)
+{
+ fire_time = now;
+ fire_time.tv_sec += interval / 1000;
+ fire_time.tv_usec += (interval % 1000) * 1000;
+}
+
+Optional<struct timeval> EventLoop::get_next_timer_expiration()
+{
+ Optional<struct timeval> soonest {};
+ for (auto& it : *s_timers) {
+ auto& fire_time = it.value->fire_time;
+ auto owner = it.value->owner.strong_ref();
+ if (it.value->fire_when_not_visible == TimerShouldFireWhenNotVisible::No
+ && owner && !owner->is_visible_for_timer_purposes()) {
+ continue;
+ }
+ if (!soonest.has_value() || fire_time.tv_sec < soonest.value().tv_sec || (fire_time.tv_sec == soonest.value().tv_sec && fire_time.tv_usec < soonest.value().tv_usec))
+ soonest = fire_time;
+ }
+ return soonest;
+}
+
+int EventLoop::register_timer(Object& object, int milliseconds, bool should_reload, TimerShouldFireWhenNotVisible fire_when_not_visible)
+{
+ ASSERT(milliseconds >= 0);
+ auto timer = make<EventLoopTimer>();
+ timer->owner = object;
+ timer->interval = milliseconds;
+ timeval now;
+ timespec now_spec;
+ clock_gettime(CLOCK_MONOTONIC_COARSE, &now_spec);
+ now.tv_sec = now_spec.tv_sec;
+ now.tv_usec = now_spec.tv_nsec / 1000;
+ timer->reload(now);
+ timer->should_reload = should_reload;
+ timer->fire_when_not_visible = fire_when_not_visible;
+ int timer_id = s_id_allocator->allocate();
+ timer->timer_id = timer_id;
+ s_timers->set(timer_id, move(timer));
+ return timer_id;
+}
+
+bool EventLoop::unregister_timer(int timer_id)
+{
+ s_id_allocator->deallocate(timer_id);
+ auto it = s_timers->find(timer_id);
+ if (it == s_timers->end())
+ return false;
+ s_timers->remove(it);
+ return true;
+}
+
+void EventLoop::register_notifier(Badge<Notifier>, Notifier& notifier)
+{
+ s_notifiers->set(&notifier);
+}
+
+void EventLoop::unregister_notifier(Badge<Notifier>, Notifier& notifier)
+{
+ s_notifiers->remove(&notifier);
+}
+
+void EventLoop::wake()
+{
+ int wake_event = 0;
+ int nwritten = write(s_wake_pipe_fds[1], &wake_event, sizeof(wake_event));
+ if (nwritten < 0) {
+ perror("EventLoop::wake: write");
+ ASSERT_NOT_REACHED();
+ }
+}
+
+EventLoop::QueuedEvent::QueuedEvent(Object& receiver, NonnullOwnPtr<Event> event)
+ : receiver(receiver)
+ , event(move(event))
+{
+}
+
+EventLoop::QueuedEvent::QueuedEvent(QueuedEvent&& other)
+ : receiver(other.receiver)
+ , event(move(other.event))
+{
+}
+
+EventLoop::QueuedEvent::~QueuedEvent()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibCore/EventLoop.h b/Userland/Libraries/LibCore/EventLoop.h
new file mode 100644
index 0000000000..d299b93407
--- /dev/null
+++ b/Userland/Libraries/LibCore/EventLoop.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/Function.h>
+#include <AK/HashMap.h>
+#include <AK/Noncopyable.h>
+#include <AK/NonnullOwnPtr.h>
+#include <AK/Vector.h>
+#include <AK/WeakPtr.h>
+#include <LibCore/Forward.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+namespace Core {
+
+class EventLoop {
+public:
+ EventLoop();
+ ~EventLoop();
+
+ int exec();
+
+ enum class WaitMode {
+ WaitForEvents,
+ PollForEvents,
+ };
+
+ // processe events, generally called by exec() in a loop.
+ // this should really only be used for integrating with other event loops
+ void pump(WaitMode = WaitMode::WaitForEvents);
+
+ void post_event(Object& receiver, NonnullOwnPtr<Event>&&);
+
+ static EventLoop& main();
+ static EventLoop& current();
+
+ bool was_exit_requested() const { return m_exit_requested; }
+
+ static int register_timer(Object&, int milliseconds, bool should_reload, TimerShouldFireWhenNotVisible);
+ static bool unregister_timer(int timer_id);
+
+ static void register_notifier(Badge<Notifier>, Notifier&);
+ static void unregister_notifier(Badge<Notifier>, Notifier&);
+
+ void quit(int);
+ void unquit();
+
+ void take_pending_events_from(EventLoop& other)
+ {
+ m_queued_events.append(move(other.m_queued_events));
+ }
+
+ static void wake();
+
+ static int register_signal(int signo, Function<void(int)> handler);
+ static void unregister_signal(int handler_id);
+
+ // Note: Boost uses Parent/Child/Prepare, but we don't really have anything
+ // interesting to do in the parent or before forking.
+ enum class ForkEvent {
+ Child,
+ };
+ static void notify_forked(ForkEvent);
+
+private:
+ bool start_rpc_server();
+ void wait_for_event(WaitMode);
+ Optional<struct timeval> get_next_timer_expiration();
+ static void dispatch_signal(int);
+ static void handle_signal(int);
+
+ struct QueuedEvent {
+ AK_MAKE_NONCOPYABLE(QueuedEvent);
+
+ public:
+ QueuedEvent(Object& receiver, NonnullOwnPtr<Event>);
+ QueuedEvent(QueuedEvent&&);
+ ~QueuedEvent();
+
+ WeakPtr<Object> receiver;
+ NonnullOwnPtr<Event> event;
+ };
+
+ Vector<QueuedEvent, 64> m_queued_events;
+ static pid_t s_pid;
+
+ bool m_exit_requested { false };
+ int m_exit_code { 0 };
+
+ static int s_wake_pipe_fds[2];
+
+ struct Private;
+ NonnullOwnPtr<Private> m_private;
+};
+
+}
diff --git a/Userland/Libraries/LibCore/File.cpp b/Userland/Libraries/LibCore/File.cpp
new file mode 100644
index 0000000000..07192a9b18
--- /dev/null
+++ b/Userland/Libraries/LibCore/File.cpp
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef __serenity__
+# include <Kernel/API/Syscall.h>
+#endif
+#include <AK/ScopeGuard.h>
+#include <LibCore/File.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+namespace Core {
+
+Result<NonnullRefPtr<File>, String> File::open(const String& filename, IODevice::OpenMode mode, mode_t permissions)
+{
+ auto file = File::construct(filename);
+ if (!file->open_impl(mode, permissions))
+ return String(file->error_string());
+ return file;
+}
+
+File::File(const StringView& filename, Object* parent)
+ : IODevice(parent)
+ , m_filename(filename)
+{
+}
+
+File::~File()
+{
+ if (m_should_close_file_descriptor == ShouldCloseFileDescriptor::Yes && mode() != NotOpen)
+ close();
+}
+
+bool File::open(int fd, IODevice::OpenMode mode, ShouldCloseFileDescriptor should_close)
+{
+ set_fd(fd);
+ set_mode(mode);
+ m_should_close_file_descriptor = should_close;
+ return true;
+}
+
+bool File::open(IODevice::OpenMode mode)
+{
+ return open_impl(mode, 0666);
+}
+
+bool File::open_impl(IODevice::OpenMode mode, mode_t permissions)
+{
+ ASSERT(!m_filename.is_null());
+ int flags = 0;
+ if ((mode & IODevice::ReadWrite) == IODevice::ReadWrite) {
+ flags |= O_RDWR | O_CREAT;
+ } else if (mode & IODevice::ReadOnly) {
+ flags |= O_RDONLY;
+ } else if (mode & IODevice::WriteOnly) {
+ flags |= O_WRONLY | O_CREAT;
+ bool should_truncate = !((mode & IODevice::Append) || (mode & IODevice::MustBeNew));
+ if (should_truncate)
+ flags |= O_TRUNC;
+ }
+ if (mode & IODevice::Append)
+ flags |= O_APPEND;
+ if (mode & IODevice::Truncate)
+ flags |= O_TRUNC;
+ if (mode & IODevice::MustBeNew)
+ flags |= O_EXCL;
+ int fd = ::open(m_filename.characters(), flags, permissions);
+ if (fd < 0) {
+ set_error(errno);
+ return false;
+ }
+
+ set_fd(fd);
+ set_mode(mode);
+ return true;
+}
+
+bool File::is_directory() const
+{
+ struct stat stat;
+ if (fstat(fd(), &stat) < 0)
+ return false;
+ return S_ISDIR(stat.st_mode);
+}
+
+bool File::is_directory(const String& filename)
+{
+ struct stat st;
+ if (stat(filename.characters(), &st) < 0)
+ return false;
+ return S_ISDIR(st.st_mode);
+}
+
+bool File::exists(const String& filename)
+{
+ struct stat st;
+ return stat(filename.characters(), &st) == 0;
+}
+
+String File::real_path_for(const String& filename)
+{
+ if (filename.is_null())
+ return {};
+ auto* path = realpath(filename.characters(), nullptr);
+ String real_path(path);
+ free(path);
+ return real_path;
+}
+
+bool File::ensure_parent_directories(const String& path)
+{
+ ASSERT(path.starts_with("/"));
+
+ int saved_errno = 0;
+ ScopeGuard restore_errno = [&saved_errno] { errno = saved_errno; };
+
+ char* parent_buffer = strdup(path.characters());
+ ScopeGuard free_buffer = [parent_buffer] { free(parent_buffer); };
+
+ const char* parent = dirname(parent_buffer);
+
+ int rc = mkdir(parent, 0755);
+ saved_errno = errno;
+
+ if (rc == 0 || errno == EEXIST)
+ return true;
+
+ if (errno != ENOENT)
+ return false;
+
+ bool ok = ensure_parent_directories(parent);
+ saved_errno = errno;
+ if (!ok)
+ return false;
+
+ rc = mkdir(parent, 0755);
+ saved_errno = errno;
+ return rc == 0;
+}
+
+#ifdef __serenity__
+
+String File::read_link(const StringView& link_path)
+{
+ // First, try using a 64-byte buffer, that ought to be enough for anybody.
+ char small_buffer[64];
+ Syscall::SC_readlink_params small_params {
+ { link_path.characters_without_null_termination(), link_path.length() },
+ { small_buffer, sizeof(small_buffer) }
+ };
+ int rc = syscall(SC_readlink, &small_params);
+ if (rc < 0) {
+ errno = -rc;
+ return {};
+ }
+ size_t size = rc;
+ // If the call was successful, the syscall (unlike the LibC wrapper)
+ // returns the full size of the link. Let's see if our small buffer
+ // was enough to read the whole link.
+ if (size <= sizeof(small_buffer))
+ return { small_buffer, size };
+ // Nope, but at least now we know the right size.
+ char* large_buffer_ptr;
+ auto large_buffer = StringImpl::create_uninitialized(size, large_buffer_ptr);
+ Syscall::SC_readlink_params large_params {
+ { link_path.characters_without_null_termination(), link_path.length() },
+ { large_buffer_ptr, (size_t)size }
+ };
+ rc = syscall(SC_readlink, &large_params);
+ if (rc < 0) {
+ errno = -rc;
+ return {};
+ }
+ size_t new_size = rc;
+ if (new_size == size)
+ return { *large_buffer };
+
+ // If we're here, the symlink has changed while we were looking at it.
+ // If it became shorter, our buffer is valid, we just have to trim it a bit.
+ if (new_size < size)
+ return { large_buffer_ptr, new_size };
+ // Otherwise, here's not much we can do, unless we want to loop endlessly
+ // in this case. Let's leave it up to the caller whether to loop.
+ errno = -EAGAIN;
+ return {};
+}
+
+#else
+
+// This is a sad version for other systems. It has to always make a copy of the
+// link path, and to always make two syscalls to get the right size first.
+String File::read_link(const StringView& link_path)
+{
+ String link_path_str = link_path;
+ struct stat statbuf;
+ int rc = lstat(link_path_str.characters(), &statbuf);
+ if (rc < 0)
+ return {};
+ char* buffer_ptr;
+ auto buffer = StringImpl::create_uninitialized(statbuf.st_size, buffer_ptr);
+ rc = readlink(link_path_str.characters(), buffer_ptr, statbuf.st_size);
+ if (rc < 0)
+ return {};
+ // (See above.)
+ if (rc == statbuf.st_size)
+ return { *buffer };
+ return { buffer_ptr, (size_t)rc };
+}
+
+#endif
+
+static RefPtr<File> stdin_file;
+static RefPtr<File> stdout_file;
+static RefPtr<File> stderr_file;
+
+NonnullRefPtr<File> File::standard_input()
+{
+ if (!stdin_file) {
+ stdin_file = File::construct();
+ stdin_file->open(STDIN_FILENO, IODevice::ReadOnly, ShouldCloseFileDescriptor::No);
+ }
+ return *stdin_file;
+}
+
+NonnullRefPtr<File> File::standard_output()
+{
+ if (!stdout_file) {
+ stdout_file = File::construct();
+ stdout_file->open(STDOUT_FILENO, IODevice::WriteOnly, ShouldCloseFileDescriptor::No);
+ }
+ return *stdout_file;
+}
+
+NonnullRefPtr<File> File::standard_error()
+{
+ if (!stderr_file) {
+ stderr_file = File::construct();
+ stderr_file->open(STDERR_FILENO, IODevice::WriteOnly, ShouldCloseFileDescriptor::No);
+ }
+ return *stderr_file;
+}
+}
diff --git a/Userland/Libraries/LibCore/File.h b/Userland/Libraries/LibCore/File.h
new file mode 100644
index 0000000000..040a2813b7
--- /dev/null
+++ b/Userland/Libraries/LibCore/File.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Result.h>
+#include <AK/String.h>
+#include <LibCore/IODevice.h>
+
+namespace Core {
+
+class File final : public IODevice {
+ C_OBJECT(File)
+public:
+ virtual ~File() override;
+
+ static Result<NonnullRefPtr<File>, String> open(const String& filename, IODevice::OpenMode, mode_t = 0644);
+
+ String filename() const { return m_filename; }
+ void set_filename(const StringView& filename) { m_filename = filename; }
+
+ bool is_directory() const;
+ static bool is_directory(const String& filename);
+
+ static bool exists(const String& filename);
+ static String real_path_for(const String& filename);
+ static String read_link(const StringView& link_path);
+ static bool ensure_parent_directories(const String& path);
+
+ virtual bool open(IODevice::OpenMode) override;
+
+ enum class ShouldCloseFileDescriptor {
+ No = 0,
+ Yes
+ };
+ bool open(int fd, IODevice::OpenMode, ShouldCloseFileDescriptor);
+
+ static NonnullRefPtr<File> standard_input();
+ static NonnullRefPtr<File> standard_output();
+ static NonnullRefPtr<File> standard_error();
+
+private:
+ File(Object* parent = nullptr)
+ : IODevice(parent)
+ {
+ }
+ explicit File(const StringView&, Object* parent = nullptr);
+
+ bool open_impl(IODevice::OpenMode, mode_t);
+
+ String m_filename;
+ ShouldCloseFileDescriptor m_should_close_file_descriptor { ShouldCloseFileDescriptor::Yes };
+};
+
+}
diff --git a/Userland/Libraries/LibCore/FileStream.h b/Userland/Libraries/LibCore/FileStream.h
new file mode 100644
index 0000000000..0c8774d28a
--- /dev/null
+++ b/Userland/Libraries/LibCore/FileStream.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Buffered.h>
+#include <AK/ByteBuffer.h>
+#include <AK/Stream.h>
+#include <LibCore/File.h>
+
+namespace Core {
+
+class InputFileStream final : public InputStream {
+public:
+ explicit InputFileStream(NonnullRefPtr<File> file)
+ : m_file(file)
+ {
+ }
+
+ static Result<InputFileStream, String> open(StringView filename, IODevice::OpenMode mode = IODevice::OpenMode::ReadOnly, mode_t permissions = 0644)
+ {
+ ASSERT((mode & 0xf) == IODevice::OpenMode::ReadOnly || (mode & 0xf) == IODevice::OpenMode::ReadWrite);
+
+ auto file_result = File::open(filename, mode, permissions);
+
+ if (file_result.is_error())
+ return file_result.error();
+
+ return InputFileStream { file_result.value() };
+ }
+
+ static Result<Buffered<InputFileStream>, String> open_buffered(StringView filename, IODevice::OpenMode mode = IODevice::OpenMode::ReadOnly, mode_t permissions = 0644)
+ {
+ ASSERT((mode & 0xf) == IODevice::OpenMode::ReadOnly || (mode & 0xf) == IODevice::OpenMode::ReadWrite);
+
+ auto file_result = File::open(filename, mode, permissions);
+
+ if (file_result.is_error())
+ return file_result.error();
+
+ return Buffered<InputFileStream> { file_result.value() };
+ }
+
+ size_t read(Bytes bytes) override
+ {
+ if (has_any_error())
+ return 0;
+
+ const auto buffer = m_file->read(bytes.size());
+ return buffer.bytes().copy_to(bytes);
+ }
+
+ bool read_or_error(Bytes bytes) override
+ {
+ if (read(bytes) < bytes.size()) {
+ set_fatal_error();
+ return false;
+ }
+
+ return true;
+ }
+
+ bool discard_or_error(size_t count) override { return m_file->seek(count, IODevice::SeekMode::FromCurrentPosition); }
+
+ bool unreliable_eof() const override { return m_file->eof(); }
+
+ void close()
+ {
+ if (!m_file->close())
+ set_fatal_error();
+ }
+
+private:
+ NonnullRefPtr<File> m_file;
+};
+
+class OutputFileStream : public OutputStream {
+public:
+ explicit OutputFileStream(NonnullRefPtr<File> file)
+ : m_file(file)
+ {
+ }
+
+ static Result<OutputFileStream, String> open(StringView filename, IODevice::OpenMode mode = IODevice::OpenMode::WriteOnly, mode_t permissions = 0644)
+ {
+ ASSERT((mode & 0xf) == IODevice::OpenMode::WriteOnly || (mode & 0xf) == IODevice::OpenMode::ReadWrite);
+
+ auto file_result = File::open(filename, mode, permissions);
+
+ if (file_result.is_error())
+ return file_result.error();
+
+ return OutputFileStream { file_result.value() };
+ }
+
+ static Result<Buffered<OutputFileStream>, String> open_buffered(StringView filename, IODevice::OpenMode mode = IODevice::OpenMode::WriteOnly, mode_t permissions = 0644)
+ {
+ ASSERT((mode & 0xf) == IODevice::OpenMode::WriteOnly || (mode & 0xf) == IODevice::OpenMode::ReadWrite);
+
+ auto file_result = File::open(filename, mode, permissions);
+
+ if (file_result.is_error())
+ return file_result.error();
+
+ return Buffered<OutputFileStream> { file_result.value() };
+ }
+
+ static OutputFileStream standard_output()
+ {
+ return OutputFileStream { Core::File::standard_output() };
+ }
+
+ static Buffered<OutputFileStream> stdout_buffered()
+ {
+ return Buffered<OutputFileStream> { Core::File::standard_output() };
+ }
+
+ size_t write(ReadonlyBytes bytes) override
+ {
+ if (!m_file->write(bytes.data(), bytes.size())) {
+ set_fatal_error();
+ return 0;
+ }
+
+ return bytes.size();
+ }
+
+ bool write_or_error(ReadonlyBytes bytes) override
+ {
+ if (write(bytes) < bytes.size()) {
+ set_fatal_error();
+ return false;
+ }
+
+ return true;
+ }
+
+ void close()
+ {
+ if (!m_file->close())
+ set_fatal_error();
+ }
+
+private:
+ NonnullRefPtr<File> m_file;
+};
+
+}
diff --git a/Userland/Libraries/LibCore/Forward.h b/Userland/Libraries/LibCore/Forward.h
new file mode 100644
index 0000000000..09497cb0bf
--- /dev/null
+++ b/Userland/Libraries/LibCore/Forward.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace Core {
+
+class ArgsParser;
+class ChildEvent;
+class ConfigFile;
+class CustomEvent;
+class DateTime;
+class DirIterator;
+class ElapsedTimer;
+class Event;
+class EventLoop;
+class File;
+class IODevice;
+class LocalServer;
+class LocalSocket;
+class MimeData;
+class NetworkJob;
+class NetworkResponse;
+class Notifier;
+class Object;
+class ProcessStatisticsReader;
+class Socket;
+class SocketAddress;
+class TCPServer;
+class TCPSocket;
+class Timer;
+class TimerEvent;
+class UDPServer;
+class UDPSocket;
+
+enum class TimerShouldFireWhenNotVisible;
+
+}
diff --git a/Userland/Libraries/LibCore/GetPassword.cpp b/Userland/Libraries/LibCore/GetPassword.cpp
new file mode 100644
index 0000000000..dfcff21583
--- /dev/null
+++ b/Userland/Libraries/LibCore/GetPassword.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca>
+ * Copyright (c) 2021, Emanuele Torre <torreemanuele6@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/GetPassword.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <termios.h>
+
+namespace Core {
+
+Result<String, OSError> get_password(const StringView& prompt)
+{
+ if (write(STDOUT_FILENO, prompt.characters_without_null_termination(), prompt.length()) < 0)
+ return OSError(errno);
+
+ termios original {};
+ if (tcgetattr(STDIN_FILENO, &original) < 0)
+ return OSError(errno);
+
+ termios no_echo = original;
+ no_echo.c_lflag &= ~ECHO;
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &no_echo) < 0)
+ return OSError(errno);
+
+ char* password = nullptr;
+ size_t n = 0;
+
+ auto line_length = getline(&password, &n, stdin);
+ auto saved_errno = errno;
+
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &original);
+ putchar('\n');
+
+ if (line_length < 0)
+ return OSError(saved_errno);
+
+ ASSERT(line_length != 0);
+
+ // Remove trailing '\n' read by getline().
+ password[line_length - 1] = '\0';
+
+ String s(password);
+ free(password);
+ return s;
+}
+}
diff --git a/Userland/Libraries/LibCore/GetPassword.h b/Userland/Libraries/LibCore/GetPassword.h
new file mode 100644
index 0000000000..c62a071935
--- /dev/null
+++ b/Userland/Libraries/LibCore/GetPassword.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/OSError.h>
+#include <AK/Result.h>
+#include <AK/String.h>
+
+namespace Core {
+
+Result<String, OSError> get_password(const StringView& prompt = "Password: ");
+
+}
diff --git a/Userland/Libraries/LibCore/Gzip.cpp b/Userland/Libraries/LibCore/Gzip.cpp
new file mode 100644
index 0000000000..49cf14c3a0
--- /dev/null
+++ b/Userland/Libraries/LibCore/Gzip.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ByteBuffer.h>
+#include <AK/Optional.h>
+#include <LibCore/Gzip.h>
+#include <LibCore/puff.h>
+#include <limits.h>
+#include <stddef.h>
+
+//#define DEBUG_GZIP
+
+namespace Core {
+
+bool Gzip::is_compressed(const ByteBuffer& data)
+{
+ return data.size() > 2 && data[0] == 0x1F && data[1] == 0x8b;
+}
+
+// skips the gzip header
+// see: https://tools.ietf.org/html/rfc1952#page-5
+static Optional<ByteBuffer> get_gzip_payload(const ByteBuffer& data)
+{
+ size_t current = 0;
+ auto read_byte = [&]() {
+ if (current >= data.size()) {
+ ASSERT_NOT_REACHED();
+ return (u8)0;
+ }
+ return data[current++];
+ };
+
+#ifdef DEBUG_GZIP
+ dbgln("get_gzip_payload: Skipping over gzip header.");
+#endif
+
+ // Magic Header
+ if (read_byte() != 0x1F || read_byte() != 0x8B) {
+ dbgln("get_gzip_payload: Wrong magic number.");
+ return Optional<ByteBuffer>();
+ }
+
+ // Compression method
+ auto method = read_byte();
+ if (method != 8) {
+ dbgln("get_gzip_payload: Wrong compression method={}", method);
+ return Optional<ByteBuffer>();
+ }
+
+ u8 flags = read_byte();
+
+ // Timestamp, Extra flags, OS
+ current += 6;
+
+ // FEXTRA
+ if (flags & 4) {
+ u16 length = read_byte() & read_byte() << 8;
+ dbgln("get_gzip_payload: Header has FEXTRA flag set. length={}", length);
+ current += length;
+ }
+
+ // FNAME
+ if (flags & 8) {
+ dbgln("get_gzip_payload: Header has FNAME flag set.");
+ while (read_byte() != '\0')
+ ;
+ }
+
+ // FCOMMENT
+ if (flags & 16) {
+ dbgln("get_gzip_payload: Header has FCOMMENT flag set.");
+ while (read_byte() != '\0')
+ ;
+ }
+
+ // FHCRC
+ if (flags & 2) {
+ dbgln("get_gzip_payload: Header has FHCRC flag set.");
+ current += 2;
+ }
+
+ auto new_size = data.size() - current;
+#ifdef DEBUG_GZIP
+ dbg() << "get_gzip_payload: Returning slice from " << current << " with size " << new_size;
+#endif
+ return data.slice(current, new_size);
+}
+
+Optional<ByteBuffer> Gzip::decompress(const ByteBuffer& data)
+{
+ ASSERT(is_compressed(data));
+
+#ifdef DEBUG_GZIP
+ dbg() << "Gzip::decompress: Decompressing gzip compressed data. Size = " << data.size();
+#endif
+ auto optional_payload = get_gzip_payload(data);
+ if (!optional_payload.has_value()) {
+ return Optional<ByteBuffer>();
+ }
+
+ auto source = optional_payload.value();
+ unsigned long source_len = source.size();
+ auto destination = ByteBuffer::create_uninitialized(1024);
+ while (true) {
+ unsigned long destination_len = destination.size();
+
+#ifdef DEBUG_GZIP
+ dbg() << "Gzip::decompress: Calling puff()\n"
+ << " destination_data = " << destination.data() << "\n"
+ << " destination_len = " << destination_len << "\n"
+ << " source_data = " << source.data() << "\n"
+ << " source_len = " << source_len;
+#endif
+
+ auto puff_ret = puff(
+ destination.data(), &destination_len,
+ source.data(), &source_len);
+
+ if (puff_ret == 0) {
+#ifdef DEBUG_GZIP
+ dbgln("Gzip::decompress: Decompression success.");
+#endif
+ destination.trim(destination_len);
+ break;
+ }
+
+ if (puff_ret == 1) {
+ // FIXME: Find a better way of decompressing without needing to try over and over again.
+#ifdef DEBUG_GZIP
+ dbgln("Gzip::decompress: Output buffer exhausted. Growing.");
+#endif
+ destination.grow(destination.size() * 2);
+ } else {
+ dbgln("Gzip::decompress: Error. puff() returned: {}", puff_ret);
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ return destination;
+}
+
+}
diff --git a/Userland/Libraries/LibCore/Gzip.h b/Userland/Libraries/LibCore/Gzip.h
new file mode 100644
index 0000000000..a96af59536
--- /dev/null
+++ b/Userland/Libraries/LibCore/Gzip.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/Optional.h>
+#include <AK/String.h>
+
+namespace Core {
+
+class Gzip {
+public:
+ static bool is_compressed(const ByteBuffer& data);
+ static Optional<ByteBuffer> decompress(const ByteBuffer& data);
+};
+
+}
diff --git a/Userland/Libraries/LibCore/IODevice.cpp b/Userland/Libraries/LibCore/IODevice.cpp
new file mode 100644
index 0000000000..453526e408
--- /dev/null
+++ b/Userland/Libraries/LibCore/IODevice.cpp
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ByteBuffer.h>
+#include <AK/PrintfImplementation.h>
+#include <LibCore/IODevice.h>
+#include <LibCore/SyscallUtils.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+namespace Core {
+
+IODevice::IODevice(Object* parent)
+ : Object(parent)
+{
+}
+
+IODevice::~IODevice()
+{
+}
+
+const char* IODevice::error_string() const
+{
+ return strerror(m_error);
+}
+
+int IODevice::read(u8* buffer, int length)
+{
+ auto read_buffer = read(length);
+ if (read_buffer.is_null())
+ return 0;
+ memcpy(buffer, read_buffer.data(), length);
+ return read_buffer.size();
+}
+
+ByteBuffer IODevice::read(size_t max_size)
+{
+ if (m_fd < 0)
+ return {};
+ if (!max_size)
+ return {};
+ auto buffer = ByteBuffer::create_uninitialized(max_size);
+ auto* buffer_ptr = (char*)buffer.data();
+ size_t remaining_buffer_space = buffer.size();
+ size_t taken_from_buffered = 0;
+ if (!m_buffered_data.is_empty()) {
+ taken_from_buffered = min(remaining_buffer_space, m_buffered_data.size());
+ memcpy(buffer_ptr, m_buffered_data.data(), taken_from_buffered);
+ Vector<u8> new_buffered_data;
+ new_buffered_data.append(m_buffered_data.data() + taken_from_buffered, m_buffered_data.size() - taken_from_buffered);
+ m_buffered_data = move(new_buffered_data);
+ remaining_buffer_space -= taken_from_buffered;
+ buffer_ptr += taken_from_buffered;
+ }
+ if (!remaining_buffer_space)
+ return buffer;
+ int nread = ::read(m_fd, buffer_ptr, remaining_buffer_space);
+ if (nread < 0) {
+ if (taken_from_buffered) {
+ buffer.trim(taken_from_buffered);
+ return buffer;
+ }
+ set_error(errno);
+ return {};
+ }
+ if (nread == 0) {
+ set_eof(true);
+ if (taken_from_buffered) {
+ buffer.trim(taken_from_buffered);
+ return buffer;
+ }
+ return {};
+ }
+ buffer.trim(taken_from_buffered + nread);
+ return buffer;
+}
+
+bool IODevice::can_read_from_fd() const
+{
+ // FIXME: Can we somehow remove this once Core::Socket is implemented using non-blocking sockets?
+ fd_set rfds;
+ FD_ZERO(&rfds);
+ FD_SET(m_fd, &rfds);
+ struct timeval timeout {
+ 0, 0
+ };
+ int rc = Core::safe_syscall(select, m_fd + 1, &rfds, nullptr, nullptr, &timeout);
+ if (rc < 0) {
+ // NOTE: We don't set m_error here.
+ perror("IODevice::can_read: select");
+ return false;
+ }
+ return FD_ISSET(m_fd, &rfds);
+}
+
+bool IODevice::can_read_line() const
+{
+ if (m_eof && !m_buffered_data.is_empty())
+ return true;
+ if (m_buffered_data.contains_slow('\n'))
+ return true;
+ if (!can_read_from_fd())
+ return false;
+ populate_read_buffer();
+ if (m_eof && !m_buffered_data.is_empty())
+ return true;
+ return m_buffered_data.contains_slow('\n');
+}
+
+bool IODevice::can_read() const
+{
+ return !m_buffered_data.is_empty() || can_read_from_fd();
+}
+
+ByteBuffer IODevice::read_all()
+{
+ off_t file_size = 0;
+ struct stat st;
+ int rc = fstat(fd(), &st);
+ if (rc == 0)
+ file_size = st.st_size;
+
+ Vector<u8> data;
+ data.ensure_capacity(file_size);
+
+ if (!m_buffered_data.is_empty()) {
+ data.append(m_buffered_data.data(), m_buffered_data.size());
+ m_buffered_data.clear();
+ }
+
+ while (true) {
+ char read_buffer[4096];
+ int nread = ::read(m_fd, read_buffer, sizeof(read_buffer));
+ if (nread < 0) {
+ set_error(errno);
+ break;
+ }
+ if (nread == 0) {
+ set_eof(true);
+ break;
+ }
+ data.append((const u8*)read_buffer, nread);
+ }
+ if (data.is_empty())
+ return {};
+ return ByteBuffer::copy(data.data(), data.size());
+}
+
+String IODevice::read_line(size_t max_size)
+{
+ if (m_fd < 0)
+ return {};
+ if (!max_size)
+ return {};
+ if (!can_read_line())
+ return {};
+ if (m_eof) {
+ if (m_buffered_data.size() > max_size) {
+ dbgprintf("IODevice::read_line: At EOF but there's more than max_size(%zu) buffered\n", max_size);
+ return {};
+ }
+ auto line = String((const char*)m_buffered_data.data(), m_buffered_data.size(), Chomp);
+ m_buffered_data.clear();
+ return line;
+ }
+ auto line = ByteBuffer::create_uninitialized(max_size + 1);
+ size_t line_index = 0;
+ while (line_index < max_size) {
+ u8 ch = m_buffered_data[line_index];
+ line[line_index++] = ch;
+ if (ch == '\n') {
+ Vector<u8> new_buffered_data;
+ new_buffered_data.append(m_buffered_data.data() + line_index, m_buffered_data.size() - line_index);
+ m_buffered_data = move(new_buffered_data);
+ line.trim(line_index);
+ return String::copy(line, Chomp);
+ }
+ }
+ return {};
+}
+
+bool IODevice::populate_read_buffer() const
+{
+ if (m_fd < 0)
+ return false;
+ u8 buffer[1024];
+ int nread = ::read(m_fd, buffer, sizeof(buffer));
+ if (nread < 0) {
+ set_error(errno);
+ return false;
+ }
+ if (nread == 0) {
+ set_eof(true);
+ return false;
+ }
+ m_buffered_data.append(buffer, nread);
+ return true;
+}
+
+bool IODevice::close()
+{
+ if (fd() < 0 || mode() == NotOpen)
+ return false;
+ int rc = ::close(fd());
+ if (rc < 0) {
+ set_error(errno);
+ return false;
+ }
+ set_fd(-1);
+ set_mode(IODevice::NotOpen);
+ return true;
+}
+
+bool IODevice::seek(i64 offset, SeekMode mode, off_t* pos)
+{
+ int m = SEEK_SET;
+ switch (mode) {
+ case SeekMode::SetPosition:
+ m = SEEK_SET;
+ break;
+ case SeekMode::FromCurrentPosition:
+ m = SEEK_CUR;
+ break;
+ case SeekMode::FromEndPosition:
+ m = SEEK_END;
+ break;
+ }
+ off_t rc = lseek(m_fd, offset, m);
+ if (rc < 0) {
+ set_error(errno);
+ if (pos)
+ *pos = -1;
+ return false;
+ }
+ m_buffered_data.clear();
+ m_eof = false;
+ if (pos)
+ *pos = rc;
+ return true;
+}
+
+bool IODevice::truncate(off_t size)
+{
+ int rc = ftruncate(m_fd, size);
+ if (rc < 0) {
+ set_error(errno);
+ return false;
+ }
+ return true;
+}
+
+bool IODevice::write(const u8* data, int size)
+{
+ int rc = ::write(m_fd, data, size);
+ if (rc < 0) {
+ perror("IODevice::write: write");
+ set_error(errno);
+ return false;
+ }
+ return rc == size;
+}
+
+int IODevice::printf(const char* format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+ // FIXME: We're not propagating write() failures to client here!
+ int ret = printf_internal([this](char*&, char ch) {
+ write((const u8*)&ch, 1);
+ },
+ nullptr, format, ap);
+ va_end(ap);
+ return ret;
+}
+
+void IODevice::set_fd(int fd)
+{
+ if (m_fd == fd)
+ return;
+
+ m_fd = fd;
+ did_update_fd(fd);
+}
+
+bool IODevice::write(const StringView& v)
+{
+ return write((const u8*)v.characters_without_null_termination(), v.length());
+}
+
+LineIterator::LineIterator(IODevice& device, bool is_end)
+ : m_device(device)
+ , m_is_end(is_end)
+{
+ ++*this;
+}
+
+bool LineIterator::at_end() const
+{
+ return m_device->eof();
+}
+
+LineIterator& LineIterator::operator++()
+{
+ m_buffer = m_device->read_line();
+ return *this;
+}
+
+}
diff --git a/Userland/Libraries/LibCore/IODevice.h b/Userland/Libraries/LibCore/IODevice.h
new file mode 100644
index 0000000000..70a75532a1
--- /dev/null
+++ b/Userland/Libraries/LibCore/IODevice.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibCore/Object.h>
+
+namespace Core {
+
+// This is not necessarily a valid iterator in all contexts,
+// if we had concepts, this would be InputIterator, not Copyable, Movable.
+class LineIterator {
+ AK_MAKE_NONCOPYABLE(LineIterator);
+
+public:
+ explicit LineIterator(IODevice&, bool is_end = false);
+
+ bool operator==(const LineIterator& other) const { return &other == this || (at_end() && other.is_end()) || (other.at_end() && is_end()); }
+ bool is_end() const { return m_is_end; }
+ bool at_end() const;
+
+ LineIterator& operator++();
+
+ StringView operator*() const { return m_buffer; }
+
+private:
+ NonnullRefPtr<IODevice> m_device;
+ bool m_is_end { false };
+ String m_buffer;
+};
+
+class IODevice : public Object {
+ C_OBJECT_ABSTRACT(IODevice)
+public:
+ enum OpenMode {
+ NotOpen = 0,
+ ReadOnly = 1,
+ WriteOnly = 2,
+ ReadWrite = 3,
+ Append = 4,
+ Truncate = 8,
+ MustBeNew = 16,
+ };
+
+ virtual ~IODevice() override;
+
+ int fd() const { return m_fd; }
+ unsigned mode() const { return m_mode; }
+ bool is_open() const { return m_mode != NotOpen; }
+ bool eof() const { return m_eof; }
+
+ int error() const { return m_error; }
+ const char* error_string() const;
+
+ bool has_error() const { return m_error != 0; }
+
+ int read(u8* buffer, int length);
+
+ ByteBuffer read(size_t max_size);
+ ByteBuffer read_all();
+ String read_line(size_t max_size = 16384);
+
+ bool write(const u8*, int size);
+ bool write(const StringView&);
+
+ bool truncate(off_t);
+
+ bool can_read_line() const;
+
+ bool can_read() const;
+
+ enum class SeekMode {
+ SetPosition,
+ FromCurrentPosition,
+ FromEndPosition,
+ };
+
+ bool seek(i64, SeekMode = SeekMode::SetPosition, off_t* = nullptr);
+
+ virtual bool open(IODevice::OpenMode) = 0;
+ virtual bool close();
+
+ int printf(const char*, ...);
+
+ LineIterator line_begin() & { return LineIterator(*this); }
+ LineIterator line_end() { return LineIterator(*this, true); }
+
+protected:
+ explicit IODevice(Object* parent = nullptr);
+
+ void set_fd(int);
+ void set_mode(OpenMode mode) { m_mode = mode; }
+ void set_error(int error) const { m_error = error; }
+ void set_eof(bool eof) const { m_eof = eof; }
+
+ virtual void did_update_fd(int) { }
+
+private:
+ bool populate_read_buffer() const;
+ bool can_read_from_fd() const;
+
+ int m_fd { -1 };
+ OpenMode m_mode { NotOpen };
+ mutable int m_error { 0 };
+ mutable bool m_eof { false };
+ mutable Vector<u8> m_buffered_data;
+};
+
+}
diff --git a/Userland/Libraries/LibCore/IODeviceStreamReader.h b/Userland/Libraries/LibCore/IODeviceStreamReader.h
new file mode 100644
index 0000000000..0339c30272
--- /dev/null
+++ b/Userland/Libraries/LibCore/IODeviceStreamReader.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/StdLibExtras.h>
+#include <LibCore/IODevice.h>
+
+namespace Core {
+
+class IODeviceStreamReader {
+public:
+ IODeviceStreamReader(IODevice& device)
+ : m_device(device)
+ {
+ }
+
+ bool handle_read_failure()
+ {
+ return exchange(m_had_failure, false);
+ }
+
+ template<typename T>
+ IODeviceStreamReader& operator>>(T& value)
+ {
+ int nread = m_device.read((u8*)&value, sizeof(T));
+ ASSERT(nread == sizeof(T));
+ if (nread != sizeof(T))
+ m_had_failure = true;
+ return *this;
+ }
+
+private:
+ IODevice& m_device;
+ bool m_had_failure { false };
+};
+
+}
diff --git a/Userland/Libraries/LibCore/LocalServer.cpp b/Userland/Libraries/LibCore/LocalServer.cpp
new file mode 100644
index 0000000000..092cf82388
--- /dev/null
+++ b/Userland/Libraries/LibCore/LocalServer.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/LocalServer.h>
+#include <LibCore/LocalSocket.h>
+#include <LibCore/Notifier.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifndef SOCK_NONBLOCK
+# include <sys/ioctl.h>
+#endif
+
+namespace Core {
+
+LocalServer::LocalServer(Object* parent)
+ : Object(parent)
+{
+}
+
+LocalServer::~LocalServer()
+{
+ if (m_fd >= 0)
+ ::close(m_fd);
+}
+
+bool LocalServer::take_over_from_system_server()
+{
+ if (m_listening)
+ return false;
+
+ constexpr auto socket_takeover = "SOCKET_TAKEOVER";
+
+ if (getenv(socket_takeover)) {
+ // Sanity check: it has to be a socket.
+ struct stat stat;
+ int rc = fstat(3, &stat);
+ if (rc == 0 && S_ISSOCK(stat.st_mode)) {
+ // The SystemServer has passed us the socket as fd 3,
+ // so use that instead of creating our own.
+ m_fd = 3;
+ // It had to be !CLOEXEC for obvious reasons, but we
+ // don't need it to be !CLOEXEC anymore, so set the
+ // CLOEXEC flag now.
+ fcntl(m_fd, F_SETFD, FD_CLOEXEC);
+ // We wouldn't want our children to think we're passing
+ // them a socket either, so unset the env variable.
+ unsetenv(socket_takeover);
+
+ m_listening = true;
+ setup_notifier();
+ return true;
+ } else {
+ if (rc != 0)
+ perror("fstat");
+ dbgln("It's not a socket, what the heck??");
+ }
+ }
+
+ dbgln("Failed to take the socket over from SystemServer");
+
+ return false;
+}
+
+void LocalServer::setup_notifier()
+{
+ m_notifier = Notifier::construct(m_fd, Notifier::Event::Read, this);
+ m_notifier->on_ready_to_read = [this] {
+ if (on_ready_to_accept)
+ on_ready_to_accept();
+ };
+}
+
+bool LocalServer::listen(const String& address)
+{
+ if (m_listening)
+ return false;
+
+ int rc;
+
+#ifdef SOCK_NONBLOCK
+ m_fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+#else
+ m_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ int option = 1;
+ ioctl(m_fd, FIONBIO, &option);
+ fcntl(m_fd, F_SETFD, FD_CLOEXEC);
+#endif
+ ASSERT(m_fd >= 0);
+#ifndef __APPLE__
+ rc = fchmod(m_fd, 0600);
+ if (rc < 0) {
+ perror("fchmod");
+ ASSERT_NOT_REACHED();
+ }
+#endif
+
+ auto socket_address = SocketAddress::local(address);
+ auto un_optional = socket_address.to_sockaddr_un();
+ if (!un_optional.has_value()) {
+ perror("bind");
+ return false;
+ }
+ auto un = un_optional.value();
+ rc = ::bind(m_fd, (const sockaddr*)&un, sizeof(un));
+ if (rc < 0) {
+ perror("bind");
+ return false;
+ }
+
+ rc = ::listen(m_fd, 5);
+ if (rc < 0) {
+ perror("listen");
+ return false;
+ }
+
+ m_listening = true;
+ setup_notifier();
+ return true;
+}
+
+RefPtr<LocalSocket> LocalServer::accept()
+{
+ ASSERT(m_listening);
+ sockaddr_un un;
+ socklen_t un_size = sizeof(un);
+ int accepted_fd = ::accept(m_fd, (sockaddr*)&un, &un_size);
+ if (accepted_fd < 0) {
+ perror("accept");
+ return nullptr;
+ }
+
+ return LocalSocket::construct(accepted_fd);
+}
+
+}
diff --git a/Userland/Libraries/LibCore/LocalServer.h b/Userland/Libraries/LibCore/LocalServer.h
new file mode 100644
index 0000000000..bcb4fa6c00
--- /dev/null
+++ b/Userland/Libraries/LibCore/LocalServer.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibCore/Notifier.h>
+#include <LibCore/Object.h>
+
+namespace Core {
+
+class LocalServer : public Object {
+ C_OBJECT(LocalServer)
+public:
+ virtual ~LocalServer() override;
+
+ bool take_over_from_system_server();
+ bool is_listening() const { return m_listening; }
+ bool listen(const String& address);
+
+ RefPtr<LocalSocket> accept();
+
+ Function<void()> on_ready_to_accept;
+
+private:
+ explicit LocalServer(Object* parent = nullptr);
+
+ void setup_notifier();
+
+ int m_fd { -1 };
+ bool m_listening { false };
+ RefPtr<Notifier> m_notifier;
+};
+
+}
diff --git a/Userland/Libraries/LibCore/LocalSocket.cpp b/Userland/Libraries/LibCore/LocalSocket.cpp
new file mode 100644
index 0000000000..d49aa1d166
--- /dev/null
+++ b/Userland/Libraries/LibCore/LocalSocket.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/LocalSocket.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#ifndef SOCK_NONBLOCK
+# include <sys/ioctl.h>
+#endif
+
+namespace Core {
+
+LocalSocket::LocalSocket(int fd, Object* parent)
+ : Socket(Socket::Type::Local, parent)
+{
+ // NOTE: This constructor is used by LocalServer::accept(), so the socket is already connected.
+ m_connected = true;
+ set_fd(fd);
+ set_mode(IODevice::ReadWrite);
+ set_error(0);
+}
+
+LocalSocket::LocalSocket(Object* parent)
+ : Socket(Socket::Type::Local, parent)
+{
+
+#ifdef SOCK_NONBLOCK
+ int fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+#else
+ int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ int option = 1;
+ ioctl(fd, FIONBIO, &option);
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+#endif
+
+ if (fd < 0) {
+ set_error(errno);
+ } else {
+ set_fd(fd);
+ set_mode(IODevice::ReadWrite);
+ set_error(0);
+ }
+}
+
+LocalSocket::~LocalSocket()
+{
+}
+
+RefPtr<LocalSocket> LocalSocket::take_over_accepted_socket_from_system_server()
+{
+ constexpr auto socket_takeover = "SOCKET_TAKEOVER";
+ if (!getenv(socket_takeover))
+ return nullptr;
+
+ // The SystemServer has passed us the socket as fd 3,
+ // so use that instead of creating our own.
+ constexpr int fd = 3;
+
+ // Sanity check: it has to be a socket.
+ struct stat stat;
+ int rc = fstat(fd, &stat);
+ if (rc < 0 || !S_ISSOCK(stat.st_mode)) {
+ if (rc != 0)
+ perror("fstat");
+ dbgln("ERROR: The fd we got from SystemServer is not a socket");
+ return nullptr;
+ }
+
+ auto socket = LocalSocket::construct(fd);
+
+ // It had to be !CLOEXEC for obvious reasons, but we
+ // don't need it to be !CLOEXEC anymore, so set the
+ // CLOEXEC flag now.
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ // We wouldn't want our children to think we're passing
+ // them a socket either, so unset the env variable.
+ unsetenv(socket_takeover);
+ return socket;
+}
+
+}
diff --git a/Userland/Libraries/LibCore/LocalSocket.h b/Userland/Libraries/LibCore/LocalSocket.h
new file mode 100644
index 0000000000..921e16aaa2
--- /dev/null
+++ b/Userland/Libraries/LibCore/LocalSocket.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibCore/Socket.h>
+
+namespace Core {
+
+class LocalSocket final : public Socket {
+ C_OBJECT(LocalSocket)
+public:
+ virtual ~LocalSocket() override;
+
+ static RefPtr<LocalSocket> take_over_accepted_socket_from_system_server();
+
+private:
+ explicit LocalSocket(Object* parent = nullptr);
+ LocalSocket(int fd, Object* parent = nullptr);
+};
+
+}
diff --git a/Userland/Libraries/LibCore/MimeData.cpp b/Userland/Libraries/LibCore/MimeData.cpp
new file mode 100644
index 0000000000..e48908661c
--- /dev/null
+++ b/Userland/Libraries/LibCore/MimeData.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibCore/MimeData.h>
+
+namespace Core {
+
+Vector<String> MimeData::formats() const
+{
+ Vector<String> mime_types;
+ mime_types.ensure_capacity(m_data.size());
+ for (auto it : m_data)
+ mime_types.unchecked_append(it.key);
+ return mime_types;
+}
+
+Vector<URL> MimeData::urls() const
+{
+ auto it = m_data.find("text/uri-list");
+ if (it == m_data.end())
+ return {};
+ Vector<URL> urls;
+ for (auto& line : StringView(it->value).split_view('\n')) {
+ urls.append(URL(line));
+ }
+ return urls;
+}
+
+void MimeData::set_urls(const Vector<URL>& urls)
+{
+ StringBuilder builder;
+ for (auto& url : urls) {
+ builder.append(url.to_string());
+ builder.append('\n');
+ }
+ set_data("text/uri-list", builder.to_byte_buffer());
+}
+
+String MimeData::text() const
+{
+ return String::copy(m_data.get("text/plain").value_or({}));
+}
+
+void MimeData::set_text(const String& text)
+{
+ set_data("text/plain", text.to_byte_buffer());
+}
+
+String guess_mime_type_based_on_filename(const StringView& path)
+{
+ if (path.ends_with(".pbm", CaseSensitivity::CaseInsensitive))
+ return "image/x‑portable‑bitmap";
+ if (path.ends_with(".pgm", CaseSensitivity::CaseInsensitive))
+ return "image/x‑portable‑graymap";
+ if (path.ends_with(".png", CaseSensitivity::CaseInsensitive))
+ return "image/png";
+ if (path.ends_with(".ppm", CaseSensitivity::CaseInsensitive))
+ return "image/x‑portable‑pixmap";
+ if (path.ends_with(".gif", CaseSensitivity::CaseInsensitive))
+ return "image/gif";
+ if (path.ends_with(".bmp", CaseSensitivity::CaseInsensitive))
+ return "image/bmp";
+ if (path.ends_with(".jpg", CaseSensitivity::CaseInsensitive) || path.ends_with(".jpeg", CaseSensitivity::CaseInsensitive))
+ return "image/jpeg";
+ if (path.ends_with(".svg", CaseSensitivity::CaseInsensitive))
+ return "image/svg+xml";
+ if (path.ends_with(".md", CaseSensitivity::CaseInsensitive))
+ return "text/markdown";
+ if (path.ends_with(".html", CaseSensitivity::CaseInsensitive) || path.ends_with(".htm", CaseSensitivity::CaseInsensitive))
+ return "text/html";
+ if (path.ends_with("/", CaseSensitivity::CaseInsensitive))
+ return "text/html";
+ if (path.ends_with(".csv", CaseSensitivity::CaseInsensitive))
+ return "text/csv";
+ return "text/plain";
+}
+
+}
diff --git a/Userland/Libraries/LibCore/MimeData.h b/Userland/Libraries/LibCore/MimeData.h
new file mode 100644
index 0000000000..fe575ab675
--- /dev/null
+++ b/Userland/Libraries/LibCore/MimeData.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/HashMap.h>
+#include <AK/URL.h>
+#include <LibCore/Object.h>
+
+namespace Core {
+
+class MimeData : public Object {
+ C_OBJECT(MimeData);
+
+public:
+ virtual ~MimeData() { }
+
+ ByteBuffer data(const String& mime_type) const { return m_data.get(mime_type).value_or({}); }
+ void set_data(const String& mime_type, const ByteBuffer& data) { m_data.set(mime_type, data); }
+
+ bool has_format(const String& mime_type) const { return m_data.contains(mime_type); }
+ Vector<String> formats() const;
+
+ // Convenience helpers for "text/plain"
+ bool has_text() const { return has_format("text/plain"); }
+ String text() const;
+ void set_text(const String&);
+
+ // Convenience helpers for "text/uri-list"
+ bool has_urls() const { return has_format("text/uri-list"); }
+ Vector<URL> urls() const;
+ void set_urls(const Vector<URL>&);
+
+ const HashMap<String, ByteBuffer>& all_data() const { return m_data; }
+
+private:
+ MimeData() { }
+ explicit MimeData(const HashMap<String, ByteBuffer>& data)
+ : m_data(data)
+ {
+ }
+
+ HashMap<String, ByteBuffer> m_data;
+};
+
+String guess_mime_type_based_on_filename(const StringView&);
+
+}
diff --git a/Userland/Libraries/LibCore/NetworkJob.cpp b/Userland/Libraries/LibCore/NetworkJob.cpp
new file mode 100644
index 0000000000..0b48a93a72
--- /dev/null
+++ b/Userland/Libraries/LibCore/NetworkJob.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/NetworkJob.h>
+#include <LibCore/NetworkResponse.h>
+#include <stdio.h>
+
+//#define CNETWORKJOB_DEBUG
+
+namespace Core {
+
+NetworkJob::NetworkJob(OutputStream& output_stream)
+ : m_output_stream(output_stream)
+{
+}
+
+NetworkJob::~NetworkJob()
+{
+}
+
+void NetworkJob::start()
+{
+}
+
+void NetworkJob::shutdown()
+{
+}
+
+void NetworkJob::did_finish(NonnullRefPtr<NetworkResponse>&& response)
+{
+ // NOTE: We protect ourselves here, since the on_finish callback may otherwise
+ // trigger destruction of this job somehow.
+ NonnullRefPtr<NetworkJob> protector(*this);
+
+ m_response = move(response);
+#ifdef CNETWORKJOB_DEBUG
+ dbg() << *this << " job did_finish!";
+#endif
+ ASSERT(on_finish);
+ on_finish(true);
+ shutdown();
+}
+
+void NetworkJob::did_fail(Error error)
+{
+ // NOTE: We protect ourselves here, since the on_finish callback may otherwise
+ // trigger destruction of this job somehow.
+ NonnullRefPtr<NetworkJob> protector(*this);
+
+ m_error = error;
+#ifdef CNETWORKJOB_DEBUG
+ dbgprintf("%s{%p} job did_fail! error: %u (%s)\n", class_name(), this, (unsigned)error, to_string(error));
+#endif
+ ASSERT(on_finish);
+ on_finish(false);
+ shutdown();
+}
+
+void NetworkJob::did_progress(Optional<u32> total_size, u32 downloaded)
+{
+ // NOTE: We protect ourselves here, since the callback may otherwise
+ // trigger destruction of this job somehow.
+ NonnullRefPtr<NetworkJob> protector(*this);
+
+ if (on_progress)
+ on_progress(total_size, downloaded);
+}
+
+const char* to_string(NetworkJob::Error error)
+{
+ switch (error) {
+ case NetworkJob::Error::ProtocolFailed:
+ return "ProtocolFailed";
+ case NetworkJob::Error::ConnectionFailed:
+ return "ConnectionFailed";
+ case NetworkJob::Error::TransmissionFailed:
+ return "TransmissionFailed";
+ case NetworkJob::Error::Cancelled:
+ return "Cancelled";
+ default:
+ return "(Unknown error)";
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibCore/NetworkJob.h b/Userland/Libraries/LibCore/NetworkJob.h
new file mode 100644
index 0000000000..94ead44805
--- /dev/null
+++ b/Userland/Libraries/LibCore/NetworkJob.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/Stream.h>
+#include <LibCore/Object.h>
+
+namespace Core {
+
+class NetworkJob : public Object {
+ C_OBJECT_ABSTRACT(NetworkJob)
+public:
+ enum class Error {
+ None,
+ ConnectionFailed,
+ TransmissionFailed,
+ ProtocolFailed,
+ Cancelled,
+ };
+ virtual ~NetworkJob() override;
+
+ // Could fire twice, after Headers and after Trailers!
+ Function<void(const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers, Optional<u32> response_code)> on_headers_received;
+ Function<void(bool success)> on_finish;
+ Function<void(Optional<u32>, u32)> on_progress;
+
+ bool is_cancelled() const { return m_error == Error::Cancelled; }
+ bool has_error() const { return m_error != Error::None; }
+ Error error() const { return m_error; }
+ NetworkResponse* response() { return m_response.ptr(); }
+ const NetworkResponse* response() const { return m_response.ptr(); }
+
+ virtual void start() = 0;
+ virtual void shutdown() = 0;
+
+ void cancel()
+ {
+ shutdown();
+ m_error = Error::Cancelled;
+ }
+
+protected:
+ NetworkJob(OutputStream&);
+ void did_finish(NonnullRefPtr<NetworkResponse>&&);
+ void did_fail(Error);
+ void did_progress(Optional<u32> total_size, u32 downloaded);
+
+ size_t do_write(ReadonlyBytes bytes) { return m_output_stream.write(bytes); }
+
+private:
+ RefPtr<NetworkResponse> m_response;
+ OutputStream& m_output_stream;
+ Error m_error { Error::None };
+};
+
+const char* to_string(NetworkJob::Error);
+
+}
diff --git a/Userland/Libraries/LibCore/NetworkResponse.cpp b/Userland/Libraries/LibCore/NetworkResponse.cpp
new file mode 100644
index 0000000000..ebaf7eff7d
--- /dev/null
+++ b/Userland/Libraries/LibCore/NetworkResponse.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/NetworkResponse.h>
+
+namespace Core {
+
+NetworkResponse::NetworkResponse()
+{
+}
+
+NetworkResponse::~NetworkResponse()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibCore/NetworkResponse.h b/Userland/Libraries/LibCore/NetworkResponse.h
new file mode 100644
index 0000000000..d2ff33a569
--- /dev/null
+++ b/Userland/Libraries/LibCore/NetworkResponse.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/RefCounted.h>
+
+namespace Core {
+
+class NetworkResponse : public RefCounted<NetworkResponse> {
+public:
+ virtual ~NetworkResponse();
+
+ bool is_error() const { return m_error; }
+
+protected:
+ explicit NetworkResponse();
+
+ bool m_error { false };
+};
+
+}
diff --git a/Userland/Libraries/LibCore/Notifier.cpp b/Userland/Libraries/LibCore/Notifier.cpp
new file mode 100644
index 0000000000..655a73ed1b
--- /dev/null
+++ b/Userland/Libraries/LibCore/Notifier.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Badge.h>
+#include <LibCore/Event.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/Notifier.h>
+
+namespace Core {
+
+Notifier::Notifier(int fd, unsigned event_mask, Object* parent)
+ : Object(parent)
+ , m_fd(fd)
+ , m_event_mask(event_mask)
+{
+ set_enabled(true);
+}
+
+Notifier::~Notifier()
+{
+ set_enabled(false);
+}
+
+void Notifier::set_enabled(bool enabled)
+{
+ if (m_fd < 0)
+ return;
+ if (enabled)
+ Core::EventLoop::register_notifier({}, *this);
+ else
+ Core::EventLoop::unregister_notifier({}, *this);
+}
+
+void Notifier::close()
+{
+ if (m_fd < 0)
+ return;
+ set_enabled(false);
+ m_fd = -1;
+}
+
+void Notifier::event(Core::Event& event)
+{
+ if (event.type() == Core::Event::NotifierRead && on_ready_to_read) {
+ on_ready_to_read();
+ } else if (event.type() == Core::Event::NotifierWrite && on_ready_to_write) {
+ on_ready_to_write();
+ } else {
+ Object::event(event);
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibCore/Notifier.h b/Userland/Libraries/LibCore/Notifier.h
new file mode 100644
index 0000000000..b197b55204
--- /dev/null
+++ b/Userland/Libraries/LibCore/Notifier.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <LibCore/Object.h>
+
+namespace Core {
+
+class Notifier : public Object {
+ C_OBJECT(Notifier)
+public:
+ enum Event {
+ None = 0,
+ Read = 1,
+ Write = 2,
+ Exceptional = 4,
+ };
+
+ virtual ~Notifier() override;
+
+ void set_enabled(bool);
+
+ Function<void()> on_ready_to_read;
+ Function<void()> on_ready_to_write;
+
+ void close();
+
+ int fd() const { return m_fd; }
+ unsigned event_mask() const { return m_event_mask; }
+ void set_event_mask(unsigned event_mask) { m_event_mask = event_mask; }
+
+ void event(Core::Event&) override;
+
+private:
+ Notifier(int fd, unsigned event_mask, Object* parent = nullptr);
+
+ int m_fd { -1 };
+ unsigned m_event_mask { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibCore/Object.cpp b/Userland/Libraries/LibCore/Object.cpp
new file mode 100644
index 0000000000..55cd102409
--- /dev/null
+++ b/Userland/Libraries/LibCore/Object.cpp
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/Badge.h>
+#include <AK/JsonObject.h>
+#include <LibCore/Event.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/Object.h>
+#include <stdio.h>
+
+namespace Core {
+
+IntrusiveList<Object, &Object::m_all_objects_list_node>& Object::all_objects()
+{
+ static IntrusiveList<Object, &Object::m_all_objects_list_node> objects;
+ return objects;
+}
+
+Object::Object(Object* parent)
+ : m_parent(parent)
+{
+ all_objects().append(*this);
+ if (m_parent)
+ m_parent->add_child(*this);
+
+ REGISTER_READONLY_STRING_PROPERTY("class_name", class_name);
+ REGISTER_STRING_PROPERTY("name", name, set_name);
+
+ register_property(
+ "address", [this] { return FlatPtr(this); },
+ [](auto&) { return false; });
+
+ register_property(
+ "parent", [this] { return FlatPtr(this->parent()); },
+ [](auto&) { return false; });
+}
+
+Object::~Object()
+{
+ // NOTE: We move our children out to a stack vector to prevent other
+ // code from trying to iterate over them.
+ auto children = move(m_children);
+ // NOTE: We also unparent the children, so that they won't try to unparent
+ // themselves in their own destructors.
+ for (auto& child : children)
+ child.m_parent = nullptr;
+
+ all_objects().remove(*this);
+ stop_timer();
+ if (m_parent)
+ m_parent->remove_child(*this);
+}
+
+void Object::event(Core::Event& event)
+{
+ switch (event.type()) {
+ case Core::Event::Timer:
+ return timer_event(static_cast<TimerEvent&>(event));
+ case Core::Event::ChildAdded:
+ case Core::Event::ChildRemoved:
+ return child_event(static_cast<ChildEvent&>(event));
+ case Core::Event::Invalid:
+ ASSERT_NOT_REACHED();
+ break;
+ case Core::Event::Custom:
+ return custom_event(static_cast<CustomEvent&>(event));
+ default:
+ break;
+ }
+}
+
+void Object::add_child(Object& object)
+{
+ // FIXME: Should we support reparenting objects?
+ ASSERT(!object.parent() || object.parent() == this);
+ object.m_parent = this;
+ m_children.append(object);
+ Core::ChildEvent child_event(Core::Event::ChildAdded, object);
+ event(child_event);
+}
+
+void Object::insert_child_before(Object& new_child, Object& before_child)
+{
+ // FIXME: Should we support reparenting objects?
+ ASSERT(!new_child.parent() || new_child.parent() == this);
+ new_child.m_parent = this;
+ m_children.insert_before_matching(new_child, [&](auto& existing_child) { return existing_child.ptr() == &before_child; });
+ Core::ChildEvent child_event(Core::Event::ChildAdded, new_child, &before_child);
+ event(child_event);
+}
+
+void Object::remove_child(Object& object)
+{
+ for (size_t i = 0; i < m_children.size(); ++i) {
+ if (m_children.ptr_at(i).ptr() == &object) {
+ // NOTE: We protect the child so it survives the handling of ChildRemoved.
+ NonnullRefPtr<Object> protector = object;
+ object.m_parent = nullptr;
+ m_children.remove(i);
+ Core::ChildEvent child_event(Core::Event::ChildRemoved, object);
+ event(child_event);
+ return;
+ }
+ }
+ ASSERT_NOT_REACHED();
+}
+
+void Object::remove_all_children()
+{
+ while (!m_children.is_empty())
+ m_children.first().remove_from_parent();
+}
+
+void Object::timer_event(Core::TimerEvent&)
+{
+}
+
+void Object::child_event(Core::ChildEvent&)
+{
+}
+
+void Object::custom_event(CustomEvent&)
+{
+}
+
+void Object::start_timer(int ms, TimerShouldFireWhenNotVisible fire_when_not_visible)
+{
+ if (m_timer_id) {
+ dbgln("{} {:p} already has a timer!", class_name(), this);
+ ASSERT_NOT_REACHED();
+ }
+
+ m_timer_id = Core::EventLoop::register_timer(*this, ms, true, fire_when_not_visible);
+}
+
+void Object::stop_timer()
+{
+ if (!m_timer_id)
+ return;
+ bool success = Core::EventLoop::unregister_timer(m_timer_id);
+ ASSERT(success);
+ m_timer_id = 0;
+}
+
+void Object::dump_tree(int indent)
+{
+ for (int i = 0; i < indent; ++i) {
+ printf(" ");
+ }
+ printf("%s{%p}", class_name(), this);
+ if (!name().is_null())
+ printf(" %s", name().characters());
+ printf("\n");
+
+ for_each_child([&](auto& child) {
+ child.dump_tree(indent + 2);
+ return IterationDecision::Continue;
+ });
+}
+
+void Object::deferred_invoke(Function<void(Object&)> invokee)
+{
+ Core::EventLoop::current().post_event(*this, make<Core::DeferredInvocationEvent>(move(invokee)));
+}
+
+void Object::save_to(JsonObject& json)
+{
+ for (auto& it : m_properties) {
+ auto& property = it.value;
+ json.set(property->name(), property->get());
+ }
+}
+
+JsonValue Object::property(const StringView& name) const
+{
+ auto it = m_properties.find(name);
+ if (it == m_properties.end())
+ return JsonValue();
+ return it->value->get();
+}
+
+bool Object::set_property(const StringView& name, const JsonValue& value)
+{
+ auto it = m_properties.find(name);
+ if (it == m_properties.end())
+ return false;
+ return it->value->set(value);
+}
+
+bool Object::is_ancestor_of(const Object& other) const
+{
+ if (&other == this)
+ return false;
+ for (auto* ancestor = other.parent(); ancestor; ancestor = ancestor->parent()) {
+ if (ancestor == this)
+ return true;
+ }
+ return false;
+}
+
+void Object::dispatch_event(Core::Event& e, Object* stay_within)
+{
+ ASSERT(!stay_within || stay_within == this || stay_within->is_ancestor_of(*this));
+ auto* target = this;
+ do {
+ target->event(e);
+ target = target->parent();
+ if (target == stay_within) {
+ // Prevent the event from bubbling any further.
+ return;
+ }
+ } while (target && !e.is_accepted());
+}
+
+bool Object::is_visible_for_timer_purposes() const
+{
+ if (parent())
+ return parent()->is_visible_for_timer_purposes();
+ return true;
+}
+
+void Object::increment_inspector_count(Badge<RPCClient>)
+{
+ ++m_inspector_count;
+ if (m_inspector_count == 1)
+ did_begin_inspection();
+}
+
+void Object::decrement_inspector_count(Badge<RPCClient>)
+{
+ --m_inspector_count;
+ if (!m_inspector_count)
+ did_end_inspection();
+}
+
+void Object::register_property(const String& name, Function<JsonValue()> getter, Function<bool(const JsonValue&)> setter)
+{
+ m_properties.set(name, make<Property>(name, move(getter), move(setter)));
+}
+
+const LogStream& operator<<(const LogStream& stream, const Object& object)
+{
+ return stream << object.class_name() << '{' << &object << '}';
+}
+
+}
diff --git a/Userland/Libraries/LibCore/Object.h b/Userland/Libraries/LibCore/Object.h
new file mode 100644
index 0000000000..9270d347d0
--- /dev/null
+++ b/Userland/Libraries/LibCore/Object.h
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/HashMap.h>
+#include <AK/IntrusiveList.h>
+#include <AK/Noncopyable.h>
+#include <AK/NonnullRefPtrVector.h>
+#include <AK/String.h>
+#include <AK/TypeCasts.h>
+#include <AK/Weakable.h>
+#include <LibCore/Forward.h>
+#include <LibCore/Property.h>
+
+namespace Core {
+
+class RPCClient;
+
+enum class TimerShouldFireWhenNotVisible {
+ No = 0,
+ Yes
+};
+
+#define C_OBJECT(klass) \
+public: \
+ virtual const char* class_name() const override { return #klass; } \
+ template<class... Args> \
+ static inline NonnullRefPtr<klass> construct(Args&&... args) \
+ { \
+ return adopt(*new klass(forward<Args>(args)...)); \
+ }
+
+#define C_OBJECT_ABSTRACT(klass) \
+public: \
+ virtual const char* class_name() const override { return #klass; }
+
+class Object
+ : public RefCounted<Object>
+ , public Weakable<Object> {
+ // NOTE: No C_OBJECT macro for Core::Object itself.
+
+ AK_MAKE_NONCOPYABLE(Object);
+ AK_MAKE_NONMOVABLE(Object);
+
+ IntrusiveListNode m_all_objects_list_node;
+
+public:
+ virtual ~Object();
+
+ virtual const char* class_name() const = 0;
+ virtual void event(Core::Event&);
+
+ const String& name() const { return m_name; }
+ void set_name(const StringView& name) { m_name = name; }
+
+ NonnullRefPtrVector<Object>& children() { return m_children; }
+ const NonnullRefPtrVector<Object>& children() const { return m_children; }
+
+ template<typename Callback>
+ void for_each_child(Callback callback)
+ {
+ for (auto& child : m_children) {
+ if (callback(child) == IterationDecision::Break)
+ return;
+ }
+ }
+
+ template<typename T, typename Callback>
+ void for_each_child_of_type(Callback callback) requires IsBaseOf<Object, T>::value;
+
+ template<typename T>
+ T* find_child_of_type_named(const String&) requires IsBaseOf<Object, T>::value;
+
+ template<typename T>
+ T* find_descendant_of_type_named(const String&) requires IsBaseOf<Object, T>::value;
+
+ bool is_ancestor_of(const Object&) const;
+
+ Object* parent() { return m_parent; }
+ const Object* parent() const { return m_parent; }
+
+ void start_timer(int ms, TimerShouldFireWhenNotVisible = TimerShouldFireWhenNotVisible::No);
+ void stop_timer();
+ bool has_timer() const { return m_timer_id; }
+
+ void add_child(Object&);
+ void insert_child_before(Object& new_child, Object& before_child);
+ void remove_child(Object&);
+ void remove_all_children();
+
+ void dump_tree(int indent = 0);
+
+ void deferred_invoke(Function<void(Object&)>);
+
+ void save_to(AK::JsonObject&);
+
+ bool set_property(const StringView& name, const JsonValue& value);
+ JsonValue property(const StringView& name) const;
+ const HashMap<String, NonnullOwnPtr<Property>>& properties() const { return m_properties; }
+
+ static IntrusiveList<Object, &Object::m_all_objects_list_node>& all_objects();
+
+ void dispatch_event(Core::Event&, Object* stay_within = nullptr);
+
+ void remove_from_parent()
+ {
+ if (m_parent)
+ m_parent->remove_child(*this);
+ }
+
+ template<class T, class... Args>
+ inline T& add(Args&&... args)
+ {
+ auto child = T::construct(forward<Args>(args)...);
+ add_child(*child);
+ return child;
+ }
+
+ virtual bool is_visible_for_timer_purposes() const;
+
+ bool is_being_inspected() const { return m_inspector_count; }
+
+ void increment_inspector_count(Badge<RPCClient>);
+ void decrement_inspector_count(Badge<RPCClient>);
+
+protected:
+ explicit Object(Object* parent = nullptr);
+
+ void register_property(const String& name, Function<JsonValue()> getter, Function<bool(const JsonValue&)> setter = nullptr);
+
+ virtual void timer_event(TimerEvent&);
+ virtual void custom_event(CustomEvent&);
+
+ // NOTE: You may get child events for children that are not yet fully constructed!
+ virtual void child_event(ChildEvent&);
+
+ virtual void did_begin_inspection() { }
+ virtual void did_end_inspection() { }
+
+private:
+ Object* m_parent { nullptr };
+ String m_name;
+ int m_timer_id { 0 };
+ unsigned m_inspector_count { 0 };
+ HashMap<String, NonnullOwnPtr<Property>> m_properties;
+ NonnullRefPtrVector<Object> m_children;
+};
+
+}
+
+template<>
+struct AK::Formatter<Core::Object> : AK::Formatter<FormatString> {
+ void format(FormatBuilder& builder, const Core::Object& value)
+ {
+ return AK::Formatter<FormatString>::format(builder, "{}({})", value.class_name(), &value);
+ }
+};
+
+namespace Core {
+template<typename T, typename Callback>
+inline void Object::for_each_child_of_type(Callback callback) requires IsBaseOf<Object, T>::value
+{
+ for_each_child([&](auto& child) {
+ if (auto* child_as_t = dynamic_cast<T*>(&child); child_as_t)
+ return callback(*child_as_t);
+ return IterationDecision::Continue;
+ });
+}
+
+template<typename T>
+T* Object::find_child_of_type_named(const String& name) requires IsBaseOf<Object, T>::value
+{
+ T* found_child = nullptr;
+ for_each_child_of_type<T>([&](auto& child) {
+ if (child.name() == name) {
+ found_child = &child;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+
+ return found_child;
+}
+
+template<typename T>
+T* Object::find_descendant_of_type_named(const String& name) requires IsBaseOf<Object, T>::value
+{
+ auto* this_as_t = dynamic_cast<T*>(this);
+ if (this_as_t && this->name() == name)
+ return this_as_t;
+ T* found_child = nullptr;
+ for_each_child([&](auto& child) {
+ found_child = child.template find_descendant_of_type_named<T>(name);
+ if (found_child)
+ return IterationDecision::Break;
+ return IterationDecision::Continue;
+ });
+ return found_child;
+}
+
+const LogStream& operator<<(const LogStream&, const Object&);
+
+#define REGISTER_INT_PROPERTY(property_name, getter, setter) \
+ register_property( \
+ property_name, \
+ [this] { return this->getter(); }, \
+ [this](auto& value) { \
+ this->setter(value.template to_number<int>()); \
+ return true; \
+ });
+
+#define REGISTER_BOOL_PROPERTY(property_name, getter, setter) \
+ register_property( \
+ property_name, \
+ [this] { return this->getter(); }, \
+ [this](auto& value) { \
+ this->setter(value.to_bool()); \
+ return true; \
+ });
+
+#define REGISTER_STRING_PROPERTY(property_name, getter, setter) \
+ register_property( \
+ property_name, \
+ [this] { return this->getter(); }, \
+ [this](auto& value) { \
+ this->setter(value.to_string()); \
+ return true; \
+ });
+
+#define REGISTER_READONLY_STRING_PROPERTY(property_name, getter) \
+ register_property( \
+ property_name, \
+ [this] { return this->getter(); }, \
+ {});
+
+#define REGISTER_RECT_PROPERTY(property_name, getter, setter) \
+ register_property( \
+ property_name, \
+ [this] { \
+ auto rect = this->getter(); \
+ JsonObject rect_object; \
+ rect_object.set("x", rect.x()); \
+ rect_object.set("y", rect.y()); \
+ rect_object.set("width", rect.width()); \
+ rect_object.set("height", rect.height()); \
+ return rect_object; \
+ }, \
+ [this](auto& value) { \
+ if (!value.is_object()) \
+ return false; \
+ Gfx::IntRect rect; \
+ rect.set_x(value.as_object().get("x").to_i32()); \
+ rect.set_y(value.as_object().get("y").to_i32()); \
+ rect.set_width(value.as_object().get("width").to_i32()); \
+ rect.set_height(value.as_object().get("height").to_i32()); \
+ setter(rect); \
+ return true; \
+ });
+
+#define REGISTER_SIZE_PROPERTY(property_name, getter, setter) \
+ register_property( \
+ property_name, \
+ [this] { \
+ auto size = this->getter(); \
+ JsonObject size_object; \
+ size_object.set("width", size.width()); \
+ size_object.set("height", size.height()); \
+ return size_object; \
+ }, \
+ [this](auto& value) { \
+ if (!value.is_object()) \
+ return false; \
+ Gfx::IntSize size; \
+ size.set_width(value.as_object().get("width").to_i32()); \
+ size.set_height(value.as_object().get("height").to_i32()); \
+ setter(size); \
+ return true; \
+ });
+
+#define REGISTER_ENUM_PROPERTY(property_name, getter, setter, EnumType, ...) \
+ register_property( \
+ property_name, \
+ [this]() -> JsonValue { \
+ struct { \
+ EnumType enum_value; \
+ String string_value; \
+ } options[] = { __VA_ARGS__ }; \
+ auto enum_value = getter(); \
+ for (size_t i = 0; i < array_size(options); ++i) { \
+ auto& option = options[i]; \
+ if (enum_value == option.enum_value) \
+ return option.string_value; \
+ } \
+ return JsonValue(); \
+ }, \
+ [this](auto& value) { \
+ struct { \
+ EnumType enum_value; \
+ String string_value; \
+ } options[] = { __VA_ARGS__ }; \
+ auto string_value = value.as_string(); \
+ for (size_t i = 0; i < array_size(options); ++i) { \
+ auto& option = options[i]; \
+ if (string_value == option.string_value) { \
+ setter(option.enum_value); \
+ return true; \
+ } \
+ } \
+ return false; \
+ })
+
+#define REGISTER_TEXT_ALIGNMENT_PROPERTY(property_name, getter, setter) \
+ REGISTER_ENUM_PROPERTY( \
+ property_name, getter, setter, Gfx::TextAlignment, \
+ { Gfx::TextAlignment::TopLeft, "TopLeft" }, \
+ { Gfx::TextAlignment::CenterLeft, "CenterLeft" }, \
+ { Gfx::TextAlignment::Center, "Center" }, \
+ { Gfx::TextAlignment::CenterRight, "CenterRight" }, \
+ { Gfx::TextAlignment::TopRight, "TopRight" }, \
+ { Gfx::TextAlignment::BottomRight, "BottomRight" })
+}
diff --git a/Userland/Libraries/LibCore/ProcessStatisticsReader.cpp b/Userland/Libraries/LibCore/ProcessStatisticsReader.cpp
new file mode 100644
index 0000000000..566053d3af
--- /dev/null
+++ b/Userland/Libraries/LibCore/ProcessStatisticsReader.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ByteBuffer.h>
+#include <AK/JsonArray.h>
+#include <AK/JsonObject.h>
+#include <AK/JsonValue.h>
+#include <LibCore/File.h>
+#include <LibCore/ProcessStatisticsReader.h>
+#include <pwd.h>
+#include <stdio.h>
+
+namespace Core {
+
+HashMap<uid_t, String> ProcessStatisticsReader::s_usernames;
+
+Optional<HashMap<pid_t, Core::ProcessStatistics>> ProcessStatisticsReader::get_all(RefPtr<Core::File>& proc_all_file)
+{
+ if (proc_all_file) {
+ if (!proc_all_file->seek(0, Core::File::SeekMode::SetPosition)) {
+ fprintf(stderr, "ProcessStatisticsReader: Failed to refresh /proc/all: %s\n", proc_all_file->error_string());
+ return {};
+ }
+ } else {
+ proc_all_file = Core::File::construct("/proc/all");
+ if (!proc_all_file->open(Core::IODevice::ReadOnly)) {
+ fprintf(stderr, "ProcessStatisticsReader: Failed to open /proc/all: %s\n", proc_all_file->error_string());
+ return {};
+ }
+ }
+
+ HashMap<pid_t, Core::ProcessStatistics> map;
+
+ auto file_contents = proc_all_file->read_all();
+ auto json = JsonValue::from_string(file_contents);
+ if (!json.has_value())
+ return {};
+ json.value().as_array().for_each([&](auto& value) {
+ const JsonObject& process_object = value.as_object();
+ Core::ProcessStatistics process;
+
+ // kernel data first
+ process.pid = process_object.get("pid").to_u32();
+ process.pgid = process_object.get("pgid").to_u32();
+ process.pgp = process_object.get("pgp").to_u32();
+ process.sid = process_object.get("sid").to_u32();
+ process.uid = process_object.get("uid").to_u32();
+ process.gid = process_object.get("gid").to_u32();
+ process.ppid = process_object.get("ppid").to_u32();
+ process.nfds = process_object.get("nfds").to_u32();
+ process.name = process_object.get("name").to_string();
+ process.executable = process_object.get("executable").to_string();
+ process.tty = process_object.get("tty").to_string();
+ process.pledge = process_object.get("pledge").to_string();
+ process.veil = process_object.get("veil").to_string();
+ process.amount_virtual = process_object.get("amount_virtual").to_u32();
+ process.amount_resident = process_object.get("amount_resident").to_u32();
+ process.amount_shared = process_object.get("amount_shared").to_u32();
+ process.amount_dirty_private = process_object.get("amount_dirty_private").to_u32();
+ process.amount_clean_inode = process_object.get("amount_clean_inode").to_u32();
+ process.amount_purgeable_volatile = process_object.get("amount_purgeable_volatile").to_u32();
+ process.amount_purgeable_nonvolatile = process_object.get("amount_purgeable_nonvolatile").to_u32();
+
+ auto& thread_array = process_object.get_ptr("threads")->as_array();
+ process.threads.ensure_capacity(thread_array.size());
+ thread_array.for_each([&](auto& value) {
+ auto& thread_object = value.as_object();
+ Core::ThreadStatistics thread;
+ thread.tid = thread_object.get("tid").to_u32();
+ thread.times_scheduled = thread_object.get("times_scheduled").to_u32();
+ thread.name = thread_object.get("name").to_string();
+ thread.state = thread_object.get("state").to_string();
+ thread.ticks_user = thread_object.get("ticks_user").to_u32();
+ thread.ticks_kernel = thread_object.get("ticks_kernel").to_u32();
+ thread.cpu = thread_object.get("cpu").to_u32();
+ thread.priority = thread_object.get("priority").to_u32();
+ thread.effective_priority = thread_object.get("effective_priority").to_u32();
+ thread.syscall_count = thread_object.get("syscall_count").to_u32();
+ thread.inode_faults = thread_object.get("inode_faults").to_u32();
+ thread.zero_faults = thread_object.get("zero_faults").to_u32();
+ thread.cow_faults = thread_object.get("cow_faults").to_u32();
+ thread.unix_socket_read_bytes = thread_object.get("unix_socket_read_bytes").to_u32();
+ thread.unix_socket_write_bytes = thread_object.get("unix_socket_write_bytes").to_u32();
+ thread.ipv4_socket_read_bytes = thread_object.get("ipv4_socket_read_bytes").to_u32();
+ thread.ipv4_socket_write_bytes = thread_object.get("ipv4_socket_write_bytes").to_u32();
+ thread.file_read_bytes = thread_object.get("file_read_bytes").to_u32();
+ thread.file_write_bytes = thread_object.get("file_write_bytes").to_u32();
+ process.threads.append(move(thread));
+ });
+
+ // and synthetic data last
+ process.username = username_from_uid(process.uid);
+ map.set(process.pid, process);
+ });
+
+ return map;
+}
+
+Optional<HashMap<pid_t, Core::ProcessStatistics>> ProcessStatisticsReader::get_all()
+{
+ RefPtr<Core::File> proc_all_file;
+ return get_all(proc_all_file);
+}
+
+String ProcessStatisticsReader::username_from_uid(uid_t uid)
+{
+ if (s_usernames.is_empty()) {
+ setpwent();
+ while (auto* passwd = getpwent())
+ s_usernames.set(passwd->pw_uid, passwd->pw_name);
+ endpwent();
+ }
+
+ auto it = s_usernames.find(uid);
+ if (it != s_usernames.end())
+ return (*it).value;
+ return String::number(uid);
+}
+}
diff --git a/Userland/Libraries/LibCore/ProcessStatisticsReader.h b/Userland/Libraries/LibCore/ProcessStatisticsReader.h
new file mode 100644
index 0000000000..b6e506cd9d
--- /dev/null
+++ b/Userland/Libraries/LibCore/ProcessStatisticsReader.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/String.h>
+#include <LibCore/File.h>
+#include <unistd.h>
+
+namespace Core {
+
+struct ThreadStatistics {
+ pid_t tid;
+ unsigned times_scheduled;
+ unsigned ticks_user;
+ unsigned ticks_kernel;
+ unsigned syscall_count;
+ unsigned inode_faults;
+ unsigned zero_faults;
+ unsigned cow_faults;
+ unsigned unix_socket_read_bytes;
+ unsigned unix_socket_write_bytes;
+ unsigned ipv4_socket_read_bytes;
+ unsigned ipv4_socket_write_bytes;
+ unsigned file_read_bytes;
+ unsigned file_write_bytes;
+ String state;
+ u32 cpu;
+ u32 priority;
+ u32 effective_priority;
+ String name;
+};
+
+struct ProcessStatistics {
+ // Keep this in sync with /proc/all.
+ // From the kernel side:
+ pid_t pid;
+ pid_t pgid;
+ pid_t pgp;
+ pid_t sid;
+ uid_t uid;
+ gid_t gid;
+ pid_t ppid;
+ unsigned nfds;
+ String name;
+ String executable;
+ String tty;
+ String pledge;
+ String veil;
+ size_t amount_virtual;
+ size_t amount_resident;
+ size_t amount_shared;
+ size_t amount_dirty_private;
+ size_t amount_clean_inode;
+ size_t amount_purgeable_volatile;
+ size_t amount_purgeable_nonvolatile;
+
+ Vector<Core::ThreadStatistics> threads;
+
+ // synthetic
+ String username;
+};
+
+class ProcessStatisticsReader {
+public:
+ static Optional<HashMap<pid_t, Core::ProcessStatistics>> get_all(RefPtr<Core::File>&);
+ static Optional<HashMap<pid_t, Core::ProcessStatistics>> get_all();
+
+private:
+ static String username_from_uid(uid_t);
+ static HashMap<uid_t, String> s_usernames;
+};
+
+}
diff --git a/Userland/Libraries/LibCore/Property.cpp b/Userland/Libraries/LibCore/Property.cpp
new file mode 100644
index 0000000000..8c6c2587c6
--- /dev/null
+++ b/Userland/Libraries/LibCore/Property.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/Property.h>
+
+namespace Core {
+
+Property::Property(String name, Function<JsonValue()> getter, Function<bool(const JsonValue&)> setter)
+ : m_name(move(name))
+ , m_getter(move(getter))
+ , m_setter(move(setter))
+{
+}
+
+Property::~Property()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibCore/Property.h b/Userland/Libraries/LibCore/Property.h
new file mode 100644
index 0000000000..aa04e833fb
--- /dev/null
+++ b/Userland/Libraries/LibCore/Property.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/JsonValue.h>
+
+namespace Core {
+
+class Property {
+ AK_MAKE_NONCOPYABLE(Property);
+
+public:
+ Property(String name, Function<JsonValue()> getter, Function<bool(const JsonValue&)> setter = nullptr);
+ ~Property();
+
+ bool set(const JsonValue& value)
+ {
+ if (!m_setter)
+ return false;
+ return m_setter(value);
+ }
+
+ JsonValue get() const { return m_getter(); }
+
+ const String& name() const { return m_name; }
+ bool is_readonly() const { return !m_setter; }
+
+private:
+ String m_name;
+ Function<JsonValue()> m_getter;
+ Function<bool(const JsonValue&)> m_setter;
+};
+
+}
diff --git a/Userland/Libraries/LibCore/Socket.cpp b/Userland/Libraries/LibCore/Socket.cpp
new file mode 100644
index 0000000000..4ced6734b1
--- /dev/null
+++ b/Userland/Libraries/LibCore/Socket.cpp
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ByteBuffer.h>
+#include <LibCore/Notifier.h>
+#include <LibCore/Socket.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+//#define CSOCKET_DEBUG
+
+namespace Core {
+
+Socket::Socket(Type type, Object* parent)
+ : IODevice(parent)
+ , m_type(type)
+{
+ register_property(
+ "source_address", [this] { return m_source_address.to_string(); },
+ [](auto&) { return false; });
+
+ register_property(
+ "destination_address", [this] { return m_destination_address.to_string(); },
+ [](auto&) { return false; });
+
+ register_property(
+ "source_port", [this] { return m_source_port; },
+ [](auto&) { return false; });
+
+ register_property(
+ "destination_port", [this] { return m_destination_port; },
+ [](auto&) { return false; });
+
+ register_property(
+ "connected", [this] { return m_connected; },
+ [](auto&) { return false; });
+}
+
+Socket::~Socket()
+{
+ close();
+}
+
+bool Socket::connect(const String& hostname, int port)
+{
+ auto* hostent = gethostbyname(hostname.characters());
+ if (!hostent) {
+ dbgln("Socket::connect: Unable to resolve '{}'", hostname);
+ return false;
+ }
+
+ IPv4Address host_address((const u8*)hostent->h_addr_list[0]);
+#ifdef CSOCKET_DEBUG
+ dbg() << "Socket::connect: Resolved '" << hostname << "' to " << host_address;
+#endif
+ return connect(host_address, port);
+}
+
+void Socket::set_blocking(bool blocking)
+{
+ int flags = fcntl(fd(), F_GETFL, 0);
+ ASSERT(flags >= 0);
+ if (blocking)
+ flags = fcntl(fd(), F_SETFL, flags & ~O_NONBLOCK);
+ else
+ flags = fcntl(fd(), F_SETFL, flags | O_NONBLOCK);
+ ASSERT(flags == 0);
+}
+
+bool Socket::connect(const SocketAddress& address, int port)
+{
+ ASSERT(!is_connected());
+ ASSERT(address.type() == SocketAddress::Type::IPv4);
+#ifdef CSOCKET_DEBUG
+ dbg() << *this << " connecting to " << address << "...";
+#endif
+
+ ASSERT(port > 0 && port <= 65535);
+
+ struct sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ auto ipv4_address = address.ipv4_address();
+ memcpy(&addr.sin_addr.s_addr, &ipv4_address, sizeof(IPv4Address));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+
+ m_destination_address = address;
+ m_destination_port = port;
+
+ return common_connect((struct sockaddr*)&addr, sizeof(addr));
+}
+
+bool Socket::connect(const SocketAddress& address)
+{
+ ASSERT(!is_connected());
+ ASSERT(address.type() == SocketAddress::Type::Local);
+#ifdef CSOCKET_DEBUG
+ dbg() << *this << " connecting to " << address << "...";
+#endif
+
+ sockaddr_un saddr;
+ saddr.sun_family = AF_LOCAL;
+ auto dest_address = address.to_string();
+ bool fits = dest_address.copy_characters_to_buffer(saddr.sun_path, sizeof(saddr.sun_path));
+ if (!fits) {
+ fprintf(stderr, "Core::Socket: Failed to connect() to %s: Path is too long!\n", dest_address.characters());
+ errno = EINVAL;
+ return false;
+ }
+ m_destination_address = address;
+
+ return common_connect((const sockaddr*)&saddr, sizeof(saddr));
+}
+
+bool Socket::common_connect(const struct sockaddr* addr, socklen_t addrlen)
+{
+ auto connected = [this] {
+#ifdef CSOCKET_DEBUG
+ dbg() << *this << " connected!";
+#endif
+ if (!m_connected) {
+ m_connected = true;
+ ensure_read_notifier();
+ if (m_notifier) {
+ m_notifier->remove_from_parent();
+ m_notifier = nullptr;
+ }
+ if (on_connected)
+ on_connected();
+ }
+ };
+ int rc = ::connect(fd(), addr, addrlen);
+ if (rc < 0) {
+ if (errno == EINPROGRESS) {
+#ifdef CSOCKET_DEBUG
+ dbg() << *this << " connection in progress (EINPROGRESS)";
+#endif
+ m_notifier = Notifier::construct(fd(), Notifier::Event::Write, this);
+ m_notifier->on_ready_to_write = move(connected);
+ return true;
+ }
+ int saved_errno = errno;
+ fprintf(stderr, "Core::Socket: Failed to connect() to %s: %s\n", destination_address().to_string().characters(), strerror(saved_errno));
+ errno = saved_errno;
+ return false;
+ }
+#ifdef CSOCKET_DEBUG
+ dbg() << *this << " connected ok!";
+#endif
+ connected();
+ return true;
+}
+
+ByteBuffer Socket::receive(int max_size)
+{
+ auto buffer = read(max_size);
+ if (eof())
+ m_connected = false;
+ return buffer;
+}
+
+bool Socket::send(ReadonlyBytes data)
+{
+ ssize_t nsent = ::send(fd(), data.data(), data.size(), 0);
+ if (nsent < 0) {
+ set_error(errno);
+ return false;
+ }
+ ASSERT(static_cast<size_t>(nsent) == data.size());
+ return true;
+}
+
+void Socket::did_update_fd(int fd)
+{
+ if (fd < 0) {
+ if (m_read_notifier) {
+ m_read_notifier->remove_from_parent();
+ m_read_notifier = nullptr;
+ }
+ if (m_notifier) {
+ m_notifier->remove_from_parent();
+ m_notifier = nullptr;
+ }
+ return;
+ }
+ if (m_connected) {
+ ensure_read_notifier();
+ } else {
+ // I don't think it would be right if we updated the fd while not connected *but* while having a notifier..
+ ASSERT(!m_read_notifier);
+ }
+}
+
+void Socket::ensure_read_notifier()
+{
+ ASSERT(m_connected);
+ m_read_notifier = Notifier::construct(fd(), Notifier::Event::Read, this);
+ m_read_notifier->on_ready_to_read = [this] {
+ if (!can_read())
+ return;
+ if (on_ready_to_read)
+ on_ready_to_read();
+ };
+}
+
+}
diff --git a/Userland/Libraries/LibCore/Socket.h b/Userland/Libraries/LibCore/Socket.h
new file mode 100644
index 0000000000..fcbd2af31d
--- /dev/null
+++ b/Userland/Libraries/LibCore/Socket.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/Span.h>
+#include <LibCore/IODevice.h>
+#include <LibCore/SocketAddress.h>
+
+namespace Core {
+
+class Socket : public IODevice {
+ C_OBJECT(Socket)
+public:
+ enum class Type {
+ Invalid,
+ TCP,
+ UDP,
+ Local,
+ };
+ virtual ~Socket() override;
+
+ Type type() const { return m_type; }
+
+ virtual bool connect(const String& hostname, int port);
+ bool connect(const SocketAddress&, int port);
+ bool connect(const SocketAddress&);
+
+ ByteBuffer receive(int max_size);
+ bool send(ReadonlyBytes);
+
+ bool is_connected() const { return m_connected; }
+ void set_blocking(bool blocking);
+
+ SocketAddress source_address() const { return m_source_address; }
+ int source_port() const { return m_source_port; }
+
+ SocketAddress destination_address() const { return m_destination_address; }
+ int destination_port() const { return m_destination_port; }
+
+ Function<void()> on_connected;
+ Function<void()> on_ready_to_read;
+
+protected:
+ Socket(Type, Object* parent);
+
+ SocketAddress m_source_address;
+ SocketAddress m_destination_address;
+ int m_source_port { -1 };
+ int m_destination_port { -1 };
+ bool m_connected { false };
+
+ virtual void did_update_fd(int) override;
+ virtual bool common_connect(const struct sockaddr*, socklen_t);
+
+private:
+ virtual bool open(IODevice::OpenMode) override { ASSERT_NOT_REACHED(); }
+ void ensure_read_notifier();
+
+ Type m_type { Type::Invalid };
+ RefPtr<Notifier> m_notifier;
+ RefPtr<Notifier> m_read_notifier;
+};
+
+}
diff --git a/Userland/Libraries/LibCore/SocketAddress.cpp b/Userland/Libraries/LibCore/SocketAddress.cpp
new file mode 100644
index 0000000000..d4d451a150
--- /dev/null
+++ b/Userland/Libraries/LibCore/SocketAddress.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/SocketAddress.h>
+
+namespace Core {
+
+const LogStream& operator<<(const LogStream& stream, const SocketAddress& value)
+{
+ return stream << value.to_string();
+}
+
+}
diff --git a/Userland/Libraries/LibCore/SocketAddress.h b/Userland/Libraries/LibCore/SocketAddress.h
new file mode 100644
index 0000000000..717f4e8fd3
--- /dev/null
+++ b/Userland/Libraries/LibCore/SocketAddress.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/IPv4Address.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+namespace Core {
+
+class SocketAddress {
+public:
+ enum class Type {
+ Invalid,
+ IPv4,
+ Local
+ };
+
+ SocketAddress() { }
+ SocketAddress(const IPv4Address& address)
+ : m_type(Type::IPv4)
+ , m_ipv4_address(address)
+ {
+ }
+
+ SocketAddress(const IPv4Address& address, u16 port)
+ : m_type(Type::IPv4)
+ , m_ipv4_address(address)
+ , m_port(port)
+ {
+ }
+
+ static SocketAddress local(const String& address)
+ {
+ SocketAddress addr;
+ addr.m_type = Type::Local;
+ addr.m_local_address = address;
+ return addr;
+ }
+
+ Type type() const { return m_type; }
+ bool is_valid() const { return m_type != Type::Invalid; }
+ IPv4Address ipv4_address() const { return m_ipv4_address; }
+ u16 port() const { return m_port; }
+
+ String to_string() const
+ {
+ switch (m_type) {
+ case Type::IPv4:
+ return String::format("%s:%d", m_ipv4_address.to_string().characters(), m_port);
+ case Type::Local:
+ return m_local_address;
+ default:
+ return "[SocketAddress]";
+ }
+ }
+
+ Optional<sockaddr_un> to_sockaddr_un() const
+ {
+ ASSERT(type() == Type::Local);
+ sockaddr_un address;
+ address.sun_family = AF_LOCAL;
+ bool fits = m_local_address.copy_characters_to_buffer(address.sun_path, sizeof(address.sun_path));
+ if (!fits)
+ return {};
+ return address;
+ }
+
+ sockaddr_in to_sockaddr_in() const
+ {
+ ASSERT(type() == Type::IPv4);
+ sockaddr_in address {};
+ address.sin_family = AF_INET;
+ address.sin_addr.s_addr = m_ipv4_address.to_in_addr_t();
+ address.sin_port = htons(m_port);
+ return address;
+ }
+
+private:
+ Type m_type { Type::Invalid };
+ IPv4Address m_ipv4_address;
+ u16 m_port { 0 };
+ String m_local_address;
+};
+
+const LogStream& operator<<(const LogStream&, const SocketAddress&);
+
+}
diff --git a/Userland/Libraries/LibCore/StandardPaths.cpp b/Userland/Libraries/LibCore/StandardPaths.cpp
new file mode 100644
index 0000000000..3ed9197835
--- /dev/null
+++ b/Userland/Libraries/LibCore/StandardPaths.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LexicalPath.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <LibCore/StandardPaths.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+namespace Core {
+
+String StandardPaths::home_directory()
+{
+ if (auto* home_env = getenv("HOME"))
+ return LexicalPath::canonicalized_path(home_env);
+
+ auto* pwd = getpwuid(getuid());
+ String path = pwd ? pwd->pw_dir : "/";
+ endpwent();
+ return LexicalPath::canonicalized_path(path);
+}
+
+String StandardPaths::desktop_directory()
+{
+ StringBuilder builder;
+ builder.append(home_directory());
+ builder.append("/Desktop");
+ return LexicalPath::canonicalized_path(builder.to_string());
+}
+
+String StandardPaths::downloads_directory()
+{
+ StringBuilder builder;
+ builder.append(home_directory());
+ builder.append("/Downloads");
+ return LexicalPath::canonicalized_path(builder.to_string());
+}
+
+String StandardPaths::config_directory()
+{
+ StringBuilder builder;
+ builder.append(home_directory());
+ builder.append("/.config");
+ return LexicalPath::canonicalized_path(builder.to_string());
+}
+
+String StandardPaths::tempfile_directory()
+{
+ return "/tmp";
+}
+
+}
diff --git a/Userland/Libraries/LibCore/StandardPaths.h b/Userland/Libraries/LibCore/StandardPaths.h
new file mode 100644
index 0000000000..51346250c2
--- /dev/null
+++ b/Userland/Libraries/LibCore/StandardPaths.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+
+namespace Core {
+
+class StandardPaths {
+public:
+ static String home_directory();
+ static String desktop_directory();
+ static String downloads_directory();
+ static String tempfile_directory();
+ static String config_directory();
+};
+
+}
diff --git a/Userland/Libraries/LibCore/SyscallUtils.h b/Userland/Libraries/LibCore/SyscallUtils.h
new file mode 100644
index 0000000000..1c24efd9c2
--- /dev/null
+++ b/Userland/Libraries/LibCore/SyscallUtils.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/LogStream.h>
+#include <AK/StdLibExtras.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+namespace Core {
+
+template<typename Syscall, class... Args>
+inline int safe_syscall(Syscall syscall, Args&&... args)
+{
+ for (;;) {
+ int sysret = syscall(forward<Args>(args)...);
+ if (sysret == -1) {
+#ifdef SAFE_SYSCALL_DEBUG
+ int saved_errno = errno;
+ dbg() << "Core::safe_syscall: " << sysret << " (" << saved_errno << ": " << strerror(saved_errno) << ")";
+#endif
+ if (errno == EINTR)
+ continue;
+ ASSERT_NOT_REACHED();
+ }
+ return sysret;
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibCore/TCPServer.cpp b/Userland/Libraries/LibCore/TCPServer.cpp
new file mode 100644
index 0000000000..0ef241b717
--- /dev/null
+++ b/Userland/Libraries/LibCore/TCPServer.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/IPv4Address.h>
+#include <AK/Types.h>
+#include <LibCore/Notifier.h>
+#include <LibCore/TCPServer.h>
+#include <LibCore/TCPSocket.h>
+#include <stdio.h>
+#include <sys/socket.h>
+
+#ifndef SOCK_NONBLOCK
+# include <sys/ioctl.h>
+#endif
+namespace Core {
+
+TCPServer::TCPServer(Object* parent)
+ : Object(parent)
+{
+#ifdef SOCK_NONBLOCK
+ m_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+#else
+ m_fd = socket(AF_INET, SOCK_STREAM, 0);
+ int option = 1;
+ ioctl(m_fd, FIONBIO, &option);
+ fcntl(m_fd, F_SETFD, FD_CLOEXEC);
+#endif
+ ASSERT(m_fd >= 0);
+}
+
+TCPServer::~TCPServer()
+{
+ ::close(m_fd);
+}
+
+bool TCPServer::listen(const IPv4Address& address, u16 port)
+{
+ if (m_listening)
+ return false;
+
+ auto socket_address = SocketAddress(address, port);
+ auto in = socket_address.to_sockaddr_in();
+ if (::bind(m_fd, (const sockaddr*)&in, sizeof(in)) < 0) {
+ perror("TCPServer::listen: bind");
+ return false;
+ }
+
+ if (::listen(m_fd, 5) < 0) {
+ perror("TCPServer::listen: listen");
+ return false;
+ }
+ m_listening = true;
+
+ m_notifier = Notifier::construct(m_fd, Notifier::Event::Read, this);
+ m_notifier->on_ready_to_read = [this] {
+ if (on_ready_to_accept)
+ on_ready_to_accept();
+ };
+ return true;
+}
+
+RefPtr<TCPSocket> TCPServer::accept()
+{
+ ASSERT(m_listening);
+ sockaddr_in in;
+ socklen_t in_size = sizeof(in);
+ int accepted_fd = ::accept(m_fd, (sockaddr*)&in, &in_size);
+ if (accepted_fd < 0) {
+ perror("accept");
+ return nullptr;
+ }
+
+ return TCPSocket::construct(accepted_fd);
+}
+
+Optional<IPv4Address> TCPServer::local_address() const
+{
+ if (m_fd == -1)
+ return {};
+
+ sockaddr_in address;
+ socklen_t len = sizeof(address);
+ if (getsockname(m_fd, (sockaddr*)&address, &len) != 0)
+ return {};
+
+ return IPv4Address(address.sin_addr.s_addr);
+}
+
+Optional<u16> TCPServer::local_port() const
+{
+ if (m_fd == -1)
+ return {};
+
+ sockaddr_in address;
+ socklen_t len = sizeof(address);
+ if (getsockname(m_fd, (sockaddr*)&address, &len) != 0)
+ return {};
+
+ return ntohs(address.sin_port);
+}
+
+}
diff --git a/Userland/Libraries/LibCore/TCPServer.h b/Userland/Libraries/LibCore/TCPServer.h
new file mode 100644
index 0000000000..b516ec1500
--- /dev/null
+++ b/Userland/Libraries/LibCore/TCPServer.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/IPv4Address.h>
+#include <LibCore/Notifier.h>
+#include <LibCore/Object.h>
+
+namespace Core {
+
+class TCPServer : public Object {
+ C_OBJECT(TCPServer)
+public:
+ virtual ~TCPServer() override;
+
+ bool is_listening() const { return m_listening; }
+ bool listen(const IPv4Address& address, u16 port);
+
+ RefPtr<TCPSocket> accept();
+
+ Optional<IPv4Address> local_address() const;
+ Optional<u16> local_port() const;
+
+ Function<void()> on_ready_to_accept;
+
+private:
+ explicit TCPServer(Object* parent = nullptr);
+
+ int m_fd { -1 };
+ bool m_listening { false };
+ RefPtr<Notifier> m_notifier;
+};
+
+}
diff --git a/Userland/Libraries/LibCore/TCPSocket.cpp b/Userland/Libraries/LibCore/TCPSocket.cpp
new file mode 100644
index 0000000000..6029a82432
--- /dev/null
+++ b/Userland/Libraries/LibCore/TCPSocket.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/TCPSocket.h>
+#include <errno.h>
+#include <sys/socket.h>
+
+#ifndef SOCK_NONBLOCK
+# include <sys/ioctl.h>
+#endif
+
+namespace Core {
+
+TCPSocket::TCPSocket(int fd, Object* parent)
+ : Socket(Socket::Type::TCP, parent)
+{
+ // NOTE: This constructor is used by TCPServer::accept(), so the socket is already connected.
+ m_connected = true;
+ set_fd(fd);
+ set_mode(IODevice::ReadWrite);
+ set_error(0);
+}
+
+TCPSocket::TCPSocket(Object* parent)
+ : Socket(Socket::Type::TCP, parent)
+{
+#ifdef SOCK_NONBLOCK
+ int fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+#else
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
+ int option = 1;
+ ioctl(fd, FIONBIO, &option);
+#endif
+ if (fd < 0) {
+ set_error(errno);
+ } else {
+ set_fd(fd);
+ set_mode(IODevice::ReadWrite);
+ set_error(0);
+ }
+}
+
+TCPSocket::~TCPSocket()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibCore/TCPSocket.h b/Userland/Libraries/LibCore/TCPSocket.h
new file mode 100644
index 0000000000..8bdd1ae70c
--- /dev/null
+++ b/Userland/Libraries/LibCore/TCPSocket.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Badge.h>
+#include <LibCore/Socket.h>
+
+namespace Core {
+
+class TCPSocket final : public Socket {
+ C_OBJECT(TCPSocket)
+public:
+ virtual ~TCPSocket() override;
+
+private:
+ TCPSocket(int fd, Object* parent = nullptr);
+ explicit TCPSocket(Object* parent = nullptr);
+};
+
+}
diff --git a/Userland/Libraries/LibCore/Timer.cpp b/Userland/Libraries/LibCore/Timer.cpp
new file mode 100644
index 0000000000..aa0a0cb17d
--- /dev/null
+++ b/Userland/Libraries/LibCore/Timer.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/Timer.h>
+
+namespace Core {
+
+Timer::Timer(Object* parent)
+ : Object(parent)
+{
+}
+
+Timer::Timer(int interval, Function<void()>&& timeout_handler, Object* parent)
+ : Object(parent)
+ , on_timeout(move(timeout_handler))
+{
+ start(interval);
+}
+
+Timer::~Timer()
+{
+}
+
+void Timer::start()
+{
+ start(m_interval);
+}
+
+void Timer::start(int interval)
+{
+ if (m_active)
+ return;
+ m_interval = interval;
+ start_timer(interval);
+ m_active = true;
+}
+
+void Timer::restart()
+{
+ restart(m_interval);
+}
+
+void Timer::restart(int interval)
+{
+ if (m_active)
+ stop();
+ start(interval);
+}
+
+void Timer::stop()
+{
+ if (!m_active)
+ return;
+ stop_timer();
+ m_active = false;
+}
+
+void Timer::timer_event(TimerEvent&)
+{
+ if (m_single_shot)
+ stop();
+ else {
+ if (m_interval_dirty) {
+ stop();
+ start(m_interval);
+ }
+ }
+
+ if (on_timeout)
+ on_timeout();
+}
+
+}
diff --git a/Userland/Libraries/LibCore/Timer.h b/Userland/Libraries/LibCore/Timer.h
new file mode 100644
index 0000000000..04f41e39a0
--- /dev/null
+++ b/Userland/Libraries/LibCore/Timer.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <LibCore/Object.h>
+
+namespace Core {
+
+class Timer final : public Object {
+ C_OBJECT(Timer);
+
+public:
+ static NonnullRefPtr<Timer> create_single_shot(int interval, Function<void()>&& timeout_handler, Object* parent = nullptr)
+ {
+ auto timer = adopt(*new Timer(interval, move(timeout_handler), parent));
+ timer->set_single_shot(true);
+ timer->stop();
+ return timer;
+ }
+
+ virtual ~Timer() override;
+
+ void start();
+ void start(int interval);
+ void restart();
+ void restart(int interval);
+ void stop();
+
+ bool is_active() const { return m_active; }
+ int interval() const { return m_interval; }
+ void set_interval(int interval)
+ {
+ if (m_interval == interval)
+ return;
+ m_interval = interval;
+ m_interval_dirty = true;
+ }
+
+ bool is_single_shot() const { return m_single_shot; }
+ void set_single_shot(bool single_shot) { m_single_shot = single_shot; }
+
+ Function<void()> on_timeout;
+
+private:
+ explicit Timer(Object* parent = nullptr);
+ Timer(int interval, Function<void()>&& timeout_handler, Object* parent = nullptr);
+
+ virtual void timer_event(TimerEvent&) override;
+
+ bool m_active { false };
+ bool m_single_shot { false };
+ bool m_interval_dirty { false };
+ int m_interval { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibCore/UDPServer.cpp b/Userland/Libraries/LibCore/UDPServer.cpp
new file mode 100644
index 0000000000..6716865e6b
--- /dev/null
+++ b/Userland/Libraries/LibCore/UDPServer.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/IPv4Address.h>
+#include <AK/Types.h>
+#include <LibCore/Notifier.h>
+#include <LibCore/UDPServer.h>
+#include <LibCore/UDPSocket.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#ifndef SOCK_NONBLOCK
+# include <sys/ioctl.h>
+#endif
+
+namespace Core {
+
+UDPServer::UDPServer(Object* parent)
+ : Object(parent)
+{
+#ifdef SOCK_NONBLOCK
+ m_fd = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+#else
+ m_fd = socket(AF_INET, SOCK_DGRAM, 0);
+ int option = 1;
+ ioctl(m_fd, FIONBIO, &option);
+ fcntl(m_fd, F_SETFD, FD_CLOEXEC);
+#endif
+ ASSERT(m_fd >= 0);
+}
+
+UDPServer::~UDPServer()
+{
+ ::close(m_fd);
+}
+
+bool UDPServer::bind(const IPv4Address& address, u16 port)
+{
+ if (m_bound)
+ return false;
+
+ auto saddr = SocketAddress(address, port);
+ auto in = saddr.to_sockaddr_in();
+
+ if (::bind(m_fd, (const sockaddr*)&in, sizeof(in)) != 0) {
+ perror("UDPServer::bind");
+ return false;
+ }
+
+ m_bound = true;
+
+ m_notifier = Notifier::construct(m_fd, Notifier::Event::Read, this);
+ m_notifier->on_ready_to_read = [this] {
+ if (on_ready_to_receive)
+ on_ready_to_receive();
+ };
+ return true;
+}
+
+ByteBuffer UDPServer::receive(size_t size, sockaddr_in& in)
+{
+ auto buf = ByteBuffer::create_uninitialized(size);
+ socklen_t in_len = sizeof(in);
+ ssize_t rlen = ::recvfrom(m_fd, buf.data(), size, 0, (sockaddr*)&in, &in_len);
+ if (rlen < 0) {
+ dbgln("recvfrom: {}", strerror(errno));
+ return {};
+ }
+
+ buf.trim(rlen);
+ return buf;
+}
+
+Optional<IPv4Address> UDPServer::local_address() const
+{
+ if (m_fd == -1)
+ return {};
+
+ sockaddr_in address;
+ socklen_t len = sizeof(address);
+ if (getsockname(m_fd, (sockaddr*)&address, &len) != 0)
+ return {};
+
+ return IPv4Address(address.sin_addr.s_addr);
+}
+
+Optional<u16> UDPServer::local_port() const
+{
+ if (m_fd == -1)
+ return {};
+
+ sockaddr_in address;
+ socklen_t len = sizeof(address);
+ if (getsockname(m_fd, (sockaddr*)&address, &len) != 0)
+ return {};
+
+ return ntohs(address.sin_port);
+}
+
+}
diff --git a/Userland/Libraries/LibCore/UDPServer.h b/Userland/Libraries/LibCore/UDPServer.h
new file mode 100644
index 0000000000..29dfd29d60
--- /dev/null
+++ b/Userland/Libraries/LibCore/UDPServer.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/Forward.h>
+#include <AK/Function.h>
+#include <LibCore/Forward.h>
+#include <LibCore/Object.h>
+#include <LibCore/SocketAddress.h>
+
+namespace Core {
+
+class UDPServer : public Object {
+ C_OBJECT(UDPServer)
+public:
+ virtual ~UDPServer() override;
+
+ bool is_bound() const { return m_bound; }
+
+ bool bind(const IPv4Address& address, u16 port);
+ ByteBuffer receive(size_t size, sockaddr_in& from);
+ ByteBuffer receive(size_t size)
+ {
+ struct sockaddr_in saddr;
+ return receive(size, saddr);
+ };
+
+ Optional<IPv4Address> local_address() const;
+ Optional<u16> local_port() const;
+
+ Function<void()> on_ready_to_receive;
+
+private:
+ explicit UDPServer(Object* parent = nullptr);
+
+ int m_fd { -1 };
+ bool m_bound { false };
+ RefPtr<Notifier> m_notifier;
+};
+
+}
diff --git a/Userland/Libraries/LibCore/UDPSocket.cpp b/Userland/Libraries/LibCore/UDPSocket.cpp
new file mode 100644
index 0000000000..c319ac3dc1
--- /dev/null
+++ b/Userland/Libraries/LibCore/UDPSocket.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/UDPSocket.h>
+#include <errno.h>
+#include <sys/socket.h>
+
+#ifndef SOCK_NONBLOCK
+# include <sys/ioctl.h>
+#endif
+
+namespace Core {
+
+UDPSocket::UDPSocket(Object* parent)
+ : Socket(Socket::Type::UDP, parent)
+{
+#ifdef SOCK_NONBLOCK
+ int fd = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+#else
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ int option = 1;
+ ioctl(fd, FIONBIO, &option);
+#endif
+
+ if (fd < 0) {
+ set_error(errno);
+ } else {
+ set_fd(fd);
+ set_mode(IODevice::ReadWrite);
+ set_error(0);
+ }
+}
+
+UDPSocket::~UDPSocket()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibCore/UDPSocket.h b/Userland/Libraries/LibCore/UDPSocket.h
new file mode 100644
index 0000000000..3bb65d9831
--- /dev/null
+++ b/Userland/Libraries/LibCore/UDPSocket.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibCore/Socket.h>
+
+namespace Core {
+
+class UDPSocket final : public Socket {
+ C_OBJECT(UDPSocket)
+public:
+ virtual ~UDPSocket() override;
+
+private:
+ explicit UDPSocket(Object* parent = nullptr);
+};
+
+}
diff --git a/Userland/Libraries/LibCore/puff.cpp b/Userland/Libraries/LibCore/puff.cpp
new file mode 100644
index 0000000000..b17f26a00e
--- /dev/null
+++ b/Userland/Libraries/LibCore/puff.cpp
@@ -0,0 +1,832 @@
+/*
+ * puff.c
+ * Copyright (C) 2002-2013 Mark Adler
+ * For conditions of distribution and use, see copyright notice in puff.h
+ * version 2.3, 21 Jan 2013
+ *
+ * puff.c is a simple inflate written to be an unambiguous way to specify the
+ * deflate format. It is not written for speed but rather simplicity. As a
+ * side benefit, this code might actually be useful when small code is more
+ * important than speed, such as bootstrap applications. For typical deflate
+ * data, zlib's inflate() is about four times as fast as puff(). zlib's
+ * inflate compiles to around 20K on my machine, whereas puff.c compiles to
+ * around 4K on my machine (a PowerPC using GNU cc). If the faster decode()
+ * function here is used, then puff() is only twice as slow as zlib's
+ * inflate().
+ *
+ * All dynamically allocated memory comes from the stack. The stack required
+ * is less than 2K bytes. This code is compatible with 16-bit int's and
+ * assumes that long's are at least 32 bits. puff.c uses the short data type,
+ * assumed to be 16 bits, for arrays in order to conserve memory. The code
+ * works whether integers are stored big endian or little endian.
+ *
+ * In the comments below are "Format notes" that describe the inflate process
+ * and document some of the less obvious aspects of the format. This source
+ * code is meant to supplement RFC 1951, which formally describes the deflate
+ * format:
+ *
+ * http://www.zlib.org/rfc-deflate.html
+ */
+
+/*
+ * Change history:
+ *
+ * 1.0 10 Feb 2002 - First version
+ * 1.1 17 Feb 2002 - Clarifications of some comments and notes
+ * - Update puff() dest and source pointers on negative
+ * errors to facilitate debugging deflators
+ * - Remove longest from struct huffman -- not needed
+ * - Simplify offs[] index in construct()
+ * - Add input size and checking, using longjmp() to
+ * maintain easy readability
+ * - Use short data type for large arrays
+ * - Use pointers instead of long to specify source and
+ * destination sizes to avoid arbitrary 4 GB limits
+ * 1.2 17 Mar 2002 - Add faster version of decode(), doubles speed (!),
+ * but leave simple version for readabilty
+ * - Make sure invalid distances detected if pointers
+ * are 16 bits
+ * - Fix fixed codes table error
+ * - Provide a scanning mode for determining size of
+ * uncompressed data
+ * 1.3 20 Mar 2002 - Go back to lengths for puff() parameters [Gailly]
+ * - Add a puff.h file for the interface
+ * - Add braces in puff() for else do [Gailly]
+ * - Use indexes instead of pointers for readability
+ * 1.4 31 Mar 2002 - Simplify construct() code set check
+ * - Fix some comments
+ * - Add FIXLCODES #define
+ * 1.5 6 Apr 2002 - Minor comment fixes
+ * 1.6 7 Aug 2002 - Minor format changes
+ * 1.7 3 Mar 2003 - Added test code for distribution
+ * - Added zlib-like license
+ * 1.8 9 Jan 2004 - Added some comments on no distance codes case
+ * 1.9 21 Feb 2008 - Fix bug on 16-bit integer architectures [Pohland]
+ * - Catch missing end-of-block symbol error
+ * 2.0 25 Jul 2008 - Add #define to permit distance too far back
+ * - Add option in TEST code for puff to write the data
+ * - Add option in TEST code to skip input bytes
+ * - Allow TEST code to read from piped stdin
+ * 2.1 4 Apr 2010 - Avoid variable initialization for happier compilers
+ * - Avoid unsigned comparisons for even happier compilers
+ * 2.2 25 Apr 2010 - Fix bug in variable initializations [Oberhumer]
+ * - Add const where appropriate [Oberhumer]
+ * - Split if's and ?'s for coverage testing
+ * - Break out test code to separate file
+ * - Move NIL to puff.h
+ * - Allow incomplete code only if single code length is 1
+ * - Add full code coverage test to Makefile
+ * 2.3 21 Jan 2013 - Check for invalid code length codes in dynamic blocks
+ */
+
+#include "puff.h" /* prototype for puff() */
+#include <setjmp.h> /* for setjmp(), longjmp(), and jmp_buf */
+
+#define local static /* for local function definitions */
+
+/*
+ * Maximums for allocations and loops. It is not useful to change these --
+ * they are fixed by the deflate format.
+ */
+#define MAXBITS 15 /* maximum bits in a code */
+#define MAXLCODES 286 /* maximum number of literal/length codes */
+#define MAXDCODES 30 /* maximum number of distance codes */
+#define MAXCODES (MAXLCODES + MAXDCODES) /* maximum codes lengths to read */
+#define FIXLCODES 288 /* number of fixed literal/length codes */
+
+/* input and output state */
+struct state {
+ /* output state */
+ unsigned char* out; /* output buffer */
+ unsigned long outlen; /* available space at out */
+ unsigned long outcnt; /* bytes written to out so far */
+
+ /* input state */
+ const unsigned char* in; /* input buffer */
+ unsigned long inlen; /* available input at in */
+ unsigned long incnt; /* bytes read so far */
+ int bitbuf; /* bit buffer */
+ int bitcnt; /* number of bits in bit buffer */
+
+ /* input limit error return state for bits() and decode() */
+ jmp_buf env;
+};
+
+/*
+ * Return need bits from the input stream. This always leaves less than
+ * eight bits in the buffer. bits() works properly for need == 0.
+ *
+ * Format notes:
+ *
+ * - Bits are stored in bytes from the least significant bit to the most
+ * significant bit. Therefore bits are dropped from the bottom of the bit
+ * buffer, using shift right, and new bytes are appended to the top of the
+ * bit buffer, using shift left.
+ */
+local int bits(struct state* s, int need)
+{
+ long val; /* bit accumulator (can use up to 20 bits) */
+
+ /* load at least need bits into val */
+ val = s->bitbuf;
+ while (s->bitcnt < need) {
+ if (s->incnt == s->inlen)
+ longjmp(s->env, 1); /* out of input */
+ val |= (long)(s->in[s->incnt++]) << s->bitcnt; /* load eight bits */
+ s->bitcnt += 8;
+ }
+
+ /* drop need bits and update buffer, always zero to seven bits left */
+ s->bitbuf = (int)(val >> need);
+ s->bitcnt -= need;
+
+ /* return need bits, zeroing the bits above that */
+ return (int)(val & ((1L << need) - 1));
+}
+
+/*
+ * Process a stored block.
+ *
+ * Format notes:
+ *
+ * - After the two-bit stored block type (00), the stored block length and
+ * stored bytes are byte-aligned for fast copying. Therefore any leftover
+ * bits in the byte that has the last bit of the type, as many as seven, are
+ * discarded. The value of the discarded bits are not defined and should not
+ * be checked against any expectation.
+ *
+ * - The second inverted copy of the stored block length does not have to be
+ * checked, but it's probably a good idea to do so anyway.
+ *
+ * - A stored block can have zero length. This is sometimes used to byte-align
+ * subsets of the compressed data for random access or partial recovery.
+ */
+local int stored(struct state* s)
+{
+ unsigned len; /* length of stored block */
+
+ /* discard leftover bits from current byte (assumes s->bitcnt < 8) */
+ s->bitbuf = 0;
+ s->bitcnt = 0;
+
+ /* get length and check against its one's complement */
+ if (s->incnt + 4 > s->inlen)
+ return 2; /* not enough input */
+ len = s->in[s->incnt++];
+ len |= s->in[s->incnt++] << 8;
+ if (s->in[s->incnt++] != (~len & 0xff) || s->in[s->incnt++] != ((~len >> 8) & 0xff))
+ return -2; /* didn't match complement! */
+
+ /* copy len bytes from in to out */
+ if (s->incnt + len > s->inlen)
+ return 2; /* not enough input */
+ if (s->out != NIL) {
+ if (s->outcnt + len > s->outlen)
+ return 1; /* not enough output space */
+ while (len--)
+ s->out[s->outcnt++] = s->in[s->incnt++];
+ } else { /* just scanning */
+ s->outcnt += len;
+ s->incnt += len;
+ }
+
+ /* done with a valid stored block */
+ return 0;
+}
+
+/*
+ * Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of
+ * each length, which for a canonical code are stepped through in order.
+ * symbol[] are the symbol values in canonical order, where the number of
+ * entries is the sum of the counts in count[]. The decoding process can be
+ * seen in the function decode() below.
+ */
+struct huffman {
+ short* count; /* number of symbols of each length */
+ short* symbol; /* canonically ordered symbols */
+};
+
+/*
+ * Decode a code from the stream s using huffman table h. Return the symbol or
+ * a negative value if there is an error. If all of the lengths are zero, i.e.
+ * an empty code, or if the code is incomplete and an invalid code is received,
+ * then -10 is returned after reading MAXBITS bits.
+ *
+ * Format notes:
+ *
+ * - The codes as stored in the compressed data are bit-reversed relative to
+ * a simple integer ordering of codes of the same lengths. Hence below the
+ * bits are pulled from the compressed data one at a time and used to
+ * build the code value reversed from what is in the stream in order to
+ * permit simple integer comparisons for decoding. A table-based decoding
+ * scheme (as used in zlib) does not need to do this reversal.
+ *
+ * - The first code for the shortest length is all zeros. Subsequent codes of
+ * the same length are simply integer increments of the previous code. When
+ * moving up a length, a zero bit is appended to the code. For a complete
+ * code, the last code of the longest length will be all ones.
+ *
+ * - Incomplete codes are handled by this decoder, since they are permitted
+ * in the deflate format. See the format notes for fixed() and dynamic().
+ */
+#ifdef SLOW
+local int decode(struct state* s, const struct huffman* h)
+{
+ int len; /* current number of bits in code */
+ int code; /* len bits being decoded */
+ int first; /* first code of length len */
+ int count; /* number of codes of length len */
+ int index; /* index of first code of length len in symbol table */
+
+ code = first = index = 0;
+ for (len = 1; len <= MAXBITS; len++) {
+ code |= bits(s, 1); /* get next bit */
+ count = h->count[len];
+ if (code - count < first) /* if length len, return symbol */
+ return h->symbol[index + (code - first)];
+ index += count; /* else update for next length */
+ first += count;
+ first <<= 1;
+ code <<= 1;
+ }
+ return -10; /* ran out of codes */
+}
+
+/*
+ * A faster version of decode() for real applications of this code. It's not
+ * as readable, but it makes puff() twice as fast. And it only makes the code
+ * a few percent larger.
+ */
+#else /* !SLOW */
+local int decode(struct state* s, const struct huffman* h)
+{
+ int len; /* current number of bits in code */
+ int code; /* len bits being decoded */
+ int first; /* first code of length len */
+ int count; /* number of codes of length len */
+ int index; /* index of first code of length len in symbol table */
+ int bitbuf; /* bits from stream */
+ int left; /* bits left in next or left to process */
+ short* next; /* next number of codes */
+
+ bitbuf = s->bitbuf;
+ left = s->bitcnt;
+ code = first = index = 0;
+ len = 1;
+ next = h->count + 1;
+ while (1) {
+ while (left--) {
+ code |= bitbuf & 1;
+ bitbuf >>= 1;
+ count = *next++;
+ if (code - count < first) { /* if length len, return symbol */
+ s->bitbuf = bitbuf;
+ s->bitcnt = (s->bitcnt - len) & 7;
+ return h->symbol[index + (code - first)];
+ }
+ index += count; /* else update for next length */
+ first += count;
+ first <<= 1;
+ code <<= 1;
+ len++;
+ }
+ left = (MAXBITS + 1) - len;
+ if (left == 0)
+ break;
+ if (s->incnt == s->inlen)
+ longjmp(s->env, 1); /* out of input */
+ bitbuf = s->in[s->incnt++];
+ if (left > 8)
+ left = 8;
+ }
+ return -10; /* ran out of codes */
+}
+#endif /* SLOW */
+
+/*
+ * Given the list of code lengths length[0..n-1] representing a canonical
+ * Huffman code for n symbols, construct the tables required to decode those
+ * codes. Those tables are the number of codes of each length, and the symbols
+ * sorted by length, retaining their original order within each length. The
+ * return value is zero for a complete code set, negative for an over-
+ * subscribed code set, and positive for an incomplete code set. The tables
+ * can be used if the return value is zero or positive, but they cannot be used
+ * if the return value is negative. If the return value is zero, it is not
+ * possible for decode() using that table to return an error--any stream of
+ * enough bits will resolve to a symbol. If the return value is positive, then
+ * it is possible for decode() using that table to return an error for received
+ * codes past the end of the incomplete lengths.
+ *
+ * Not used by decode(), but used for error checking, h->count[0] is the number
+ * of the n symbols not in the code. So n - h->count[0] is the number of
+ * codes. This is useful for checking for incomplete codes that have more than
+ * one symbol, which is an error in a dynamic block.
+ *
+ * Assumption: for all i in 0..n-1, 0 <= length[i] <= MAXBITS
+ * This is assured by the construction of the length arrays in dynamic() and
+ * fixed() and is not verified by construct().
+ *
+ * Format notes:
+ *
+ * - Permitted and expected examples of incomplete codes are one of the fixed
+ * codes and any code with a single symbol which in deflate is coded as one
+ * bit instead of zero bits. See the format notes for fixed() and dynamic().
+ *
+ * - Within a given code length, the symbols are kept in ascending order for
+ * the code bits definition.
+ */
+local int construct(struct huffman* h, const short* length, int n)
+{
+ int symbol; /* current symbol when stepping through length[] */
+ int len; /* current length when stepping through h->count[] */
+ int left; /* number of possible codes left of current length */
+ short offs[MAXBITS + 1]; /* offsets in symbol table for each length */
+
+ /* count number of codes of each length */
+ for (len = 0; len <= MAXBITS; len++)
+ h->count[len] = 0;
+ for (symbol = 0; symbol < n; symbol++)
+ (h->count[length[symbol]])++; /* assumes lengths are within bounds */
+ if (h->count[0] == n) /* no codes! */
+ return 0; /* complete, but decode() will fail */
+
+ /* check for an over-subscribed or incomplete set of lengths */
+ left = 1; /* one possible code of zero length */
+ for (len = 1; len <= MAXBITS; len++) {
+ left <<= 1; /* one more bit, double codes left */
+ left -= h->count[len]; /* deduct count from possible codes */
+ if (left < 0)
+ return left; /* over-subscribed--return negative */
+ } /* left > 0 means incomplete */
+
+ /* generate offsets into symbol table for each length for sorting */
+ offs[1] = 0;
+ for (len = 1; len < MAXBITS; len++)
+ offs[len + 1] = offs[len] + h->count[len];
+
+ /*
+ * put symbols in table sorted by length, by symbol order within each
+ * length
+ */
+ for (symbol = 0; symbol < n; symbol++)
+ if (length[symbol] != 0)
+ h->symbol[offs[length[symbol]]++] = symbol;
+
+ /* return zero for complete set, positive for incomplete set */
+ return left;
+}
+
+/*
+ * Decode literal/length and distance codes until an end-of-block code.
+ *
+ * Format notes:
+ *
+ * - Compressed data that is after the block type if fixed or after the code
+ * description if dynamic is a combination of literals and length/distance
+ * pairs terminated by and end-of-block code. Literals are simply Huffman
+ * coded bytes. A length/distance pair is a coded length followed by a
+ * coded distance to represent a string that occurs earlier in the
+ * uncompressed data that occurs again at the current location.
+ *
+ * - Literals, lengths, and the end-of-block code are combined into a single
+ * code of up to 286 symbols. They are 256 literals (0..255), 29 length
+ * symbols (257..285), and the end-of-block symbol (256).
+ *
+ * - There are 256 possible lengths (3..258), and so 29 symbols are not enough
+ * to represent all of those. Lengths 3..10 and 258 are in fact represented
+ * by just a length symbol. Lengths 11..257 are represented as a symbol and
+ * some number of extra bits that are added as an integer to the base length
+ * of the length symbol. The number of extra bits is determined by the base
+ * length symbol. These are in the static arrays below, lens[] for the base
+ * lengths and lext[] for the corresponding number of extra bits.
+ *
+ * - The reason that 258 gets its own symbol is that the longest length is used
+ * often in highly redundant files. Note that 258 can also be coded as the
+ * base value 227 plus the maximum extra value of 31. While a good deflate
+ * should never do this, it is not an error, and should be decoded properly.
+ *
+ * - If a length is decoded, including its extra bits if any, then it is
+ * followed a distance code. There are up to 30 distance symbols. Again
+ * there are many more possible distances (1..32768), so extra bits are added
+ * to a base value represented by the symbol. The distances 1..4 get their
+ * own symbol, but the rest require extra bits. The base distances and
+ * corresponding number of extra bits are below in the static arrays dist[]
+ * and dext[].
+ *
+ * - Literal bytes are simply written to the output. A length/distance pair is
+ * an instruction to copy previously uncompressed bytes to the output. The
+ * copy is from distance bytes back in the output stream, copying for length
+ * bytes.
+ *
+ * - Distances pointing before the beginning of the output data are not
+ * permitted.
+ *
+ * - Overlapped copies, where the length is greater than the distance, are
+ * allowed and common. For example, a distance of one and a length of 258
+ * simply copies the last byte 258 times. A distance of four and a length of
+ * twelve copies the last four bytes three times. A simple forward copy
+ * ignoring whether the length is greater than the distance or not implements
+ * this correctly. You should not use memcpy() since its behavior is not
+ * defined for overlapped arrays. You should not use memmove() or bcopy()
+ * since though their behavior -is- defined for overlapping arrays, it is
+ * defined to do the wrong thing in this case.
+ */
+local int codes(struct state* s,
+ const struct huffman* lencode,
+ const struct huffman* distcode)
+{
+ int symbol; /* decoded symbol */
+ int len; /* length for copy */
+ unsigned dist; /* distance for copy */
+ static const short lens[29] = { /* Size base for length codes 257..285 */
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+ 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258
+ };
+ static const short lext[29] = { /* Extra bits for length codes 257..285 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
+ 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0
+ };
+ static const short dists[30] = { /* Offset base for distance codes 0..29 */
+ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+ 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+ 8193, 12289, 16385, 24577
+ };
+ static const short dext[30] = { /* Extra bits for distance codes 0..29 */
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
+ 7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
+ 12, 12, 13, 13
+ };
+
+ /* decode literals and length/distance pairs */
+ do {
+ symbol = decode(s, lencode);
+ if (symbol < 0)
+ return symbol; /* invalid symbol */
+ if (symbol < 256) { /* literal: symbol is the byte */
+ /* write out the literal */
+ if (s->out != NIL) {
+ if (s->outcnt == s->outlen)
+ return 1;
+ s->out[s->outcnt] = symbol;
+ }
+ s->outcnt++;
+ } else if (symbol > 256) { /* length */
+ /* get and compute length */
+ symbol -= 257;
+ if (symbol >= 29)
+ return -10; /* invalid fixed code */
+ len = lens[symbol] + bits(s, lext[symbol]);
+
+ /* get and check distance */
+ symbol = decode(s, distcode);
+ if (symbol < 0)
+ return symbol; /* invalid symbol */
+ dist = dists[symbol] + bits(s, dext[symbol]);
+#ifndef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+ if (dist > s->outcnt)
+ return -11; /* distance too far back */
+#endif
+
+ /* copy length bytes from distance bytes back */
+ if (s->out != NIL) {
+ if (s->outcnt + len > s->outlen)
+ return 1;
+ while (len--) {
+ s->out[s->outcnt] =
+#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+ dist > s->outcnt ? 0 :
+#endif
+ s->out[s->outcnt - dist];
+ s->outcnt++;
+ }
+ } else
+ s->outcnt += len;
+ }
+ } while (symbol != 256); /* end of block symbol */
+
+ /* done with a valid fixed or dynamic block */
+ return 0;
+}
+
+/*
+ * Process a fixed codes block.
+ *
+ * Format notes:
+ *
+ * - This block type can be useful for compressing small amounts of data for
+ * which the size of the code descriptions in a dynamic block exceeds the
+ * benefit of custom codes for that block. For fixed codes, no bits are
+ * spent on code descriptions. Instead the code lengths for literal/length
+ * codes and distance codes are fixed. The specific lengths for each symbol
+ * can be seen in the "for" loops below.
+ *
+ * - The literal/length code is complete, but has two symbols that are invalid
+ * and should result in an error if received. This cannot be implemented
+ * simply as an incomplete code since those two symbols are in the "middle"
+ * of the code. They are eight bits long and the longest literal/length\
+ * code is nine bits. Therefore the code must be constructed with those
+ * symbols, and the invalid symbols must be detected after decoding.
+ *
+ * - The fixed distance codes also have two invalid symbols that should result
+ * in an error if received. Since all of the distance codes are the same
+ * length, this can be implemented as an incomplete code. Then the invalid
+ * codes are detected while decoding.
+ */
+local int fixed(struct state* s)
+{
+ static int virgin = 1;
+ static short lencnt[MAXBITS + 1], lensym[FIXLCODES];
+ static short distcnt[MAXBITS + 1], distsym[MAXDCODES];
+ static struct huffman lencode, distcode;
+
+ /* build fixed huffman tables if first call (may not be thread safe) */
+ if (virgin) {
+ int symbol;
+ short lengths[FIXLCODES];
+
+ /* construct lencode and distcode */
+ lencode.count = lencnt;
+ lencode.symbol = lensym;
+ distcode.count = distcnt;
+ distcode.symbol = distsym;
+
+ /* literal/length table */
+ for (symbol = 0; symbol < 144; symbol++)
+ lengths[symbol] = 8;
+ for (; symbol < 256; symbol++)
+ lengths[symbol] = 9;
+ for (; symbol < 280; symbol++)
+ lengths[symbol] = 7;
+ for (; symbol < FIXLCODES; symbol++)
+ lengths[symbol] = 8;
+ construct(&lencode, lengths, FIXLCODES);
+
+ /* distance table */
+ for (symbol = 0; symbol < MAXDCODES; symbol++)
+ lengths[symbol] = 5;
+ construct(&distcode, lengths, MAXDCODES);
+
+ /* do this just once */
+ virgin = 0;
+ }
+
+ /* decode data until end-of-block code */
+ return codes(s, &lencode, &distcode);
+}
+
+/*
+ * Process a dynamic codes block.
+ *
+ * Format notes:
+ *
+ * - A dynamic block starts with a description of the literal/length and
+ * distance codes for that block. New dynamic blocks allow the compressor to
+ * rapidly adapt to changing data with new codes optimized for that data.
+ *
+ * - The codes used by the deflate format are "canonical", which means that
+ * the actual bits of the codes are generated in an unambiguous way simply
+ * from the number of bits in each code. Therefore the code descriptions
+ * are simply a list of code lengths for each symbol.
+ *
+ * - The code lengths are stored in order for the symbols, so lengths are
+ * provided for each of the literal/length symbols, and for each of the
+ * distance symbols.
+ *
+ * - If a symbol is not used in the block, this is represented by a zero as
+ * as the code length. This does not mean a zero-length code, but rather
+ * that no code should be created for this symbol. There is no way in the
+ * deflate format to represent a zero-length code.
+ *
+ * - The maximum number of bits in a code is 15, so the possible lengths for
+ * any code are 1..15.
+ *
+ * - The fact that a length of zero is not permitted for a code has an
+ * interesting consequence. Normally if only one symbol is used for a given
+ * code, then in fact that code could be represented with zero bits. However
+ * in deflate, that code has to be at least one bit. So for example, if
+ * only a single distance base symbol appears in a block, then it will be
+ * represented by a single code of length one, in particular one 0 bit. This
+ * is an incomplete code, since if a 1 bit is received, it has no meaning,
+ * and should result in an error. So incomplete distance codes of one symbol
+ * should be permitted, and the receipt of invalid codes should be handled.
+ *
+ * - It is also possible to have a single literal/length code, but that code
+ * must be the end-of-block code, since every dynamic block has one. This
+ * is not the most efficient way to create an empty block (an empty fixed
+ * block is fewer bits), but it is allowed by the format. So incomplete
+ * literal/length codes of one symbol should also be permitted.
+ *
+ * - If there are only literal codes and no lengths, then there are no distance
+ * codes. This is represented by one distance code with zero bits.
+ *
+ * - The list of up to 286 length/literal lengths and up to 30 distance lengths
+ * are themselves compressed using Huffman codes and run-length encoding. In
+ * the list of code lengths, a 0 symbol means no code, a 1..15 symbol means
+ * that length, and the symbols 16, 17, and 18 are run-length instructions.
+ * Each of 16, 17, and 18 are follwed by extra bits to define the length of
+ * the run. 16 copies the last length 3 to 6 times. 17 represents 3 to 10
+ * zero lengths, and 18 represents 11 to 138 zero lengths. Unused symbols
+ * are common, hence the special coding for zero lengths.
+ *
+ * - The symbols for 0..18 are Huffman coded, and so that code must be
+ * described first. This is simply a sequence of up to 19 three-bit values
+ * representing no code (0) or the code length for that symbol (1..7).
+ *
+ * - A dynamic block starts with three fixed-size counts from which is computed
+ * the number of literal/length code lengths, the number of distance code
+ * lengths, and the number of code length code lengths (ok, you come up with
+ * a better name!) in the code descriptions. For the literal/length and
+ * distance codes, lengths after those provided are considered zero, i.e. no
+ * code. The code length code lengths are received in a permuted order (see
+ * the order[] array below) to make a short code length code length list more
+ * likely. As it turns out, very short and very long codes are less likely
+ * to be seen in a dynamic code description, hence what may appear initially
+ * to be a peculiar ordering.
+ *
+ * - Given the number of literal/length code lengths (nlen) and distance code
+ * lengths (ndist), then they are treated as one long list of nlen + ndist
+ * code lengths. Therefore run-length coding can and often does cross the
+ * boundary between the two sets of lengths.
+ *
+ * - So to summarize, the code description at the start of a dynamic block is
+ * three counts for the number of code lengths for the literal/length codes,
+ * the distance codes, and the code length codes. This is followed by the
+ * code length code lengths, three bits each. This is used to construct the
+ * code length code which is used to read the remainder of the lengths. Then
+ * the literal/length code lengths and distance lengths are read as a single
+ * set of lengths using the code length codes. Codes are constructed from
+ * the resulting two sets of lengths, and then finally you can start
+ * decoding actual compressed data in the block.
+ *
+ * - For reference, a "typical" size for the code description in a dynamic
+ * block is around 80 bytes.
+ */
+local int dynamic(struct state* s)
+{
+ int nlen, ndist, ncode; /* number of lengths in descriptor */
+ int index; /* index of lengths[] */
+ int err; /* construct() return value */
+ short lengths[MAXCODES]; /* descriptor code lengths */
+ short lencnt[MAXBITS + 1], lensym[MAXLCODES]; /* lencode memory */
+ short distcnt[MAXBITS + 1], distsym[MAXDCODES]; /* distcode memory */
+ struct huffman lencode, distcode; /* length and distance codes */
+ static const short order[19] = /* permutation of code length codes */
+ { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+
+ /* construct lencode and distcode */
+ lencode.count = lencnt;
+ lencode.symbol = lensym;
+ distcode.count = distcnt;
+ distcode.symbol = distsym;
+
+ /* get number of lengths in each table, check lengths */
+ nlen = bits(s, 5) + 257;
+ ndist = bits(s, 5) + 1;
+ ncode = bits(s, 4) + 4;
+ if (nlen > MAXLCODES || ndist > MAXDCODES)
+ return -3; /* bad counts */
+
+ /* read code length code lengths (really), missing lengths are zero */
+ for (index = 0; index < ncode; index++)
+ lengths[order[index]] = bits(s, 3);
+ for (; index < 19; index++)
+ lengths[order[index]] = 0;
+
+ /* build huffman table for code lengths codes (use lencode temporarily) */
+ err = construct(&lencode, lengths, 19);
+ if (err != 0) /* require complete code set here */
+ return -4;
+
+ /* read length/literal and distance code length tables */
+ index = 0;
+ while (index < nlen + ndist) {
+ int symbol; /* decoded value */
+ int len; /* last length to repeat */
+
+ symbol = decode(s, &lencode);
+ if (symbol < 0)
+ return symbol; /* invalid symbol */
+ if (symbol < 16) /* length in 0..15 */
+ lengths[index++] = symbol;
+ else { /* repeat instruction */
+ len = 0; /* assume repeating zeros */
+ if (symbol == 16) { /* repeat last length 3..6 times */
+ if (index == 0)
+ return -5; /* no last length! */
+ len = lengths[index - 1]; /* last length */
+ symbol = 3 + bits(s, 2);
+ } else if (symbol == 17) /* repeat zero 3..10 times */
+ symbol = 3 + bits(s, 3);
+ else /* == 18, repeat zero 11..138 times */
+ symbol = 11 + bits(s, 7);
+ if (index + symbol > nlen + ndist)
+ return -6; /* too many lengths! */
+ while (symbol--) /* repeat last or zero symbol times */
+ lengths[index++] = len;
+ }
+ }
+
+ /* check for end-of-block code -- there better be one! */
+ if (lengths[256] == 0)
+ return -9;
+
+ /* build huffman table for literal/length codes */
+ err = construct(&lencode, lengths, nlen);
+ if (err && (err < 0 || nlen != lencode.count[0] + lencode.count[1]))
+ return -7; /* incomplete code ok only for single length 1 code */
+
+ /* build huffman table for distance codes */
+ err = construct(&distcode, lengths + nlen, ndist);
+ if (err && (err < 0 || ndist != distcode.count[0] + distcode.count[1]))
+ return -8; /* incomplete code ok only for single length 1 code */
+
+ /* decode data until end-of-block code */
+ return codes(s, &lencode, &distcode);
+}
+
+/*
+ * Inflate source to dest. On return, destlen and sourcelen are updated to the
+ * size of the uncompressed data and the size of the deflate data respectively.
+ * On success, the return value of puff() is zero. If there is an error in the
+ * source data, i.e. it is not in the deflate format, then a negative value is
+ * returned. If there is not enough input available or there is not enough
+ * output space, then a positive error is returned. In that case, destlen and
+ * sourcelen are not updated to facilitate retrying from the beginning with the
+ * provision of more input data or more output space. In the case of invalid
+ * inflate data (a negative error), the dest and source pointers are updated to
+ * facilitate the debugging of deflators.
+ *
+ * puff() also has a mode to determine the size of the uncompressed output with
+ * no output written. For this dest must be (unsigned char *)0. In this case,
+ * the input value of *destlen is ignored, and on return *destlen is set to the
+ * size of the uncompressed output.
+ *
+ * The return codes are:
+ *
+ * 2: available inflate data did not terminate
+ * 1: output space exhausted before completing inflate
+ * 0: successful inflate
+ * -1: invalid block type (type == 3)
+ * -2: stored block length did not match one's complement
+ * -3: dynamic block code description: too many length or distance codes
+ * -4: dynamic block code description: code lengths codes incomplete
+ * -5: dynamic block code description: repeat lengths with no first length
+ * -6: dynamic block code description: repeat more than specified lengths
+ * -7: dynamic block code description: invalid literal/length code lengths
+ * -8: dynamic block code description: invalid distance code lengths
+ * -9: dynamic block code description: missing end-of-block code
+ * -10: invalid literal/length or distance code in fixed or dynamic block
+ * -11: distance is too far back in fixed or dynamic block
+ *
+ * Format notes:
+ *
+ * - Three bits are read for each block to determine the kind of block and
+ * whether or not it is the last block. Then the block is decoded and the
+ * process repeated if it was not the last block.
+ *
+ * - The leftover bits in the last byte of the deflate data after the last
+ * block (if it was a fixed or dynamic block) are undefined and have no
+ * expected values to check.
+ */
+int puff(unsigned char* dest, /* pointer to destination pointer */
+ unsigned long* destlen, /* amount of output space */
+ const unsigned char* source, /* pointer to source data pointer */
+ unsigned long* sourcelen) /* amount of input available */
+{
+ struct state s; /* input/output state */
+ int last, type; /* block information */
+ int err; /* return value */
+
+ /* initialize output state */
+ s.out = dest;
+ s.outlen = *destlen; /* ignored if dest is NIL */
+ s.outcnt = 0;
+
+ /* initialize input state */
+ s.in = source;
+ s.inlen = *sourcelen;
+ s.incnt = 0;
+ s.bitbuf = 0;
+ s.bitcnt = 0;
+
+ /* return if bits() or decode() tries to read past available input */
+ if (setjmp(s.env) != 0) /* if came back here via longjmp() */
+ err = 2; /* then skip do-loop, return error */
+ else {
+ /* process blocks until last block or error */
+ do {
+ last = bits(&s, 1); /* one if last block */
+ type = bits(&s, 2); /* block type 0..3 */
+ err = type == 0 ? stored(&s) : (type == 1 ? fixed(&s) : (type == 2 ? dynamic(&s) : -1)); /* type == 3, invalid */
+ if (err != 0)
+ break; /* return with error */
+ } while (!last);
+ }
+
+ /* update the lengths and return */
+ if (err <= 0) {
+ *destlen = s.outcnt;
+ *sourcelen = s.incnt;
+ }
+ return err;
+}
diff --git a/Userland/Libraries/LibCore/puff.h b/Userland/Libraries/LibCore/puff.h
new file mode 100644
index 0000000000..5943910403
--- /dev/null
+++ b/Userland/Libraries/LibCore/puff.h
@@ -0,0 +1,44 @@
+#pragma once
+
+/* puff.h
+ Copyright (C) 2002-2013 Mark Adler, all rights reserved
+ version 2.3, 21 Jan 2013
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the author be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ Mark Adler madler@alumni.caltech.edu
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * See puff.c for purpose and usage.
+ */
+#ifndef NIL
+# define NIL ((unsigned char*)0) /* for no output option */
+#endif
+
+int puff(unsigned char* dest, /* pointer to destination pointer */
+ unsigned long* destlen, /* amount of output space */
+ const unsigned char* source, /* pointer to source data pointer */
+ unsigned long* sourcelen); /* amount of input available */
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/Userland/Libraries/LibCoreDump/Backtrace.cpp b/Userland/Libraries/LibCoreDump/Backtrace.cpp
new file mode 100644
index 0000000000..afa6220447
--- /dev/null
+++ b/Userland/Libraries/LibCoreDump/Backtrace.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/StringBuilder.h>
+#include <AK/Types.h>
+#include <LibCore/File.h>
+#include <LibCoreDump/Backtrace.h>
+#include <LibCoreDump/Reader.h>
+#include <LibELF/CoreDump.h>
+#include <LibELF/Image.h>
+
+namespace CoreDump {
+
+// FIXME: This cache has to be invalidated when libraries/programs are re-compiled.
+// We can store the last-modified timestamp of the elf files in ELFObjectInfo to invalidate cache entries.
+static HashMap<String, NonnullOwnPtr<ELFObjectInfo>> s_debug_info_cache;
+
+static const ELFObjectInfo* object_info_for_region(const ELF::Core::MemoryRegionInfo& region)
+{
+ auto name = region.object_name();
+
+ String path;
+ if (name.contains(".so"))
+ path = String::formatted("/usr/lib/{}", name);
+ else {
+ path = name;
+ }
+
+ if (auto it = s_debug_info_cache.find(path); it != s_debug_info_cache.end())
+ return it->value.ptr();
+
+ if (!Core::File::exists(path.characters()))
+ return nullptr;
+
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return nullptr;
+
+ auto image = make<ELF::Image>(file_or_error.value()->bytes());
+ auto info = make<ELFObjectInfo>(file_or_error.release_value(), Debug::DebugInfo { move(image) });
+ auto* info_ptr = info.ptr();
+ s_debug_info_cache.set(path, move(info));
+ return info_ptr;
+}
+
+Backtrace::Backtrace(const Reader& coredump)
+{
+ coredump.for_each_thread_info([this, &coredump](const ELF::Core::ThreadInfo& thread_info) {
+ uint32_t* ebp = (uint32_t*)thread_info.regs.ebp;
+ uint32_t* eip = (uint32_t*)thread_info.regs.eip;
+ while (ebp && eip) {
+ add_backtrace_entry(coredump, (FlatPtr)eip);
+ auto next_eip = coredump.peek_memory((FlatPtr)(ebp + 1));
+ auto next_ebp = coredump.peek_memory((FlatPtr)(ebp));
+ if (!next_eip.has_value() || !next_ebp.has_value())
+ break;
+ eip = (uint32_t*)next_eip.value();
+ ebp = (uint32_t*)next_ebp.value();
+ }
+ return IterationDecision::Continue;
+ });
+}
+
+Backtrace::~Backtrace()
+{
+}
+
+void Backtrace::add_backtrace_entry(const Reader& coredump, FlatPtr eip)
+{
+ auto* region = coredump.region_containing((FlatPtr)eip);
+ if (!region) {
+ m_entries.append({ eip, {}, {}, {} });
+ return;
+ }
+ auto object_name = region->object_name();
+ if (object_name == "Loader.so")
+ return;
+ auto* object_info = object_info_for_region(*region);
+ if (!object_info)
+ return;
+ auto function_name = object_info->debug_info.elf().symbolicate(eip - region->region_start);
+ auto source_position = object_info->debug_info.get_source_position(eip - region->region_start);
+ m_entries.append({ eip, object_name, function_name, source_position });
+}
+
+String Backtrace::Entry::to_string(bool color) const
+{
+ StringBuilder builder;
+ builder.appendff("{:p}: ", eip);
+ if (object_name.is_empty()) {
+ builder.append("???");
+ return builder.build();
+ }
+ builder.appendff("[{}] {}", object_name, function_name.is_empty() ? "???" : function_name);
+ if (source_position.has_value()) {
+ auto& source_position = this->source_position.value();
+ auto fmt = color ? " (\033[34;1m{}\033[0m:{})" : " ({}:{})";
+ builder.appendff(fmt, LexicalPath(source_position.file_path).basename(), source_position.line_number);
+ }
+ return builder.build();
+}
+
+}
diff --git a/Userland/Libraries/LibCoreDump/Backtrace.h b/Userland/Libraries/LibCoreDump/Backtrace.h
new file mode 100644
index 0000000000..6915b170c2
--- /dev/null
+++ b/Userland/Libraries/LibCoreDump/Backtrace.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+#include <LibCoreDump/Reader.h>
+#include <LibDebug/DebugInfo.h>
+
+namespace CoreDump {
+
+struct ELFObjectInfo {
+ ELFObjectInfo(NonnullRefPtr<MappedFile> file, Debug::DebugInfo&& debug_info)
+ : file(move(file))
+ , debug_info(move(debug_info))
+ {
+ }
+
+ NonnullRefPtr<MappedFile> file;
+ Debug::DebugInfo debug_info;
+};
+
+class Backtrace {
+public:
+ struct Entry {
+ FlatPtr eip;
+ String object_name;
+ String function_name;
+ Optional<Debug::DebugInfo::SourcePosition> source_position;
+
+ String to_string(bool color = false) const;
+ };
+
+ Backtrace(const Reader&);
+ ~Backtrace();
+
+ const Vector<Entry> entries() const { return m_entries; }
+
+private:
+ void add_backtrace_entry(const Reader&, FlatPtr eip);
+
+ Vector<Entry> m_entries;
+};
+
+}
diff --git a/Userland/Libraries/LibCoreDump/CMakeLists.txt b/Userland/Libraries/LibCoreDump/CMakeLists.txt
new file mode 100644
index 0000000000..f6a9457d30
--- /dev/null
+++ b/Userland/Libraries/LibCoreDump/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(SOURCES
+ Backtrace.cpp
+ Reader.cpp
+)
+
+serenity_lib(LibCoreDump coredump)
+target_link_libraries(LibCoreDump LibC LibCore LibDebug)
diff --git a/Userland/Libraries/LibCoreDump/Forward.h b/Userland/Libraries/LibCoreDump/Forward.h
new file mode 100644
index 0000000000..e63caa1eb8
--- /dev/null
+++ b/Userland/Libraries/LibCoreDump/Forward.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace CoreDump {
+
+class Backtrace;
+class Reader;
+
+}
diff --git a/Userland/Libraries/LibCoreDump/Reader.cpp b/Userland/Libraries/LibCoreDump/Reader.cpp
new file mode 100644
index 0000000000..e12611bd46
--- /dev/null
+++ b/Userland/Libraries/LibCoreDump/Reader.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/JsonObject.h>
+#include <AK/JsonValue.h>
+#include <LibCoreDump/Backtrace.h>
+#include <LibCoreDump/Reader.h>
+#include <string.h>
+#include <sys/stat.h>
+
+namespace CoreDump {
+
+OwnPtr<Reader> Reader::create(const String& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return {};
+ return adopt_own(*new Reader(file_or_error.release_value()));
+}
+
+Reader::Reader(NonnullRefPtr<MappedFile> coredump_file)
+ : m_coredump_file(move(coredump_file))
+ , m_coredump_image(m_coredump_file->bytes())
+{
+ size_t index = 0;
+ m_coredump_image.for_each_program_header([this, &index](auto pheader) {
+ if (pheader.type() == PT_NOTE) {
+ m_notes_segment_index = index;
+ return IterationDecision::Break;
+ }
+ ++index;
+ return IterationDecision::Continue;
+ });
+ ASSERT(m_notes_segment_index != -1);
+}
+
+Reader::~Reader()
+{
+}
+
+Reader::NotesEntryIterator::NotesEntryIterator(const u8* notes_data)
+ : m_current((const ELF::Core::NotesEntry*)notes_data)
+ , start(notes_data)
+{
+}
+
+ELF::Core::NotesEntryHeader::Type Reader::NotesEntryIterator::type() const
+{
+ ASSERT(m_current->header.type == ELF::Core::NotesEntryHeader::Type::ProcessInfo
+ || m_current->header.type == ELF::Core::NotesEntryHeader::Type::MemoryRegionInfo
+ || m_current->header.type == ELF::Core::NotesEntryHeader::Type::ThreadInfo
+ || m_current->header.type == ELF::Core::NotesEntryHeader::Type::Metadata
+ || m_current->header.type == ELF::Core::NotesEntryHeader::Type::Null);
+ return m_current->header.type;
+}
+
+const ELF::Core::NotesEntry* Reader::NotesEntryIterator::current() const
+{
+ return m_current;
+}
+
+void Reader::NotesEntryIterator::next()
+{
+ ASSERT(!at_end());
+ switch (type()) {
+ case ELF::Core::NotesEntryHeader::Type::ProcessInfo: {
+ const auto* current = reinterpret_cast<const ELF::Core::ProcessInfo*>(m_current);
+ m_current = reinterpret_cast<const ELF::Core::NotesEntry*>(current->executable_path + strlen(current->executable_path) + 1);
+ break;
+ }
+ case ELF::Core::NotesEntryHeader::Type::ThreadInfo: {
+ const auto* current = reinterpret_cast<const ELF::Core::ThreadInfo*>(m_current);
+ m_current = reinterpret_cast<const ELF::Core::NotesEntry*>(current + 1);
+ break;
+ }
+ case ELF::Core::NotesEntryHeader::Type::MemoryRegionInfo: {
+ const auto* current = reinterpret_cast<const ELF::Core::MemoryRegionInfo*>(m_current);
+ m_current = reinterpret_cast<const ELF::Core::NotesEntry*>(current->region_name + strlen(current->region_name) + 1);
+ break;
+ }
+ case ELF::Core::NotesEntryHeader::Type::Metadata: {
+ const auto* current = reinterpret_cast<const ELF::Core::Metadata*>(m_current);
+ m_current = reinterpret_cast<const ELF::Core::NotesEntry*>(current->json_data + strlen(current->json_data) + 1);
+ break;
+ }
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+bool Reader::NotesEntryIterator::at_end() const
+{
+ return type() == ELF::Core::NotesEntryHeader::Type::Null;
+}
+
+Optional<uint32_t> Reader::peek_memory(FlatPtr address) const
+{
+ const auto* region = region_containing(address);
+ if (!region)
+ return {};
+
+ FlatPtr offset_in_region = address - region->region_start;
+ const char* region_data = image().program_header(region->program_header_index).raw_data();
+ return *(const uint32_t*)(&region_data[offset_in_region]);
+}
+
+const ELF::Core::ProcessInfo& Reader::process_info() const
+{
+ for (NotesEntryIterator it((const u8*)m_coredump_image.program_header(m_notes_segment_index).raw_data()); !it.at_end(); it.next()) {
+ if (it.type() != ELF::Core::NotesEntryHeader::Type::ProcessInfo)
+ continue;
+ return reinterpret_cast<const ELF::Core::ProcessInfo&>(*it.current());
+ }
+ ASSERT_NOT_REACHED();
+}
+
+const ELF::Core::MemoryRegionInfo* Reader::region_containing(FlatPtr address) const
+{
+ const ELF::Core::MemoryRegionInfo* ret = nullptr;
+ for_each_memory_region_info([&ret, address](const ELF::Core::MemoryRegionInfo& region_info) {
+ if (region_info.region_start <= address && region_info.region_end >= address) {
+ ret = &region_info;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+ return ret;
+}
+
+const Backtrace Reader::backtrace() const
+{
+ return Backtrace(*this);
+}
+
+const HashMap<String, String> Reader::metadata() const
+{
+ const ELF::Core::Metadata* metadata_notes_entry = nullptr;
+ for (NotesEntryIterator it((const u8*)m_coredump_image.program_header(m_notes_segment_index).raw_data()); !it.at_end(); it.next()) {
+ if (it.type() != ELF::Core::NotesEntryHeader::Type::Metadata)
+ continue;
+ metadata_notes_entry = reinterpret_cast<const ELF::Core::Metadata*>(it.current());
+ break;
+ }
+ if (!metadata_notes_entry)
+ return {};
+ auto metadata_json_value = JsonValue::from_string(metadata_notes_entry->json_data);
+ if (!metadata_json_value.has_value())
+ return {};
+ if (!metadata_json_value.value().is_object())
+ return {};
+ HashMap<String, String> metadata;
+ metadata_json_value.value().as_object().for_each_member([&](auto& key, auto& value) {
+ metadata.set(key, value.as_string_or({}));
+ });
+ return metadata;
+}
+
+struct LibraryData {
+ String name;
+ OwnPtr<MappedFile> file;
+ ELF::Image lib_elf;
+};
+
+const Reader::LibraryData* Reader::library_containing(FlatPtr address) const
+{
+ static HashMap<String, OwnPtr<LibraryData>> cached_libs;
+ auto* region = region_containing(address);
+ if (!region)
+ return {};
+
+ auto name = region->object_name();
+
+ String path;
+ if (name.contains(".so"))
+ path = String::format("/usr/lib/%s", name.characters());
+ else {
+ path = name;
+ }
+
+ if (!cached_libs.contains(path)) {
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return {};
+ auto image = ELF::Image(file_or_error.value()->bytes());
+ cached_libs.set(path, make<LibraryData>(name, region->region_start, file_or_error.release_value(), move(image)));
+ }
+
+ auto lib_data = cached_libs.get(path).value();
+ return lib_data;
+}
+
+}
diff --git a/Userland/Libraries/LibCoreDump/Reader.h b/Userland/Libraries/LibCoreDump/Reader.h
new file mode 100644
index 0000000000..9ac411a204
--- /dev/null
+++ b/Userland/Libraries/LibCoreDump/Reader.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/MappedFile.h>
+#include <AK/Noncopyable.h>
+#include <AK/OwnPtr.h>
+#include <LibCoreDump/Forward.h>
+#include <LibELF/CoreDump.h>
+#include <LibELF/Image.h>
+
+namespace CoreDump {
+
+class Reader {
+ AK_MAKE_NONCOPYABLE(Reader);
+ AK_MAKE_NONMOVABLE(Reader);
+
+public:
+ static OwnPtr<Reader> create(const String&);
+ ~Reader();
+
+ const ELF::Core::ProcessInfo& process_info() const;
+
+ template<typename Func>
+ void for_each_memory_region_info(Func func) const;
+
+ template<typename Func>
+ void for_each_thread_info(Func func) const;
+
+ const ELF::Image& image() const { return m_coredump_image; }
+
+ Optional<uint32_t> peek_memory(FlatPtr address) const;
+ const ELF::Core::MemoryRegionInfo* region_containing(FlatPtr address) const;
+
+ struct LibraryData {
+ String name;
+ FlatPtr base_address { 0 };
+ NonnullRefPtr<MappedFile> file;
+ ELF::Image lib_elf;
+ };
+ const LibraryData* library_containing(FlatPtr address) const;
+
+ const Backtrace backtrace() const;
+ const HashMap<String, String> metadata() const;
+
+private:
+ Reader(NonnullRefPtr<MappedFile>);
+
+ class NotesEntryIterator {
+ public:
+ NotesEntryIterator(const u8* notes_data);
+
+ ELF::Core::NotesEntryHeader::Type type() const;
+ const ELF::Core::NotesEntry* current() const;
+
+ void next();
+ bool at_end() const;
+
+ private:
+ const ELF::Core::NotesEntry* m_current { nullptr };
+ const u8* start { nullptr };
+ };
+
+ NonnullRefPtr<MappedFile> m_coredump_file;
+ ELF::Image m_coredump_image;
+ ssize_t m_notes_segment_index { -1 };
+};
+
+template<typename Func>
+void Reader::for_each_memory_region_info(Func func) const
+{
+ for (NotesEntryIterator it((const u8*)m_coredump_image.program_header(m_notes_segment_index).raw_data()); !it.at_end(); it.next()) {
+ if (it.type() != ELF::Core::NotesEntryHeader::Type::MemoryRegionInfo)
+ continue;
+ auto& memory_region_info = reinterpret_cast<const ELF::Core::MemoryRegionInfo&>(*it.current());
+ IterationDecision decision = func(memory_region_info);
+ if (decision == IterationDecision::Break)
+ return;
+ }
+}
+
+template<typename Func>
+void Reader::for_each_thread_info(Func func) const
+{
+ for (NotesEntryIterator it((const u8*)m_coredump_image.program_header(m_notes_segment_index).raw_data()); !it.at_end(); it.next()) {
+ if (it.type() != ELF::Core::NotesEntryHeader::Type::ThreadInfo)
+ continue;
+ auto& thread_info = reinterpret_cast<const ELF::Core::ThreadInfo&>(*it.current());
+ IterationDecision decision = func(thread_info);
+ if (decision == IterationDecision::Break)
+ return;
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibCpp/CMakeLists.txt b/Userland/Libraries/LibCpp/CMakeLists.txt
new file mode 100644
index 0000000000..f9e022bddd
--- /dev/null
+++ b/Userland/Libraries/LibCpp/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(SOURCES
+ Lexer.cpp
+)
+
+serenity_lib(LibCpp cpp)
+target_link_libraries(LibCpp LibC)
diff --git a/Userland/Libraries/LibCpp/Lexer.cpp b/Userland/Libraries/LibCpp/Lexer.cpp
new file mode 100644
index 0000000000..831822e5a5
--- /dev/null
+++ b/Userland/Libraries/LibCpp/Lexer.cpp
@@ -0,0 +1,789 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Lexer.h"
+#include <AK/HashTable.h>
+#include <AK/StdLibExtras.h>
+#include <AK/String.h>
+#include <ctype.h>
+
+namespace Cpp {
+
+Lexer::Lexer(const StringView& input)
+ : m_input(input)
+{
+}
+
+char Lexer::peek(size_t offset) const
+{
+ if ((m_index + offset) >= m_input.length())
+ return 0;
+ return m_input[m_index + offset];
+}
+
+char Lexer::consume()
+{
+ ASSERT(m_index < m_input.length());
+ char ch = m_input[m_index++];
+ m_previous_position = m_position;
+ if (ch == '\n') {
+ m_position.line++;
+ m_position.column = 0;
+ } else {
+ m_position.column++;
+ }
+ return ch;
+}
+
+static bool is_valid_first_character_of_identifier(char ch)
+{
+ return isalpha(ch) || ch == '_' || ch == '$';
+}
+
+static bool is_valid_nonfirst_character_of_identifier(char ch)
+{
+ return is_valid_first_character_of_identifier(ch) || isdigit(ch);
+}
+
+constexpr const char* s_known_keywords[] = {
+ "alignas",
+ "alignof",
+ "and",
+ "and_eq",
+ "asm",
+ "bitand",
+ "bitor",
+ "bool",
+ "break",
+ "case",
+ "catch",
+ "class",
+ "compl",
+ "const",
+ "const_cast",
+ "constexpr",
+ "continue",
+ "decltype",
+ "default",
+ "delete",
+ "do",
+ "dynamic_cast",
+ "else",
+ "enum",
+ "explicit",
+ "export",
+ "extern",
+ "false",
+ "final",
+ "for",
+ "friend",
+ "goto",
+ "if",
+ "inline",
+ "mutable",
+ "namespace",
+ "new",
+ "noexcept",
+ "not",
+ "not_eq",
+ "nullptr",
+ "operator",
+ "or",
+ "or_eq",
+ "override",
+ "private",
+ "protected",
+ "public",
+ "register",
+ "reinterpret_cast",
+ "return",
+ "signed",
+ "sizeof",
+ "static",
+ "static_assert",
+ "static_cast",
+ "struct",
+ "switch",
+ "template",
+ "this",
+ "thread_local",
+ "throw",
+ "true",
+ "try",
+ "typedef",
+ "typeid",
+ "typename",
+ "union",
+ "using",
+ "virtual",
+ "volatile",
+ "while",
+ "xor",
+ "xor_eq"
+};
+
+constexpr const char* s_known_types[] = {
+ "ByteBuffer",
+ "CircularDeque",
+ "CircularQueue",
+ "Deque",
+ "DoublyLinkedList",
+ "FileSystemPath",
+ "Array",
+ "Function",
+ "HashMap",
+ "HashTable",
+ "IPv4Address",
+ "InlineLinkedList",
+ "IntrusiveList",
+ "JsonArray",
+ "JsonObject",
+ "JsonValue",
+ "MappedFile",
+ "NetworkOrdered",
+ "NonnullOwnPtr",
+ "NonnullOwnPtrVector",
+ "NonnullRefPtr",
+ "NonnullRefPtrVector",
+ "Optional",
+ "OwnPtr",
+ "RefPtr",
+ "Result",
+ "ScopeGuard",
+ "SinglyLinkedList",
+ "String",
+ "StringBuilder",
+ "StringImpl",
+ "StringView",
+ "Utf8View",
+ "Vector",
+ "WeakPtr",
+ "auto",
+ "char",
+ "char16_t",
+ "char32_t",
+ "char8_t",
+ "double",
+ "float",
+ "i16",
+ "i32",
+ "i64",
+ "i8",
+ "int",
+ "int",
+ "long",
+ "short",
+ "signed",
+ "u16",
+ "u32",
+ "u64",
+ "u8",
+ "unsigned",
+ "void",
+ "wchar_t"
+};
+
+static bool is_keyword(const StringView& string)
+{
+ static HashTable<String> keywords(array_size(s_known_keywords));
+ if (keywords.is_empty()) {
+ keywords.set_from(s_known_keywords);
+ }
+ return keywords.contains(string);
+}
+
+static bool is_known_type(const StringView& string)
+{
+ static HashTable<String> types(array_size(s_known_types));
+ if (types.is_empty()) {
+ types.set_from(s_known_types);
+ }
+ return types.contains(string);
+}
+
+Vector<Token> Lexer::lex()
+{
+ Vector<Token> tokens;
+
+ size_t token_start_index = 0;
+ Position token_start_position;
+
+ auto emit_token = [&](auto type) {
+ Token token;
+ token.m_type = type;
+ token.m_start = m_position;
+ token.m_end = m_position;
+ tokens.append(token);
+ consume();
+ };
+
+ auto begin_token = [&] {
+ token_start_index = m_index;
+ token_start_position = m_position;
+ };
+ auto commit_token = [&](auto type) {
+ Token token;
+ token.m_type = type;
+ token.m_start = token_start_position;
+ token.m_end = m_previous_position;
+ tokens.append(token);
+ };
+
+ auto emit_token_equals = [&](auto type, auto equals_type) {
+ if (peek(1) == '=') {
+ begin_token();
+ consume();
+ consume();
+ commit_token(equals_type);
+ return;
+ }
+ emit_token(type);
+ };
+
+ auto match_escape_sequence = [&]() -> size_t {
+ switch (peek(1)) {
+ case '\'':
+ case '"':
+ case '?':
+ case '\\':
+ case 'a':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ case 'v':
+ return 2;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7': {
+ size_t octal_digits = 1;
+ for (size_t i = 0; i < 2; ++i) {
+ char next = peek(2 + i);
+ if (next < '0' || next > '7')
+ break;
+ ++octal_digits;
+ }
+ return 1 + octal_digits;
+ }
+ case 'x': {
+ size_t hex_digits = 0;
+ while (isxdigit(peek(2 + hex_digits)))
+ ++hex_digits;
+ return 2 + hex_digits;
+ }
+ case 'u':
+ case 'U': {
+ bool is_unicode = true;
+ size_t number_of_digits = peek(1) == 'u' ? 4 : 8;
+ for (size_t i = 0; i < number_of_digits; ++i) {
+ if (!isxdigit(peek(2 + i))) {
+ is_unicode = false;
+ break;
+ }
+ }
+ return is_unicode ? 2 + number_of_digits : 0;
+ }
+ default:
+ return 0;
+ }
+ };
+
+ auto match_string_prefix = [&](char quote) -> size_t {
+ if (peek() == quote)
+ return 1;
+ if (peek() == 'L' && peek(1) == quote)
+ return 2;
+ if (peek() == 'u') {
+ if (peek(1) == quote)
+ return 2;
+ if (peek(1) == '8' && peek(2) == quote)
+ return 3;
+ }
+ if (peek() == 'U' && peek(1) == quote)
+ return 2;
+ return 0;
+ };
+
+ while (m_index < m_input.length()) {
+ auto ch = peek();
+ if (isspace(ch)) {
+ begin_token();
+ while (isspace(peek()))
+ consume();
+ commit_token(Token::Type::Whitespace);
+ continue;
+ }
+ if (ch == '(') {
+ emit_token(Token::Type::LeftParen);
+ continue;
+ }
+ if (ch == ')') {
+ emit_token(Token::Type::RightParen);
+ continue;
+ }
+ if (ch == '{') {
+ emit_token(Token::Type::LeftCurly);
+ continue;
+ }
+ if (ch == '}') {
+ emit_token(Token::Type::RightCurly);
+ continue;
+ }
+ if (ch == '[') {
+ emit_token(Token::Type::LeftBracket);
+ continue;
+ }
+ if (ch == ']') {
+ emit_token(Token::Type::RightBracket);
+ continue;
+ }
+ if (ch == '<') {
+ begin_token();
+ consume();
+ if (peek() == '<') {
+ consume();
+ if (peek() == '=') {
+ consume();
+ commit_token(Token::Type::LessLessEquals);
+ continue;
+ }
+ commit_token(Token::Type::LessLess);
+ continue;
+ }
+ if (peek() == '=') {
+ consume();
+ commit_token(Token::Type::LessEquals);
+ continue;
+ }
+ if (peek() == '>') {
+ consume();
+ commit_token(Token::Type::LessGreater);
+ continue;
+ }
+ commit_token(Token::Type::Less);
+ continue;
+ }
+ if (ch == '>') {
+ begin_token();
+ consume();
+ if (peek() == '>') {
+ consume();
+ if (peek() == '=') {
+ consume();
+ commit_token(Token::Type::GreaterGreaterEquals);
+ continue;
+ }
+ commit_token(Token::Type::GreaterGreater);
+ continue;
+ }
+ if (peek() == '=') {
+ consume();
+ commit_token(Token::Type::GreaterEquals);
+ continue;
+ }
+ commit_token(Token::Type::Greater);
+ continue;
+ }
+ if (ch == ',') {
+ emit_token(Token::Type::Comma);
+ continue;
+ }
+ if (ch == '+') {
+ begin_token();
+ consume();
+ if (peek() == '+') {
+ consume();
+ commit_token(Token::Type::PlusPlus);
+ continue;
+ }
+ if (peek() == '=') {
+ consume();
+ commit_token(Token::Type::PlusEquals);
+ continue;
+ }
+ commit_token(Token::Type::Plus);
+ continue;
+ }
+ if (ch == '-') {
+ begin_token();
+ consume();
+ if (peek() == '-') {
+ consume();
+ commit_token(Token::Type::MinusMinus);
+ continue;
+ }
+ if (peek() == '=') {
+ consume();
+ commit_token(Token::Type::MinusEquals);
+ continue;
+ }
+ if (peek() == '>') {
+ consume();
+ if (peek() == '*') {
+ consume();
+ commit_token(Token::Type::ArrowAsterisk);
+ continue;
+ }
+ commit_token(Token::Type::Arrow);
+ continue;
+ }
+ commit_token(Token::Type::Minus);
+ continue;
+ }
+ if (ch == '*') {
+ emit_token_equals(Token::Type::Asterisk, Token::Type::AsteriskEquals);
+ continue;
+ }
+ if (ch == '%') {
+ emit_token_equals(Token::Type::Percent, Token::Type::PercentEquals);
+ continue;
+ }
+ if (ch == '^') {
+ emit_token_equals(Token::Type::Caret, Token::Type::CaretEquals);
+ continue;
+ }
+ if (ch == '!') {
+ emit_token_equals(Token::Type::ExclamationMark, Token::Type::ExclamationMarkEquals);
+ continue;
+ }
+ if (ch == '=') {
+ emit_token_equals(Token::Type::Equals, Token::Type::EqualsEquals);
+ continue;
+ }
+ if (ch == '&') {
+ begin_token();
+ consume();
+ if (peek() == '&') {
+ consume();
+ commit_token(Token::Type::AndAnd);
+ continue;
+ }
+ if (peek() == '=') {
+ consume();
+ commit_token(Token::Type::AndEquals);
+ continue;
+ }
+ commit_token(Token::Type::And);
+ continue;
+ }
+ if (ch == '|') {
+ begin_token();
+ consume();
+ if (peek() == '|') {
+ consume();
+ commit_token(Token::Type::PipePipe);
+ continue;
+ }
+ if (peek() == '=') {
+ consume();
+ commit_token(Token::Type::PipeEquals);
+ continue;
+ }
+ commit_token(Token::Type::Pipe);
+ continue;
+ }
+ if (ch == '~') {
+ emit_token(Token::Type::Tilde);
+ continue;
+ }
+ if (ch == '?') {
+ emit_token(Token::Type::QuestionMark);
+ continue;
+ }
+ if (ch == ':') {
+ begin_token();
+ consume();
+ if (peek() == ':') {
+ consume();
+ if (peek() == '*') {
+ consume();
+ commit_token(Token::Type::ColonColonAsterisk);
+ continue;
+ }
+ commit_token(Token::Type::ColonColon);
+ continue;
+ }
+ commit_token(Token::Type::Colon);
+ continue;
+ }
+ if (ch == ';') {
+ emit_token(Token::Type::Semicolon);
+ continue;
+ }
+ if (ch == '.') {
+ begin_token();
+ consume();
+ if (peek() == '*') {
+ consume();
+ commit_token(Token::Type::DotAsterisk);
+ continue;
+ }
+ commit_token(Token::Type::Dot);
+ continue;
+ }
+ if (ch == '#') {
+ begin_token();
+ consume();
+
+ if (is_valid_first_character_of_identifier(peek()))
+ while (peek() && is_valid_nonfirst_character_of_identifier(peek()))
+ consume();
+
+ auto directive = StringView(m_input.characters_without_null_termination() + token_start_index, m_index - token_start_index);
+ if (directive == "#include") {
+ commit_token(Token::Type::IncludeStatement);
+
+ begin_token();
+ while (isspace(peek()))
+ consume();
+ commit_token(Token::Type::Whitespace);
+
+ begin_token();
+ if (peek() == '<' || peek() == '"') {
+ char closing = consume() == '<' ? '>' : '"';
+ while (peek() && peek() != closing && peek() != '\n')
+ consume();
+
+ if (peek() && consume() == '\n') {
+ commit_token(Token::Type::IncludePath);
+ continue;
+ }
+
+ commit_token(Token::Type::IncludePath);
+ begin_token();
+ }
+ }
+
+ while (peek() && peek() != '\n')
+ consume();
+
+ commit_token(Token::Type::PreprocessorStatement);
+ continue;
+ }
+ if (ch == '/' && peek(1) == '/') {
+ begin_token();
+ while (peek() && peek() != '\n')
+ consume();
+ commit_token(Token::Type::Comment);
+ continue;
+ }
+ if (ch == '/' && peek(1) == '*') {
+ begin_token();
+ consume();
+ consume();
+ bool comment_block_ends = false;
+ while (peek()) {
+ if (peek() == '*' && peek(1) == '/') {
+ comment_block_ends = true;
+ break;
+ }
+
+ consume();
+ }
+
+ if (comment_block_ends) {
+ consume();
+ consume();
+ }
+
+ commit_token(Token::Type::Comment);
+ continue;
+ }
+ if (ch == '/') {
+ emit_token_equals(Token::Type::Slash, Token::Type::SlashEquals);
+ continue;
+ }
+ if (size_t prefix = match_string_prefix('"'); prefix > 0) {
+ begin_token();
+ for (size_t i = 0; i < prefix; ++i)
+ consume();
+ while (peek()) {
+ if (peek() == '\\') {
+ if (size_t escape = match_escape_sequence(); escape > 0) {
+ commit_token(Token::Type::DoubleQuotedString);
+ begin_token();
+ for (size_t i = 0; i < escape; ++i)
+ consume();
+ commit_token(Token::Type::EscapeSequence);
+ begin_token();
+ continue;
+ }
+ }
+
+ if (consume() == '"')
+ break;
+ }
+ commit_token(Token::Type::DoubleQuotedString);
+ continue;
+ }
+ if (size_t prefix = match_string_prefix('R'); prefix > 0 && peek(prefix) == '"') {
+ begin_token();
+ for (size_t i = 0; i < prefix + 1; ++i)
+ consume();
+ size_t prefix_start = m_index;
+ while (peek() && peek() != '(')
+ consume();
+ StringView prefix_string = m_input.substring_view(prefix_start, m_index - prefix_start);
+ while (peek()) {
+ if (consume() == '"') {
+ ASSERT(m_index >= prefix_string.length() + 2);
+ ASSERT(m_input[m_index - 1] == '"');
+ if (m_input[m_index - 1 - prefix_string.length() - 1] == ')') {
+ StringView suffix_string = m_input.substring_view(m_index - 1 - prefix_string.length(), prefix_string.length());
+ if (prefix_string == suffix_string)
+ break;
+ }
+ }
+ }
+ commit_token(Token::Type::RawString);
+ continue;
+ }
+ if (size_t prefix = match_string_prefix('\''); prefix > 0) {
+ begin_token();
+ for (size_t i = 0; i < prefix; ++i)
+ consume();
+ while (peek()) {
+ if (peek() == '\\') {
+ if (size_t escape = match_escape_sequence(); escape > 0) {
+ commit_token(Token::Type::SingleQuotedString);
+ begin_token();
+ for (size_t i = 0; i < escape; ++i)
+ consume();
+ commit_token(Token::Type::EscapeSequence);
+ begin_token();
+ continue;
+ }
+ }
+
+ if (consume() == '\'')
+ break;
+ }
+ commit_token(Token::Type::SingleQuotedString);
+ continue;
+ }
+ if (isdigit(ch) || (ch == '.' && isdigit(peek(1)))) {
+ begin_token();
+ consume();
+
+ auto type = ch == '.' ? Token::Type::Float : Token::Type::Integer;
+ bool is_hex = false;
+ bool is_binary = false;
+
+ auto match_exponent = [&]() -> size_t {
+ char ch = peek();
+ if (ch != 'e' && ch != 'E' && ch != 'p' && ch != 'P')
+ return 0;
+
+ type = Token::Type::Float;
+ size_t length = 1;
+ ch = peek(length);
+ if (ch == '+' || ch == '-') {
+ ++length;
+ }
+ for (ch = peek(length); isdigit(ch); ch = peek(length)) {
+ ++length;
+ }
+ return length;
+ };
+
+ auto match_type_literal = [&]() -> size_t {
+ size_t length = 0;
+ for (;;) {
+ char ch = peek(length);
+ if ((ch == 'u' || ch == 'U') && type == Token::Type::Integer) {
+ ++length;
+ } else if ((ch == 'f' || ch == 'F') && !is_binary) {
+ type = Token::Type::Float;
+ ++length;
+ } else if (ch == 'l' || ch == 'L') {
+ ++length;
+ } else
+ return length;
+ }
+ };
+
+ if (peek() == 'b' || peek() == 'B') {
+ consume();
+ is_binary = true;
+ for (char ch = peek(); ch == '0' || ch == '1' || (ch == '\'' && peek(1) != '\''); ch = peek()) {
+ consume();
+ }
+ } else {
+ if (peek() == 'x' || peek() == 'X') {
+ consume();
+ is_hex = true;
+ }
+
+ for (char ch = peek(); (is_hex ? isxdigit(ch) : isdigit(ch)) || (ch == '\'' && peek(1) != '\'') || ch == '.'; ch = peek()) {
+ if (ch == '.') {
+ if (type == Token::Type::Integer) {
+ type = Token::Type::Float;
+ } else
+ break;
+ };
+ consume();
+ }
+ }
+
+ if (!is_binary) {
+ size_t length = match_exponent();
+ for (size_t i = 0; i < length; ++i)
+ consume();
+ }
+
+ size_t length = match_type_literal();
+ for (size_t i = 0; i < length; ++i)
+ consume();
+
+ commit_token(type);
+ continue;
+ }
+ if (is_valid_first_character_of_identifier(ch)) {
+ begin_token();
+ while (peek() && is_valid_nonfirst_character_of_identifier(peek()))
+ consume();
+ auto token_view = StringView(m_input.characters_without_null_termination() + token_start_index, m_index - token_start_index);
+ if (is_keyword(token_view))
+ commit_token(Token::Type::Keyword);
+ else if (is_known_type(token_view))
+ commit_token(Token::Type::KnownType);
+ else
+ commit_token(Token::Type::Identifier);
+ continue;
+ }
+ dbgln("Unimplemented token character: {}", ch);
+ emit_token(Token::Type::Unknown);
+ }
+ return tokens;
+}
+
+}
diff --git a/Userland/Libraries/LibCpp/Lexer.h b/Userland/Libraries/LibCpp/Lexer.h
new file mode 100644
index 0000000000..351dee8aa5
--- /dev/null
+++ b/Userland/Libraries/LibCpp/Lexer.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/StringView.h>
+#include <AK/Vector.h>
+
+namespace Cpp {
+
+#define FOR_EACH_TOKEN_TYPE \
+ __TOKEN(Unknown) \
+ __TOKEN(Whitespace) \
+ __TOKEN(PreprocessorStatement) \
+ __TOKEN(IncludeStatement) \
+ __TOKEN(IncludePath) \
+ __TOKEN(LeftParen) \
+ __TOKEN(RightParen) \
+ __TOKEN(LeftCurly) \
+ __TOKEN(RightCurly) \
+ __TOKEN(LeftBracket) \
+ __TOKEN(RightBracket) \
+ __TOKEN(Less) \
+ __TOKEN(Greater) \
+ __TOKEN(LessEquals) \
+ __TOKEN(GreaterEquals) \
+ __TOKEN(LessLess) \
+ __TOKEN(GreaterGreater) \
+ __TOKEN(LessLessEquals) \
+ __TOKEN(GreaterGreaterEquals) \
+ __TOKEN(LessGreater) \
+ __TOKEN(Comma) \
+ __TOKEN(Plus) \
+ __TOKEN(PlusPlus) \
+ __TOKEN(PlusEquals) \
+ __TOKEN(Minus) \
+ __TOKEN(MinusMinus) \
+ __TOKEN(MinusEquals) \
+ __TOKEN(Asterisk) \
+ __TOKEN(AsteriskEquals) \
+ __TOKEN(Slash) \
+ __TOKEN(SlashEquals) \
+ __TOKEN(Percent) \
+ __TOKEN(PercentEquals) \
+ __TOKEN(Caret) \
+ __TOKEN(CaretEquals) \
+ __TOKEN(ExclamationMark) \
+ __TOKEN(ExclamationMarkEquals) \
+ __TOKEN(Equals) \
+ __TOKEN(EqualsEquals) \
+ __TOKEN(And) \
+ __TOKEN(AndAnd) \
+ __TOKEN(AndEquals) \
+ __TOKEN(Pipe) \
+ __TOKEN(PipePipe) \
+ __TOKEN(PipeEquals) \
+ __TOKEN(Tilde) \
+ __TOKEN(QuestionMark) \
+ __TOKEN(Colon) \
+ __TOKEN(ColonColon) \
+ __TOKEN(ColonColonAsterisk) \
+ __TOKEN(Semicolon) \
+ __TOKEN(Dot) \
+ __TOKEN(DotAsterisk) \
+ __TOKEN(Arrow) \
+ __TOKEN(ArrowAsterisk) \
+ __TOKEN(DoubleQuotedString) \
+ __TOKEN(SingleQuotedString) \
+ __TOKEN(RawString) \
+ __TOKEN(EscapeSequence) \
+ __TOKEN(Comment) \
+ __TOKEN(Integer) \
+ __TOKEN(Float) \
+ __TOKEN(Keyword) \
+ __TOKEN(KnownType) \
+ __TOKEN(Identifier)
+
+struct Position {
+ size_t line;
+ size_t column;
+};
+
+struct Token {
+ enum class Type {
+#define __TOKEN(x) x,
+ FOR_EACH_TOKEN_TYPE
+#undef __TOKEN
+ };
+
+ const char* to_string() const
+ {
+ switch (m_type) {
+#define __TOKEN(x) \
+ case Type::x: \
+ return #x;
+ FOR_EACH_TOKEN_TYPE
+#undef __TOKEN
+ }
+ ASSERT_NOT_REACHED();
+ }
+
+ Type m_type { Type::Unknown };
+ Position m_start;
+ Position m_end;
+};
+
+class Lexer {
+public:
+ Lexer(const StringView&);
+
+ Vector<Token> lex();
+
+private:
+ char peek(size_t offset = 0) const;
+ char consume();
+
+ StringView m_input;
+ size_t m_index { 0 };
+ Position m_previous_position { 0, 0 };
+ Position m_position { 0, 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibCrypt/CMakeLists.txt b/Userland/Libraries/LibCrypt/CMakeLists.txt
new file mode 100644
index 0000000000..f24d3f318a
--- /dev/null
+++ b/Userland/Libraries/LibCrypt/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(SOURCES
+ crypt.cpp
+)
+
+serenity_libc(LibCrypt crypt)
+target_link_libraries(LibCrypt LibC LibCrypto)
diff --git a/Userland/Libraries/LibCrypt/crypt.cpp b/Userland/Libraries/LibCrypt/crypt.cpp
new file mode 100644
index 0000000000..0a789bfe6a
--- /dev/null
+++ b/Userland/Libraries/LibCrypt/crypt.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <AK/Base64.h>
+#include <AK/Types.h>
+#include <LibCrypto/Hash/SHA2.h>
+#include <string.h>
+#include <unistd.h>
+
+extern "C" {
+
+static struct crypt_data crypt_data;
+
+char* crypt(const char* key, const char* salt)
+{
+ crypt_data.initialized = true;
+ return crypt_r(key, salt, &crypt_data);
+}
+
+static constexpr size_t crypt_salt_max = 16;
+static constexpr size_t sha_string_length = 44;
+
+char* crypt_r(const char* key, const char* salt, struct crypt_data* data)
+{
+ if (!data->initialized) {
+ errno = EINVAL;
+ return nullptr;
+ }
+
+ if (salt[0] == '$') {
+ if (salt[1] == '5') {
+ const char* salt_value = salt + 3;
+ size_t salt_len = min(strcspn(salt_value, "$"), crypt_salt_max);
+ size_t header_len = salt_len + 3;
+
+ bool fits = String(salt, header_len).copy_characters_to_buffer(data->result, sizeof(data->result));
+ if (!fits) {
+ errno = EINVAL;
+ return nullptr;
+ }
+ data->result[header_len] = '$';
+
+ Crypto::Hash::SHA256 sha;
+ sha.update(key);
+ sha.update((const u8*)salt_value, salt_len);
+
+ auto digest = sha.digest();
+ auto string = encode_base64(ReadonlyBytes(digest.immutable_data(), digest.data_length()));
+
+ fits = string.copy_characters_to_buffer(data->result + header_len + 1, sizeof(data->result) - header_len - 1);
+ if (!fits) {
+ errno = EINVAL;
+ return nullptr;
+ }
+
+ return data->result;
+ }
+ }
+
+ // DES crypt is not available.
+ errno = EINVAL;
+ return nullptr;
+}
+}
diff --git a/Userland/Libraries/LibCrypto/ASN1/ASN1.h b/Userland/Libraries/LibCrypto/ASN1/ASN1.h
new file mode 100644
index 0000000000..4c0ad50a6c
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/ASN1/ASN1.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+#include <LibCrypto/BigInt/UnsignedBigInteger.h>
+
+namespace Crypto {
+
+namespace ASN1 {
+
+enum class Kind {
+ Eol,
+ Boolean,
+ Integer,
+ ShortInteger,
+ BitString,
+ OctetString,
+ Null,
+ ObjectIdentifier,
+ IA5String,
+ PrintableString,
+ Utf8String,
+ UTCTime,
+ Choice,
+ Sequence,
+ Set,
+ SetOf
+};
+
+static inline StringView kind_name(Kind kind)
+{
+ switch (kind) {
+ case Kind::Eol:
+ return "EndOfList";
+ case Kind::Boolean:
+ return "Boolean";
+ case Kind::Integer:
+ return "Integer";
+ case Kind::ShortInteger:
+ return "ShortInteger";
+ case Kind::BitString:
+ return "BitString";
+ case Kind::OctetString:
+ return "OctetString";
+ case Kind::Null:
+ return "Null";
+ case Kind::ObjectIdentifier:
+ return "ObjectIdentifier";
+ case Kind::IA5String:
+ return "IA5String";
+ case Kind::PrintableString:
+ return "PrintableString";
+ case Kind::Utf8String:
+ return "UTF8String";
+ case Kind::UTCTime:
+ return "UTCTime";
+ case Kind::Choice:
+ return "Choice";
+ case Kind::Sequence:
+ return "Sequence";
+ case Kind::Set:
+ return "Set";
+ case Kind::SetOf:
+ return "SetOf";
+ }
+
+ return "InvalidKind";
+}
+
+struct List {
+ Kind kind;
+ void* data;
+ size_t size;
+ bool used;
+ List *prev, *next, *child, *parent;
+};
+
+static constexpr void set(List& list, Kind type, void* data, size_t size)
+{
+ list.kind = type;
+ list.data = data;
+ list.size = size;
+ list.used = false;
+}
+}
+
+}
diff --git a/Userland/Libraries/LibCrypto/ASN1/DER.h b/Userland/Libraries/LibCrypto/ASN1/DER.h
new file mode 100644
index 0000000000..47334724d3
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/ASN1/DER.h
@@ -0,0 +1,474 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+#include <LibCrypto/ASN1/ASN1.h>
+#include <LibCrypto/BigInt/UnsignedBigInteger.h>
+
+namespace Crypto {
+
+static bool der_decode_integer(const u8* in, size_t length, UnsignedBigInteger& number)
+{
+ if (length < 3) {
+ dbgln("invalid header size");
+ return false;
+ }
+
+ size_t x { 0 };
+ // must start with 0x02
+ if ((in[x++] & 0x1f) != 0x02) {
+ dbgln("not an integer {} ({} follows)", in[x - 1], in[x]);
+ return false;
+ }
+
+ // decode length
+ size_t z = in[x++];
+ if ((x & 0x80) == 0) {
+ // overflow
+ if (x + z > length) {
+ dbgln("would overflow {} > {}", z + x, length);
+ return false;
+ }
+
+ number = UnsignedBigInteger::import_data(in + x, z);
+ return true;
+ } else {
+ // actual big integer
+ z &= 0x7f;
+
+ // overflow
+ if ((x + z) > length || z > 4 || z == 0) {
+ dbgln("would overflow {} > {}", z + x, length);
+ return false;
+ }
+
+ size_t y = 0;
+ while (z--) {
+ y = ((size_t)(in[x++])) | (y << 8);
+ }
+
+ // overflow
+ if (x + y > length) {
+ dbgln("would overflow {} > {}", y + x, length);
+ return false;
+ }
+
+ number = UnsignedBigInteger::import_data(in + x, y);
+ return true;
+ }
+
+ // see if it's negative
+ if (in[x] & 0x80) {
+ dbgln("negative bigint unsupported in der_decode_integer");
+ return false;
+ }
+
+ return true;
+}
+static bool der_length_integer(UnsignedBigInteger* num, size_t* out_length)
+{
+ auto& bigint = *num;
+ size_t value_length = bigint.trimmed_length() * sizeof(u32);
+ auto length = value_length;
+ if (length == 0) {
+ ++length;
+ } else {
+ // the number comes with a 0 padding to make it positive in 2's comp
+ // add that zero if the msb is 1, but only if we haven't padded it
+ // ourselves
+ auto ms2b = (u16)(bigint.words()[bigint.trimmed_length() - 1] >> 16);
+
+ if ((ms2b & 0xff00) == 0) {
+ if (!(((u8)ms2b) & 0x80))
+ --length;
+ } else if (ms2b & 0x8000) {
+ ++length;
+ }
+ }
+ if (value_length < 128) {
+ ++length;
+ } else {
+ ++length;
+ while (value_length) {
+ ++length;
+ value_length >>= 8;
+ }
+ }
+ // kind
+ ++length;
+ *out_length = length;
+ return true;
+}
+constexpr static bool der_decode_object_identifier(const u8* in, size_t in_length, u8* words, u8* out_length)
+{
+ if (in_length < 3)
+ return false; // invalid header
+
+ if (*out_length < 2)
+ return false; // need at least two words
+
+ size_t x { 0 };
+ if ((in[x++] & 0x1f) != 0x06) {
+ return false; // invalid header value
+ }
+
+ size_t length { 0 };
+ if (in[x] < 128) {
+ length = in[x++];
+ } else {
+ if ((in[x] < 0x81) | (in[x] > 0x82))
+ return false; // invalid header
+
+ size_t y = in[x++] & 0x7f;
+ while (y--)
+ length = (length << 8) | (size_t)in[x++];
+ }
+
+ if (length < 1 || length + x > in_length)
+ return false; // invalid length or overflow
+
+ size_t y { 0 }, t { 0 };
+ while (length--) {
+ t = (t << 7) | (in[x] & 0x7f);
+ if (!(in[x++] & 0x80)) {
+ if (y >= *out_length)
+ return false; // overflow
+
+ if (y == 0) {
+ words[0] = t / 40;
+ words[1] = t % 40;
+ y = 2;
+ } else {
+ words[y++] = t;
+ }
+ t = 0;
+ }
+ }
+ *out_length = y;
+ return true;
+}
+
+static constexpr size_t der_object_identifier_bits(size_t x)
+{
+ x &= 0xffffffff;
+ size_t c { 0 };
+ while (x) {
+ ++c;
+ x >>= 1;
+ }
+ return c;
+}
+
+constexpr static bool der_length_object_identifier(u8* words, size_t num_words, size_t* out_length)
+{
+ if (num_words < 2)
+ return false;
+
+ if (words[0] > 3 || (words[0] < 2 && words[1] > 39))
+ return false;
+
+ size_t z { 0 };
+ size_t wordbuf = words[0] * 40 + words[1];
+ for (size_t y = 0; y < num_words; ++y) {
+ auto t = der_object_identifier_bits(wordbuf);
+ z = t / 7 + (!!(t % 7)) + (!!(wordbuf == 0));
+ if (y < num_words - 1)
+ wordbuf = words[y + 1];
+ }
+
+ if (z < 128) {
+ z += 2;
+ } else if (z < 256) {
+ z += 3;
+ } else {
+ z += 4;
+ }
+ *out_length = z;
+ return true;
+}
+
+constexpr static bool der_length_sequence(ASN1::List* list, size_t in_length, size_t* out_length)
+{
+ size_t y { 0 }, x { 0 };
+ for (size_t i = 0; i < in_length; ++i) {
+ auto type = list[i].kind;
+ auto size = list[i].size;
+ auto data = list[i].data;
+
+ if (type == ASN1::Kind::Eol)
+ break;
+
+ switch (type) {
+ case ASN1::Kind::Integer:
+ if (!der_length_integer((UnsignedBigInteger*)data, &x)) {
+ return false;
+ }
+ y += x;
+ break;
+ case ASN1::Kind::ObjectIdentifier:
+ if (!der_length_object_identifier((u8*)data, size, &x)) {
+ return false;
+ }
+ y += x;
+ break;
+ case ASN1::Kind::Sequence:
+ if (!der_length_sequence((ASN1::List*)data, size, &x)) {
+ return false;
+ }
+ y += x;
+ break;
+ default:
+ dbgln("Unhandled Kind {}", ASN1::kind_name(type));
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ }
+
+ if (y < 128) {
+ y += 2;
+ } else if (y < 256) {
+ y += 3;
+ } else if (y < 65536) {
+ y += 4;
+ } else if (y < 16777216ul) {
+ y += 5;
+ } else {
+ dbgln("invalid length {}", y);
+ return false;
+ }
+ *out_length = y;
+ return true;
+}
+
+static inline bool der_decode_sequence(const u8* in, size_t in_length, ASN1::List* list, size_t out_length, bool ordered = true)
+{
+ if (in_length < 2) {
+ dbgln("header too small");
+ return false; // invalid header
+ }
+ size_t x { 0 };
+ if (in[x++] != 0x30) {
+ dbgln("not a sequence: {}", in[x - 1]);
+ return false; // not a sequence
+ }
+ size_t block_size { 0 };
+ size_t y { 0 };
+ if (in[x] < 128) {
+ block_size = in[x++];
+ } else if (in[x] & 0x80) {
+ if ((in[x] < 0x81) || (in[x] > 0x83)) {
+ dbgln("invalid length element {}", in[x]);
+ return false;
+ }
+
+ y = in[x++] & 0x7f;
+
+ if (x + y > in_length) {
+ dbgln("would overflow {} > {}", x + y, in_length);
+ return false; // overflow
+ }
+ block_size = 0;
+ while (y--)
+ block_size = (block_size << 8) | (size_t)in[x++];
+ }
+
+ // overflow
+ if (x + block_size > in_length) {
+ dbgln("would overflow {} > {}", x + block_size, in_length);
+ return false;
+ }
+
+ for (size_t i = 0; i < out_length; ++i)
+ list[i].used = false;
+
+ in_length = block_size;
+ for (size_t i = 0; i < out_length; ++i) {
+ size_t z = 0;
+ auto kind = list[i].kind;
+ auto size = list[i].size;
+ auto data = list[i].data;
+
+ if (!ordered && list[i].used) {
+ continue;
+ }
+
+ switch (kind) {
+ case ASN1::Kind::Integer:
+ z = in_length;
+ if (!der_decode_integer(in + x, z, *(UnsignedBigInteger*)data)) {
+ dbgln("could not decode an integer");
+ return false;
+ }
+ if (!der_length_integer((UnsignedBigInteger*)data, &z)) {
+ dbgln("could not figure out the length");
+ return false;
+ }
+ break;
+ case ASN1::Kind::ObjectIdentifier:
+ z = in_length;
+ if (!der_decode_object_identifier(in + x, z, (u8*)data, (u8*)&size)) {
+ if (!ordered)
+ continue;
+ return false;
+ }
+ list[i].size = size;
+ if (!der_length_object_identifier((u8*)data, size, &z)) {
+ return false;
+ }
+ break;
+ case ASN1::Kind::Sequence:
+ if ((in[x] & 0x3f) != 0x30) {
+ dbgln("Not a sequence: {}", (in[x] & 0x3f));
+ return false;
+ }
+ z = in_length;
+ if (!der_decode_sequence(in + x, z, (ASN1::List*)data, size)) {
+ if (!ordered)
+ continue;
+ return false;
+ }
+ if (!der_length_sequence((ASN1::List*)data, size, &z)) {
+ return false;
+ }
+ break;
+ default:
+ dbgln("Unhandled ASN1 kind {}", ASN1::kind_name(kind));
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ x += z;
+ in_length -= z;
+ list[i].used = true;
+ if (!ordered)
+ i = -1;
+ }
+ for (size_t i = 0; i < out_length; ++i)
+ if (!list[i].used) {
+ dbgln("index {} was not read", i);
+ return false;
+ }
+
+ return true;
+}
+
+template<size_t element_count>
+struct der_decode_sequence_many_base {
+ constexpr void set(size_t index, ASN1::Kind kind, size_t size, void* data)
+ {
+ ASN1::set(m_list[index], kind, data, size);
+ }
+
+ constexpr der_decode_sequence_many_base(const u8* in, size_t in_length)
+ : m_in(in)
+ , m_in_length(in_length)
+ {
+ }
+
+ ASN1::List* list() { return m_list; }
+ const u8* in() { return m_in; }
+ size_t in_length() { return m_in_length; }
+
+protected:
+ ASN1::List m_list[element_count];
+ const u8* m_in;
+ size_t m_in_length;
+};
+
+template<size_t element_count>
+struct der_decode_sequence_many : public der_decode_sequence_many_base<element_count> {
+
+ template<typename ElementType, typename... Args>
+ constexpr void construct(size_t index, ASN1::Kind kind, size_t size, ElementType data, Args... args)
+ {
+ der_decode_sequence_many_base<element_count>::set(index, kind, size, (void*)data);
+ construct(index + 1, args...);
+ }
+
+ constexpr void construct(size_t index)
+ {
+ ASSERT(index == element_count);
+ }
+
+ template<typename... Args>
+ constexpr der_decode_sequence_many(const u8* in, size_t in_length, Args... args)
+ : der_decode_sequence_many_base<element_count>(in, in_length)
+ {
+ construct(0, args...);
+ }
+
+ constexpr operator bool()
+ {
+ return der_decode_sequence(this->m_in, this->m_in_length, this->m_list, element_count);
+ }
+};
+
+// FIXME: Move these terrible constructs into their own place
+constexpr static void decode_b64_block(const u8 in[4], u8 out[3])
+{
+ out[0] = (u8)(in[0] << 2 | in[1] >> 4);
+ out[1] = (u8)(in[1] << 4 | in[2] >> 2);
+ out[2] = (u8)(((in[2] << 6) & 0xc0) | in[3]);
+}
+
+constexpr static char base64_chars[] { "|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\\]^_`abcdefghijklmnopq" };
+constexpr static size_t decode_b64(const u8* in_buffer, size_t in_length, ByteBuffer& out_buffer)
+{
+ u8 in[4] { 0 }, out[3] { 0 }, v { 0 };
+ size_t i { 0 }, length { 0 };
+ size_t output_offset { 0 };
+
+ const u8* ptr = in_buffer;
+
+ while (ptr <= in_buffer + in_length) {
+ for (length = 0, i = 0; i < 4 && (ptr <= in_buffer + in_length); ++i) {
+ v = 0;
+ while ((ptr <= in_buffer + in_length) && !v) {
+ v = ptr[0];
+ ++ptr;
+ v = (u8)((v < 43 || v > 122) ? 0 : base64_chars[v - 43]);
+ if (v)
+ v = (u8)(v == '$' ? 0 : v - 61);
+ }
+ if (ptr <= in_buffer + in_length) {
+ ++length;
+ if (v)
+ in[i] = v - 1;
+
+ } else {
+ in[i] = 0;
+ }
+ }
+ if (length) {
+ decode_b64_block(in, out);
+ out_buffer.overwrite(output_offset, out, length - 1);
+ output_offset += length - 1;
+ }
+ }
+ return output_offset;
+}
+}
diff --git a/Userland/Libraries/LibCrypto/ASN1/PEM.h b/Userland/Libraries/LibCrypto/ASN1/PEM.h
new file mode 100644
index 0000000000..b304d621f6
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/ASN1/PEM.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Span.h>
+#include <LibCrypto/ASN1/ASN1.h>
+#include <LibCrypto/ASN1/DER.h>
+
+namespace Crypto {
+
+static ByteBuffer decode_pem(ReadonlyBytes data_in, size_t cert_index = 0)
+{
+ size_t i { 0 };
+ size_t start_at { 0 };
+ size_t idx { 0 };
+ size_t input_length = data_in.size();
+ auto alloc_len = input_length / 4 * 3;
+ auto output = ByteBuffer::create_uninitialized(alloc_len);
+
+ for (i = 0; i < input_length; i++) {
+ if ((data_in[i] == '\n') || (data_in[i] == '\r'))
+ continue;
+
+ if (data_in[i] != '-') {
+ // Read entire line.
+ while ((i < input_length) && (data_in[i] != '\n'))
+ i++;
+ continue;
+ }
+
+ if (data_in[i] == '-') {
+ auto end_idx = i;
+ // Read until end of line.
+ while ((i < input_length) && (data_in[i] != '\n'))
+ i++;
+ if (start_at) {
+ if (cert_index > 0) {
+ cert_index--;
+ start_at = 0;
+ } else {
+ idx = decode_b64(data_in.offset(start_at), end_idx - start_at, output);
+ break;
+ }
+ } else
+ start_at = i + 1;
+ }
+ }
+ return output.slice(0, idx);
+}
+
+}
diff --git a/Userland/Libraries/LibCrypto/Authentication/GHash.cpp b/Userland/Libraries/LibCrypto/Authentication/GHash.cpp
new file mode 100644
index 0000000000..d932ae51f4
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Authentication/GHash.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/MemoryStream.h>
+#include <AK/Types.h>
+#include <AK/Vector.h>
+#include <LibCrypto/Authentication/GHash.h>
+#include <LibCrypto/BigInt/UnsignedBigInteger.h>
+
+namespace {
+
+static u32 to_u32(const u8* b)
+{
+ return AK::convert_between_host_and_big_endian(*(const u32*)b);
+}
+
+static void to_u8s(u8* b, const u32* w)
+{
+ for (auto i = 0; i < 4; ++i) {
+ auto& e = *((u32*)(b + i * 4));
+ e = AK::convert_between_host_and_big_endian(w[i]);
+ }
+}
+
+}
+
+namespace Crypto {
+namespace Authentication {
+
+GHash::TagType GHash::process(ReadonlyBytes aad, ReadonlyBytes cipher)
+{
+ u32 tag[4] { 0, 0, 0, 0 };
+
+ auto transform_one = [&](auto& buf) {
+ size_t i = 0;
+ for (; i < buf.size(); i += 16) {
+ if (i + 16 <= buf.size()) {
+ for (auto j = 0; j < 4; ++j) {
+ tag[j] ^= to_u32(buf.offset(i + j * 4));
+ }
+ galois_multiply(tag, m_key, tag);
+ }
+ }
+
+ if (i > buf.size()) {
+ static u8 buffer[16];
+ Bytes buffer_bytes { buffer, 16 };
+ OutputMemoryStream stream { buffer_bytes };
+ stream.write(buf.slice(i - 16));
+ stream.fill_to_end(0);
+
+ for (auto j = 0; j < 4; ++j) {
+ tag[j] ^= to_u32(buffer_bytes.offset(j * 4));
+ }
+ galois_multiply(tag, m_key, tag);
+ }
+ };
+
+ transform_one(aad);
+ transform_one(cipher);
+
+ auto aad_bits = 8 * (u64)aad.size();
+ auto cipher_bits = 8 * (u64)cipher.size();
+
+ auto high = [](u64 value) -> u32 { return value >> 32; };
+ auto low = [](u64 value) -> u32 { return value & 0xffffffff; };
+
+#ifdef GHASH_PROCESS_DEBUG
+ dbg() << "AAD bits: " << high(aad_bits) << " : " << low(aad_bits);
+ dbg() << "Cipher bits: " << high(cipher_bits) << " : " << low(cipher_bits);
+
+ dbg() << "Tag bits: " << tag[0] << " : " << tag[1] << " : " << tag[2] << " : " << tag[3];
+#endif
+
+ tag[0] ^= high(aad_bits);
+ tag[1] ^= low(aad_bits);
+ tag[2] ^= high(cipher_bits);
+ tag[3] ^= low(cipher_bits);
+
+#ifdef GHASH_PROCESS_DEBUG
+ dbg() << "Tag bits: " << tag[0] << " : " << tag[1] << " : " << tag[2] << " : " << tag[3];
+#endif
+
+ galois_multiply(tag, m_key, tag);
+
+ TagType digest;
+ to_u8s(digest.data, tag);
+
+ return digest;
+}
+
+/// Galois Field multiplication using <x^127 + x^7 + x^2 + x + 1>.
+/// Note that x, y, and z are strictly BE.
+void galois_multiply(u32 (&z)[4], const u32 (&_x)[4], const u32 (&_y)[4])
+{
+ u32 x[4] { _x[0], _x[1], _x[2], _x[3] };
+ u32 y[4] { _y[0], _y[1], _y[2], _y[3] };
+ __builtin_memset(z, 0, sizeof(z));
+
+ for (ssize_t i = 127; i > -1; --i) {
+ if ((y[3 - (i / 32)] >> (i % 32)) & 1) {
+ z[0] ^= x[0];
+ z[1] ^= x[1];
+ z[2] ^= x[2];
+ z[3] ^= x[3];
+ }
+ auto a0 = x[0] & 1;
+ x[0] >>= 1;
+ auto a1 = x[1] & 1;
+ x[1] >>= 1;
+ x[1] |= a0 << 31;
+ auto a2 = x[2] & 1;
+ x[2] >>= 1;
+ x[2] |= a1 << 31;
+ auto a3 = x[3] & 1;
+ x[3] >>= 1;
+ x[3] |= a2 << 31;
+
+ if (a3)
+ x[0] ^= 0xe1000000;
+ }
+}
+
+}
+}
diff --git a/Userland/Libraries/LibCrypto/Authentication/GHash.h b/Userland/Libraries/LibCrypto/Authentication/GHash.h
new file mode 100644
index 0000000000..7d183e52cf
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Authentication/GHash.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <LibCrypto/Hash/HashFunction.h>
+
+namespace Crypto {
+namespace Authentication {
+
+void galois_multiply(u32 (&z)[4], const u32 (&x)[4], const u32 (&y)[4]);
+
+struct GHashDigest {
+ constexpr static size_t Size = 16;
+ u8 data[Size];
+
+ const u8* immutable_data() const { return data; }
+ size_t data_length() { return Size; }
+};
+
+class GHash final {
+public:
+ using TagType = GHashDigest;
+
+ template<size_t N>
+ explicit GHash(const char (&key)[N])
+ : GHash({ key, N })
+ {
+ }
+
+ explicit GHash(const ReadonlyBytes& key)
+ {
+ for (size_t i = 0; i < 16; i += 4)
+ m_key[i / 4] = AK::convert_between_host_and_big_endian(*(const u32*)(key.offset(i)));
+ }
+
+ constexpr static size_t digest_size() { return TagType::Size; }
+
+ String class_name() const { return "GHash"; }
+
+ TagType process(ReadonlyBytes aad, ReadonlyBytes cipher);
+
+private:
+ inline void transform(ReadonlyBytes, ReadonlyBytes);
+
+ u32 m_key[4];
+};
+
+}
+
+}
diff --git a/Userland/Libraries/LibCrypto/Authentication/HMAC.h b/Userland/Libraries/LibCrypto/Authentication/HMAC.h
new file mode 100644
index 0000000000..ef49bc06b7
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Authentication/HMAC.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/StringView.h>
+#include <AK/Types.h>
+#include <AK/Vector.h>
+
+constexpr static auto IPAD = 0x36;
+constexpr static auto OPAD = 0x5c;
+
+namespace Crypto {
+namespace Authentication {
+
+template<typename HashT>
+class HMAC {
+public:
+ using HashType = HashT;
+ using TagType = typename HashType::DigestType;
+
+ size_t digest_size() const { return m_inner_hasher.digest_size(); }
+
+ template<typename KeyBufferType, typename... Args>
+ HMAC(KeyBufferType key, Args... args)
+ : m_inner_hasher(args...)
+ , m_outer_hasher(args...)
+ {
+ derive_key(key);
+ reset();
+ }
+
+ TagType process(const u8* message, size_t length)
+ {
+ reset();
+ update(message, length);
+ return digest();
+ }
+
+ void update(const u8* message, size_t length)
+ {
+ m_inner_hasher.update(message, length);
+ }
+
+ TagType process(ReadonlyBytes span) { return process(span.data(), span.size()); }
+ TagType process(const StringView& string) { return process((const u8*)string.characters_without_null_termination(), string.length()); }
+
+ void update(ReadonlyBytes span) { return update(span.data(), span.size()); }
+ void update(const StringView& string) { return update((const u8*)string.characters_without_null_termination(), string.length()); }
+
+ TagType digest()
+ {
+ m_outer_hasher.update(m_inner_hasher.digest().immutable_data(), m_inner_hasher.digest_size());
+ auto result = m_outer_hasher.digest();
+ reset();
+ return result;
+ }
+
+ void reset()
+ {
+ m_inner_hasher.reset();
+ m_outer_hasher.reset();
+ m_inner_hasher.update(m_key_data, m_inner_hasher.block_size());
+ m_outer_hasher.update(m_key_data + m_inner_hasher.block_size(), m_outer_hasher.block_size());
+ }
+
+ String class_name() const
+ {
+ StringBuilder builder;
+ builder.append("HMAC-");
+ builder.append(m_inner_hasher.class_name());
+ return builder.build();
+ }
+
+private:
+ void derive_key(const u8* key, size_t length)
+ {
+ auto block_size = m_inner_hasher.block_size();
+ u8 v_key[block_size];
+ __builtin_memset(v_key, 0, block_size);
+ auto key_buffer = Bytes { v_key, block_size };
+ // m_key_data is zero'd, so copying the data in
+ // the first few bytes leaves the rest zero, which
+ // is exactly what we want (zero padding)
+ if (length > block_size) {
+ m_inner_hasher.update(key, length);
+ auto digest = m_inner_hasher.digest();
+ // FIXME: should we check if the hash function creates more data than its block size?
+ key_buffer.overwrite(0, digest.immutable_data(), m_inner_hasher.digest_size());
+ } else {
+ key_buffer.overwrite(0, key, length);
+ }
+
+ // fill out the inner and outer padded keys
+ auto* i_key = m_key_data;
+ auto* o_key = m_key_data + block_size;
+ for (size_t i = 0; i < block_size; ++i) {
+ auto key_byte = key_buffer[i];
+ i_key[i] = key_byte ^ IPAD;
+ o_key[i] = key_byte ^ OPAD;
+ }
+ }
+
+ void derive_key(ReadonlyBytes key) { derive_key(key.data(), key.size()); }
+ void derive_key(const StringView& key) { derive_key(key.bytes()); }
+
+ HashType m_inner_hasher, m_outer_hasher;
+ u8 m_key_data[2048];
+};
+
+}
+}
diff --git a/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp b/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp
new file mode 100644
index 0000000000..c0699018e6
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "SignedBigInteger.h"
+#include <AK/StringBuilder.h>
+
+namespace Crypto {
+
+SignedBigInteger SignedBigInteger::import_data(const u8* ptr, size_t length)
+{
+ bool sign = *ptr;
+ auto unsigned_data = UnsignedBigInteger::import_data(ptr + 1, length - 1);
+ return { move(unsigned_data), sign };
+}
+
+size_t SignedBigInteger::export_data(Bytes data, bool remove_leading_zeros) const
+{
+ // FIXME: Support this:
+ // m <0XX> -> m <XX> (if remove_leading_zeros)
+ ASSERT(!remove_leading_zeros);
+
+ data[0] = m_sign;
+ auto bytes_view = data.slice(1, data.size() - 1);
+ return m_unsigned_data.export_data(bytes_view, remove_leading_zeros) + 1;
+}
+
+SignedBigInteger SignedBigInteger::from_base10(StringView str)
+{
+ bool sign = false;
+ if (str.length() > 1) {
+ auto maybe_sign = str[0];
+ if (maybe_sign == '-') {
+ str = str.substring_view(1, str.length() - 1);
+ sign = true;
+ }
+ if (maybe_sign == '+')
+ str = str.substring_view(1, str.length() - 1);
+ }
+ auto unsigned_data = UnsignedBigInteger::from_base10(str);
+ return { move(unsigned_data), sign };
+}
+
+String SignedBigInteger::to_base10() const
+{
+ StringBuilder builder;
+
+ if (m_sign)
+ builder.append('-');
+
+ builder.append(m_unsigned_data.to_base10());
+
+ return builder.to_string();
+}
+
+FLATTEN SignedBigInteger SignedBigInteger::plus(const SignedBigInteger& other) const
+{
+ // If both are of the same sign, just add the unsigned data and return.
+ if (m_sign == other.m_sign)
+ return { other.m_unsigned_data.plus(m_unsigned_data), m_sign };
+
+ // One value is signed while the other is not.
+ return m_sign ? other.minus(this->m_unsigned_data) : minus(other.m_unsigned_data);
+}
+
+FLATTEN SignedBigInteger SignedBigInteger::minus(const SignedBigInteger& other) const
+{
+ // If the signs are different, convert the op to an addition.
+ if (m_sign != other.m_sign) {
+ // -x - y = - (x + y)
+ // x - -y = (x + y)
+ SignedBigInteger result { other.m_unsigned_data.plus(this->m_unsigned_data) };
+ if (m_sign)
+ result.negate();
+ return result;
+ }
+
+ if (!m_sign) {
+ // Both operands are positive.
+ // x - y = - (y - x)
+ if (m_unsigned_data < other.m_unsigned_data) {
+ // The result will be negative.
+ return { other.m_unsigned_data.minus(m_unsigned_data), true };
+ }
+
+ // The result will be either zero, or positive.
+ return SignedBigInteger { m_unsigned_data.minus(other.m_unsigned_data) };
+ }
+
+ // Both operands are negative.
+ // -x - -y = y - x
+ if (m_unsigned_data < other.m_unsigned_data) {
+ // The result will be positive.
+ return SignedBigInteger { other.m_unsigned_data.minus(m_unsigned_data), true };
+ }
+ // The result will be either zero, or negative.
+ // y - x = - (x - y)
+ return SignedBigInteger { m_unsigned_data.minus(other.m_unsigned_data) };
+}
+
+FLATTEN SignedBigInteger SignedBigInteger::plus(const UnsignedBigInteger& other) const
+{
+ if (m_sign) {
+ if (other < m_unsigned_data)
+ return { m_unsigned_data.minus(other), true };
+
+ return { other.minus(m_unsigned_data), false };
+ }
+
+ return { m_unsigned_data.plus(other), false };
+}
+
+FLATTEN SignedBigInteger SignedBigInteger::minus(const UnsignedBigInteger& other) const
+{
+ if (m_sign)
+ return { m_unsigned_data.plus(m_unsigned_data), true };
+
+ if (other < m_unsigned_data)
+ return { m_unsigned_data.minus(other), false };
+
+ return { other.minus(m_unsigned_data), true };
+}
+
+FLATTEN SignedBigInteger SignedBigInteger::bitwise_or(const UnsignedBigInteger& other) const
+{
+ return { unsigned_value().bitwise_or(other), m_sign };
+}
+
+FLATTEN SignedBigInteger SignedBigInteger::bitwise_and(const UnsignedBigInteger& other) const
+{
+ return { unsigned_value().bitwise_and(other), false };
+}
+
+FLATTEN SignedBigInteger SignedBigInteger::bitwise_xor(const UnsignedBigInteger& other) const
+{
+ return { unsigned_value().bitwise_xor(other), m_sign };
+}
+
+FLATTEN SignedBigInteger SignedBigInteger::bitwise_not() const
+{
+ return { unsigned_value().bitwise_not(), !m_sign };
+}
+
+FLATTEN SignedBigInteger SignedBigInteger::bitwise_or(const SignedBigInteger& other) const
+{
+ auto result = bitwise_or(other.unsigned_value());
+
+ // The sign bit will have to be OR'd manually.
+ if (other.is_negative())
+ result.negate();
+
+ return result;
+}
+
+FLATTEN SignedBigInteger SignedBigInteger::bitwise_and(const SignedBigInteger& other) const
+{
+ auto result = bitwise_and(other.unsigned_value());
+
+ // The sign bit will have to be AND'd manually.
+ result.m_sign = is_negative() || other.is_negative();
+
+ return result;
+}
+
+FLATTEN SignedBigInteger SignedBigInteger::bitwise_xor(const SignedBigInteger& other) const
+{
+ auto result = bitwise_xor(other.unsigned_value());
+
+ // The sign bit will have to be XOR'd manually.
+ result.m_sign = is_negative() ^ other.is_negative();
+
+ return result;
+}
+
+bool SignedBigInteger::operator==(const UnsignedBigInteger& other) const
+{
+ if (m_sign)
+ return false;
+ return m_unsigned_data == other;
+}
+
+bool SignedBigInteger::operator!=(const UnsignedBigInteger& other) const
+{
+ if (m_sign)
+ return true;
+ return m_unsigned_data != other;
+}
+
+bool SignedBigInteger::operator<(const UnsignedBigInteger& other) const
+{
+ if (m_sign)
+ return true;
+ return m_unsigned_data < other;
+}
+
+FLATTEN SignedBigInteger SignedBigInteger::shift_left(size_t num_bits) const
+{
+ return SignedBigInteger { m_unsigned_data.shift_left(num_bits), m_sign };
+}
+
+FLATTEN SignedBigInteger SignedBigInteger::multiplied_by(const SignedBigInteger& other) const
+{
+ bool result_sign = m_sign ^ other.m_sign;
+ return { m_unsigned_data.multiplied_by(other.m_unsigned_data), result_sign };
+}
+
+FLATTEN SignedDivisionResult SignedBigInteger::divided_by(const SignedBigInteger& divisor) const
+{
+ // Aa / Bb -> (A^B)q, Ar
+ bool result_sign = m_sign ^ divisor.m_sign;
+ auto unsigned_division_result = m_unsigned_data.divided_by(divisor.m_unsigned_data);
+ return {
+ { move(unsigned_division_result.quotient), result_sign },
+ { move(unsigned_division_result.remainder), m_sign }
+ };
+}
+
+void SignedBigInteger::set_bit_inplace(size_t bit_index)
+{
+ m_unsigned_data.set_bit_inplace(bit_index);
+}
+
+bool SignedBigInteger::operator==(const SignedBigInteger& other) const
+{
+ if (is_invalid() != other.is_invalid())
+ return false;
+
+ if (m_unsigned_data == 0 && other.m_unsigned_data == 0)
+ return true;
+
+ return m_sign == other.m_sign && m_unsigned_data == other.m_unsigned_data;
+}
+
+bool SignedBigInteger::operator!=(const SignedBigInteger& other) const
+{
+ return !(*this == other);
+}
+
+bool SignedBigInteger::operator<(const SignedBigInteger& other) const
+{
+ if (m_sign ^ other.m_sign)
+ return m_sign;
+
+ if (m_sign)
+ return other.m_unsigned_data < m_unsigned_data;
+
+ return m_unsigned_data < other.m_unsigned_data;
+}
+
+}
diff --git a/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.h b/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.h
new file mode 100644
index 0000000000..b2ff1dd118
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.h
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Span.h>
+#include <LibCrypto/BigInt/UnsignedBigInteger.h>
+
+namespace Crypto {
+
+struct SignedDivisionResult;
+
+class SignedBigInteger {
+public:
+ SignedBigInteger(i32 x)
+ : m_sign(x < 0)
+ , m_unsigned_data(abs(x))
+ {
+ }
+
+ SignedBigInteger(UnsignedBigInteger&& unsigned_data, bool sign)
+ : m_sign(sign)
+ , m_unsigned_data(move(unsigned_data))
+ {
+ }
+
+ explicit SignedBigInteger(UnsignedBigInteger unsigned_data)
+ : m_sign(false)
+ , m_unsigned_data(move(unsigned_data))
+ {
+ }
+
+ SignedBigInteger()
+ : m_sign(false)
+ , m_unsigned_data()
+ {
+ }
+
+ static SignedBigInteger create_invalid()
+ {
+ return { UnsignedBigInteger::create_invalid(), false };
+ }
+
+ static SignedBigInteger import_data(const AK::StringView& data) { return import_data((const u8*)data.characters_without_null_termination(), data.length()); }
+ static SignedBigInteger import_data(const u8* ptr, size_t length);
+
+ size_t export_data(Bytes, bool remove_leading_zeros = false) const;
+
+ static SignedBigInteger from_base10(StringView str);
+ String to_base10() const;
+
+ const UnsignedBigInteger& unsigned_value() const { return m_unsigned_data; }
+ const Vector<u32, STARTING_WORD_SIZE> words() const { return m_unsigned_data.words(); }
+ bool is_negative() const { return m_sign; }
+
+ void negate() { m_sign = !m_sign; }
+
+ void set_to_0() { m_unsigned_data.set_to_0(); }
+ void set_to(i32 other)
+ {
+ m_unsigned_data.set_to((u32)other);
+ m_sign = other < 0;
+ }
+ void set_to(const SignedBigInteger& other)
+ {
+ m_unsigned_data.set_to(other.m_unsigned_data);
+ m_sign = other.m_sign;
+ }
+
+ void invalidate()
+ {
+ m_unsigned_data.invalidate();
+ }
+
+ bool is_invalid() const { return m_unsigned_data.is_invalid(); }
+
+ // These get + 1 byte for the sign.
+ size_t length() const { return m_unsigned_data.length() + 1; }
+ size_t trimmed_length() const { return m_unsigned_data.trimmed_length() + 1; };
+
+ SignedBigInteger plus(const SignedBigInteger& other) const;
+ SignedBigInteger minus(const SignedBigInteger& other) const;
+ SignedBigInteger bitwise_or(const SignedBigInteger& other) const;
+ SignedBigInteger bitwise_and(const SignedBigInteger& other) const;
+ SignedBigInteger bitwise_xor(const SignedBigInteger& other) const;
+ SignedBigInteger bitwise_not() const;
+ SignedBigInteger shift_left(size_t num_bits) const;
+ SignedBigInteger multiplied_by(const SignedBigInteger& other) const;
+ SignedDivisionResult divided_by(const SignedBigInteger& divisor) const;
+
+ SignedBigInteger plus(const UnsignedBigInteger& other) const;
+ SignedBigInteger minus(const UnsignedBigInteger& other) const;
+ SignedBigInteger bitwise_or(const UnsignedBigInteger& other) const;
+ SignedBigInteger bitwise_and(const UnsignedBigInteger& other) const;
+ SignedBigInteger bitwise_xor(const UnsignedBigInteger& other) const;
+ SignedBigInteger multiplied_by(const UnsignedBigInteger& other) const;
+ SignedDivisionResult divided_by(const UnsignedBigInteger& divisor) const;
+
+ void set_bit_inplace(size_t bit_index);
+
+ bool operator==(const SignedBigInteger& other) const;
+ bool operator!=(const SignedBigInteger& other) const;
+ bool operator<(const SignedBigInteger& other) const;
+
+ bool operator==(const UnsignedBigInteger& other) const;
+ bool operator!=(const UnsignedBigInteger& other) const;
+ bool operator<(const UnsignedBigInteger& other) const;
+
+private:
+ bool m_sign { false };
+ UnsignedBigInteger m_unsigned_data;
+};
+
+struct SignedDivisionResult {
+ Crypto::SignedBigInteger quotient;
+ Crypto::SignedBigInteger remainder;
+};
+
+}
+
+inline const LogStream&
+operator<<(const LogStream& stream, const Crypto::SignedBigInteger value)
+{
+ if (value.is_invalid()) {
+ stream << "Invalid BigInt";
+ return stream;
+ }
+ if (value.is_negative())
+ stream << "-";
+
+ stream << value.unsigned_value();
+ return stream;
+}
+
+inline Crypto::SignedBigInteger
+operator""_sbigint(const char* string, size_t length)
+{
+ return Crypto::SignedBigInteger::from_base10({ string, length });
+}
diff --git a/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp b/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp
new file mode 100644
index 0000000000..ef838ec782
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp
@@ -0,0 +1,745 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "UnsignedBigInteger.h"
+#include <AK/StringBuilder.h>
+
+namespace Crypto {
+
+UnsignedBigInteger::UnsignedBigInteger(const u8* ptr, size_t length)
+{
+ m_words.resize_and_keep_capacity((length + sizeof(u32) - 1) / sizeof(u32));
+ size_t in = length, out = 0;
+ while (in >= sizeof(u32)) {
+ in -= sizeof(u32);
+ u32 word = ((u32)ptr[in] << 24) | ((u32)ptr[in + 1] << 16) | ((u32)ptr[in + 2] << 8) | (u32)ptr[in + 3];
+ m_words[out++] = word;
+ }
+ if (in > 0) {
+ u32 word = 0;
+ for (size_t i = 0; i < in; i++) {
+ word <<= 8;
+ word |= (u32)ptr[i];
+ }
+ m_words[out++] = word;
+ }
+}
+
+UnsignedBigInteger UnsignedBigInteger::create_invalid()
+{
+ UnsignedBigInteger invalid(0);
+ invalid.invalidate();
+ return invalid;
+}
+
+size_t UnsignedBigInteger::export_data(Bytes data, bool remove_leading_zeros) const
+{
+ size_t word_count = trimmed_length();
+ size_t out = 0;
+ if (word_count > 0) {
+ ssize_t leading_zeros = -1;
+ if (remove_leading_zeros) {
+ u32 word = m_words[word_count - 1];
+ for (size_t i = 0; i < sizeof(u32); i++) {
+ u8 byte = (u8)(word >> ((sizeof(u32) - i - 1) * 8));
+ data[out++] = byte;
+ if (leading_zeros < 0 && byte != 0)
+ leading_zeros = (int)i;
+ }
+ }
+ for (size_t i = word_count - (remove_leading_zeros ? 1 : 0); i > 0; i--) {
+ auto word = m_words[i - 1];
+ data[out++] = (u8)(word >> 24);
+ data[out++] = (u8)(word >> 16);
+ data[out++] = (u8)(word >> 8);
+ data[out++] = (u8)word;
+ }
+ if (leading_zeros > 0)
+ out -= leading_zeros;
+ }
+ return out;
+}
+
+UnsignedBigInteger UnsignedBigInteger::from_base10(const String& str)
+{
+ UnsignedBigInteger result;
+ UnsignedBigInteger ten { 10 };
+
+ for (auto& c : str) {
+ result = result.multiplied_by(ten).plus(c - '0');
+ }
+ return result;
+}
+
+String UnsignedBigInteger::to_base10() const
+{
+ if (*this == UnsignedBigInteger { 0 })
+ return "0";
+
+ StringBuilder builder;
+ UnsignedBigInteger temp(*this);
+ UnsignedBigInteger quotient;
+ UnsignedBigInteger remainder;
+
+ while (temp != UnsignedBigInteger { 0 }) {
+ divide_u16_without_allocation(temp, 10, quotient, remainder);
+ ASSERT(remainder.words()[0] < 10);
+ builder.append(static_cast<char>(remainder.words()[0] + '0'));
+ temp.set_to(quotient);
+ }
+
+ auto reversed_string = builder.to_string();
+ builder.clear();
+ for (int i = reversed_string.length() - 1; i >= 0; --i) {
+ builder.append(reversed_string[i]);
+ }
+
+ return builder.to_string();
+}
+
+void UnsignedBigInteger::set_to_0()
+{
+ m_words.clear_with_capacity();
+ m_is_invalid = false;
+ m_cached_trimmed_length = {};
+}
+
+void UnsignedBigInteger::set_to(u32 other)
+{
+ m_is_invalid = false;
+ m_words.resize_and_keep_capacity(1);
+ m_words[0] = other;
+ m_cached_trimmed_length = {};
+}
+
+void UnsignedBigInteger::set_to(const UnsignedBigInteger& other)
+{
+ m_is_invalid = other.m_is_invalid;
+ m_words.resize_and_keep_capacity(other.m_words.size());
+ __builtin_memcpy(m_words.data(), other.m_words.data(), other.m_words.size() * sizeof(u32));
+ m_cached_trimmed_length = {};
+}
+
+size_t UnsignedBigInteger::trimmed_length() const
+{
+ if (!m_cached_trimmed_length.has_value()) {
+ size_t num_leading_zeroes = 0;
+ for (int i = length() - 1; i >= 0; --i, ++num_leading_zeroes) {
+ if (m_words[i] != 0)
+ break;
+ }
+ m_cached_trimmed_length = length() - num_leading_zeroes;
+ }
+ return m_cached_trimmed_length.value();
+}
+
+FLATTEN UnsignedBigInteger UnsignedBigInteger::plus(const UnsignedBigInteger& other) const
+{
+ UnsignedBigInteger result;
+
+ add_without_allocation(*this, other, result);
+
+ return result;
+}
+
+FLATTEN UnsignedBigInteger UnsignedBigInteger::minus(const UnsignedBigInteger& other) const
+{
+ UnsignedBigInteger result;
+
+ subtract_without_allocation(*this, other, result);
+
+ return result;
+}
+
+FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_or(const UnsignedBigInteger& other) const
+{
+ UnsignedBigInteger result;
+
+ bitwise_or_without_allocation(*this, other, result);
+
+ return result;
+}
+
+FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_and(const UnsignedBigInteger& other) const
+{
+ UnsignedBigInteger result;
+
+ bitwise_and_without_allocation(*this, other, result);
+
+ return result;
+}
+
+FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_xor(const UnsignedBigInteger& other) const
+{
+ UnsignedBigInteger result;
+
+ bitwise_xor_without_allocation(*this, other, result);
+
+ return result;
+}
+
+FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_not() const
+{
+ UnsignedBigInteger result;
+
+ bitwise_not_without_allocation(*this, result);
+
+ return result;
+}
+
+FLATTEN UnsignedBigInteger UnsignedBigInteger::shift_left(size_t num_bits) const
+{
+ UnsignedBigInteger output;
+ UnsignedBigInteger temp_result;
+ UnsignedBigInteger temp_plus;
+
+ shift_left_without_allocation(*this, num_bits, temp_result, temp_plus, output);
+
+ return output;
+}
+
+FLATTEN UnsignedBigInteger UnsignedBigInteger::multiplied_by(const UnsignedBigInteger& other) const
+{
+ UnsignedBigInteger result;
+ UnsignedBigInteger temp_shift_result;
+ UnsignedBigInteger temp_shift_plus;
+ UnsignedBigInteger temp_shift;
+ UnsignedBigInteger temp_plus;
+
+ multiply_without_allocation(*this, other, temp_shift_result, temp_shift_plus, temp_shift, temp_plus, result);
+
+ return result;
+}
+
+FLATTEN UnsignedDivisionResult UnsignedBigInteger::divided_by(const UnsignedBigInteger& divisor) const
+{
+ UnsignedBigInteger quotient;
+ UnsignedBigInteger remainder;
+
+ // If we actually have a u16-compatible divisor, short-circuit to the
+ // less computationally-intensive "divide_u16_without_allocation" method.
+ if (divisor.trimmed_length() == 1 && divisor.m_words[0] < (1 << 16)) {
+ divide_u16_without_allocation(*this, divisor.m_words[0], quotient, remainder);
+ return UnsignedDivisionResult { quotient, remainder };
+ }
+
+ UnsignedBigInteger temp_shift_result;
+ UnsignedBigInteger temp_shift_plus;
+ UnsignedBigInteger temp_shift;
+ UnsignedBigInteger temp_minus;
+
+ divide_without_allocation(*this, divisor, temp_shift_result, temp_shift_plus, temp_shift, temp_minus, quotient, remainder);
+
+ return UnsignedDivisionResult { quotient, remainder };
+}
+
+void UnsignedBigInteger::set_bit_inplace(size_t bit_index)
+{
+ const size_t word_index = bit_index / UnsignedBigInteger::BITS_IN_WORD;
+ const size_t inner_word_index = bit_index % UnsignedBigInteger::BITS_IN_WORD;
+
+ m_words.ensure_capacity(word_index);
+
+ for (size_t i = length(); i <= word_index; ++i) {
+ m_words.unchecked_append(0);
+ }
+ m_words[word_index] |= (1 << inner_word_index);
+
+ m_cached_trimmed_length = {};
+}
+
+bool UnsignedBigInteger::operator==(const UnsignedBigInteger& other) const
+{
+ if (is_invalid() != other.is_invalid())
+ return false;
+
+ auto length = trimmed_length();
+
+ if (length != other.trimmed_length())
+ return false;
+
+ return !__builtin_memcmp(m_words.data(), other.words().data(), length);
+}
+
+bool UnsignedBigInteger::operator!=(const UnsignedBigInteger& other) const
+{
+ return !(*this == other);
+}
+
+bool UnsignedBigInteger::operator<(const UnsignedBigInteger& other) const
+{
+ auto length = trimmed_length();
+ auto other_length = other.trimmed_length();
+
+ if (length < other_length) {
+ return true;
+ }
+
+ if (length > other_length) {
+ return false;
+ }
+
+ if (length == 0) {
+ return false;
+ }
+ for (int i = length - 1; i >= 0; --i) {
+ if (m_words[i] == other.m_words[i])
+ continue;
+ return m_words[i] < other.m_words[i];
+ }
+ return false;
+}
+
+/**
+ * Complexity: O(N) where N is the number of words in the larger number
+ */
+void UnsignedBigInteger::add_without_allocation(
+ const UnsignedBigInteger& left,
+ const UnsignedBigInteger& right,
+ UnsignedBigInteger& output)
+{
+ const UnsignedBigInteger* const longer = (left.length() > right.length()) ? &left : &right;
+ const UnsignedBigInteger* const shorter = (longer == &right) ? &left : &right;
+
+ u8 carry = 0;
+
+ output.set_to_0();
+ output.m_words.resize_and_keep_capacity(longer->length());
+
+ for (size_t i = 0; i < shorter->length(); ++i) {
+ u32 word_addition_result = shorter->m_words[i] + longer->m_words[i];
+ u8 carry_out = 0;
+ // if there was a carry, the result will be smaller than any of the operands
+ if (word_addition_result + carry < shorter->m_words[i]) {
+ carry_out = 1;
+ }
+ if (carry) {
+ word_addition_result++;
+ }
+ carry = carry_out;
+ output.m_words[i] = word_addition_result;
+ }
+
+ for (size_t i = shorter->length(); i < longer->length(); ++i) {
+ u32 word_addition_result = longer->m_words[i] + carry;
+
+ carry = 0;
+ if (word_addition_result < longer->m_words[i]) {
+ carry = 1;
+ }
+ output.m_words[i] = word_addition_result;
+ }
+ if (carry) {
+ output.m_words.append(carry);
+ }
+}
+
+/**
+ * Complexity: O(N) where N is the number of words in the larger number
+ */
+void UnsignedBigInteger::subtract_without_allocation(
+ const UnsignedBigInteger& left,
+ const UnsignedBigInteger& right,
+ UnsignedBigInteger& output)
+{
+ if (left < right) {
+ output.invalidate();
+ return;
+ }
+
+ u8 borrow = 0;
+ auto own_length = left.length();
+ auto other_length = right.length();
+
+ output.set_to_0();
+ output.m_words.resize_and_keep_capacity(own_length);
+
+ for (size_t i = 0; i < own_length; ++i) {
+ u32 other_word = (i < other_length) ? right.m_words[i] : 0;
+ i64 temp = static_cast<i64>(left.m_words[i]) - static_cast<i64>(other_word) - static_cast<i64>(borrow);
+ // If temp < 0, we had an underflow
+ borrow = (temp >= 0) ? 0 : 1;
+ if (temp < 0) {
+ temp += (UINT32_MAX + 1);
+ }
+ output.m_words[i] = temp;
+ }
+
+ // This assertion should not fail, because we verified that *this>=other at the beginning of the function
+ ASSERT(borrow == 0);
+}
+
+/**
+ * Complexity: O(N) where N is the number of words in the shorter value
+ * Method:
+ * Apply <op> word-wise until words in the shorter value are used up
+ * then copy the rest of the words verbatim from the longer value.
+ */
+FLATTEN void UnsignedBigInteger::bitwise_or_without_allocation(
+ const UnsignedBigInteger& left,
+ const UnsignedBigInteger& right,
+ UnsignedBigInteger& output)
+{
+ // If either of the BigInts are invalid, the output is just the other one.
+ if (left.is_invalid()) {
+ output.set_to(right);
+ return;
+ }
+ if (right.is_invalid()) {
+ output.set_to(left);
+ return;
+ }
+
+ const UnsignedBigInteger *shorter, *longer;
+ if (left.length() < right.length()) {
+ shorter = &left;
+ longer = &right;
+ } else {
+ shorter = &right;
+ longer = &left;
+ }
+
+ output.m_words.resize_and_keep_capacity(longer->length());
+
+ size_t longer_offset = longer->length() - shorter->length();
+ for (size_t i = 0; i < shorter->length(); ++i)
+ output.m_words[i] = longer->words()[i] | shorter->words()[i];
+
+ __builtin_memcpy(output.m_words.data() + shorter->length(), longer->words().data() + shorter->length(), sizeof(u32) * longer_offset);
+}
+
+/**
+ * Complexity: O(N) where N is the number of words in the shorter value
+ * Method:
+ * Apply 'and' word-wise until words in the shorter value are used up
+ * and zero the rest.
+ */
+FLATTEN void UnsignedBigInteger::bitwise_and_without_allocation(
+ const UnsignedBigInteger& left,
+ const UnsignedBigInteger& right,
+ UnsignedBigInteger& output)
+{
+ // If either of the BigInts are invalid, the output is just the other one.
+ if (left.is_invalid()) {
+ output.set_to(right);
+ return;
+ }
+ if (right.is_invalid()) {
+ output.set_to(left);
+ return;
+ }
+
+ const UnsignedBigInteger *shorter, *longer;
+ if (left.length() < right.length()) {
+ shorter = &left;
+ longer = &right;
+ } else {
+ shorter = &right;
+ longer = &left;
+ }
+
+ output.m_words.resize_and_keep_capacity(longer->length());
+
+ size_t longer_offset = longer->length() - shorter->length();
+ for (size_t i = 0; i < shorter->length(); ++i)
+ output.m_words[i] = longer->words()[i] & shorter->words()[i];
+
+ __builtin_memset(output.m_words.data() + shorter->length(), 0, sizeof(u32) * longer_offset);
+}
+
+/**
+ * Complexity: O(N) where N is the number of words in the shorter value
+ * Method:
+ * Apply 'xor' word-wise until words in the shorter value are used up
+ * and copy the rest.
+ */
+FLATTEN void UnsignedBigInteger::bitwise_xor_without_allocation(
+ const UnsignedBigInteger& left,
+ const UnsignedBigInteger& right,
+ UnsignedBigInteger& output)
+{
+ // If either of the BigInts are invalid, the output is just the other one.
+ if (left.is_invalid()) {
+ output.set_to(right);
+ return;
+ }
+ if (right.is_invalid()) {
+ output.set_to(left);
+ return;
+ }
+
+ const UnsignedBigInteger *shorter, *longer;
+ if (left.length() < right.length()) {
+ shorter = &left;
+ longer = &right;
+ } else {
+ shorter = &right;
+ longer = &left;
+ }
+
+ output.m_words.resize_and_keep_capacity(longer->length());
+
+ size_t longer_offset = longer->length() - shorter->length();
+ for (size_t i = 0; i < shorter->length(); ++i)
+ output.m_words[i] = longer->words()[i] ^ shorter->words()[i];
+
+ __builtin_memcpy(output.m_words.data() + shorter->length(), longer->words().data() + shorter->length(), sizeof(u32) * longer_offset);
+}
+
+/**
+ * Complexity: O(N) where N is the number of words
+ */
+FLATTEN void UnsignedBigInteger::bitwise_not_without_allocation(
+ const UnsignedBigInteger& right,
+ UnsignedBigInteger& output)
+{
+ // If the value is invalid, the output value is invalid as well.
+ if (right.is_invalid()) {
+ output.invalidate();
+ return;
+ }
+ if (right.length() == 0) {
+ output.set_to_0();
+ return;
+ }
+
+ output.m_words.resize_and_keep_capacity(right.length());
+
+ if (right.length() > 1) {
+ for (size_t i = 0; i < right.length() - 1; ++i)
+ output.m_words[i] = ~right.words()[i];
+ }
+
+ auto last_word_index = right.length() - 1;
+ auto last_word = right.words()[last_word_index];
+
+ output.m_words[last_word_index] = ((u32)0xffffffffffffffff >> __builtin_clz(last_word)) & ~last_word;
+}
+
+/**
+ * Complexity : O(N + num_bits % 8) where N is the number of words in the number
+ * Shift method :
+ * Start by shifting by whole words in num_bits (by putting missing words at the start),
+ * then shift the number's words two by two by the remaining amount of bits.
+ */
+FLATTEN void UnsignedBigInteger::shift_left_without_allocation(
+ const UnsignedBigInteger& number,
+ size_t num_bits,
+ UnsignedBigInteger& temp_result,
+ UnsignedBigInteger& temp_plus,
+ UnsignedBigInteger& output)
+{
+ // We can only do shift operations on individual words
+ // where the shift amount is <= size of word (32).
+ // But we do know how to shift by a multiple of word size (e.g 64=32*2)
+ // So we first shift the result by how many whole words fit in 'num_bits'
+ shift_left_by_n_words(number, num_bits / UnsignedBigInteger::BITS_IN_WORD, temp_result);
+
+ output.set_to(temp_result);
+
+ // And now we shift by the leftover amount of bits
+ num_bits %= UnsignedBigInteger::BITS_IN_WORD;
+
+ if (num_bits == 0) {
+ return;
+ }
+
+ for (size_t i = 0; i < temp_result.length(); ++i) {
+ u32 current_word_of_temp_result = shift_left_get_one_word(temp_result, num_bits, i);
+ output.m_words[i] = current_word_of_temp_result;
+ }
+
+ // Shifting the last word can produce a carry
+ u32 carry_word = shift_left_get_one_word(temp_result, num_bits, temp_result.length());
+ if (carry_word != 0) {
+
+ // output += (carry_word << temp_result.length())
+ // FIXME : Using temp_plus this way to transform carry_word into a bigint is not
+ // efficient nor pretty. Maybe we should have an "add_with_shift" method ?
+ temp_plus.set_to_0();
+ temp_plus.m_words.append(carry_word);
+ shift_left_by_n_words(temp_plus, temp_result.length(), temp_result);
+ add_without_allocation(output, temp_result, temp_plus);
+ output.set_to(temp_plus);
+ }
+}
+
+/**
+ * Complexity: O(N^2) where N is the number of words in the larger number
+ * Multiplication method:
+ * An integer is equal to the sum of the powers of two
+ * according to the indexes of its 'on' bits.
+ * So to multiple x*y, we go over each '1' bit in x (say the i'th bit),
+ * and add y<<i to the result.
+ */
+FLATTEN void UnsignedBigInteger::multiply_without_allocation(
+ const UnsignedBigInteger& left,
+ const UnsignedBigInteger& right,
+ UnsignedBigInteger& temp_shift_result,
+ UnsignedBigInteger& temp_shift_plus,
+ UnsignedBigInteger& temp_shift,
+ UnsignedBigInteger& temp_plus,
+ UnsignedBigInteger& output)
+{
+ output.set_to_0();
+
+ // iterate all bits
+ for (size_t word_index = 0; word_index < left.length(); ++word_index) {
+ for (size_t bit_index = 0; bit_index < UnsignedBigInteger::BITS_IN_WORD; ++bit_index) {
+ // If the bit is off - skip over it
+ if (!(left.m_words[word_index] & (1 << bit_index)))
+ continue;
+
+ const size_t shift_amount = word_index * UnsignedBigInteger::BITS_IN_WORD + bit_index;
+
+ // output += (right << shift_amount);
+ shift_left_without_allocation(right, shift_amount, temp_shift_result, temp_shift_plus, temp_shift);
+ add_without_allocation(output, temp_shift, temp_plus);
+ output.set_to(temp_plus);
+ }
+ }
+}
+
+/**
+ * Complexity: O(N^2) where N is the number of words in the larger number
+ * Division method:
+ * We loop over the bits of the divisor, attempting to subtract divisor<<i from the dividend.
+ * If the result is non-negative, it means that divisor*2^i "fits" in the dividend,
+ * so we set the ith bit in the quotient and reduce divisor<<i from the dividend.
+ * When we're done, what's left from the dividend is the remainder.
+ */
+FLATTEN void UnsignedBigInteger::divide_without_allocation(
+ const UnsignedBigInteger& numerator,
+ const UnsignedBigInteger& denominator,
+ UnsignedBigInteger& temp_shift_result,
+ UnsignedBigInteger& temp_shift_plus,
+ UnsignedBigInteger& temp_shift,
+ UnsignedBigInteger& temp_minus,
+ UnsignedBigInteger& quotient,
+ UnsignedBigInteger& remainder)
+{
+ quotient.set_to_0();
+ remainder.set_to(numerator);
+
+ // iterate all bits
+ for (int word_index = numerator.trimmed_length() - 1; word_index >= 0; --word_index) {
+ for (int bit_index = UnsignedBigInteger::BITS_IN_WORD - 1; bit_index >= 0; --bit_index) {
+ const size_t shift_amount = word_index * UnsignedBigInteger::BITS_IN_WORD + bit_index;
+ shift_left_without_allocation(denominator, shift_amount, temp_shift_result, temp_shift_plus, temp_shift);
+
+ subtract_without_allocation(remainder, temp_shift, temp_minus);
+ if (!temp_minus.is_invalid()) {
+ remainder.set_to(temp_minus);
+ quotient.set_bit_inplace(shift_amount);
+ }
+ }
+ }
+}
+
+/**
+ * Complexity : O(N) where N is the number of digits in the numerator
+ * Division method :
+ * Starting from the most significant one, for each half-word of the numerator, combine it
+ * with the existing remainder if any, divide the combined number as a u32 operation and
+ * update the quotient / remainder as needed.
+ */
+FLATTEN void UnsignedBigInteger::divide_u16_without_allocation(
+ const UnsignedBigInteger& numerator,
+ u32 denominator,
+ UnsignedBigInteger& quotient,
+ UnsignedBigInteger& remainder)
+{
+ ASSERT(denominator < (1 << 16));
+ u32 remainder_word = 0;
+ auto numerator_length = numerator.trimmed_length();
+ quotient.set_to_0();
+ quotient.m_words.resize(numerator_length);
+ for (int word_index = numerator_length - 1; word_index >= 0; --word_index) {
+ auto word_high = numerator.m_words[word_index] >> 16;
+ auto word_low = numerator.m_words[word_index] & ((1 << 16) - 1);
+
+ auto number_to_divide_high = (remainder_word << 16) | word_high;
+ auto quotient_high = number_to_divide_high / denominator;
+ remainder_word = number_to_divide_high % denominator;
+
+ auto number_to_divide_low = remainder_word << 16 | word_low;
+ auto quotient_low = number_to_divide_low / denominator;
+ remainder_word = number_to_divide_low % denominator;
+
+ quotient.m_words[word_index] = (quotient_high << 16) | quotient_low;
+ }
+ remainder.set_to(remainder_word);
+}
+
+ALWAYS_INLINE void UnsignedBigInteger::shift_left_by_n_words(
+ const UnsignedBigInteger& number,
+ const size_t number_of_words,
+ UnsignedBigInteger& output)
+{
+ // shifting left by N words means just inserting N zeroes to the beginning of the words vector
+ output.set_to_0();
+ output.m_words.resize_and_keep_capacity(number_of_words + number.length());
+
+ __builtin_memset(output.m_words.data(), 0, number_of_words * sizeof(unsigned));
+ __builtin_memcpy(&output.m_words.data()[number_of_words], number.m_words.data(), number.m_words.size() * sizeof(unsigned));
+}
+
+/**
+ * Returns the word at a requested index in the result of a shift operation
+ */
+ALWAYS_INLINE u32 UnsignedBigInteger::shift_left_get_one_word(
+ const UnsignedBigInteger& number,
+ const size_t num_bits,
+ const size_t result_word_index)
+{
+ // "<= length()" (rather than length() - 1) is intentional,
+ // The result inedx of length() is used when calculating the carry word
+ ASSERT(result_word_index <= number.length());
+ ASSERT(num_bits <= UnsignedBigInteger::BITS_IN_WORD);
+ u32 result = 0;
+
+ // we need to check for "num_bits != 0" since shifting right by 32 is apparently undefined behaviour!
+ if (result_word_index > 0 && num_bits != 0) {
+ result += number.m_words[result_word_index - 1] >> (UnsignedBigInteger::BITS_IN_WORD - num_bits);
+ }
+ if (result_word_index < number.length() && num_bits < 32) {
+ result += number.m_words[result_word_index] << num_bits;
+ }
+ return result;
+}
+}
+
+void AK::Formatter<Crypto::UnsignedBigInteger>::format(FormatBuilder& fmtbuilder, const Crypto::UnsignedBigInteger& value)
+{
+ if (value.is_invalid())
+ return Formatter<StringView>::format(fmtbuilder, "invalid");
+
+ StringBuilder builder;
+ for (int i = value.length() - 1; i >= 0; --i)
+ builder.appendff("{}|", value.words()[i]);
+
+ return Formatter<StringView>::format(fmtbuilder, builder.string_view());
+}
diff --git a/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.h b/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.h
new file mode 100644
index 0000000000..07d2ca4848
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/LogStream.h>
+#include <AK/Span.h>
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <AK/Vector.h>
+
+namespace Crypto {
+
+struct UnsignedDivisionResult;
+constexpr size_t STARTING_WORD_SIZE = 512;
+
+class UnsignedBigInteger {
+public:
+ UnsignedBigInteger(u32 x) { m_words.append(x); }
+
+ explicit UnsignedBigInteger(AK::Vector<u32, STARTING_WORD_SIZE>&& words)
+ : m_words(move(words))
+ {
+ }
+
+ explicit UnsignedBigInteger(const u8* ptr, size_t length);
+
+ UnsignedBigInteger() { }
+
+ static UnsignedBigInteger create_invalid();
+
+ static UnsignedBigInteger import_data(const AK::StringView& data) { return import_data((const u8*)data.characters_without_null_termination(), data.length()); }
+ static UnsignedBigInteger import_data(const u8* ptr, size_t length)
+ {
+ return UnsignedBigInteger(ptr, length);
+ }
+
+ size_t export_data(Bytes, bool remove_leading_zeros = false) const;
+
+ static UnsignedBigInteger from_base10(const String& str);
+ String to_base10() const;
+
+ const AK::Vector<u32, STARTING_WORD_SIZE>& words() const { return m_words; }
+
+ void set_to_0();
+ void set_to(u32 other);
+ void set_to(const UnsignedBigInteger& other);
+
+ void invalidate()
+ {
+ m_is_invalid = true;
+ m_cached_trimmed_length = {};
+ }
+
+ bool is_invalid() const { return m_is_invalid; }
+
+ size_t length() const { return m_words.size(); }
+ // The "trimmed length" is the number of words after trimming leading zeroed words
+ size_t trimmed_length() const;
+
+ UnsignedBigInteger plus(const UnsignedBigInteger& other) const;
+ UnsignedBigInteger minus(const UnsignedBigInteger& other) const;
+ UnsignedBigInteger bitwise_or(const UnsignedBigInteger& other) const;
+ UnsignedBigInteger bitwise_and(const UnsignedBigInteger& other) const;
+ UnsignedBigInteger bitwise_xor(const UnsignedBigInteger& other) const;
+ UnsignedBigInteger bitwise_not() const;
+ UnsignedBigInteger shift_left(size_t num_bits) const;
+ UnsignedBigInteger multiplied_by(const UnsignedBigInteger& other) const;
+ UnsignedDivisionResult divided_by(const UnsignedBigInteger& divisor) const;
+
+ void set_bit_inplace(size_t bit_index);
+
+ static void add_without_allocation(const UnsignedBigInteger& left, const UnsignedBigInteger& right, UnsignedBigInteger& output);
+ static void subtract_without_allocation(const UnsignedBigInteger& left, const UnsignedBigInteger& right, UnsignedBigInteger& output);
+ static void bitwise_or_without_allocation(const UnsignedBigInteger& left, const UnsignedBigInteger& right, UnsignedBigInteger& output);
+ static void bitwise_and_without_allocation(const UnsignedBigInteger& left, const UnsignedBigInteger& right, UnsignedBigInteger& output);
+ static void bitwise_xor_without_allocation(const UnsignedBigInteger& left, const UnsignedBigInteger& right, UnsignedBigInteger& output);
+ static void bitwise_not_without_allocation(const UnsignedBigInteger& left, UnsignedBigInteger& output);
+ static void shift_left_without_allocation(const UnsignedBigInteger& number, size_t bits_to_shift_by, UnsignedBigInteger& temp_result, UnsignedBigInteger& temp_plus, UnsignedBigInteger& output);
+ static void multiply_without_allocation(const UnsignedBigInteger& left, const UnsignedBigInteger& right, UnsignedBigInteger& temp_shift_result, UnsignedBigInteger& temp_shift_plus, UnsignedBigInteger& temp_shift, UnsignedBigInteger& temp_plus, UnsignedBigInteger& output);
+ static void divide_without_allocation(const UnsignedBigInteger& numerator, const UnsignedBigInteger& denominator, UnsignedBigInteger& temp_shift_result, UnsignedBigInteger& temp_shift_plus, UnsignedBigInteger& temp_shift, UnsignedBigInteger& temp_minus, UnsignedBigInteger& quotient, UnsignedBigInteger& remainder);
+ static void divide_u16_without_allocation(const UnsignedBigInteger& numerator, u32 denominator, UnsignedBigInteger& quotient, UnsignedBigInteger& remainder);
+
+ bool operator==(const UnsignedBigInteger& other) const;
+ bool operator!=(const UnsignedBigInteger& other) const;
+ bool operator<(const UnsignedBigInteger& other) const;
+
+private:
+ ALWAYS_INLINE static void shift_left_by_n_words(const UnsignedBigInteger& number, size_t number_of_words, UnsignedBigInteger& output);
+ ALWAYS_INLINE static u32 shift_left_get_one_word(const UnsignedBigInteger& number, size_t num_bits, size_t result_word_index);
+
+ static constexpr size_t BITS_IN_WORD = 32;
+ // Little endian
+ // m_word[0] + m_word[1] * 256 + m_word[2] * 65536 + ...
+ AK::Vector<u32, STARTING_WORD_SIZE> m_words;
+
+ // Used to indicate a negative result, or a result of an invalid operation
+ bool m_is_invalid { false };
+
+ mutable Optional<size_t> m_cached_trimmed_length;
+};
+
+struct UnsignedDivisionResult {
+ Crypto::UnsignedBigInteger quotient;
+ Crypto::UnsignedBigInteger remainder;
+};
+
+}
+
+inline const LogStream&
+operator<<(const LogStream& stream, const Crypto::UnsignedBigInteger& value)
+{
+ if (value.is_invalid()) {
+ stream << "Invalid BigInt";
+ return stream;
+ }
+ for (int i = value.length() - 1; i >= 0; --i) {
+ stream << value.words()[i] << "|";
+ }
+ return stream;
+}
+
+template<>
+struct AK::Formatter<Crypto::UnsignedBigInteger> : Formatter<StringView> {
+ void format(FormatBuilder&, const Crypto::UnsignedBigInteger&);
+};
+
+inline Crypto::UnsignedBigInteger
+operator""_bigint(const char* string, size_t length)
+{
+ return Crypto::UnsignedBigInteger::from_base10({ string, length });
+}
diff --git a/Userland/Libraries/LibCrypto/CMakeLists.txt b/Userland/Libraries/LibCrypto/CMakeLists.txt
new file mode 100644
index 0000000000..bda4aa5d20
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/CMakeLists.txt
@@ -0,0 +1,16 @@
+set(SOURCES
+ Authentication/GHash.cpp
+ BigInt/SignedBigInteger.cpp
+ BigInt/UnsignedBigInteger.cpp
+ Checksum/Adler32.cpp
+ Checksum/CRC32.cpp
+ Cipher/AES.cpp
+ Hash/MD5.cpp
+ Hash/SHA1.cpp
+ Hash/SHA2.cpp
+ NumberTheory/ModularFunctions.cpp
+ PK/RSA.cpp
+)
+
+serenity_lib(LibCrypto crypto)
+target_link_libraries(LibCrypto LibC)
diff --git a/Userland/Libraries/LibCrypto/Checksum/Adler32.cpp b/Userland/Libraries/LibCrypto/Checksum/Adler32.cpp
new file mode 100644
index 0000000000..6a7de8f0e4
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Checksum/Adler32.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Span.h>
+#include <AK/Types.h>
+#include <LibCrypto/Checksum/Adler32.h>
+
+namespace Crypto::Checksum {
+
+void Adler32::update(ReadonlyBytes data)
+{
+ for (size_t i = 0; i < data.size(); i++) {
+ m_state_a = (m_state_a + data.at(i)) % 65521;
+ m_state_b = (m_state_b + m_state_a) % 65521;
+ }
+};
+
+u32 Adler32::digest()
+{
+ return (m_state_b << 16) | m_state_a;
+}
+
+}
diff --git a/Userland/Libraries/LibCrypto/Checksum/Adler32.h b/Userland/Libraries/LibCrypto/Checksum/Adler32.h
new file mode 100644
index 0000000000..7b33ecae24
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Checksum/Adler32.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Span.h>
+#include <AK/Types.h>
+#include <LibCrypto/Checksum/ChecksumFunction.h>
+
+namespace Crypto::Checksum {
+
+class Adler32 : public ChecksumFunction<u32> {
+public:
+ Adler32() { }
+ Adler32(ReadonlyBytes data)
+ {
+ update(data);
+ }
+
+ Adler32(u32 initial_a, u32 initial_b, ReadonlyBytes data)
+ : m_state_a(initial_a)
+ , m_state_b(initial_b)
+ {
+ update(data);
+ }
+
+ void update(ReadonlyBytes data);
+ u32 digest();
+
+private:
+ u32 m_state_a { 1 };
+ u32 m_state_b { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibCrypto/Checksum/CRC32.cpp b/Userland/Libraries/LibCrypto/Checksum/CRC32.cpp
new file mode 100644
index 0000000000..2fe0689a21
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Checksum/CRC32.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Span.h>
+#include <AK/Types.h>
+#include <LibCrypto/Checksum/CRC32.h>
+
+namespace Crypto::Checksum {
+
+void CRC32::update(ReadonlyBytes data)
+{
+ for (size_t i = 0; i < data.size(); i++) {
+ m_state = table[(m_state ^ data.at(i)) & 0xFF] ^ (m_state >> 8);
+ }
+};
+
+u32 CRC32::digest()
+{
+ return ~m_state;
+}
+
+}
diff --git a/Userland/Libraries/LibCrypto/Checksum/CRC32.h b/Userland/Libraries/LibCrypto/Checksum/CRC32.h
new file mode 100644
index 0000000000..df6d17ec16
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Checksum/CRC32.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/LogStream.h>
+#include <AK/Span.h>
+#include <AK/Types.h>
+#include <LibCrypto/Checksum/ChecksumFunction.h>
+
+namespace Crypto::Checksum {
+
+struct Table {
+ u32 data[256];
+
+ constexpr Table()
+ : data()
+ {
+ for (auto i = 0; i < 256; i++) {
+ u32 value = i;
+
+ for (auto j = 0; j < 8; j++) {
+ if (value & 1) {
+ value = 0xEDB88320 ^ (value >> 1);
+ } else {
+ value = value >> 1;
+ }
+ }
+
+ data[i] = value;
+ }
+ }
+
+ constexpr u32 operator[](int index) const
+ {
+ return data[index];
+ }
+};
+
+constexpr static auto table = Table();
+
+class CRC32 : public ChecksumFunction<u32> {
+public:
+ CRC32() { }
+ CRC32(ReadonlyBytes data)
+ {
+ update(data);
+ }
+
+ CRC32(u32 initial_state, ReadonlyBytes data)
+ : m_state(initial_state)
+ {
+ update(data);
+ }
+
+ void update(ReadonlyBytes data);
+ u32 digest();
+
+private:
+ u32 m_state { ~0u };
+};
+
+}
diff --git a/Userland/Libraries/LibCrypto/Checksum/ChecksumFunction.h b/Userland/Libraries/LibCrypto/Checksum/ChecksumFunction.h
new file mode 100644
index 0000000000..c62ff1d4d9
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Checksum/ChecksumFunction.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Span.h>
+
+namespace Crypto::Checksum {
+
+template<typename ChecksumType>
+class ChecksumFunction {
+public:
+ virtual void update(ReadonlyBytes data) = 0;
+ virtual ChecksumType digest() = 0;
+};
+
+}
diff --git a/Userland/Libraries/LibCrypto/Cipher/AES.cpp b/Userland/Libraries/LibCrypto/Cipher/AES.cpp
new file mode 100644
index 0000000000..1ce0e12240
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Cipher/AES.cpp
@@ -0,0 +1,429 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibCrypto/Cipher/AES.h>
+
+namespace Crypto {
+namespace Cipher {
+
+template<typename T>
+constexpr u32 get_key(T pt)
+{
+ return ((u32)(pt)[0] << 24) ^ ((u32)(pt)[1] << 16) ^ ((u32)(pt)[2] << 8) ^ ((u32)(pt)[3]);
+}
+
+constexpr void swap_keys(u32* keys, size_t i, size_t j)
+{
+ u32 temp = keys[i];
+ keys[i] = keys[j];
+ keys[j] = temp;
+}
+
+String AESCipherBlock::to_string() const
+{
+ StringBuilder builder;
+ for (size_t i = 0; i < BlockSizeInBits / 8; ++i)
+ builder.appendf("%02x", m_data[i]);
+ return builder.build();
+}
+
+String AESCipherKey::to_string() const
+{
+ StringBuilder builder;
+ for (size_t i = 0; i < (rounds() + 1) * 4; ++i)
+ builder.appendf("%02x", m_rd_keys[i]);
+ return builder.build();
+}
+
+void AESCipherKey::expand_encrypt_key(ReadonlyBytes user_key, size_t bits)
+{
+ u32* round_key;
+ u32 temp;
+ size_t i { 0 };
+
+ ASSERT(!user_key.is_null());
+ ASSERT(is_valid_key_size(bits));
+ ASSERT(user_key.size() == bits / 8);
+
+ round_key = round_keys();
+
+ if (bits == 128) {
+ m_rounds = 10;
+ } else if (bits == 192) {
+ m_rounds = 12;
+ } else {
+ m_rounds = 14;
+ }
+
+ round_key[0] = get_key(user_key.data());
+ round_key[1] = get_key(user_key.data() + 4);
+ round_key[2] = get_key(user_key.data() + 8);
+ round_key[3] = get_key(user_key.data() + 12);
+ if (bits == 128) {
+ for (;;) {
+ temp = round_key[3];
+ // clang-format off
+ round_key[4] = round_key[0] ^
+ (AESTables::Encode2[(temp >> 16) & 0xff] & 0xff000000) ^
+ (AESTables::Encode3[(temp >> 8) & 0xff] & 0x00ff0000) ^
+ (AESTables::Encode0[(temp ) & 0xff] & 0x0000ff00) ^
+ (AESTables::Encode1[(temp >> 24) ] & 0x000000ff) ^ AESTables::RCON[i];
+ // clang-format on
+ round_key[5] = round_key[1] ^ round_key[4];
+ round_key[6] = round_key[2] ^ round_key[5];
+ round_key[7] = round_key[3] ^ round_key[6];
+ ++i;
+ if (i == 10)
+ break;
+ round_key += 4;
+ }
+ return;
+ }
+
+ round_key[4] = get_key(user_key.data() + 16);
+ round_key[5] = get_key(user_key.data() + 20);
+ if (bits == 192) {
+ for (;;) {
+ temp = round_key[5];
+ // clang-format off
+ round_key[6] = round_key[0] ^
+ (AESTables::Encode2[(temp >> 16) & 0xff] & 0xff000000) ^
+ (AESTables::Encode3[(temp >> 8) & 0xff] & 0x00ff0000) ^
+ (AESTables::Encode0[(temp ) & 0xff] & 0x0000ff00) ^
+ (AESTables::Encode1[(temp >> 24) ] & 0x000000ff) ^ AESTables::RCON[i];
+ // clang-format on
+ round_key[7] = round_key[1] ^ round_key[6];
+ round_key[8] = round_key[2] ^ round_key[7];
+ round_key[9] = round_key[3] ^ round_key[8];
+
+ ++i;
+ if (i == 8)
+ break;
+
+ round_key[10] = round_key[4] ^ round_key[9];
+ round_key[11] = round_key[5] ^ round_key[10];
+
+ round_key += 6;
+ }
+ return;
+ }
+
+ round_key[6] = get_key(user_key.data() + 24);
+ round_key[7] = get_key(user_key.data() + 28);
+ if (true) { // bits == 256
+ for (;;) {
+ temp = round_key[7];
+ // clang-format off
+ round_key[8] = round_key[0] ^
+ (AESTables::Encode2[(temp >> 16) & 0xff] & 0xff000000) ^
+ (AESTables::Encode3[(temp >> 8) & 0xff] & 0x00ff0000) ^
+ (AESTables::Encode0[(temp ) & 0xff] & 0x0000ff00) ^
+ (AESTables::Encode1[(temp >> 24) ] & 0x000000ff) ^ AESTables::RCON[i];
+ // clang-format on
+ round_key[9] = round_key[1] ^ round_key[8];
+ round_key[10] = round_key[2] ^ round_key[9];
+ round_key[11] = round_key[3] ^ round_key[10];
+
+ ++i;
+ if (i == 7)
+ break;
+
+ temp = round_key[11];
+ // clang-format off
+ round_key[12] = round_key[4] ^
+ (AESTables::Encode2[(temp >> 24) ] & 0xff000000) ^
+ (AESTables::Encode3[(temp >> 16) & 0xff] & 0x00ff0000) ^
+ (AESTables::Encode0[(temp >> 8) & 0xff] & 0x0000ff00) ^
+ (AESTables::Encode1[(temp ) & 0xff] & 0x000000ff) ;
+ // clang-format on
+ round_key[13] = round_key[5] ^ round_key[12];
+ round_key[14] = round_key[6] ^ round_key[13];
+ round_key[15] = round_key[7] ^ round_key[14];
+
+ round_key += 8;
+ }
+ return;
+ }
+}
+
+void AESCipherKey::expand_decrypt_key(ReadonlyBytes user_key, size_t bits)
+{
+ u32* round_key;
+
+ expand_encrypt_key(user_key, bits);
+
+ round_key = round_keys();
+
+ // reorder round keys
+ for (size_t i = 0, j = 4 * rounds(); i < j; i += 4, j -= 4) {
+ swap_keys(round_key, i, j);
+ swap_keys(round_key, i + 1, j + 1);
+ swap_keys(round_key, i + 2, j + 2);
+ swap_keys(round_key, i + 3, j + 3);
+ }
+
+ // apply inverse mix-column to middle rounds
+ for (size_t i = 1; i < rounds(); ++i) {
+ round_key += 4;
+ // clang-format off
+ round_key[0] =
+ AESTables::Decode0[AESTables::Encode1[(round_key[0] >> 24) ] & 0xff] ^
+ AESTables::Decode1[AESTables::Encode1[(round_key[0] >> 16) & 0xff] & 0xff] ^
+ AESTables::Decode2[AESTables::Encode1[(round_key[0] >> 8) & 0xff] & 0xff] ^
+ AESTables::Decode3[AESTables::Encode1[(round_key[0] ) & 0xff] & 0xff] ;
+ round_key[1] =
+ AESTables::Decode0[AESTables::Encode1[(round_key[1] >> 24) ] & 0xff] ^
+ AESTables::Decode1[AESTables::Encode1[(round_key[1] >> 16) & 0xff] & 0xff] ^
+ AESTables::Decode2[AESTables::Encode1[(round_key[1] >> 8) & 0xff] & 0xff] ^
+ AESTables::Decode3[AESTables::Encode1[(round_key[1] ) & 0xff] & 0xff] ;
+ round_key[2] =
+ AESTables::Decode0[AESTables::Encode1[(round_key[2] >> 24) ] & 0xff] ^
+ AESTables::Decode1[AESTables::Encode1[(round_key[2] >> 16) & 0xff] & 0xff] ^
+ AESTables::Decode2[AESTables::Encode1[(round_key[2] >> 8) & 0xff] & 0xff] ^
+ AESTables::Decode3[AESTables::Encode1[(round_key[2] ) & 0xff] & 0xff] ;
+ round_key[3] =
+ AESTables::Decode0[AESTables::Encode1[(round_key[3] >> 24) ] & 0xff] ^
+ AESTables::Decode1[AESTables::Encode1[(round_key[3] >> 16) & 0xff] & 0xff] ^
+ AESTables::Decode2[AESTables::Encode1[(round_key[3] >> 8) & 0xff] & 0xff] ^
+ AESTables::Decode3[AESTables::Encode1[(round_key[3] ) & 0xff] & 0xff] ;
+ // clang-format on
+ }
+}
+
+void AESCipher::encrypt_block(const AESCipherBlock& in, AESCipherBlock& out)
+{
+ u32 s0, s1, s2, s3, t0, t1, t2, t3;
+ size_t r { 0 };
+
+ const auto& dec_key = key();
+ const auto* round_keys = dec_key.round_keys();
+
+ s0 = get_key(in.bytes().offset_pointer(0)) ^ round_keys[0];
+ s1 = get_key(in.bytes().offset_pointer(4)) ^ round_keys[1];
+ s2 = get_key(in.bytes().offset_pointer(8)) ^ round_keys[2];
+ s3 = get_key(in.bytes().offset_pointer(12)) ^ round_keys[3];
+
+ r = dec_key.rounds() >> 1;
+
+ // apply the first |r - 1| rounds
+ auto i { 0 };
+ for (;;) {
+ ++i;
+ // clang-format off
+ t0 = AESTables::Encode0[(s0 >> 24) ] ^
+ AESTables::Encode1[(s1 >> 16) & 0xff] ^
+ AESTables::Encode2[(s2 >> 8) & 0xff] ^
+ AESTables::Encode3[(s3 ) & 0xff] ^ round_keys[4];
+ t1 = AESTables::Encode0[(s1 >> 24) ] ^
+ AESTables::Encode1[(s2 >> 16) & 0xff] ^
+ AESTables::Encode2[(s3 >> 8) & 0xff] ^
+ AESTables::Encode3[(s0 ) & 0xff] ^ round_keys[5];
+ t2 = AESTables::Encode0[(s2 >> 24) ] ^
+ AESTables::Encode1[(s3 >> 16) & 0xff] ^
+ AESTables::Encode2[(s0 >> 8) & 0xff] ^
+ AESTables::Encode3[(s1 ) & 0xff] ^ round_keys[6];
+ t3 = AESTables::Encode0[(s3 >> 24) ] ^
+ AESTables::Encode1[(s0 >> 16) & 0xff] ^
+ AESTables::Encode2[(s1 >> 8) & 0xff] ^
+ AESTables::Encode3[(s2 ) & 0xff] ^ round_keys[7];
+ // clang-format on
+
+ round_keys += 8;
+ --r;
+ ++i;
+ if (r == 0)
+ break;
+
+ // clang-format off
+ s0 = AESTables::Encode0[(t0 >> 24) ] ^
+ AESTables::Encode1[(t1 >> 16) & 0xff] ^
+ AESTables::Encode2[(t2 >> 8) & 0xff] ^
+ AESTables::Encode3[(t3 ) & 0xff] ^ round_keys[0];
+ s1 = AESTables::Encode0[(t1 >> 24) ] ^
+ AESTables::Encode1[(t2 >> 16) & 0xff] ^
+ AESTables::Encode2[(t3 >> 8) & 0xff] ^
+ AESTables::Encode3[(t0 ) & 0xff] ^ round_keys[1];
+ s2 = AESTables::Encode0[(t2 >> 24) ] ^
+ AESTables::Encode1[(t3 >> 16) & 0xff] ^
+ AESTables::Encode2[(t0 >> 8) & 0xff] ^
+ AESTables::Encode3[(t1 ) & 0xff] ^ round_keys[2];
+ s3 = AESTables::Encode0[(t3 >> 24) ] ^
+ AESTables::Encode1[(t0 >> 16) & 0xff] ^
+ AESTables::Encode2[(t1 >> 8) & 0xff] ^
+ AESTables::Encode3[(t2 ) & 0xff] ^ round_keys[3];
+ // clang-format on
+ }
+
+ // apply the last round and put the encrypted data into out
+ // clang-format off
+ s0 = (AESTables::Encode2[(t0 >> 24) ] & 0xff000000) ^
+ (AESTables::Encode3[(t1 >> 16) & 0xff] & 0x00ff0000) ^
+ (AESTables::Encode0[(t2 >> 8) & 0xff] & 0x0000ff00) ^
+ (AESTables::Encode1[(t3 ) & 0xff] & 0x000000ff) ^ round_keys[0];
+ out.put(0, s0);
+
+ s1 = (AESTables::Encode2[(t1 >> 24) ] & 0xff000000) ^
+ (AESTables::Encode3[(t2 >> 16) & 0xff] & 0x00ff0000) ^
+ (AESTables::Encode0[(t3 >> 8) & 0xff] & 0x0000ff00) ^
+ (AESTables::Encode1[(t0 ) & 0xff] & 0x000000ff) ^ round_keys[1];
+ out.put(4, s1);
+
+ s2 = (AESTables::Encode2[(t2 >> 24) ] & 0xff000000) ^
+ (AESTables::Encode3[(t3 >> 16) & 0xff] & 0x00ff0000) ^
+ (AESTables::Encode0[(t0 >> 8) & 0xff] & 0x0000ff00) ^
+ (AESTables::Encode1[(t1 ) & 0xff] & 0x000000ff) ^ round_keys[2];
+ out.put(8, s2);
+
+ s3 = (AESTables::Encode2[(t3 >> 24) ] & 0xff000000) ^
+ (AESTables::Encode3[(t0 >> 16) & 0xff] & 0x00ff0000) ^
+ (AESTables::Encode0[(t1 >> 8) & 0xff] & 0x0000ff00) ^
+ (AESTables::Encode1[(t2 ) & 0xff] & 0x000000ff) ^ round_keys[3];
+ out.put(12, s3);
+ // clang-format on
+}
+
+void AESCipher::decrypt_block(const AESCipherBlock& in, AESCipherBlock& out)
+{
+
+ u32 s0, s1, s2, s3, t0, t1, t2, t3;
+ size_t r { 0 };
+
+ const auto& dec_key = key();
+ const auto* round_keys = dec_key.round_keys();
+
+ s0 = get_key(in.bytes().offset_pointer(0)) ^ round_keys[0];
+ s1 = get_key(in.bytes().offset_pointer(4)) ^ round_keys[1];
+ s2 = get_key(in.bytes().offset_pointer(8)) ^ round_keys[2];
+ s3 = get_key(in.bytes().offset_pointer(12)) ^ round_keys[3];
+
+ r = dec_key.rounds() >> 1;
+
+ // apply the first |r - 1| rounds
+ for (;;) {
+ // clang-format off
+ t0 = AESTables::Decode0[(s0 >> 24) ] ^
+ AESTables::Decode1[(s3 >> 16) & 0xff] ^
+ AESTables::Decode2[(s2 >> 8) & 0xff] ^
+ AESTables::Decode3[(s1 ) & 0xff] ^ round_keys[4];
+ t1 = AESTables::Decode0[(s1 >> 24) ] ^
+ AESTables::Decode1[(s0 >> 16) & 0xff] ^
+ AESTables::Decode2[(s3 >> 8) & 0xff] ^
+ AESTables::Decode3[(s2 ) & 0xff] ^ round_keys[5];
+ t2 = AESTables::Decode0[(s2 >> 24) ] ^
+ AESTables::Decode1[(s1 >> 16) & 0xff] ^
+ AESTables::Decode2[(s0 >> 8) & 0xff] ^
+ AESTables::Decode3[(s3 ) & 0xff] ^ round_keys[6];
+ t3 = AESTables::Decode0[(s3 >> 24) ] ^
+ AESTables::Decode1[(s2 >> 16) & 0xff] ^
+ AESTables::Decode2[(s1 >> 8) & 0xff] ^
+ AESTables::Decode3[(s0 ) & 0xff] ^ round_keys[7];
+ // clang-format on
+
+ round_keys += 8;
+ --r;
+ if (r == 0)
+ break;
+
+ // clang-format off
+ s0 = AESTables::Decode0[(t0 >> 24) ] ^
+ AESTables::Decode1[(t3 >> 16) & 0xff] ^
+ AESTables::Decode2[(t2 >> 8) & 0xff] ^
+ AESTables::Decode3[(t1 ) & 0xff] ^ round_keys[0];
+ s1 = AESTables::Decode0[(t1 >> 24) ] ^
+ AESTables::Decode1[(t0 >> 16) & 0xff] ^
+ AESTables::Decode2[(t3 >> 8) & 0xff] ^
+ AESTables::Decode3[(t2 ) & 0xff] ^ round_keys[1];
+ s2 = AESTables::Decode0[(t2 >> 24) ] ^
+ AESTables::Decode1[(t1 >> 16) & 0xff] ^
+ AESTables::Decode2[(t0 >> 8) & 0xff] ^
+ AESTables::Decode3[(t3 ) & 0xff] ^ round_keys[2];
+ s3 = AESTables::Decode0[(t3 >> 24) ] ^
+ AESTables::Decode1[(t2 >> 16) & 0xff] ^
+ AESTables::Decode2[(t1 >> 8) & 0xff] ^
+ AESTables::Decode3[(t0 ) & 0xff] ^ round_keys[3];
+ // clang-format on
+ }
+
+ // apply the last round and put the decrypted data into out
+ // clang-format off
+ s0 = ((u32)AESTables::Decode4[(t0 >> 24) ] << 24) ^
+ ((u32)AESTables::Decode4[(t3 >> 16) & 0xff] << 16) ^
+ ((u32)AESTables::Decode4[(t2 >> 8) & 0xff] << 8) ^
+ ((u32)AESTables::Decode4[(t1 ) & 0xff] ) ^ round_keys[0];
+ out.put(0, s0);
+
+ s1 = ((u32)AESTables::Decode4[(t1 >> 24) ] << 24) ^
+ ((u32)AESTables::Decode4[(t0 >> 16) & 0xff] << 16) ^
+ ((u32)AESTables::Decode4[(t3 >> 8) & 0xff] << 8) ^
+ ((u32)AESTables::Decode4[(t2 ) & 0xff] ) ^ round_keys[1];
+ out.put(4, s1);
+
+ s2 = ((u32)AESTables::Decode4[(t2 >> 24) ] << 24) ^
+ ((u32)AESTables::Decode4[(t1 >> 16) & 0xff] << 16) ^
+ ((u32)AESTables::Decode4[(t0 >> 8) & 0xff] << 8) ^
+ ((u32)AESTables::Decode4[(t3 ) & 0xff] ) ^ round_keys[2];
+ out.put(8, s2);
+
+ s3 = ((u32)AESTables::Decode4[(t3 >> 24) ] << 24) ^
+ ((u32)AESTables::Decode4[(t2 >> 16) & 0xff] << 16) ^
+ ((u32)AESTables::Decode4[(t1 >> 8) & 0xff] << 8) ^
+ ((u32)AESTables::Decode4[(t0 ) & 0xff] ) ^ round_keys[3];
+ out.put(12, s3);
+ // clang-format on
+}
+
+void AESCipherBlock::overwrite(ReadonlyBytes bytes)
+{
+ auto data = bytes.data();
+ auto length = bytes.size();
+
+ ASSERT(length <= this->data_size());
+ this->bytes().overwrite(0, data, length);
+ if (length < this->data_size()) {
+ switch (padding_mode()) {
+ case PaddingMode::Null:
+ // fill with zeros
+ __builtin_memset(m_data + length, 0, this->data_size() - length);
+ break;
+ case PaddingMode::CMS:
+ // fill with the length of the padding bytes
+ __builtin_memset(m_data + length, this->data_size() - length, this->data_size() - length);
+ break;
+ case PaddingMode::RFC5246:
+ // fill with the length of the padding bytes minus one
+ __builtin_memset(m_data + length, this->data_size() - length - 1, this->data_size() - length);
+ break;
+ default:
+ // FIXME: We should handle the rest of the common padding modes
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ }
+}
+}
+
+}
diff --git a/Userland/Libraries/LibCrypto/Cipher/AES.h b/Userland/Libraries/LibCrypto/Cipher/AES.h
new file mode 100644
index 0000000000..7ce273d66d
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Cipher/AES.h
@@ -0,0 +1,2481 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <LibCrypto/Cipher/Cipher.h>
+#include <LibCrypto/Cipher/Mode/CBC.h>
+#include <LibCrypto/Cipher/Mode/CTR.h>
+#include <LibCrypto/Cipher/Mode/GCM.h>
+
+namespace Crypto {
+namespace Cipher {
+struct AESCipherBlock : public CipherBlock {
+public:
+ static constexpr size_t BlockSizeInBits = 128;
+
+ explicit AESCipherBlock(PaddingMode mode = PaddingMode::CMS)
+ : CipherBlock(mode)
+ {
+ }
+ AESCipherBlock(const u8* data, size_t length, PaddingMode mode = PaddingMode::CMS)
+ : AESCipherBlock(mode)
+ {
+ CipherBlock::overwrite(data, length);
+ }
+
+ static size_t block_size() { return BlockSizeInBits / 8; };
+
+ virtual ReadonlyBytes bytes() const override { return ReadonlyBytes { m_data, sizeof(m_data) }; }
+ virtual Bytes bytes() override { return Bytes { m_data, sizeof(m_data) }; }
+
+ virtual void overwrite(ReadonlyBytes) override;
+ virtual void overwrite(const u8* data, size_t size) override { overwrite({ data, size }); }
+
+ virtual void apply_initialization_vector(const u8* ivec) override
+ {
+ for (size_t i = 0; i < block_size(); ++i)
+ m_data[i] ^= ivec[i];
+ }
+
+ String to_string() const;
+
+private:
+ size_t data_size() const { return sizeof(m_data); }
+
+ u8 m_data[BlockSizeInBits / 8] {};
+};
+
+struct AESCipherKey : public CipherKey {
+ virtual ReadonlyBytes bytes() const override { return ReadonlyBytes { m_rd_keys, sizeof(m_rd_keys) }; };
+ virtual void expand_encrypt_key(ReadonlyBytes user_key, size_t bits) override;
+ virtual void expand_decrypt_key(ReadonlyBytes user_key, size_t bits) override;
+ static bool is_valid_key_size(size_t bits) { return bits == 128 || bits == 192 || bits == 256; };
+ String to_string() const;
+ const u32* round_keys() const
+ {
+ return (const u32*)m_rd_keys;
+ }
+
+ AESCipherKey(ReadonlyBytes user_key, size_t key_bits, Intent intent)
+ : m_bits(key_bits)
+ {
+ if (intent == Intent::Encryption)
+ expand_encrypt_key(user_key, key_bits);
+ else
+ expand_decrypt_key(user_key, key_bits);
+ }
+
+ virtual ~AESCipherKey() override { }
+
+ size_t rounds() const { return m_rounds; }
+ size_t length() const { return m_bits / 8; }
+
+protected:
+ u32* round_keys()
+ {
+ return (u32*)m_rd_keys;
+ }
+
+private:
+ static constexpr size_t MAX_ROUND_COUNT = 14;
+ u32 m_rd_keys[(MAX_ROUND_COUNT + 1) * 4] { 0 };
+ size_t m_rounds;
+ size_t m_bits;
+};
+
+class AESCipher final : public Cipher<AESCipherKey, AESCipherBlock> {
+public:
+ using CBCMode = CBC<AESCipher>;
+ using CTRMode = CTR<AESCipher>;
+ using GCMMode = GCM<AESCipher>;
+
+ constexpr static size_t BlockSizeInBits = BlockType::BlockSizeInBits;
+
+ AESCipher(ReadonlyBytes user_key, size_t key_bits, Intent intent = Intent::Encryption, PaddingMode mode = PaddingMode::CMS)
+ : Cipher<AESCipherKey, AESCipherBlock>(mode)
+ , m_key(user_key, key_bits, intent)
+ {
+ }
+
+ virtual const AESCipherKey& key() const override { return m_key; };
+ virtual AESCipherKey& key() override { return m_key; };
+
+ virtual void encrypt_block(const BlockType& in, BlockType& out) override;
+ virtual void decrypt_block(const BlockType& in, BlockType& out) override;
+
+ virtual String class_name() const override { return "AES"; }
+
+protected:
+ AESCipherKey m_key;
+};
+
+namespace AESTables {
+// Encryption constant tables
+constexpr u32 Encode0[256] = {
+ 0xc66363a5U,
+ 0xf87c7c84U,
+ 0xee777799U,
+ 0xf67b7b8dU,
+ 0xfff2f20dU,
+ 0xd66b6bbdU,
+ 0xde6f6fb1U,
+ 0x91c5c554U,
+ 0x60303050U,
+ 0x02010103U,
+ 0xce6767a9U,
+ 0x562b2b7dU,
+ 0xe7fefe19U,
+ 0xb5d7d762U,
+ 0x4dababe6U,
+ 0xec76769aU,
+ 0x8fcaca45U,
+ 0x1f82829dU,
+ 0x89c9c940U,
+ 0xfa7d7d87U,
+ 0xeffafa15U,
+ 0xb25959ebU,
+ 0x8e4747c9U,
+ 0xfbf0f00bU,
+ 0x41adadecU,
+ 0xb3d4d467U,
+ 0x5fa2a2fdU,
+ 0x45afafeaU,
+ 0x239c9cbfU,
+ 0x53a4a4f7U,
+ 0xe4727296U,
+ 0x9bc0c05bU,
+ 0x75b7b7c2U,
+ 0xe1fdfd1cU,
+ 0x3d9393aeU,
+ 0x4c26266aU,
+ 0x6c36365aU,
+ 0x7e3f3f41U,
+ 0xf5f7f702U,
+ 0x83cccc4fU,
+ 0x6834345cU,
+ 0x51a5a5f4U,
+ 0xd1e5e534U,
+ 0xf9f1f108U,
+ 0xe2717193U,
+ 0xabd8d873U,
+ 0x62313153U,
+ 0x2a15153fU,
+ 0x0804040cU,
+ 0x95c7c752U,
+ 0x46232365U,
+ 0x9dc3c35eU,
+ 0x30181828U,
+ 0x379696a1U,
+ 0x0a05050fU,
+ 0x2f9a9ab5U,
+ 0x0e070709U,
+ 0x24121236U,
+ 0x1b80809bU,
+ 0xdfe2e23dU,
+ 0xcdebeb26U,
+ 0x4e272769U,
+ 0x7fb2b2cdU,
+ 0xea75759fU,
+ 0x1209091bU,
+ 0x1d83839eU,
+ 0x582c2c74U,
+ 0x341a1a2eU,
+ 0x361b1b2dU,
+ 0xdc6e6eb2U,
+ 0xb45a5aeeU,
+ 0x5ba0a0fbU,
+ 0xa45252f6U,
+ 0x763b3b4dU,
+ 0xb7d6d661U,
+ 0x7db3b3ceU,
+ 0x5229297bU,
+ 0xdde3e33eU,
+ 0x5e2f2f71U,
+ 0x13848497U,
+ 0xa65353f5U,
+ 0xb9d1d168U,
+ 0x00000000U,
+ 0xc1eded2cU,
+ 0x40202060U,
+ 0xe3fcfc1fU,
+ 0x79b1b1c8U,
+ 0xb65b5bedU,
+ 0xd46a6abeU,
+ 0x8dcbcb46U,
+ 0x67bebed9U,
+ 0x7239394bU,
+ 0x944a4adeU,
+ 0x984c4cd4U,
+ 0xb05858e8U,
+ 0x85cfcf4aU,
+ 0xbbd0d06bU,
+ 0xc5efef2aU,
+ 0x4faaaae5U,
+ 0xedfbfb16U,
+ 0x864343c5U,
+ 0x9a4d4dd7U,
+ 0x66333355U,
+ 0x11858594U,
+ 0x8a4545cfU,
+ 0xe9f9f910U,
+ 0x04020206U,
+ 0xfe7f7f81U,
+ 0xa05050f0U,
+ 0x783c3c44U,
+ 0x259f9fbaU,
+ 0x4ba8a8e3U,
+ 0xa25151f3U,
+ 0x5da3a3feU,
+ 0x804040c0U,
+ 0x058f8f8aU,
+ 0x3f9292adU,
+ 0x219d9dbcU,
+ 0x70383848U,
+ 0xf1f5f504U,
+ 0x63bcbcdfU,
+ 0x77b6b6c1U,
+ 0xafdada75U,
+ 0x42212163U,
+ 0x20101030U,
+ 0xe5ffff1aU,
+ 0xfdf3f30eU,
+ 0xbfd2d26dU,
+ 0x81cdcd4cU,
+ 0x180c0c14U,
+ 0x26131335U,
+ 0xc3ecec2fU,
+ 0xbe5f5fe1U,
+ 0x359797a2U,
+ 0x884444ccU,
+ 0x2e171739U,
+ 0x93c4c457U,
+ 0x55a7a7f2U,
+ 0xfc7e7e82U,
+ 0x7a3d3d47U,
+ 0xc86464acU,
+ 0xba5d5de7U,
+ 0x3219192bU,
+ 0xe6737395U,
+ 0xc06060a0U,
+ 0x19818198U,
+ 0x9e4f4fd1U,
+ 0xa3dcdc7fU,
+ 0x44222266U,
+ 0x542a2a7eU,
+ 0x3b9090abU,
+ 0x0b888883U,
+ 0x8c4646caU,
+ 0xc7eeee29U,
+ 0x6bb8b8d3U,
+ 0x2814143cU,
+ 0xa7dede79U,
+ 0xbc5e5ee2U,
+ 0x160b0b1dU,
+ 0xaddbdb76U,
+ 0xdbe0e03bU,
+ 0x64323256U,
+ 0x743a3a4eU,
+ 0x140a0a1eU,
+ 0x924949dbU,
+ 0x0c06060aU,
+ 0x4824246cU,
+ 0xb85c5ce4U,
+ 0x9fc2c25dU,
+ 0xbdd3d36eU,
+ 0x43acacefU,
+ 0xc46262a6U,
+ 0x399191a8U,
+ 0x319595a4U,
+ 0xd3e4e437U,
+ 0xf279798bU,
+ 0xd5e7e732U,
+ 0x8bc8c843U,
+ 0x6e373759U,
+ 0xda6d6db7U,
+ 0x018d8d8cU,
+ 0xb1d5d564U,
+ 0x9c4e4ed2U,
+ 0x49a9a9e0U,
+ 0xd86c6cb4U,
+ 0xac5656faU,
+ 0xf3f4f407U,
+ 0xcfeaea25U,
+ 0xca6565afU,
+ 0xf47a7a8eU,
+ 0x47aeaee9U,
+ 0x10080818U,
+ 0x6fbabad5U,
+ 0xf0787888U,
+ 0x4a25256fU,
+ 0x5c2e2e72U,
+ 0x381c1c24U,
+ 0x57a6a6f1U,
+ 0x73b4b4c7U,
+ 0x97c6c651U,
+ 0xcbe8e823U,
+ 0xa1dddd7cU,
+ 0xe874749cU,
+ 0x3e1f1f21U,
+ 0x964b4bddU,
+ 0x61bdbddcU,
+ 0x0d8b8b86U,
+ 0x0f8a8a85U,
+ 0xe0707090U,
+ 0x7c3e3e42U,
+ 0x71b5b5c4U,
+ 0xcc6666aaU,
+ 0x904848d8U,
+ 0x06030305U,
+ 0xf7f6f601U,
+ 0x1c0e0e12U,
+ 0xc26161a3U,
+ 0x6a35355fU,
+ 0xae5757f9U,
+ 0x69b9b9d0U,
+ 0x17868691U,
+ 0x99c1c158U,
+ 0x3a1d1d27U,
+ 0x279e9eb9U,
+ 0xd9e1e138U,
+ 0xebf8f813U,
+ 0x2b9898b3U,
+ 0x22111133U,
+ 0xd26969bbU,
+ 0xa9d9d970U,
+ 0x078e8e89U,
+ 0x339494a7U,
+ 0x2d9b9bb6U,
+ 0x3c1e1e22U,
+ 0x15878792U,
+ 0xc9e9e920U,
+ 0x87cece49U,
+ 0xaa5555ffU,
+ 0x50282878U,
+ 0xa5dfdf7aU,
+ 0x038c8c8fU,
+ 0x59a1a1f8U,
+ 0x09898980U,
+ 0x1a0d0d17U,
+ 0x65bfbfdaU,
+ 0xd7e6e631U,
+ 0x844242c6U,
+ 0xd06868b8U,
+ 0x824141c3U,
+ 0x299999b0U,
+ 0x5a2d2d77U,
+ 0x1e0f0f11U,
+ 0x7bb0b0cbU,
+ 0xa85454fcU,
+ 0x6dbbbbd6U,
+ 0x2c16163aU,
+};
+constexpr u32 Encode1[256] = {
+ 0xa5c66363U,
+ 0x84f87c7cU,
+ 0x99ee7777U,
+ 0x8df67b7bU,
+ 0x0dfff2f2U,
+ 0xbdd66b6bU,
+ 0xb1de6f6fU,
+ 0x5491c5c5U,
+ 0x50603030U,
+ 0x03020101U,
+ 0xa9ce6767U,
+ 0x7d562b2bU,
+ 0x19e7fefeU,
+ 0x62b5d7d7U,
+ 0xe64dababU,
+ 0x9aec7676U,
+ 0x458fcacaU,
+ 0x9d1f8282U,
+ 0x4089c9c9U,
+ 0x87fa7d7dU,
+ 0x15effafaU,
+ 0xebb25959U,
+ 0xc98e4747U,
+ 0x0bfbf0f0U,
+ 0xec41adadU,
+ 0x67b3d4d4U,
+ 0xfd5fa2a2U,
+ 0xea45afafU,
+ 0xbf239c9cU,
+ 0xf753a4a4U,
+ 0x96e47272U,
+ 0x5b9bc0c0U,
+ 0xc275b7b7U,
+ 0x1ce1fdfdU,
+ 0xae3d9393U,
+ 0x6a4c2626U,
+ 0x5a6c3636U,
+ 0x417e3f3fU,
+ 0x02f5f7f7U,
+ 0x4f83ccccU,
+ 0x5c683434U,
+ 0xf451a5a5U,
+ 0x34d1e5e5U,
+ 0x08f9f1f1U,
+ 0x93e27171U,
+ 0x73abd8d8U,
+ 0x53623131U,
+ 0x3f2a1515U,
+ 0x0c080404U,
+ 0x5295c7c7U,
+ 0x65462323U,
+ 0x5e9dc3c3U,
+ 0x28301818U,
+ 0xa1379696U,
+ 0x0f0a0505U,
+ 0xb52f9a9aU,
+ 0x090e0707U,
+ 0x36241212U,
+ 0x9b1b8080U,
+ 0x3ddfe2e2U,
+ 0x26cdebebU,
+ 0x694e2727U,
+ 0xcd7fb2b2U,
+ 0x9fea7575U,
+ 0x1b120909U,
+ 0x9e1d8383U,
+ 0x74582c2cU,
+ 0x2e341a1aU,
+ 0x2d361b1bU,
+ 0xb2dc6e6eU,
+ 0xeeb45a5aU,
+ 0xfb5ba0a0U,
+ 0xf6a45252U,
+ 0x4d763b3bU,
+ 0x61b7d6d6U,
+ 0xce7db3b3U,
+ 0x7b522929U,
+ 0x3edde3e3U,
+ 0x715e2f2fU,
+ 0x97138484U,
+ 0xf5a65353U,
+ 0x68b9d1d1U,
+ 0x00000000U,
+ 0x2cc1ededU,
+ 0x60402020U,
+ 0x1fe3fcfcU,
+ 0xc879b1b1U,
+ 0xedb65b5bU,
+ 0xbed46a6aU,
+ 0x468dcbcbU,
+ 0xd967bebeU,
+ 0x4b723939U,
+ 0xde944a4aU,
+ 0xd4984c4cU,
+ 0xe8b05858U,
+ 0x4a85cfcfU,
+ 0x6bbbd0d0U,
+ 0x2ac5efefU,
+ 0xe54faaaaU,
+ 0x16edfbfbU,
+ 0xc5864343U,
+ 0xd79a4d4dU,
+ 0x55663333U,
+ 0x94118585U,
+ 0xcf8a4545U,
+ 0x10e9f9f9U,
+ 0x06040202U,
+ 0x81fe7f7fU,
+ 0xf0a05050U,
+ 0x44783c3cU,
+ 0xba259f9fU,
+ 0xe34ba8a8U,
+ 0xf3a25151U,
+ 0xfe5da3a3U,
+ 0xc0804040U,
+ 0x8a058f8fU,
+ 0xad3f9292U,
+ 0xbc219d9dU,
+ 0x48703838U,
+ 0x04f1f5f5U,
+ 0xdf63bcbcU,
+ 0xc177b6b6U,
+ 0x75afdadaU,
+ 0x63422121U,
+ 0x30201010U,
+ 0x1ae5ffffU,
+ 0x0efdf3f3U,
+ 0x6dbfd2d2U,
+ 0x4c81cdcdU,
+ 0x14180c0cU,
+ 0x35261313U,
+ 0x2fc3ececU,
+ 0xe1be5f5fU,
+ 0xa2359797U,
+ 0xcc884444U,
+ 0x392e1717U,
+ 0x5793c4c4U,
+ 0xf255a7a7U,
+ 0x82fc7e7eU,
+ 0x477a3d3dU,
+ 0xacc86464U,
+ 0xe7ba5d5dU,
+ 0x2b321919U,
+ 0x95e67373U,
+ 0xa0c06060U,
+ 0x98198181U,
+ 0xd19e4f4fU,
+ 0x7fa3dcdcU,
+ 0x66442222U,
+ 0x7e542a2aU,
+ 0xab3b9090U,
+ 0x830b8888U,
+ 0xca8c4646U,
+ 0x29c7eeeeU,
+ 0xd36bb8b8U,
+ 0x3c281414U,
+ 0x79a7dedeU,
+ 0xe2bc5e5eU,
+ 0x1d160b0bU,
+ 0x76addbdbU,
+ 0x3bdbe0e0U,
+ 0x56643232U,
+ 0x4e743a3aU,
+ 0x1e140a0aU,
+ 0xdb924949U,
+ 0x0a0c0606U,
+ 0x6c482424U,
+ 0xe4b85c5cU,
+ 0x5d9fc2c2U,
+ 0x6ebdd3d3U,
+ 0xef43acacU,
+ 0xa6c46262U,
+ 0xa8399191U,
+ 0xa4319595U,
+ 0x37d3e4e4U,
+ 0x8bf27979U,
+ 0x32d5e7e7U,
+ 0x438bc8c8U,
+ 0x596e3737U,
+ 0xb7da6d6dU,
+ 0x8c018d8dU,
+ 0x64b1d5d5U,
+ 0xd29c4e4eU,
+ 0xe049a9a9U,
+ 0xb4d86c6cU,
+ 0xfaac5656U,
+ 0x07f3f4f4U,
+ 0x25cfeaeaU,
+ 0xafca6565U,
+ 0x8ef47a7aU,
+ 0xe947aeaeU,
+ 0x18100808U,
+ 0xd56fbabaU,
+ 0x88f07878U,
+ 0x6f4a2525U,
+ 0x725c2e2eU,
+ 0x24381c1cU,
+ 0xf157a6a6U,
+ 0xc773b4b4U,
+ 0x5197c6c6U,
+ 0x23cbe8e8U,
+ 0x7ca1ddddU,
+ 0x9ce87474U,
+ 0x213e1f1fU,
+ 0xdd964b4bU,
+ 0xdc61bdbdU,
+ 0x860d8b8bU,
+ 0x850f8a8aU,
+ 0x90e07070U,
+ 0x427c3e3eU,
+ 0xc471b5b5U,
+ 0xaacc6666U,
+ 0xd8904848U,
+ 0x05060303U,
+ 0x01f7f6f6U,
+ 0x121c0e0eU,
+ 0xa3c26161U,
+ 0x5f6a3535U,
+ 0xf9ae5757U,
+ 0xd069b9b9U,
+ 0x91178686U,
+ 0x5899c1c1U,
+ 0x273a1d1dU,
+ 0xb9279e9eU,
+ 0x38d9e1e1U,
+ 0x13ebf8f8U,
+ 0xb32b9898U,
+ 0x33221111U,
+ 0xbbd26969U,
+ 0x70a9d9d9U,
+ 0x89078e8eU,
+ 0xa7339494U,
+ 0xb62d9b9bU,
+ 0x223c1e1eU,
+ 0x92158787U,
+ 0x20c9e9e9U,
+ 0x4987ceceU,
+ 0xffaa5555U,
+ 0x78502828U,
+ 0x7aa5dfdfU,
+ 0x8f038c8cU,
+ 0xf859a1a1U,
+ 0x80098989U,
+ 0x171a0d0dU,
+ 0xda65bfbfU,
+ 0x31d7e6e6U,
+ 0xc6844242U,
+ 0xb8d06868U,
+ 0xc3824141U,
+ 0xb0299999U,
+ 0x775a2d2dU,
+ 0x111e0f0fU,
+ 0xcb7bb0b0U,
+ 0xfca85454U,
+ 0xd66dbbbbU,
+ 0x3a2c1616U,
+};
+constexpr u32 Encode2[256] = {
+ 0x63a5c663U,
+ 0x7c84f87cU,
+ 0x7799ee77U,
+ 0x7b8df67bU,
+ 0xf20dfff2U,
+ 0x6bbdd66bU,
+ 0x6fb1de6fU,
+ 0xc55491c5U,
+ 0x30506030U,
+ 0x01030201U,
+ 0x67a9ce67U,
+ 0x2b7d562bU,
+ 0xfe19e7feU,
+ 0xd762b5d7U,
+ 0xabe64dabU,
+ 0x769aec76U,
+ 0xca458fcaU,
+ 0x829d1f82U,
+ 0xc94089c9U,
+ 0x7d87fa7dU,
+ 0xfa15effaU,
+ 0x59ebb259U,
+ 0x47c98e47U,
+ 0xf00bfbf0U,
+ 0xadec41adU,
+ 0xd467b3d4U,
+ 0xa2fd5fa2U,
+ 0xafea45afU,
+ 0x9cbf239cU,
+ 0xa4f753a4U,
+ 0x7296e472U,
+ 0xc05b9bc0U,
+ 0xb7c275b7U,
+ 0xfd1ce1fdU,
+ 0x93ae3d93U,
+ 0x266a4c26U,
+ 0x365a6c36U,
+ 0x3f417e3fU,
+ 0xf702f5f7U,
+ 0xcc4f83ccU,
+ 0x345c6834U,
+ 0xa5f451a5U,
+ 0xe534d1e5U,
+ 0xf108f9f1U,
+ 0x7193e271U,
+ 0xd873abd8U,
+ 0x31536231U,
+ 0x153f2a15U,
+ 0x040c0804U,
+ 0xc75295c7U,
+ 0x23654623U,
+ 0xc35e9dc3U,
+ 0x18283018U,
+ 0x96a13796U,
+ 0x050f0a05U,
+ 0x9ab52f9aU,
+ 0x07090e07U,
+ 0x12362412U,
+ 0x809b1b80U,
+ 0xe23ddfe2U,
+ 0xeb26cdebU,
+ 0x27694e27U,
+ 0xb2cd7fb2U,
+ 0x759fea75U,
+ 0x091b1209U,
+ 0x839e1d83U,
+ 0x2c74582cU,
+ 0x1a2e341aU,
+ 0x1b2d361bU,
+ 0x6eb2dc6eU,
+ 0x5aeeb45aU,
+ 0xa0fb5ba0U,
+ 0x52f6a452U,
+ 0x3b4d763bU,
+ 0xd661b7d6U,
+ 0xb3ce7db3U,
+ 0x297b5229U,
+ 0xe33edde3U,
+ 0x2f715e2fU,
+ 0x84971384U,
+ 0x53f5a653U,
+ 0xd168b9d1U,
+ 0x00000000U,
+ 0xed2cc1edU,
+ 0x20604020U,
+ 0xfc1fe3fcU,
+ 0xb1c879b1U,
+ 0x5bedb65bU,
+ 0x6abed46aU,
+ 0xcb468dcbU,
+ 0xbed967beU,
+ 0x394b7239U,
+ 0x4ade944aU,
+ 0x4cd4984cU,
+ 0x58e8b058U,
+ 0xcf4a85cfU,
+ 0xd06bbbd0U,
+ 0xef2ac5efU,
+ 0xaae54faaU,
+ 0xfb16edfbU,
+ 0x43c58643U,
+ 0x4dd79a4dU,
+ 0x33556633U,
+ 0x85941185U,
+ 0x45cf8a45U,
+ 0xf910e9f9U,
+ 0x02060402U,
+ 0x7f81fe7fU,
+ 0x50f0a050U,
+ 0x3c44783cU,
+ 0x9fba259fU,
+ 0xa8e34ba8U,
+ 0x51f3a251U,
+ 0xa3fe5da3U,
+ 0x40c08040U,
+ 0x8f8a058fU,
+ 0x92ad3f92U,
+ 0x9dbc219dU,
+ 0x38487038U,
+ 0xf504f1f5U,
+ 0xbcdf63bcU,
+ 0xb6c177b6U,
+ 0xda75afdaU,
+ 0x21634221U,
+ 0x10302010U,
+ 0xff1ae5ffU,
+ 0xf30efdf3U,
+ 0xd26dbfd2U,
+ 0xcd4c81cdU,
+ 0x0c14180cU,
+ 0x13352613U,
+ 0xec2fc3ecU,
+ 0x5fe1be5fU,
+ 0x97a23597U,
+ 0x44cc8844U,
+ 0x17392e17U,
+ 0xc45793c4U,
+ 0xa7f255a7U,
+ 0x7e82fc7eU,
+ 0x3d477a3dU,
+ 0x64acc864U,
+ 0x5de7ba5dU,
+ 0x192b3219U,
+ 0x7395e673U,
+ 0x60a0c060U,
+ 0x81981981U,
+ 0x4fd19e4fU,
+ 0xdc7fa3dcU,
+ 0x22664422U,
+ 0x2a7e542aU,
+ 0x90ab3b90U,
+ 0x88830b88U,
+ 0x46ca8c46U,
+ 0xee29c7eeU,
+ 0xb8d36bb8U,
+ 0x143c2814U,
+ 0xde79a7deU,
+ 0x5ee2bc5eU,
+ 0x0b1d160bU,
+ 0xdb76addbU,
+ 0xe03bdbe0U,
+ 0x32566432U,
+ 0x3a4e743aU,
+ 0x0a1e140aU,
+ 0x49db9249U,
+ 0x060a0c06U,
+ 0x246c4824U,
+ 0x5ce4b85cU,
+ 0xc25d9fc2U,
+ 0xd36ebdd3U,
+ 0xacef43acU,
+ 0x62a6c462U,
+ 0x91a83991U,
+ 0x95a43195U,
+ 0xe437d3e4U,
+ 0x798bf279U,
+ 0xe732d5e7U,
+ 0xc8438bc8U,
+ 0x37596e37U,
+ 0x6db7da6dU,
+ 0x8d8c018dU,
+ 0xd564b1d5U,
+ 0x4ed29c4eU,
+ 0xa9e049a9U,
+ 0x6cb4d86cU,
+ 0x56faac56U,
+ 0xf407f3f4U,
+ 0xea25cfeaU,
+ 0x65afca65U,
+ 0x7a8ef47aU,
+ 0xaee947aeU,
+ 0x08181008U,
+ 0xbad56fbaU,
+ 0x7888f078U,
+ 0x256f4a25U,
+ 0x2e725c2eU,
+ 0x1c24381cU,
+ 0xa6f157a6U,
+ 0xb4c773b4U,
+ 0xc65197c6U,
+ 0xe823cbe8U,
+ 0xdd7ca1ddU,
+ 0x749ce874U,
+ 0x1f213e1fU,
+ 0x4bdd964bU,
+ 0xbddc61bdU,
+ 0x8b860d8bU,
+ 0x8a850f8aU,
+ 0x7090e070U,
+ 0x3e427c3eU,
+ 0xb5c471b5U,
+ 0x66aacc66U,
+ 0x48d89048U,
+ 0x03050603U,
+ 0xf601f7f6U,
+ 0x0e121c0eU,
+ 0x61a3c261U,
+ 0x355f6a35U,
+ 0x57f9ae57U,
+ 0xb9d069b9U,
+ 0x86911786U,
+ 0xc15899c1U,
+ 0x1d273a1dU,
+ 0x9eb9279eU,
+ 0xe138d9e1U,
+ 0xf813ebf8U,
+ 0x98b32b98U,
+ 0x11332211U,
+ 0x69bbd269U,
+ 0xd970a9d9U,
+ 0x8e89078eU,
+ 0x94a73394U,
+ 0x9bb62d9bU,
+ 0x1e223c1eU,
+ 0x87921587U,
+ 0xe920c9e9U,
+ 0xce4987ceU,
+ 0x55ffaa55U,
+ 0x28785028U,
+ 0xdf7aa5dfU,
+ 0x8c8f038cU,
+ 0xa1f859a1U,
+ 0x89800989U,
+ 0x0d171a0dU,
+ 0xbfda65bfU,
+ 0xe631d7e6U,
+ 0x42c68442U,
+ 0x68b8d068U,
+ 0x41c38241U,
+ 0x99b02999U,
+ 0x2d775a2dU,
+ 0x0f111e0fU,
+ 0xb0cb7bb0U,
+ 0x54fca854U,
+ 0xbbd66dbbU,
+ 0x163a2c16U,
+};
+constexpr u32 Encode3[256] = {
+ 0x6363a5c6U,
+ 0x7c7c84f8U,
+ 0x777799eeU,
+ 0x7b7b8df6U,
+ 0xf2f20dffU,
+ 0x6b6bbdd6U,
+ 0x6f6fb1deU,
+ 0xc5c55491U,
+ 0x30305060U,
+ 0x01010302U,
+ 0x6767a9ceU,
+ 0x2b2b7d56U,
+ 0xfefe19e7U,
+ 0xd7d762b5U,
+ 0xababe64dU,
+ 0x76769aecU,
+ 0xcaca458fU,
+ 0x82829d1fU,
+ 0xc9c94089U,
+ 0x7d7d87faU,
+ 0xfafa15efU,
+ 0x5959ebb2U,
+ 0x4747c98eU,
+ 0xf0f00bfbU,
+ 0xadadec41U,
+ 0xd4d467b3U,
+ 0xa2a2fd5fU,
+ 0xafafea45U,
+ 0x9c9cbf23U,
+ 0xa4a4f753U,
+ 0x727296e4U,
+ 0xc0c05b9bU,
+ 0xb7b7c275U,
+ 0xfdfd1ce1U,
+ 0x9393ae3dU,
+ 0x26266a4cU,
+ 0x36365a6cU,
+ 0x3f3f417eU,
+ 0xf7f702f5U,
+ 0xcccc4f83U,
+ 0x34345c68U,
+ 0xa5a5f451U,
+ 0xe5e534d1U,
+ 0xf1f108f9U,
+ 0x717193e2U,
+ 0xd8d873abU,
+ 0x31315362U,
+ 0x15153f2aU,
+ 0x04040c08U,
+ 0xc7c75295U,
+ 0x23236546U,
+ 0xc3c35e9dU,
+ 0x18182830U,
+ 0x9696a137U,
+ 0x05050f0aU,
+ 0x9a9ab52fU,
+ 0x0707090eU,
+ 0x12123624U,
+ 0x80809b1bU,
+ 0xe2e23ddfU,
+ 0xebeb26cdU,
+ 0x2727694eU,
+ 0xb2b2cd7fU,
+ 0x75759feaU,
+ 0x09091b12U,
+ 0x83839e1dU,
+ 0x2c2c7458U,
+ 0x1a1a2e34U,
+ 0x1b1b2d36U,
+ 0x6e6eb2dcU,
+ 0x5a5aeeb4U,
+ 0xa0a0fb5bU,
+ 0x5252f6a4U,
+ 0x3b3b4d76U,
+ 0xd6d661b7U,
+ 0xb3b3ce7dU,
+ 0x29297b52U,
+ 0xe3e33eddU,
+ 0x2f2f715eU,
+ 0x84849713U,
+ 0x5353f5a6U,
+ 0xd1d168b9U,
+ 0x00000000U,
+ 0xeded2cc1U,
+ 0x20206040U,
+ 0xfcfc1fe3U,
+ 0xb1b1c879U,
+ 0x5b5bedb6U,
+ 0x6a6abed4U,
+ 0xcbcb468dU,
+ 0xbebed967U,
+ 0x39394b72U,
+ 0x4a4ade94U,
+ 0x4c4cd498U,
+ 0x5858e8b0U,
+ 0xcfcf4a85U,
+ 0xd0d06bbbU,
+ 0xefef2ac5U,
+ 0xaaaae54fU,
+ 0xfbfb16edU,
+ 0x4343c586U,
+ 0x4d4dd79aU,
+ 0x33335566U,
+ 0x85859411U,
+ 0x4545cf8aU,
+ 0xf9f910e9U,
+ 0x02020604U,
+ 0x7f7f81feU,
+ 0x5050f0a0U,
+ 0x3c3c4478U,
+ 0x9f9fba25U,
+ 0xa8a8e34bU,
+ 0x5151f3a2U,
+ 0xa3a3fe5dU,
+ 0x4040c080U,
+ 0x8f8f8a05U,
+ 0x9292ad3fU,
+ 0x9d9dbc21U,
+ 0x38384870U,
+ 0xf5f504f1U,
+ 0xbcbcdf63U,
+ 0xb6b6c177U,
+ 0xdada75afU,
+ 0x21216342U,
+ 0x10103020U,
+ 0xffff1ae5U,
+ 0xf3f30efdU,
+ 0xd2d26dbfU,
+ 0xcdcd4c81U,
+ 0x0c0c1418U,
+ 0x13133526U,
+ 0xecec2fc3U,
+ 0x5f5fe1beU,
+ 0x9797a235U,
+ 0x4444cc88U,
+ 0x1717392eU,
+ 0xc4c45793U,
+ 0xa7a7f255U,
+ 0x7e7e82fcU,
+ 0x3d3d477aU,
+ 0x6464acc8U,
+ 0x5d5de7baU,
+ 0x19192b32U,
+ 0x737395e6U,
+ 0x6060a0c0U,
+ 0x81819819U,
+ 0x4f4fd19eU,
+ 0xdcdc7fa3U,
+ 0x22226644U,
+ 0x2a2a7e54U,
+ 0x9090ab3bU,
+ 0x8888830bU,
+ 0x4646ca8cU,
+ 0xeeee29c7U,
+ 0xb8b8d36bU,
+ 0x14143c28U,
+ 0xdede79a7U,
+ 0x5e5ee2bcU,
+ 0x0b0b1d16U,
+ 0xdbdb76adU,
+ 0xe0e03bdbU,
+ 0x32325664U,
+ 0x3a3a4e74U,
+ 0x0a0a1e14U,
+ 0x4949db92U,
+ 0x06060a0cU,
+ 0x24246c48U,
+ 0x5c5ce4b8U,
+ 0xc2c25d9fU,
+ 0xd3d36ebdU,
+ 0xacacef43U,
+ 0x6262a6c4U,
+ 0x9191a839U,
+ 0x9595a431U,
+ 0xe4e437d3U,
+ 0x79798bf2U,
+ 0xe7e732d5U,
+ 0xc8c8438bU,
+ 0x3737596eU,
+ 0x6d6db7daU,
+ 0x8d8d8c01U,
+ 0xd5d564b1U,
+ 0x4e4ed29cU,
+ 0xa9a9e049U,
+ 0x6c6cb4d8U,
+ 0x5656faacU,
+ 0xf4f407f3U,
+ 0xeaea25cfU,
+ 0x6565afcaU,
+ 0x7a7a8ef4U,
+ 0xaeaee947U,
+ 0x08081810U,
+ 0xbabad56fU,
+ 0x787888f0U,
+ 0x25256f4aU,
+ 0x2e2e725cU,
+ 0x1c1c2438U,
+ 0xa6a6f157U,
+ 0xb4b4c773U,
+ 0xc6c65197U,
+ 0xe8e823cbU,
+ 0xdddd7ca1U,
+ 0x74749ce8U,
+ 0x1f1f213eU,
+ 0x4b4bdd96U,
+ 0xbdbddc61U,
+ 0x8b8b860dU,
+ 0x8a8a850fU,
+ 0x707090e0U,
+ 0x3e3e427cU,
+ 0xb5b5c471U,
+ 0x6666aaccU,
+ 0x4848d890U,
+ 0x03030506U,
+ 0xf6f601f7U,
+ 0x0e0e121cU,
+ 0x6161a3c2U,
+ 0x35355f6aU,
+ 0x5757f9aeU,
+ 0xb9b9d069U,
+ 0x86869117U,
+ 0xc1c15899U,
+ 0x1d1d273aU,
+ 0x9e9eb927U,
+ 0xe1e138d9U,
+ 0xf8f813ebU,
+ 0x9898b32bU,
+ 0x11113322U,
+ 0x6969bbd2U,
+ 0xd9d970a9U,
+ 0x8e8e8907U,
+ 0x9494a733U,
+ 0x9b9bb62dU,
+ 0x1e1e223cU,
+ 0x87879215U,
+ 0xe9e920c9U,
+ 0xcece4987U,
+ 0x5555ffaaU,
+ 0x28287850U,
+ 0xdfdf7aa5U,
+ 0x8c8c8f03U,
+ 0xa1a1f859U,
+ 0x89898009U,
+ 0x0d0d171aU,
+ 0xbfbfda65U,
+ 0xe6e631d7U,
+ 0x4242c684U,
+ 0x6868b8d0U,
+ 0x4141c382U,
+ 0x9999b029U,
+ 0x2d2d775aU,
+ 0x0f0f111eU,
+ 0xb0b0cb7bU,
+ 0x5454fca8U,
+ 0xbbbbd66dU,
+ 0x16163a2cU,
+};
+
+// Decryption constant tables
+constexpr u32 Decode0[256] = {
+ 0x51f4a750U,
+ 0x7e416553U,
+ 0x1a17a4c3U,
+ 0x3a275e96U,
+ 0x3bab6bcbU,
+ 0x1f9d45f1U,
+ 0xacfa58abU,
+ 0x4be30393U,
+ 0x2030fa55U,
+ 0xad766df6U,
+ 0x88cc7691U,
+ 0xf5024c25U,
+ 0x4fe5d7fcU,
+ 0xc52acbd7U,
+ 0x26354480U,
+ 0xb562a38fU,
+ 0xdeb15a49U,
+ 0x25ba1b67U,
+ 0x45ea0e98U,
+ 0x5dfec0e1U,
+ 0xc32f7502U,
+ 0x814cf012U,
+ 0x8d4697a3U,
+ 0x6bd3f9c6U,
+ 0x038f5fe7U,
+ 0x15929c95U,
+ 0xbf6d7aebU,
+ 0x955259daU,
+ 0xd4be832dU,
+ 0x587421d3U,
+ 0x49e06929U,
+ 0x8ec9c844U,
+ 0x75c2896aU,
+ 0xf48e7978U,
+ 0x99583e6bU,
+ 0x27b971ddU,
+ 0xbee14fb6U,
+ 0xf088ad17U,
+ 0xc920ac66U,
+ 0x7dce3ab4U,
+ 0x63df4a18U,
+ 0xe51a3182U,
+ 0x97513360U,
+ 0x62537f45U,
+ 0xb16477e0U,
+ 0xbb6bae84U,
+ 0xfe81a01cU,
+ 0xf9082b94U,
+ 0x70486858U,
+ 0x8f45fd19U,
+ 0x94de6c87U,
+ 0x527bf8b7U,
+ 0xab73d323U,
+ 0x724b02e2U,
+ 0xe31f8f57U,
+ 0x6655ab2aU,
+ 0xb2eb2807U,
+ 0x2fb5c203U,
+ 0x86c57b9aU,
+ 0xd33708a5U,
+ 0x302887f2U,
+ 0x23bfa5b2U,
+ 0x02036abaU,
+ 0xed16825cU,
+ 0x8acf1c2bU,
+ 0xa779b492U,
+ 0xf307f2f0U,
+ 0x4e69e2a1U,
+ 0x65daf4cdU,
+ 0x0605bed5U,
+ 0xd134621fU,
+ 0xc4a6fe8aU,
+ 0x342e539dU,
+ 0xa2f355a0U,
+ 0x058ae132U,
+ 0xa4f6eb75U,
+ 0x0b83ec39U,
+ 0x4060efaaU,
+ 0x5e719f06U,
+ 0xbd6e1051U,
+ 0x3e218af9U,
+ 0x96dd063dU,
+ 0xdd3e05aeU,
+ 0x4de6bd46U,
+ 0x91548db5U,
+ 0x71c45d05U,
+ 0x0406d46fU,
+ 0x605015ffU,
+ 0x1998fb24U,
+ 0xd6bde997U,
+ 0x894043ccU,
+ 0x67d99e77U,
+ 0xb0e842bdU,
+ 0x07898b88U,
+ 0xe7195b38U,
+ 0x79c8eedbU,
+ 0xa17c0a47U,
+ 0x7c420fe9U,
+ 0xf8841ec9U,
+ 0x00000000U,
+ 0x09808683U,
+ 0x322bed48U,
+ 0x1e1170acU,
+ 0x6c5a724eU,
+ 0xfd0efffbU,
+ 0x0f853856U,
+ 0x3daed51eU,
+ 0x362d3927U,
+ 0x0a0fd964U,
+ 0x685ca621U,
+ 0x9b5b54d1U,
+ 0x24362e3aU,
+ 0x0c0a67b1U,
+ 0x9357e70fU,
+ 0xb4ee96d2U,
+ 0x1b9b919eU,
+ 0x80c0c54fU,
+ 0x61dc20a2U,
+ 0x5a774b69U,
+ 0x1c121a16U,
+ 0xe293ba0aU,
+ 0xc0a02ae5U,
+ 0x3c22e043U,
+ 0x121b171dU,
+ 0x0e090d0bU,
+ 0xf28bc7adU,
+ 0x2db6a8b9U,
+ 0x141ea9c8U,
+ 0x57f11985U,
+ 0xaf75074cU,
+ 0xee99ddbbU,
+ 0xa37f60fdU,
+ 0xf701269fU,
+ 0x5c72f5bcU,
+ 0x44663bc5U,
+ 0x5bfb7e34U,
+ 0x8b432976U,
+ 0xcb23c6dcU,
+ 0xb6edfc68U,
+ 0xb8e4f163U,
+ 0xd731dccaU,
+ 0x42638510U,
+ 0x13972240U,
+ 0x84c61120U,
+ 0x854a247dU,
+ 0xd2bb3df8U,
+ 0xaef93211U,
+ 0xc729a16dU,
+ 0x1d9e2f4bU,
+ 0xdcb230f3U,
+ 0x0d8652ecU,
+ 0x77c1e3d0U,
+ 0x2bb3166cU,
+ 0xa970b999U,
+ 0x119448faU,
+ 0x47e96422U,
+ 0xa8fc8cc4U,
+ 0xa0f03f1aU,
+ 0x567d2cd8U,
+ 0x223390efU,
+ 0x87494ec7U,
+ 0xd938d1c1U,
+ 0x8ccaa2feU,
+ 0x98d40b36U,
+ 0xa6f581cfU,
+ 0xa57ade28U,
+ 0xdab78e26U,
+ 0x3fadbfa4U,
+ 0x2c3a9de4U,
+ 0x5078920dU,
+ 0x6a5fcc9bU,
+ 0x547e4662U,
+ 0xf68d13c2U,
+ 0x90d8b8e8U,
+ 0x2e39f75eU,
+ 0x82c3aff5U,
+ 0x9f5d80beU,
+ 0x69d0937cU,
+ 0x6fd52da9U,
+ 0xcf2512b3U,
+ 0xc8ac993bU,
+ 0x10187da7U,
+ 0xe89c636eU,
+ 0xdb3bbb7bU,
+ 0xcd267809U,
+ 0x6e5918f4U,
+ 0xec9ab701U,
+ 0x834f9aa8U,
+ 0xe6956e65U,
+ 0xaaffe67eU,
+ 0x21bccf08U,
+ 0xef15e8e6U,
+ 0xbae79bd9U,
+ 0x4a6f36ceU,
+ 0xea9f09d4U,
+ 0x29b07cd6U,
+ 0x31a4b2afU,
+ 0x2a3f2331U,
+ 0xc6a59430U,
+ 0x35a266c0U,
+ 0x744ebc37U,
+ 0xfc82caa6U,
+ 0xe090d0b0U,
+ 0x33a7d815U,
+ 0xf104984aU,
+ 0x41ecdaf7U,
+ 0x7fcd500eU,
+ 0x1791f62fU,
+ 0x764dd68dU,
+ 0x43efb04dU,
+ 0xccaa4d54U,
+ 0xe49604dfU,
+ 0x9ed1b5e3U,
+ 0x4c6a881bU,
+ 0xc12c1fb8U,
+ 0x4665517fU,
+ 0x9d5eea04U,
+ 0x018c355dU,
+ 0xfa877473U,
+ 0xfb0b412eU,
+ 0xb3671d5aU,
+ 0x92dbd252U,
+ 0xe9105633U,
+ 0x6dd64713U,
+ 0x9ad7618cU,
+ 0x37a10c7aU,
+ 0x59f8148eU,
+ 0xeb133c89U,
+ 0xcea927eeU,
+ 0xb761c935U,
+ 0xe11ce5edU,
+ 0x7a47b13cU,
+ 0x9cd2df59U,
+ 0x55f2733fU,
+ 0x1814ce79U,
+ 0x73c737bfU,
+ 0x53f7cdeaU,
+ 0x5ffdaa5bU,
+ 0xdf3d6f14U,
+ 0x7844db86U,
+ 0xcaaff381U,
+ 0xb968c43eU,
+ 0x3824342cU,
+ 0xc2a3405fU,
+ 0x161dc372U,
+ 0xbce2250cU,
+ 0x283c498bU,
+ 0xff0d9541U,
+ 0x39a80171U,
+ 0x080cb3deU,
+ 0xd8b4e49cU,
+ 0x6456c190U,
+ 0x7bcb8461U,
+ 0xd532b670U,
+ 0x486c5c74U,
+ 0xd0b85742U,
+};
+constexpr u32 Decode1[256] = {
+ 0x5051f4a7U,
+ 0x537e4165U,
+ 0xc31a17a4U,
+ 0x963a275eU,
+ 0xcb3bab6bU,
+ 0xf11f9d45U,
+ 0xabacfa58U,
+ 0x934be303U,
+ 0x552030faU,
+ 0xf6ad766dU,
+ 0x9188cc76U,
+ 0x25f5024cU,
+ 0xfc4fe5d7U,
+ 0xd7c52acbU,
+ 0x80263544U,
+ 0x8fb562a3U,
+ 0x49deb15aU,
+ 0x6725ba1bU,
+ 0x9845ea0eU,
+ 0xe15dfec0U,
+ 0x02c32f75U,
+ 0x12814cf0U,
+ 0xa38d4697U,
+ 0xc66bd3f9U,
+ 0xe7038f5fU,
+ 0x9515929cU,
+ 0xebbf6d7aU,
+ 0xda955259U,
+ 0x2dd4be83U,
+ 0xd3587421U,
+ 0x2949e069U,
+ 0x448ec9c8U,
+ 0x6a75c289U,
+ 0x78f48e79U,
+ 0x6b99583eU,
+ 0xdd27b971U,
+ 0xb6bee14fU,
+ 0x17f088adU,
+ 0x66c920acU,
+ 0xb47dce3aU,
+ 0x1863df4aU,
+ 0x82e51a31U,
+ 0x60975133U,
+ 0x4562537fU,
+ 0xe0b16477U,
+ 0x84bb6baeU,
+ 0x1cfe81a0U,
+ 0x94f9082bU,
+ 0x58704868U,
+ 0x198f45fdU,
+ 0x8794de6cU,
+ 0xb7527bf8U,
+ 0x23ab73d3U,
+ 0xe2724b02U,
+ 0x57e31f8fU,
+ 0x2a6655abU,
+ 0x07b2eb28U,
+ 0x032fb5c2U,
+ 0x9a86c57bU,
+ 0xa5d33708U,
+ 0xf2302887U,
+ 0xb223bfa5U,
+ 0xba02036aU,
+ 0x5ced1682U,
+ 0x2b8acf1cU,
+ 0x92a779b4U,
+ 0xf0f307f2U,
+ 0xa14e69e2U,
+ 0xcd65daf4U,
+ 0xd50605beU,
+ 0x1fd13462U,
+ 0x8ac4a6feU,
+ 0x9d342e53U,
+ 0xa0a2f355U,
+ 0x32058ae1U,
+ 0x75a4f6ebU,
+ 0x390b83ecU,
+ 0xaa4060efU,
+ 0x065e719fU,
+ 0x51bd6e10U,
+ 0xf93e218aU,
+ 0x3d96dd06U,
+ 0xaedd3e05U,
+ 0x464de6bdU,
+ 0xb591548dU,
+ 0x0571c45dU,
+ 0x6f0406d4U,
+ 0xff605015U,
+ 0x241998fbU,
+ 0x97d6bde9U,
+ 0xcc894043U,
+ 0x7767d99eU,
+ 0xbdb0e842U,
+ 0x8807898bU,
+ 0x38e7195bU,
+ 0xdb79c8eeU,
+ 0x47a17c0aU,
+ 0xe97c420fU,
+ 0xc9f8841eU,
+ 0x00000000U,
+ 0x83098086U,
+ 0x48322bedU,
+ 0xac1e1170U,
+ 0x4e6c5a72U,
+ 0xfbfd0effU,
+ 0x560f8538U,
+ 0x1e3daed5U,
+ 0x27362d39U,
+ 0x640a0fd9U,
+ 0x21685ca6U,
+ 0xd19b5b54U,
+ 0x3a24362eU,
+ 0xb10c0a67U,
+ 0x0f9357e7U,
+ 0xd2b4ee96U,
+ 0x9e1b9b91U,
+ 0x4f80c0c5U,
+ 0xa261dc20U,
+ 0x695a774bU,
+ 0x161c121aU,
+ 0x0ae293baU,
+ 0xe5c0a02aU,
+ 0x433c22e0U,
+ 0x1d121b17U,
+ 0x0b0e090dU,
+ 0xadf28bc7U,
+ 0xb92db6a8U,
+ 0xc8141ea9U,
+ 0x8557f119U,
+ 0x4caf7507U,
+ 0xbbee99ddU,
+ 0xfda37f60U,
+ 0x9ff70126U,
+ 0xbc5c72f5U,
+ 0xc544663bU,
+ 0x345bfb7eU,
+ 0x768b4329U,
+ 0xdccb23c6U,
+ 0x68b6edfcU,
+ 0x63b8e4f1U,
+ 0xcad731dcU,
+ 0x10426385U,
+ 0x40139722U,
+ 0x2084c611U,
+ 0x7d854a24U,
+ 0xf8d2bb3dU,
+ 0x11aef932U,
+ 0x6dc729a1U,
+ 0x4b1d9e2fU,
+ 0xf3dcb230U,
+ 0xec0d8652U,
+ 0xd077c1e3U,
+ 0x6c2bb316U,
+ 0x99a970b9U,
+ 0xfa119448U,
+ 0x2247e964U,
+ 0xc4a8fc8cU,
+ 0x1aa0f03fU,
+ 0xd8567d2cU,
+ 0xef223390U,
+ 0xc787494eU,
+ 0xc1d938d1U,
+ 0xfe8ccaa2U,
+ 0x3698d40bU,
+ 0xcfa6f581U,
+ 0x28a57adeU,
+ 0x26dab78eU,
+ 0xa43fadbfU,
+ 0xe42c3a9dU,
+ 0x0d507892U,
+ 0x9b6a5fccU,
+ 0x62547e46U,
+ 0xc2f68d13U,
+ 0xe890d8b8U,
+ 0x5e2e39f7U,
+ 0xf582c3afU,
+ 0xbe9f5d80U,
+ 0x7c69d093U,
+ 0xa96fd52dU,
+ 0xb3cf2512U,
+ 0x3bc8ac99U,
+ 0xa710187dU,
+ 0x6ee89c63U,
+ 0x7bdb3bbbU,
+ 0x09cd2678U,
+ 0xf46e5918U,
+ 0x01ec9ab7U,
+ 0xa8834f9aU,
+ 0x65e6956eU,
+ 0x7eaaffe6U,
+ 0x0821bccfU,
+ 0xe6ef15e8U,
+ 0xd9bae79bU,
+ 0xce4a6f36U,
+ 0xd4ea9f09U,
+ 0xd629b07cU,
+ 0xaf31a4b2U,
+ 0x312a3f23U,
+ 0x30c6a594U,
+ 0xc035a266U,
+ 0x37744ebcU,
+ 0xa6fc82caU,
+ 0xb0e090d0U,
+ 0x1533a7d8U,
+ 0x4af10498U,
+ 0xf741ecdaU,
+ 0x0e7fcd50U,
+ 0x2f1791f6U,
+ 0x8d764dd6U,
+ 0x4d43efb0U,
+ 0x54ccaa4dU,
+ 0xdfe49604U,
+ 0xe39ed1b5U,
+ 0x1b4c6a88U,
+ 0xb8c12c1fU,
+ 0x7f466551U,
+ 0x049d5eeaU,
+ 0x5d018c35U,
+ 0x73fa8774U,
+ 0x2efb0b41U,
+ 0x5ab3671dU,
+ 0x5292dbd2U,
+ 0x33e91056U,
+ 0x136dd647U,
+ 0x8c9ad761U,
+ 0x7a37a10cU,
+ 0x8e59f814U,
+ 0x89eb133cU,
+ 0xeecea927U,
+ 0x35b761c9U,
+ 0xede11ce5U,
+ 0x3c7a47b1U,
+ 0x599cd2dfU,
+ 0x3f55f273U,
+ 0x791814ceU,
+ 0xbf73c737U,
+ 0xea53f7cdU,
+ 0x5b5ffdaaU,
+ 0x14df3d6fU,
+ 0x867844dbU,
+ 0x81caaff3U,
+ 0x3eb968c4U,
+ 0x2c382434U,
+ 0x5fc2a340U,
+ 0x72161dc3U,
+ 0x0cbce225U,
+ 0x8b283c49U,
+ 0x41ff0d95U,
+ 0x7139a801U,
+ 0xde080cb3U,
+ 0x9cd8b4e4U,
+ 0x906456c1U,
+ 0x617bcb84U,
+ 0x70d532b6U,
+ 0x74486c5cU,
+ 0x42d0b857U,
+};
+constexpr u32 Decode2[256] = {
+ 0xa75051f4U,
+ 0x65537e41U,
+ 0xa4c31a17U,
+ 0x5e963a27U,
+ 0x6bcb3babU,
+ 0x45f11f9dU,
+ 0x58abacfaU,
+ 0x03934be3U,
+ 0xfa552030U,
+ 0x6df6ad76U,
+ 0x769188ccU,
+ 0x4c25f502U,
+ 0xd7fc4fe5U,
+ 0xcbd7c52aU,
+ 0x44802635U,
+ 0xa38fb562U,
+ 0x5a49deb1U,
+ 0x1b6725baU,
+ 0x0e9845eaU,
+ 0xc0e15dfeU,
+ 0x7502c32fU,
+ 0xf012814cU,
+ 0x97a38d46U,
+ 0xf9c66bd3U,
+ 0x5fe7038fU,
+ 0x9c951592U,
+ 0x7aebbf6dU,
+ 0x59da9552U,
+ 0x832dd4beU,
+ 0x21d35874U,
+ 0x692949e0U,
+ 0xc8448ec9U,
+ 0x896a75c2U,
+ 0x7978f48eU,
+ 0x3e6b9958U,
+ 0x71dd27b9U,
+ 0x4fb6bee1U,
+ 0xad17f088U,
+ 0xac66c920U,
+ 0x3ab47dceU,
+ 0x4a1863dfU,
+ 0x3182e51aU,
+ 0x33609751U,
+ 0x7f456253U,
+ 0x77e0b164U,
+ 0xae84bb6bU,
+ 0xa01cfe81U,
+ 0x2b94f908U,
+ 0x68587048U,
+ 0xfd198f45U,
+ 0x6c8794deU,
+ 0xf8b7527bU,
+ 0xd323ab73U,
+ 0x02e2724bU,
+ 0x8f57e31fU,
+ 0xab2a6655U,
+ 0x2807b2ebU,
+ 0xc2032fb5U,
+ 0x7b9a86c5U,
+ 0x08a5d337U,
+ 0x87f23028U,
+ 0xa5b223bfU,
+ 0x6aba0203U,
+ 0x825ced16U,
+ 0x1c2b8acfU,
+ 0xb492a779U,
+ 0xf2f0f307U,
+ 0xe2a14e69U,
+ 0xf4cd65daU,
+ 0xbed50605U,
+ 0x621fd134U,
+ 0xfe8ac4a6U,
+ 0x539d342eU,
+ 0x55a0a2f3U,
+ 0xe132058aU,
+ 0xeb75a4f6U,
+ 0xec390b83U,
+ 0xefaa4060U,
+ 0x9f065e71U,
+ 0x1051bd6eU,
+ 0x8af93e21U,
+ 0x063d96ddU,
+ 0x05aedd3eU,
+ 0xbd464de6U,
+ 0x8db59154U,
+ 0x5d0571c4U,
+ 0xd46f0406U,
+ 0x15ff6050U,
+ 0xfb241998U,
+ 0xe997d6bdU,
+ 0x43cc8940U,
+ 0x9e7767d9U,
+ 0x42bdb0e8U,
+ 0x8b880789U,
+ 0x5b38e719U,
+ 0xeedb79c8U,
+ 0x0a47a17cU,
+ 0x0fe97c42U,
+ 0x1ec9f884U,
+ 0x00000000U,
+ 0x86830980U,
+ 0xed48322bU,
+ 0x70ac1e11U,
+ 0x724e6c5aU,
+ 0xfffbfd0eU,
+ 0x38560f85U,
+ 0xd51e3daeU,
+ 0x3927362dU,
+ 0xd9640a0fU,
+ 0xa621685cU,
+ 0x54d19b5bU,
+ 0x2e3a2436U,
+ 0x67b10c0aU,
+ 0xe70f9357U,
+ 0x96d2b4eeU,
+ 0x919e1b9bU,
+ 0xc54f80c0U,
+ 0x20a261dcU,
+ 0x4b695a77U,
+ 0x1a161c12U,
+ 0xba0ae293U,
+ 0x2ae5c0a0U,
+ 0xe0433c22U,
+ 0x171d121bU,
+ 0x0d0b0e09U,
+ 0xc7adf28bU,
+ 0xa8b92db6U,
+ 0xa9c8141eU,
+ 0x198557f1U,
+ 0x074caf75U,
+ 0xddbbee99U,
+ 0x60fda37fU,
+ 0x269ff701U,
+ 0xf5bc5c72U,
+ 0x3bc54466U,
+ 0x7e345bfbU,
+ 0x29768b43U,
+ 0xc6dccb23U,
+ 0xfc68b6edU,
+ 0xf163b8e4U,
+ 0xdccad731U,
+ 0x85104263U,
+ 0x22401397U,
+ 0x112084c6U,
+ 0x247d854aU,
+ 0x3df8d2bbU,
+ 0x3211aef9U,
+ 0xa16dc729U,
+ 0x2f4b1d9eU,
+ 0x30f3dcb2U,
+ 0x52ec0d86U,
+ 0xe3d077c1U,
+ 0x166c2bb3U,
+ 0xb999a970U,
+ 0x48fa1194U,
+ 0x642247e9U,
+ 0x8cc4a8fcU,
+ 0x3f1aa0f0U,
+ 0x2cd8567dU,
+ 0x90ef2233U,
+ 0x4ec78749U,
+ 0xd1c1d938U,
+ 0xa2fe8ccaU,
+ 0x0b3698d4U,
+ 0x81cfa6f5U,
+ 0xde28a57aU,
+ 0x8e26dab7U,
+ 0xbfa43fadU,
+ 0x9de42c3aU,
+ 0x920d5078U,
+ 0xcc9b6a5fU,
+ 0x4662547eU,
+ 0x13c2f68dU,
+ 0xb8e890d8U,
+ 0xf75e2e39U,
+ 0xaff582c3U,
+ 0x80be9f5dU,
+ 0x937c69d0U,
+ 0x2da96fd5U,
+ 0x12b3cf25U,
+ 0x993bc8acU,
+ 0x7da71018U,
+ 0x636ee89cU,
+ 0xbb7bdb3bU,
+ 0x7809cd26U,
+ 0x18f46e59U,
+ 0xb701ec9aU,
+ 0x9aa8834fU,
+ 0x6e65e695U,
+ 0xe67eaaffU,
+ 0xcf0821bcU,
+ 0xe8e6ef15U,
+ 0x9bd9bae7U,
+ 0x36ce4a6fU,
+ 0x09d4ea9fU,
+ 0x7cd629b0U,
+ 0xb2af31a4U,
+ 0x23312a3fU,
+ 0x9430c6a5U,
+ 0x66c035a2U,
+ 0xbc37744eU,
+ 0xcaa6fc82U,
+ 0xd0b0e090U,
+ 0xd81533a7U,
+ 0x984af104U,
+ 0xdaf741ecU,
+ 0x500e7fcdU,
+ 0xf62f1791U,
+ 0xd68d764dU,
+ 0xb04d43efU,
+ 0x4d54ccaaU,
+ 0x04dfe496U,
+ 0xb5e39ed1U,
+ 0x881b4c6aU,
+ 0x1fb8c12cU,
+ 0x517f4665U,
+ 0xea049d5eU,
+ 0x355d018cU,
+ 0x7473fa87U,
+ 0x412efb0bU,
+ 0x1d5ab367U,
+ 0xd25292dbU,
+ 0x5633e910U,
+ 0x47136dd6U,
+ 0x618c9ad7U,
+ 0x0c7a37a1U,
+ 0x148e59f8U,
+ 0x3c89eb13U,
+ 0x27eecea9U,
+ 0xc935b761U,
+ 0xe5ede11cU,
+ 0xb13c7a47U,
+ 0xdf599cd2U,
+ 0x733f55f2U,
+ 0xce791814U,
+ 0x37bf73c7U,
+ 0xcdea53f7U,
+ 0xaa5b5ffdU,
+ 0x6f14df3dU,
+ 0xdb867844U,
+ 0xf381caafU,
+ 0xc43eb968U,
+ 0x342c3824U,
+ 0x405fc2a3U,
+ 0xc372161dU,
+ 0x250cbce2U,
+ 0x498b283cU,
+ 0x9541ff0dU,
+ 0x017139a8U,
+ 0xb3de080cU,
+ 0xe49cd8b4U,
+ 0xc1906456U,
+ 0x84617bcbU,
+ 0xb670d532U,
+ 0x5c74486cU,
+ 0x5742d0b8U,
+};
+constexpr u32 Decode3[256] = {
+ 0xf4a75051U,
+ 0x4165537eU,
+ 0x17a4c31aU,
+ 0x275e963aU,
+ 0xab6bcb3bU,
+ 0x9d45f11fU,
+ 0xfa58abacU,
+ 0xe303934bU,
+ 0x30fa5520U,
+ 0x766df6adU,
+ 0xcc769188U,
+ 0x024c25f5U,
+ 0xe5d7fc4fU,
+ 0x2acbd7c5U,
+ 0x35448026U,
+ 0x62a38fb5U,
+ 0xb15a49deU,
+ 0xba1b6725U,
+ 0xea0e9845U,
+ 0xfec0e15dU,
+ 0x2f7502c3U,
+ 0x4cf01281U,
+ 0x4697a38dU,
+ 0xd3f9c66bU,
+ 0x8f5fe703U,
+ 0x929c9515U,
+ 0x6d7aebbfU,
+ 0x5259da95U,
+ 0xbe832dd4U,
+ 0x7421d358U,
+ 0xe0692949U,
+ 0xc9c8448eU,
+ 0xc2896a75U,
+ 0x8e7978f4U,
+ 0x583e6b99U,
+ 0xb971dd27U,
+ 0xe14fb6beU,
+ 0x88ad17f0U,
+ 0x20ac66c9U,
+ 0xce3ab47dU,
+ 0xdf4a1863U,
+ 0x1a3182e5U,
+ 0x51336097U,
+ 0x537f4562U,
+ 0x6477e0b1U,
+ 0x6bae84bbU,
+ 0x81a01cfeU,
+ 0x082b94f9U,
+ 0x48685870U,
+ 0x45fd198fU,
+ 0xde6c8794U,
+ 0x7bf8b752U,
+ 0x73d323abU,
+ 0x4b02e272U,
+ 0x1f8f57e3U,
+ 0x55ab2a66U,
+ 0xeb2807b2U,
+ 0xb5c2032fU,
+ 0xc57b9a86U,
+ 0x3708a5d3U,
+ 0x2887f230U,
+ 0xbfa5b223U,
+ 0x036aba02U,
+ 0x16825cedU,
+ 0xcf1c2b8aU,
+ 0x79b492a7U,
+ 0x07f2f0f3U,
+ 0x69e2a14eU,
+ 0xdaf4cd65U,
+ 0x05bed506U,
+ 0x34621fd1U,
+ 0xa6fe8ac4U,
+ 0x2e539d34U,
+ 0xf355a0a2U,
+ 0x8ae13205U,
+ 0xf6eb75a4U,
+ 0x83ec390bU,
+ 0x60efaa40U,
+ 0x719f065eU,
+ 0x6e1051bdU,
+ 0x218af93eU,
+ 0xdd063d96U,
+ 0x3e05aeddU,
+ 0xe6bd464dU,
+ 0x548db591U,
+ 0xc45d0571U,
+ 0x06d46f04U,
+ 0x5015ff60U,
+ 0x98fb2419U,
+ 0xbde997d6U,
+ 0x4043cc89U,
+ 0xd99e7767U,
+ 0xe842bdb0U,
+ 0x898b8807U,
+ 0x195b38e7U,
+ 0xc8eedb79U,
+ 0x7c0a47a1U,
+ 0x420fe97cU,
+ 0x841ec9f8U,
+ 0x00000000U,
+ 0x80868309U,
+ 0x2bed4832U,
+ 0x1170ac1eU,
+ 0x5a724e6cU,
+ 0x0efffbfdU,
+ 0x8538560fU,
+ 0xaed51e3dU,
+ 0x2d392736U,
+ 0x0fd9640aU,
+ 0x5ca62168U,
+ 0x5b54d19bU,
+ 0x362e3a24U,
+ 0x0a67b10cU,
+ 0x57e70f93U,
+ 0xee96d2b4U,
+ 0x9b919e1bU,
+ 0xc0c54f80U,
+ 0xdc20a261U,
+ 0x774b695aU,
+ 0x121a161cU,
+ 0x93ba0ae2U,
+ 0xa02ae5c0U,
+ 0x22e0433cU,
+ 0x1b171d12U,
+ 0x090d0b0eU,
+ 0x8bc7adf2U,
+ 0xb6a8b92dU,
+ 0x1ea9c814U,
+ 0xf1198557U,
+ 0x75074cafU,
+ 0x99ddbbeeU,
+ 0x7f60fda3U,
+ 0x01269ff7U,
+ 0x72f5bc5cU,
+ 0x663bc544U,
+ 0xfb7e345bU,
+ 0x4329768bU,
+ 0x23c6dccbU,
+ 0xedfc68b6U,
+ 0xe4f163b8U,
+ 0x31dccad7U,
+ 0x63851042U,
+ 0x97224013U,
+ 0xc6112084U,
+ 0x4a247d85U,
+ 0xbb3df8d2U,
+ 0xf93211aeU,
+ 0x29a16dc7U,
+ 0x9e2f4b1dU,
+ 0xb230f3dcU,
+ 0x8652ec0dU,
+ 0xc1e3d077U,
+ 0xb3166c2bU,
+ 0x70b999a9U,
+ 0x9448fa11U,
+ 0xe9642247U,
+ 0xfc8cc4a8U,
+ 0xf03f1aa0U,
+ 0x7d2cd856U,
+ 0x3390ef22U,
+ 0x494ec787U,
+ 0x38d1c1d9U,
+ 0xcaa2fe8cU,
+ 0xd40b3698U,
+ 0xf581cfa6U,
+ 0x7ade28a5U,
+ 0xb78e26daU,
+ 0xadbfa43fU,
+ 0x3a9de42cU,
+ 0x78920d50U,
+ 0x5fcc9b6aU,
+ 0x7e466254U,
+ 0x8d13c2f6U,
+ 0xd8b8e890U,
+ 0x39f75e2eU,
+ 0xc3aff582U,
+ 0x5d80be9fU,
+ 0xd0937c69U,
+ 0xd52da96fU,
+ 0x2512b3cfU,
+ 0xac993bc8U,
+ 0x187da710U,
+ 0x9c636ee8U,
+ 0x3bbb7bdbU,
+ 0x267809cdU,
+ 0x5918f46eU,
+ 0x9ab701ecU,
+ 0x4f9aa883U,
+ 0x956e65e6U,
+ 0xffe67eaaU,
+ 0xbccf0821U,
+ 0x15e8e6efU,
+ 0xe79bd9baU,
+ 0x6f36ce4aU,
+ 0x9f09d4eaU,
+ 0xb07cd629U,
+ 0xa4b2af31U,
+ 0x3f23312aU,
+ 0xa59430c6U,
+ 0xa266c035U,
+ 0x4ebc3774U,
+ 0x82caa6fcU,
+ 0x90d0b0e0U,
+ 0xa7d81533U,
+ 0x04984af1U,
+ 0xecdaf741U,
+ 0xcd500e7fU,
+ 0x91f62f17U,
+ 0x4dd68d76U,
+ 0xefb04d43U,
+ 0xaa4d54ccU,
+ 0x9604dfe4U,
+ 0xd1b5e39eU,
+ 0x6a881b4cU,
+ 0x2c1fb8c1U,
+ 0x65517f46U,
+ 0x5eea049dU,
+ 0x8c355d01U,
+ 0x877473faU,
+ 0x0b412efbU,
+ 0x671d5ab3U,
+ 0xdbd25292U,
+ 0x105633e9U,
+ 0xd647136dU,
+ 0xd7618c9aU,
+ 0xa10c7a37U,
+ 0xf8148e59U,
+ 0x133c89ebU,
+ 0xa927eeceU,
+ 0x61c935b7U,
+ 0x1ce5ede1U,
+ 0x47b13c7aU,
+ 0xd2df599cU,
+ 0xf2733f55U,
+ 0x14ce7918U,
+ 0xc737bf73U,
+ 0xf7cdea53U,
+ 0xfdaa5b5fU,
+ 0x3d6f14dfU,
+ 0x44db8678U,
+ 0xaff381caU,
+ 0x68c43eb9U,
+ 0x24342c38U,
+ 0xa3405fc2U,
+ 0x1dc37216U,
+ 0xe2250cbcU,
+ 0x3c498b28U,
+ 0x0d9541ffU,
+ 0xa8017139U,
+ 0x0cb3de08U,
+ 0xb4e49cd8U,
+ 0x56c19064U,
+ 0xcb84617bU,
+ 0x32b670d5U,
+ 0x6c5c7448U,
+ 0xb85742d0U,
+};
+constexpr u8 Decode4[256] = {
+ 0x52U,
+ 0x09U,
+ 0x6aU,
+ 0xd5U,
+ 0x30U,
+ 0x36U,
+ 0xa5U,
+ 0x38U,
+ 0xbfU,
+ 0x40U,
+ 0xa3U,
+ 0x9eU,
+ 0x81U,
+ 0xf3U,
+ 0xd7U,
+ 0xfbU,
+ 0x7cU,
+ 0xe3U,
+ 0x39U,
+ 0x82U,
+ 0x9bU,
+ 0x2fU,
+ 0xffU,
+ 0x87U,
+ 0x34U,
+ 0x8eU,
+ 0x43U,
+ 0x44U,
+ 0xc4U,
+ 0xdeU,
+ 0xe9U,
+ 0xcbU,
+ 0x54U,
+ 0x7bU,
+ 0x94U,
+ 0x32U,
+ 0xa6U,
+ 0xc2U,
+ 0x23U,
+ 0x3dU,
+ 0xeeU,
+ 0x4cU,
+ 0x95U,
+ 0x0bU,
+ 0x42U,
+ 0xfaU,
+ 0xc3U,
+ 0x4eU,
+ 0x08U,
+ 0x2eU,
+ 0xa1U,
+ 0x66U,
+ 0x28U,
+ 0xd9U,
+ 0x24U,
+ 0xb2U,
+ 0x76U,
+ 0x5bU,
+ 0xa2U,
+ 0x49U,
+ 0x6dU,
+ 0x8bU,
+ 0xd1U,
+ 0x25U,
+ 0x72U,
+ 0xf8U,
+ 0xf6U,
+ 0x64U,
+ 0x86U,
+ 0x68U,
+ 0x98U,
+ 0x16U,
+ 0xd4U,
+ 0xa4U,
+ 0x5cU,
+ 0xccU,
+ 0x5dU,
+ 0x65U,
+ 0xb6U,
+ 0x92U,
+ 0x6cU,
+ 0x70U,
+ 0x48U,
+ 0x50U,
+ 0xfdU,
+ 0xedU,
+ 0xb9U,
+ 0xdaU,
+ 0x5eU,
+ 0x15U,
+ 0x46U,
+ 0x57U,
+ 0xa7U,
+ 0x8dU,
+ 0x9dU,
+ 0x84U,
+ 0x90U,
+ 0xd8U,
+ 0xabU,
+ 0x00U,
+ 0x8cU,
+ 0xbcU,
+ 0xd3U,
+ 0x0aU,
+ 0xf7U,
+ 0xe4U,
+ 0x58U,
+ 0x05U,
+ 0xb8U,
+ 0xb3U,
+ 0x45U,
+ 0x06U,
+ 0xd0U,
+ 0x2cU,
+ 0x1eU,
+ 0x8fU,
+ 0xcaU,
+ 0x3fU,
+ 0x0fU,
+ 0x02U,
+ 0xc1U,
+ 0xafU,
+ 0xbdU,
+ 0x03U,
+ 0x01U,
+ 0x13U,
+ 0x8aU,
+ 0x6bU,
+ 0x3aU,
+ 0x91U,
+ 0x11U,
+ 0x41U,
+ 0x4fU,
+ 0x67U,
+ 0xdcU,
+ 0xeaU,
+ 0x97U,
+ 0xf2U,
+ 0xcfU,
+ 0xceU,
+ 0xf0U,
+ 0xb4U,
+ 0xe6U,
+ 0x73U,
+ 0x96U,
+ 0xacU,
+ 0x74U,
+ 0x22U,
+ 0xe7U,
+ 0xadU,
+ 0x35U,
+ 0x85U,
+ 0xe2U,
+ 0xf9U,
+ 0x37U,
+ 0xe8U,
+ 0x1cU,
+ 0x75U,
+ 0xdfU,
+ 0x6eU,
+ 0x47U,
+ 0xf1U,
+ 0x1aU,
+ 0x71U,
+ 0x1dU,
+ 0x29U,
+ 0xc5U,
+ 0x89U,
+ 0x6fU,
+ 0xb7U,
+ 0x62U,
+ 0x0eU,
+ 0xaaU,
+ 0x18U,
+ 0xbeU,
+ 0x1bU,
+ 0xfcU,
+ 0x56U,
+ 0x3eU,
+ 0x4bU,
+ 0xc6U,
+ 0xd2U,
+ 0x79U,
+ 0x20U,
+ 0x9aU,
+ 0xdbU,
+ 0xc0U,
+ 0xfeU,
+ 0x78U,
+ 0xcdU,
+ 0x5aU,
+ 0xf4U,
+ 0x1fU,
+ 0xddU,
+ 0xa8U,
+ 0x33U,
+ 0x88U,
+ 0x07U,
+ 0xc7U,
+ 0x31U,
+ 0xb1U,
+ 0x12U,
+ 0x10U,
+ 0x59U,
+ 0x27U,
+ 0x80U,
+ 0xecU,
+ 0x5fU,
+ 0x60U,
+ 0x51U,
+ 0x7fU,
+ 0xa9U,
+ 0x19U,
+ 0xb5U,
+ 0x4aU,
+ 0x0dU,
+ 0x2dU,
+ 0xe5U,
+ 0x7aU,
+ 0x9fU,
+ 0x93U,
+ 0xc9U,
+ 0x9cU,
+ 0xefU,
+ 0xa0U,
+ 0xe0U,
+ 0x3bU,
+ 0x4dU,
+ 0xaeU,
+ 0x2aU,
+ 0xf5U,
+ 0xb0U,
+ 0xc8U,
+ 0xebU,
+ 0xbbU,
+ 0x3cU,
+ 0x83U,
+ 0x53U,
+ 0x99U,
+ 0x61U,
+ 0x17U,
+ 0x2bU,
+ 0x04U,
+ 0x7eU,
+ 0xbaU,
+ 0x77U,
+ 0xd6U,
+ 0x26U,
+ 0xe1U,
+ 0x69U,
+ 0x14U,
+ 0x63U,
+ 0x55U,
+ 0x21U,
+ 0x0cU,
+ 0x7dU,
+};
+
+// RCON
+constexpr u32 RCON[] = {
+ 0x01000000,
+ 0x02000000,
+ 0x04000000,
+ 0x08000000,
+ 0x10000000,
+ 0x20000000,
+ 0x40000000,
+ 0x80000000,
+ 0x1B000000,
+ 0x36000000,
+};
+}
+}
+
+}
diff --git a/Userland/Libraries/LibCrypto/Cipher/Cipher.h b/Userland/Libraries/LibCrypto/Cipher/Cipher.h
new file mode 100644
index 0000000000..3b16c61cd9
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Cipher/Cipher.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Optional.h>
+#include <AK/Span.h>
+#include <AK/Types.h>
+
+namespace Crypto {
+namespace Cipher {
+
+enum class Intent {
+ Encryption,
+ Decryption,
+};
+
+enum class PaddingMode {
+ CMS, // RFC 1423
+ RFC5246, // very similar to CMS, but filled with |length - 1|, instead of |length|
+ Null,
+ // FIXME: We do not implement these yet
+ Bit,
+ Random,
+ Space,
+ ZeroLength,
+};
+
+template<typename B, typename T>
+class Cipher;
+
+struct CipherBlock {
+public:
+ explicit CipherBlock(PaddingMode mode)
+ : m_padding_mode(mode)
+ {
+ }
+
+ static size_t block_size() { ASSERT_NOT_REACHED(); }
+
+ virtual ReadonlyBytes bytes() const = 0;
+
+ virtual void overwrite(ReadonlyBytes) = 0;
+ virtual void overwrite(const u8* data, size_t size) { overwrite({ data, size }); }
+
+ virtual void apply_initialization_vector(const u8* ivec) = 0;
+
+ PaddingMode padding_mode() const { return m_padding_mode; }
+ void set_padding_mode(PaddingMode mode) { m_padding_mode = mode; }
+
+ template<typename T>
+ void put(size_t offset, T value)
+ {
+ ASSERT(offset + sizeof(T) <= bytes().size());
+ auto* ptr = bytes().offset_pointer(offset);
+ auto index { 0 };
+
+ ASSERT(sizeof(T) <= 4);
+
+ if constexpr (sizeof(T) > 3)
+ ptr[index++] = (u8)(value >> 24);
+
+ if constexpr (sizeof(T) > 2)
+ ptr[index++] = (u8)(value >> 16);
+
+ if constexpr (sizeof(T) > 1)
+ ptr[index++] = (u8)(value >> 8);
+
+ ptr[index] = (u8)value;
+ }
+
+private:
+ virtual Bytes bytes() = 0;
+ PaddingMode m_padding_mode;
+};
+
+struct CipherKey {
+ virtual ReadonlyBytes bytes() const = 0;
+ static bool is_valid_key_size(size_t) { return false; };
+
+ virtual ~CipherKey() { }
+
+protected:
+ virtual void expand_encrypt_key(ReadonlyBytes user_key, size_t bits) = 0;
+ virtual void expand_decrypt_key(ReadonlyBytes user_key, size_t bits) = 0;
+ size_t bits { 0 };
+};
+
+template<typename KeyT = CipherKey, typename BlockT = CipherBlock>
+class Cipher {
+public:
+ using KeyType = KeyT;
+ using BlockType = BlockT;
+
+ explicit Cipher<KeyT, BlockT>(PaddingMode mode)
+ : m_padding_mode(mode)
+ {
+ }
+
+ virtual const KeyType& key() const = 0;
+ virtual KeyType& key() = 0;
+
+ static size_t block_size() { return BlockType::block_size(); }
+
+ PaddingMode padding_mode() const { return m_padding_mode; }
+
+ virtual void encrypt_block(const BlockType& in, BlockType& out) = 0;
+ virtual void decrypt_block(const BlockType& in, BlockType& out) = 0;
+
+ virtual String class_name() const = 0;
+
+private:
+ PaddingMode m_padding_mode;
+};
+}
+}
diff --git a/Userland/Libraries/LibCrypto/Cipher/Mode/CBC.h b/Userland/Libraries/LibCrypto/Cipher/Mode/CBC.h
new file mode 100644
index 0000000000..68358a05ca
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Cipher/Mode/CBC.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/StringView.h>
+#include <LibCrypto/Cipher/Mode/Mode.h>
+
+namespace Crypto {
+namespace Cipher {
+
+template<typename T>
+class CBC : public Mode<T> {
+public:
+ constexpr static size_t IVSizeInBits = 128;
+
+ virtual ~CBC() { }
+ template<typename... Args>
+ explicit constexpr CBC<T>(Args... args)
+ : Mode<T>(args...)
+ {
+ }
+
+ virtual String class_name() const override
+ {
+ StringBuilder builder;
+ builder.append(this->cipher().class_name());
+ builder.append("_CBC");
+ return builder.build();
+ }
+
+ virtual size_t IV_length() const override { return IVSizeInBits / 8; }
+
+ virtual void encrypt(ReadonlyBytes in, Bytes& out, ReadonlyBytes ivec = {}, Bytes* ivec_out = nullptr) override
+ {
+ auto length = in.size();
+ if (length == 0)
+ return;
+
+ auto& cipher = this->cipher();
+
+ // FIXME: We should have two of these encrypt/decrypt functions that
+ // we SFINAE out based on whether the Cipher mode needs an ivec
+ ASSERT(!ivec.is_empty());
+ const auto* iv = ivec.data();
+
+ m_cipher_block.set_padding_mode(cipher.padding_mode());
+ size_t offset { 0 };
+ auto block_size = cipher.block_size();
+
+ while (length >= block_size) {
+ m_cipher_block.overwrite(in.slice(offset, block_size));
+ m_cipher_block.apply_initialization_vector(iv);
+ cipher.encrypt_block(m_cipher_block, m_cipher_block);
+ ASSERT(offset + block_size <= out.size());
+ __builtin_memcpy(out.offset(offset), m_cipher_block.bytes().data(), block_size);
+ iv = out.offset(offset);
+ length -= block_size;
+ offset += block_size;
+ }
+
+ if (length > 0) {
+ m_cipher_block.overwrite(in.slice(offset, length));
+ m_cipher_block.apply_initialization_vector(iv);
+ cipher.encrypt_block(m_cipher_block, m_cipher_block);
+ ASSERT(offset + block_size <= out.size());
+ __builtin_memcpy(out.offset(offset), m_cipher_block.bytes().data(), block_size);
+ iv = out.offset(offset);
+ }
+
+ if (ivec_out)
+ __builtin_memcpy(ivec_out->data(), iv, min(IV_length(), ivec_out->size()));
+ }
+
+ virtual void decrypt(ReadonlyBytes in, Bytes& out, ReadonlyBytes ivec = {}) override
+ {
+ auto length = in.size();
+ if (length == 0)
+ return;
+
+ auto& cipher = this->cipher();
+
+ ASSERT(!ivec.is_empty());
+ const auto* iv = ivec.data();
+
+ auto block_size = cipher.block_size();
+
+ // if the data is not aligned, it's not correct encrypted data
+ // FIXME (ponder): Should we simply decrypt as much as we can?
+ ASSERT(length % block_size == 0);
+
+ m_cipher_block.set_padding_mode(cipher.padding_mode());
+ size_t offset { 0 };
+
+ while (length > 0) {
+ auto* slice = in.offset(offset);
+ m_cipher_block.overwrite(slice, block_size);
+ cipher.decrypt_block(m_cipher_block, m_cipher_block);
+ m_cipher_block.apply_initialization_vector(iv);
+ auto decrypted = m_cipher_block.bytes();
+ ASSERT(offset + decrypted.size() <= out.size());
+ __builtin_memcpy(out.offset(offset), decrypted.data(), decrypted.size());
+ iv = slice;
+ length -= block_size;
+ offset += block_size;
+ }
+ out = out.slice(0, offset);
+ this->prune_padding(out);
+ }
+
+private:
+ typename T::BlockType m_cipher_block {};
+};
+
+}
+
+}
diff --git a/Userland/Libraries/LibCrypto/Cipher/Mode/CTR.h b/Userland/Libraries/LibCrypto/Cipher/Mode/CTR.h
new file mode 100644
index 0000000000..26895a8fca
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Cipher/Mode/CTR.h
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/StringView.h>
+#include <LibCrypto/Cipher/Mode/Mode.h>
+
+namespace Crypto {
+namespace Cipher {
+
+/*
+ * Heads up: CTR is a *family* of modes, because the "counter" function is
+ * implementation-defined. This makes interoperability a pain in the neurons.
+ * Here are several contradicting(!) interpretations:
+ *
+ * "The counter can be *any function* which produces a sequence which is
+ * guaranteed not to repeat for a long time, although an actual increment-by-one
+ * counter is the simplest and most popular."
+ * The illustrations show that first increment should happen *after* the first
+ * round. I call this variant BIGINT_INCR_0.
+ * The AESAVS goes a step further and requires only that "counters" do not
+ * repeat, leaving the method of counting completely open.
+ * See: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)
+ * See: https://csrc.nist.gov/csrc/media/projects/cryptographic-algorithm-validation-program/documents/aes/aesavs.pdf
+ *
+ * BIGINT_INCR_0 is the behavior of the OpenSSL command "openssl enc -aes-128-ctr",
+ * and the behavior of CRYPTO_ctr128_encrypt(). OpenSSL is not alone in the
+ * assumption that BIGINT_INCR_0 is all there is; even some NIST
+ * specification/survey(?) doesn't consider counting any other way.
+ * See: https://github.com/openssl/openssl/blob/33388b44b67145af2181b1e9528c381c8ea0d1b6/crypto/modes/ctr128.c#L71
+ * See: http://www.cryptogrium.com/aes-ctr.html
+ * See: https://web.archive.org/web/20150226072817/http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ctr/ctr-spec.pdf
+ *
+ * "[T]he successive counter blocks are derived by applying an incrementing
+ * function."
+ * It defines a *family* of functions called "Standard Incrementing Function"
+ * which only increment the lower-m bits, for some number 0<m<=blocksize.
+ * The included test vectors suggest that the first increment should happen
+ * *after* the first round. I call this INT32_INCR_0, or in general INTm_INCR_0.
+ * This in particular is the behavior of CRYPTO_ctr128_encrypt_ctr32() in OpenSSL.
+ * See: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf
+ * See: https://github.com/openssl/openssl/blob/33388b44b67145af2181b1e9528c381c8ea0d1b6/crypto/modes/ctr128.c#L147
+ *
+ * The python package "cryptography" and RFC 3686 (which appears among the
+ * first online search results when searching for "AES CTR 128 test vector")
+ * share a peculiar interpretation of CTR mode: the counter is incremented *before*
+ * the first round. RFC 3686 does not consider any other interpretation. I call
+ * this variant BIGINT_INCR_1.
+ * See: https://tools.ietf.org/html/rfc3686.html#section-6
+ * See: https://cryptography.io/en/latest/development/test-vectors/#symmetric-ciphers
+ *
+ * And finally, because the method is left open, a different increment could be
+ * used, for example little endian, or host endian, or mixed endian. Or any crazy
+ * LSFR with sufficiently large period. That is the reason for the constant part
+ * "INCR" in the previous counters.
+ *
+ * Due to this plethora of mutually-incompatible counters,
+ * the method of counting should be a template parameter.
+ * This currently implements BIGINT_INCR_0, which means perfect
+ * interoperability with openssl. The test vectors from RFC 3686 just need to be
+ * incremented by 1.
+ * TODO: Implement other counters?
+ */
+
+struct IncrementInplace {
+ void operator()(Bytes& in) const
+ {
+ for (size_t i = in.size(); i > 0;) {
+ --i;
+ if (in[i] == (u8)-1) {
+ in[i] = 0;
+ } else {
+ in[i]++;
+ break;
+ }
+ }
+ }
+};
+
+template<typename T, typename IncrementFunctionType = IncrementInplace>
+class CTR : public Mode<T> {
+public:
+ constexpr static size_t IVSizeInBits = 128;
+
+ virtual ~CTR() { }
+
+ // Must intercept `Intent`, because AES must always be set to
+ // Encryption, even when decrypting AES-CTR.
+ // TODO: How to deal with ciphers that take different arguments?
+ // FIXME: Add back the default intent parameter once clang-11 is the default in GitHub Actions.
+ // Once added back, remove the parameter where it's constructed in get_random_bytes in Kernel/Random.h.
+ template<typename KeyType, typename... Args>
+ explicit constexpr CTR(const KeyType& user_key, size_t key_bits, Intent, Args... args)
+ : Mode<T>(user_key, key_bits, Intent::Encryption, args...)
+ {
+ }
+
+ virtual String class_name() const override
+ {
+ StringBuilder builder;
+ builder.append(this->cipher().class_name());
+ builder.append("_CTR");
+ return builder.build();
+ }
+
+ virtual size_t IV_length() const override { return IVSizeInBits / 8; }
+
+ virtual void encrypt(ReadonlyBytes in, Bytes& out, ReadonlyBytes ivec = {}, Bytes* ivec_out = nullptr) override
+ {
+ // Our interpretation of "ivec" is what AES-CTR
+ // would define as nonce + IV + 4 zero bytes.
+ this->encrypt_or_stream(&in, out, ivec, ivec_out);
+ }
+
+ void key_stream(Bytes& out, const Bytes& ivec = {}, Bytes* ivec_out = nullptr)
+ {
+ this->encrypt_or_stream(nullptr, out, ivec, ivec_out);
+ }
+
+ virtual void decrypt(ReadonlyBytes in, Bytes& out, ReadonlyBytes ivec = {}) override
+ {
+ // XOR (and thus CTR) is the most symmetric mode.
+ this->encrypt(in, out, ivec);
+ }
+
+private:
+ u8 m_ivec_storage[IVSizeInBits / 8];
+ typename T::BlockType m_cipher_block {};
+
+protected:
+ constexpr static IncrementFunctionType increment {};
+
+ void encrypt_or_stream(const ReadonlyBytes* in, Bytes& out, ReadonlyBytes ivec, Bytes* ivec_out = nullptr)
+ {
+ size_t length;
+ if (in) {
+ ASSERT(in->size() <= out.size());
+ length = in->size();
+ if (length == 0)
+ return;
+ } else {
+ length = out.size();
+ }
+
+ auto& cipher = this->cipher();
+
+ // FIXME: We should have two of these encrypt/decrypt functions that
+ // we SFINAE out based on whether the Cipher mode needs an ivec
+ ASSERT(!ivec.is_empty());
+ ASSERT(ivec.size() >= IV_length());
+
+ m_cipher_block.set_padding_mode(cipher.padding_mode());
+
+ __builtin_memcpy(m_ivec_storage, ivec.data(), IV_length());
+ Bytes iv { m_ivec_storage, IV_length() };
+
+ size_t offset { 0 };
+ auto block_size = cipher.block_size();
+
+ while (length > 0) {
+ m_cipher_block.overwrite(iv.slice(0, block_size));
+
+ cipher.encrypt_block(m_cipher_block, m_cipher_block);
+ if (in) {
+ m_cipher_block.apply_initialization_vector(in->data() + offset);
+ }
+ auto write_size = min(block_size, length);
+
+ ASSERT(offset + write_size <= out.size());
+ __builtin_memcpy(out.offset(offset), m_cipher_block.bytes().data(), write_size);
+
+ increment(iv);
+ length -= write_size;
+ offset += write_size;
+ }
+
+ if (ivec_out)
+ __builtin_memcpy(ivec_out->data(), iv.data(), min(ivec_out->size(), IV_length()));
+ }
+};
+
+}
+}
diff --git a/Userland/Libraries/LibCrypto/Cipher/Mode/GCM.h b/Userland/Libraries/LibCrypto/Cipher/Mode/GCM.h
new file mode 100644
index 0000000000..4d034f5bac
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Cipher/Mode/GCM.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/OwnPtr.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/StringView.h>
+#include <LibCrypto/Authentication/GHash.h>
+#include <LibCrypto/Cipher/Mode/CTR.h>
+#include <LibCrypto/Verification.h>
+
+namespace Crypto {
+namespace Cipher {
+
+using IncrementFunction = IncrementInplace;
+
+template<typename T>
+class GCM : public CTR<T, IncrementFunction> {
+public:
+ constexpr static size_t IVSizeInBits = 128;
+
+ virtual ~GCM() { }
+
+ template<typename... Args>
+ explicit constexpr GCM<T>(Args... args)
+ : CTR<T>(args...)
+ {
+ static_assert(T::BlockSizeInBits == 128u, "GCM Mode is only available for 128-bit Ciphers");
+
+ __builtin_memset(m_auth_key_storage, 0, block_size);
+ typename T::BlockType key_block(m_auth_key_storage, block_size);
+ this->cipher().encrypt_block(key_block, key_block);
+ key_block.bytes().copy_to(m_auth_key);
+
+ m_ghash = make<Authentication::GHash>(m_auth_key);
+ }
+
+ virtual String class_name() const override
+ {
+ StringBuilder builder;
+ builder.append(this->cipher().class_name());
+ builder.append("_GCM");
+ return builder.build();
+ }
+
+ virtual size_t IV_length() const override { return IVSizeInBits / 8; }
+
+ // FIXME: This overload throws away the auth stuff, think up a better way to return more than a single bytebuffer.
+ virtual void encrypt(ReadonlyBytes in, Bytes& out, ReadonlyBytes ivec = {}, Bytes* = nullptr) override
+ {
+ ASSERT(!ivec.is_empty());
+
+ static ByteBuffer dummy;
+
+ encrypt(in, out, ivec, dummy, dummy);
+ }
+ virtual void decrypt(ReadonlyBytes in, Bytes& out, ReadonlyBytes ivec = {}) override
+ {
+ encrypt(in, out, ivec);
+ }
+
+ void encrypt(const ReadonlyBytes& in, Bytes out, const ReadonlyBytes& iv_in, const ReadonlyBytes& aad, Bytes tag)
+ {
+ auto iv_buf = ByteBuffer::copy(iv_in.data(), iv_in.size());
+ auto iv = iv_buf.bytes();
+
+ // Increment the IV for block 0
+ CTR<T>::increment(iv);
+ typename T::BlockType block0;
+ block0.overwrite(iv);
+ this->cipher().encrypt_block(block0, block0);
+
+ // Skip past block 0
+ CTR<T>::increment(iv);
+
+ if (in.is_empty())
+ CTR<T>::key_stream(out, iv);
+ else
+ CTR<T>::encrypt(in, out, iv);
+
+ auto auth_tag = m_ghash->process(aad, out);
+ block0.apply_initialization_vector(auth_tag.data);
+ block0.bytes().copy_to(tag);
+ }
+
+ VerificationConsistency decrypt(ReadonlyBytes in, Bytes out, ReadonlyBytes iv_in, ReadonlyBytes aad, ReadonlyBytes tag)
+ {
+ auto iv_buf = ByteBuffer::copy(iv_in.data(), iv_in.size());
+ auto iv = iv_buf.bytes();
+
+ // Increment the IV for block 0
+ CTR<T>::increment(iv);
+ typename T::BlockType block0;
+ block0.overwrite(iv);
+ this->cipher().encrypt_block(block0, block0);
+
+ // Skip past block 0
+ CTR<T>::increment(iv);
+
+ auto auth_tag = m_ghash->process(aad, in);
+ block0.apply_initialization_vector(auth_tag.data);
+
+ auto test_consistency = [&] {
+ if (block0.block_size() != tag.size() || __builtin_memcmp(block0.bytes().data(), tag.data(), tag.size()) != 0)
+ return VerificationConsistency::Inconsistent;
+
+ return VerificationConsistency::Consistent;
+ };
+ // FIXME: This block needs constant-time comparisons.
+
+ if (in.is_empty()) {
+ out = {};
+ return test_consistency();
+ }
+
+ CTR<T>::encrypt(in, out, iv);
+ return test_consistency();
+ }
+
+private:
+ static constexpr auto block_size = T::BlockType::BlockSizeInBits / 8;
+ u8 m_auth_key_storage[block_size];
+ Bytes m_auth_key { m_auth_key_storage, block_size };
+ OwnPtr<Authentication::GHash> m_ghash;
+};
+
+}
+
+}
diff --git a/Userland/Libraries/LibCrypto/Cipher/Mode/Mode.h b/Userland/Libraries/LibCrypto/Cipher/Mode/Mode.h
new file mode 100644
index 0000000000..a34f6a4b98
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Cipher/Mode/Mode.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/Span.h>
+#include <AK/StdLibExtras.h>
+#include <LibCrypto/Cipher/Cipher.h>
+
+namespace Crypto {
+namespace Cipher {
+
+template<typename T>
+class Mode {
+public:
+ virtual ~Mode() { }
+
+ virtual void encrypt(ReadonlyBytes in, Bytes& out, ReadonlyBytes ivec = {}, Bytes* ivec_out = nullptr) = 0;
+ virtual void decrypt(ReadonlyBytes in, Bytes& out, ReadonlyBytes ivec = {}) = 0;
+
+ virtual size_t IV_length() const = 0;
+
+ const T& cipher() const { return m_cipher; }
+
+ ByteBuffer create_aligned_buffer(size_t input_size) const
+ {
+ size_t remainder = (input_size + T::block_size()) % T::block_size();
+ if (remainder == 0)
+ return ByteBuffer::create_uninitialized(input_size);
+ else
+ return ByteBuffer::create_uninitialized(input_size + T::block_size() - remainder);
+ }
+
+ virtual String class_name() const = 0;
+ T& cipher() { return m_cipher; }
+
+protected:
+ virtual void prune_padding(Bytes& data)
+ {
+ auto size = data.size();
+ switch (m_cipher.padding_mode()) {
+ case PaddingMode::CMS: {
+ auto maybe_padding_length = data[size - 1];
+ if (maybe_padding_length >= T::block_size()) {
+ // cannot be padding (the entire block cannot be padding)
+ return;
+ }
+ for (auto i = size - maybe_padding_length; i < size; ++i) {
+ if (data[i] != maybe_padding_length) {
+ // not padding, part of data
+ return;
+ }
+ }
+ data = data.slice(0, size - maybe_padding_length);
+ break;
+ }
+ case PaddingMode::RFC5246: {
+ auto maybe_padding_length = data[size - 1];
+ // FIXME: If we want constant-time operations, this loop should not stop
+ for (auto i = size - maybe_padding_length - 1; i < size; ++i) {
+ if (data[i] != maybe_padding_length) {
+ // note that this is likely invalid padding
+ return;
+ }
+ }
+ data = data.slice(0, size - maybe_padding_length - 1);
+ break;
+ }
+ case PaddingMode::Null: {
+ while (data[size - 1] == 0)
+ --size;
+ data = data.slice(0, size);
+ break;
+ }
+ default:
+ // FIXME: support other padding modes
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ }
+
+ // FIXME: Somehow add a reference version of this
+ template<typename... Args>
+ Mode(Args... args)
+ : m_cipher(args...)
+ {
+ }
+
+private:
+ T m_cipher;
+};
+}
+
+}
diff --git a/Userland/Libraries/LibCrypto/Hash/HashFunction.h b/Userland/Libraries/LibCrypto/Hash/HashFunction.h
new file mode 100644
index 0000000000..3529615f50
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Hash/HashFunction.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/StringView.h>
+#include <AK/Types.h>
+
+namespace Crypto {
+namespace Hash {
+
+template<size_t BlockS, typename DigestT>
+class HashFunction {
+public:
+ static constexpr auto BlockSize = BlockS / 8;
+ static constexpr auto DigestSize = DigestT::Size;
+
+ using DigestType = DigestT;
+
+ static size_t block_size() { return BlockSize; };
+ static size_t digest_size() { return DigestSize; };
+
+ virtual void update(const u8*, size_t) = 0;
+
+ void update(const Bytes& buffer) { update(buffer.data(), buffer.size()); };
+ void update(const ReadonlyBytes& buffer) { update(buffer.data(), buffer.size()); };
+ void update(const ByteBuffer& buffer) { update(buffer.data(), buffer.size()); };
+ void update(const StringView& string) { update((const u8*)string.characters_without_null_termination(), string.length()); };
+
+ virtual DigestType peek() = 0;
+ virtual DigestType digest() = 0;
+
+ virtual void reset() = 0;
+
+ virtual String class_name() const = 0;
+};
+}
+}
diff --git a/Userland/Libraries/LibCrypto/Hash/HashManager.h b/Userland/Libraries/LibCrypto/Hash/HashManager.h
new file mode 100644
index 0000000000..9b47149602
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Hash/HashManager.h
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Optional.h>
+#include <AK/OwnPtr.h>
+#include <LibCrypto/Hash/HashFunction.h>
+#include <LibCrypto/Hash/MD5.h>
+#include <LibCrypto/Hash/SHA1.h>
+#include <LibCrypto/Hash/SHA2.h>
+
+namespace Crypto {
+namespace Hash {
+
+enum class HashKind {
+ None,
+ SHA1,
+ SHA256,
+ SHA512,
+ MD5,
+};
+
+struct MultiHashDigestVariant {
+
+ constexpr static size_t Size = 0;
+
+ MultiHashDigestVariant(SHA1::DigestType digest)
+ : sha1(digest)
+ , kind(HashKind::SHA1)
+ {
+ }
+
+ MultiHashDigestVariant(SHA256::DigestType digest)
+ : sha256(digest)
+ , kind(HashKind::SHA256)
+ {
+ }
+
+ MultiHashDigestVariant(SHA512::DigestType digest)
+ : sha512(digest)
+ , kind(HashKind::SHA512)
+ {
+ }
+
+ MultiHashDigestVariant(MD5::DigestType digest)
+ : md5(digest)
+ , kind(HashKind::MD5)
+ {
+ }
+
+ const u8* immutable_data() const
+ {
+ switch (kind) {
+ case HashKind::MD5:
+ return md5.value().immutable_data();
+ case HashKind::SHA1:
+ return sha1.value().immutable_data();
+ case HashKind::SHA256:
+ return sha256.value().immutable_data();
+ case HashKind::SHA512:
+ return sha512.value().immutable_data();
+ default:
+ case HashKind::None:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ }
+
+ size_t data_length()
+ {
+ switch (kind) {
+ case HashKind::MD5:
+ return md5.value().data_length();
+ case HashKind::SHA1:
+ return sha1.value().data_length();
+ case HashKind::SHA256:
+ return sha256.value().data_length();
+ case HashKind::SHA512:
+ return sha512.value().data_length();
+ default:
+ case HashKind::None:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ }
+
+ Optional<SHA1::DigestType> sha1;
+ Optional<SHA256::DigestType> sha256;
+ Optional<SHA512::DigestType> sha512;
+ Optional<MD5::DigestType> md5;
+ HashKind kind { HashKind::None };
+};
+
+class Manager final : public HashFunction<0, MultiHashDigestVariant> {
+public:
+ using HashFunction::update;
+
+ Manager()
+ {
+ m_pre_init_buffer = ByteBuffer::create_zeroed(0);
+ }
+
+ Manager(const Manager& other) // NOT a copy constructor!
+ {
+ m_pre_init_buffer = ByteBuffer::create_zeroed(0); // will not be used
+ initialize(other.m_kind);
+ }
+
+ Manager(HashKind kind)
+ {
+ m_pre_init_buffer = ByteBuffer::create_zeroed(0);
+ initialize(kind);
+ }
+
+ ~Manager()
+ {
+ m_sha1 = nullptr;
+ m_sha256 = nullptr;
+ m_sha512 = nullptr;
+ m_md5 = nullptr;
+ }
+
+ inline size_t digest_size() const
+ {
+ switch (m_kind) {
+ case HashKind::MD5:
+ return m_md5->digest_size();
+ case HashKind::SHA1:
+ return m_sha1->digest_size();
+ case HashKind::SHA256:
+ return m_sha256->digest_size();
+ case HashKind::SHA512:
+ return m_sha512->digest_size();
+ default:
+ case HashKind::None:
+ return 0;
+ }
+ }
+ inline size_t block_size() const
+ {
+ switch (m_kind) {
+ case HashKind::MD5:
+ return m_md5->block_size();
+ case HashKind::SHA1:
+ return m_sha1->block_size();
+ case HashKind::SHA256:
+ return m_sha256->block_size();
+ case HashKind::SHA512:
+ return m_sha512->block_size();
+ default:
+ case HashKind::None:
+ return 0;
+ }
+ }
+ inline void initialize(HashKind kind)
+ {
+ if (m_kind != HashKind::None) {
+ ASSERT_NOT_REACHED();
+ }
+
+ m_kind = kind;
+ switch (kind) {
+ case HashKind::MD5:
+ m_md5 = make<MD5>();
+ break;
+ case HashKind::SHA1:
+ m_sha1 = make<SHA1>();
+ break;
+ case HashKind::SHA256:
+ m_sha256 = make<SHA256>();
+ break;
+ case HashKind::SHA512:
+ m_sha512 = make<SHA512>();
+ break;
+ default:
+ case HashKind::None:
+ break;
+ }
+ }
+
+ virtual void update(const u8* data, size_t length) override
+ {
+ auto size = m_pre_init_buffer.size();
+ switch (m_kind) {
+ case HashKind::MD5:
+ if (size)
+ m_md5->update(m_pre_init_buffer);
+ m_md5->update(data, length);
+ break;
+ case HashKind::SHA1:
+ if (size)
+ m_sha1->update(m_pre_init_buffer);
+ m_sha1->update(data, length);
+ break;
+ case HashKind::SHA256:
+ if (size)
+ m_sha256->update(m_pre_init_buffer);
+ m_sha256->update(data, length);
+ break;
+ case HashKind::SHA512:
+ if (size)
+ m_sha512->update(m_pre_init_buffer);
+ m_sha512->update(data, length);
+ break;
+ default:
+ case HashKind::None:
+ m_pre_init_buffer.append(data, length);
+ return;
+ }
+ if (size)
+ m_pre_init_buffer.clear();
+ }
+
+ virtual DigestType peek() override
+ {
+ switch (m_kind) {
+ case HashKind::MD5:
+ return { m_md5->peek() };
+ case HashKind::SHA1:
+ return { m_sha1->peek() };
+ case HashKind::SHA256:
+ return { m_sha256->peek() };
+ case HashKind::SHA512:
+ return { m_sha512->peek() };
+ default:
+ case HashKind::None:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ }
+
+ virtual DigestType digest() override
+ {
+ auto digest = peek();
+ reset();
+ return digest;
+ }
+
+ virtual void reset() override
+ {
+ m_pre_init_buffer.clear();
+ switch (m_kind) {
+ case HashKind::MD5:
+ m_md5->reset();
+ break;
+ case HashKind::SHA1:
+ m_sha1->reset();
+ break;
+ case HashKind::SHA256:
+ m_sha256->reset();
+ break;
+ case HashKind::SHA512:
+ m_sha512->reset();
+ break;
+ default:
+ case HashKind::None:
+ break;
+ }
+ }
+
+ virtual String class_name() const override
+ {
+ switch (m_kind) {
+ case HashKind::MD5:
+ return m_md5->class_name();
+ case HashKind::SHA1:
+ return m_sha1->class_name();
+ case HashKind::SHA256:
+ return m_sha256->class_name();
+ case HashKind::SHA512:
+ return m_sha512->class_name();
+ default:
+ case HashKind::None:
+ return "UninitializedHashManager";
+ }
+ }
+
+ inline bool is(HashKind kind) const
+ {
+ return m_kind == kind;
+ }
+
+private:
+ OwnPtr<SHA1> m_sha1;
+ OwnPtr<SHA256> m_sha256;
+ OwnPtr<SHA512> m_sha512;
+ OwnPtr<MD5> m_md5;
+ HashKind m_kind { HashKind::None };
+ ByteBuffer m_pre_init_buffer;
+};
+
+}
+}
diff --git a/Userland/Libraries/LibCrypto/Hash/MD5.cpp b/Userland/Libraries/LibCrypto/Hash/MD5.cpp
new file mode 100644
index 0000000000..1b2a71d30a
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Hash/MD5.cpp
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Types.h>
+#include <LibCrypto/Hash/MD5.h>
+
+static constexpr u32 F(u32 x, u32 y, u32 z) { return (x & y) | ((~x) & z); };
+static constexpr u32 G(u32 x, u32 y, u32 z) { return (x & z) | ((~z) & y); };
+static constexpr u32 H(u32 x, u32 y, u32 z) { return x ^ y ^ z; };
+static constexpr u32 I(u32 x, u32 y, u32 z) { return y ^ (x | ~z); };
+static constexpr u32 ROTATE_LEFT(u32 x, size_t n)
+{
+ return (x << n) | (x >> (32 - n));
+}
+
+static constexpr void round_1(u32& a, u32 b, u32 c, u32 d, u32 x, u32 s, u32 ac)
+{
+ a += F(b, c, d) + x + ac;
+ a = ROTATE_LEFT(a, s);
+ a += b;
+}
+
+static constexpr void round_2(u32& a, u32 b, u32 c, u32 d, u32 x, u32 s, u32 ac)
+{
+ a += G(b, c, d) + x + ac;
+ a = ROTATE_LEFT(a, s);
+ a += b;
+}
+
+static constexpr void round_3(u32& a, u32 b, u32 c, u32 d, u32 x, u32 s, u32 ac)
+{
+ a += H(b, c, d) + x + ac;
+ a = ROTATE_LEFT(a, s);
+ a += b;
+}
+
+static constexpr void round_4(u32& a, u32 b, u32 c, u32 d, u32 x, u32 s, u32 ac)
+{
+ a += I(b, c, d) + x + ac;
+ a = ROTATE_LEFT(a, s);
+ a += b;
+}
+
+namespace Crypto {
+namespace Hash {
+
+void MD5::update(const u8* input, size_t length)
+{
+ auto index = (u32)(m_count[0] >> 3) & 0x3f;
+ size_t offset { 0 };
+ m_count[0] += (u32)length << 3;
+ if (m_count[0] < ((u32)length << 3)) {
+ ++m_count[1];
+ }
+ m_count[1] += (u32)length >> 29;
+
+ auto part_length = 64 - index;
+ if (length >= part_length) {
+ m_buffer.overwrite(index, input, part_length);
+ transform(m_buffer.data());
+
+ for (offset = part_length; offset + 63 < length; offset += 64)
+ transform(&input[offset]);
+
+ index = 0;
+ }
+
+ ASSERT(length < part_length || length - offset <= 64);
+ m_buffer.overwrite(index, &input[offset], length - offset);
+}
+MD5::DigestType MD5::digest()
+{
+ auto digest = peek();
+ reset();
+ return digest;
+}
+
+MD5::DigestType MD5::peek()
+{
+ DigestType digest;
+ u8 bits[8];
+
+ encode(m_count, bits, 8);
+
+ // pad the data to 56%64
+ u32 index = (u32)((m_count[0] >> 3) & 0x3f);
+ u32 pad_length = index < 56 ? 56 - index : 120 - index;
+ update(MD5Constants::PADDING, pad_length);
+
+ // append length
+ update(bits, 8);
+
+ // store state (4 registers ABCD)
+ encode(&m_A, digest.data, 4 * sizeof(m_A));
+
+ return digest;
+}
+
+void MD5::encode(const u32* from, u8* to, size_t length)
+{
+ for (size_t i = 0, j = 0; j < length; ++i, j += 4) {
+ to[j] = (u8)(from[i] & 0xff);
+ to[j + 1] = (u8)((from[i] >> 8) & 0xff);
+ to[j + 2] = (u8)((from[i] >> 16) & 0xff);
+ to[j + 3] = (u8)((from[i] >> 24) & 0xff);
+ }
+}
+
+void MD5::decode(const u8* from, u32* to, size_t length)
+{
+ for (size_t i = 0, j = 0; j < length; ++i, j += 4)
+ to[i] = (((u32)from[j]) | (((u32)from[j + 1]) << 8) | (((u32)from[j + 2]) << 16) | (((u32)from[j + 3]) << 24));
+}
+
+void MD5::transform(const u8* block)
+{
+ auto a = m_A;
+ auto b = m_B;
+ auto c = m_C;
+ auto d = m_D;
+ u32 x[16];
+
+ decode(block, x, 64);
+
+ round_1(a, b, c, d, x[0], MD5Constants::S11, 0xd76aa478); // 1
+ round_1(d, a, b, c, x[1], MD5Constants::S12, 0xe8c7b756); // 2
+ round_1(c, d, a, b, x[2], MD5Constants::S13, 0x242070db); // 3
+ round_1(b, c, d, a, x[3], MD5Constants::S14, 0xc1bdceee); // 4
+ round_1(a, b, c, d, x[4], MD5Constants::S11, 0xf57c0faf); // 5
+ round_1(d, a, b, c, x[5], MD5Constants::S12, 0x4787c62a); // 6
+ round_1(c, d, a, b, x[6], MD5Constants::S13, 0xa8304613); // 7
+ round_1(b, c, d, a, x[7], MD5Constants::S14, 0xfd469501); // 8
+ round_1(a, b, c, d, x[8], MD5Constants::S11, 0x698098d8); // 9
+ round_1(d, a, b, c, x[9], MD5Constants::S12, 0x8b44f7af); // 10
+ round_1(c, d, a, b, x[10], MD5Constants::S13, 0xffff5bb1); // 11
+ round_1(b, c, d, a, x[11], MD5Constants::S14, 0x895cd7be); // 12
+ round_1(a, b, c, d, x[12], MD5Constants::S11, 0x6b901122); // 13
+ round_1(d, a, b, c, x[13], MD5Constants::S12, 0xfd987193); // 14
+ round_1(c, d, a, b, x[14], MD5Constants::S13, 0xa679438e); // 15
+ round_1(b, c, d, a, x[15], MD5Constants::S14, 0x49b40821); // 16
+
+ round_2(a, b, c, d, x[1], MD5Constants::S21, 0xf61e2562); // 17
+ round_2(d, a, b, c, x[6], MD5Constants::S22, 0xc040b340); // 18
+ round_2(c, d, a, b, x[11], MD5Constants::S23, 0x265e5a51); // 19
+ round_2(b, c, d, a, x[0], MD5Constants::S24, 0xe9b6c7aa); // 20
+ round_2(a, b, c, d, x[5], MD5Constants::S21, 0xd62f105d); // 21
+ round_2(d, a, b, c, x[10], MD5Constants::S22, 0x2441453); // 22
+ round_2(c, d, a, b, x[15], MD5Constants::S23, 0xd8a1e681); // 23
+ round_2(b, c, d, a, x[4], MD5Constants::S24, 0xe7d3fbc8); // 24
+ round_2(a, b, c, d, x[9], MD5Constants::S21, 0x21e1cde6); // 25
+ round_2(d, a, b, c, x[14], MD5Constants::S22, 0xc33707d6); // 26
+ round_2(c, d, a, b, x[3], MD5Constants::S23, 0xf4d50d87); // 27
+ round_2(b, c, d, a, x[8], MD5Constants::S24, 0x455a14ed); // 28
+ round_2(a, b, c, d, x[13], MD5Constants::S21, 0xa9e3e905); // 29
+ round_2(d, a, b, c, x[2], MD5Constants::S22, 0xfcefa3f8); // 30
+ round_2(c, d, a, b, x[7], MD5Constants::S23, 0x676f02d9); // 31
+ round_2(b, c, d, a, x[12], MD5Constants::S24, 0x8d2a4c8a); // 32
+
+ round_3(a, b, c, d, x[5], MD5Constants::S31, 0xfffa3942); // 33
+ round_3(d, a, b, c, x[8], MD5Constants::S32, 0x8771f681); // 34
+ round_3(c, d, a, b, x[11], MD5Constants::S33, 0x6d9d6122); // 35
+ round_3(b, c, d, a, x[14], MD5Constants::S34, 0xfde5380c); // 36
+ round_3(a, b, c, d, x[1], MD5Constants::S31, 0xa4beea44); // 37
+ round_3(d, a, b, c, x[4], MD5Constants::S32, 0x4bdecfa9); // 38
+ round_3(c, d, a, b, x[7], MD5Constants::S33, 0xf6bb4b60); // 39
+ round_3(b, c, d, a, x[10], MD5Constants::S34, 0xbebfbc70); // 40
+ round_3(a, b, c, d, x[13], MD5Constants::S31, 0x289b7ec6); // 41
+ round_3(d, a, b, c, x[0], MD5Constants::S32, 0xeaa127fa); // 42
+ round_3(c, d, a, b, x[3], MD5Constants::S33, 0xd4ef3085); // 43
+ round_3(b, c, d, a, x[6], MD5Constants::S34, 0x4881d05); // 44
+ round_3(a, b, c, d, x[9], MD5Constants::S31, 0xd9d4d039); // 45
+ round_3(d, a, b, c, x[12], MD5Constants::S32, 0xe6db99e5); // 46
+ round_3(c, d, a, b, x[15], MD5Constants::S33, 0x1fa27cf8); // 47
+ round_3(b, c, d, a, x[2], MD5Constants::S34, 0xc4ac5665); // 48
+
+ round_4(a, b, c, d, x[0], MD5Constants::S41, 0xf4292244); // 49
+ round_4(d, a, b, c, x[7], MD5Constants::S42, 0x432aff97); // 50
+ round_4(c, d, a, b, x[14], MD5Constants::S43, 0xab9423a7); // 51
+ round_4(b, c, d, a, x[5], MD5Constants::S44, 0xfc93a039); // 52
+ round_4(a, b, c, d, x[12], MD5Constants::S41, 0x655b59c3); // 53
+ round_4(d, a, b, c, x[3], MD5Constants::S42, 0x8f0ccc92); // 54
+ round_4(c, d, a, b, x[10], MD5Constants::S43, 0xffeff47d); // 55
+ round_4(b, c, d, a, x[1], MD5Constants::S44, 0x85845dd1); // 56
+ round_4(a, b, c, d, x[8], MD5Constants::S41, 0x6fa87e4f); // 57
+ round_4(d, a, b, c, x[15], MD5Constants::S42, 0xfe2ce6e0); // 58
+ round_4(c, d, a, b, x[6], MD5Constants::S43, 0xa3014314); // 59
+ round_4(b, c, d, a, x[13], MD5Constants::S44, 0x4e0811a1); // 60
+ round_4(a, b, c, d, x[4], MD5Constants::S41, 0xf7537e82); // 61
+ round_4(d, a, b, c, x[11], MD5Constants::S42, 0xbd3af235); // 62
+ round_4(c, d, a, b, x[2], MD5Constants::S43, 0x2ad7d2bb); // 63
+ round_4(b, c, d, a, x[9], MD5Constants::S44, 0xeb86d391); // 64
+
+ m_A += a;
+ m_B += b;
+ m_C += c;
+ m_D += d;
+
+ __builtin_memset(x, 0, sizeof(x));
+}
+
+}
+}
diff --git a/Userland/Libraries/LibCrypto/Hash/MD5.h b/Userland/Libraries/LibCrypto/Hash/MD5.h
new file mode 100644
index 0000000000..0ff01ba756
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Hash/MD5.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <LibCrypto/Hash/HashFunction.h>
+
+namespace Crypto {
+namespace Hash {
+
+struct MD5Digest {
+ constexpr static size_t Size = 16;
+ u8 data[Size];
+
+ const u8* immutable_data() const { return data; }
+ size_t data_length() { return Size; }
+};
+
+namespace MD5Constants {
+
+constexpr u32 init_A = 0x67452301;
+constexpr u32 init_B = 0xefcdab89;
+constexpr u32 init_C = 0x98badcfe;
+constexpr u32 init_D = 0x10325476;
+constexpr u32 S11 = 7;
+constexpr u32 S12 = 12;
+constexpr u32 S13 = 17;
+constexpr u32 S14 = 22;
+constexpr u32 S21 = 5;
+constexpr u32 S22 = 9;
+constexpr u32 S23 = 14;
+constexpr u32 S24 = 20;
+constexpr u32 S31 = 4;
+constexpr u32 S32 = 11;
+constexpr u32 S33 = 16;
+constexpr u32 S34 = 23;
+constexpr u32 S41 = 6;
+constexpr u32 S42 = 10;
+constexpr u32 S43 = 15;
+constexpr u32 S44 = 21;
+constexpr u8 PADDING[] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0
+};
+
+}
+
+class MD5 final : public HashFunction<512, MD5Digest> {
+public:
+ using HashFunction::update;
+
+ MD5()
+ {
+ m_buffer = Bytes { m_data_buffer, sizeof(m_data_buffer) };
+ }
+
+ virtual void update(const u8*, size_t) override;
+ virtual DigestType digest() override;
+ virtual DigestType peek() override;
+
+ virtual String class_name() const override { return "MD5"; }
+
+ inline static DigestType hash(const u8* data, size_t length)
+ {
+ MD5 md5;
+ md5.update(data, length);
+ return md5.digest();
+ }
+
+ inline static DigestType hash(const ByteBuffer& buffer) { return hash(buffer.data(), buffer.size()); }
+ inline static DigestType hash(const StringView& buffer) { return hash((const u8*)buffer.characters_without_null_termination(), buffer.length()); }
+ inline virtual void reset() override
+ {
+ m_A = MD5Constants::init_A;
+ m_B = MD5Constants::init_B;
+ m_C = MD5Constants::init_C;
+ m_D = MD5Constants::init_D;
+
+ m_count[0] = 0;
+ m_count[1] = 0;
+
+ __builtin_memset(m_data_buffer, 0, sizeof(m_data_buffer));
+ }
+
+private:
+ inline void transform(const u8*);
+
+ static void encode(const u32* from, u8* to, size_t length);
+ static void decode(const u8* from, u32* to, size_t length);
+
+ u32 m_A { MD5Constants::init_A }, m_B { MD5Constants::init_B }, m_C { MD5Constants::init_C }, m_D { MD5Constants::init_D };
+ u32 m_count[2] { 0, 0 };
+ Bytes m_buffer;
+
+ u8 m_data_buffer[64];
+};
+
+}
+
+}
diff --git a/Userland/Libraries/LibCrypto/Hash/SHA1.cpp b/Userland/Libraries/LibCrypto/Hash/SHA1.cpp
new file mode 100644
index 0000000000..3ff04bffd9
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Hash/SHA1.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Endian.h>
+#include <AK/Types.h>
+#include <LibCrypto/Hash/SHA1.h>
+
+namespace Crypto {
+namespace Hash {
+
+inline static constexpr auto ROTATE_LEFT(u32 value, size_t bits)
+{
+ return (value << bits) | (value >> (32 - bits));
+}
+
+inline void SHA1::transform(const u8* data)
+{
+ u32 blocks[80];
+ for (size_t i = 0; i < 16; ++i)
+ blocks[i] = AK::convert_between_host_and_network_endian(((const u32*)data)[i]);
+
+ // w[i] = (w[i-3] xor w[i-8] xor w[i-14] xor w[i-16]) leftrotate 1
+ for (size_t i = 16; i < Rounds; ++i)
+ blocks[i] = ROTATE_LEFT(blocks[i - 3] ^ blocks[i - 8] ^ blocks[i - 14] ^ blocks[i - 16], 1);
+
+ auto a = m_state[0], b = m_state[1], c = m_state[2], d = m_state[3], e = m_state[4];
+ u32 f, k;
+
+ for (size_t i = 0; i < Rounds; ++i) {
+ if (i <= 19) {
+ f = (b & c) | ((~b) & d);
+ k = SHA1Constants::RoundConstants[0];
+ } else if (i <= 39) {
+ f = b ^ c ^ d;
+ k = SHA1Constants::RoundConstants[1];
+ } else if (i <= 59) {
+ f = (b & c) | (b & d) | (c & d);
+ k = SHA1Constants::RoundConstants[2];
+ } else {
+ f = b ^ c ^ d;
+ k = SHA1Constants::RoundConstants[3];
+ }
+ auto temp = ROTATE_LEFT(a, 5) + f + e + k + blocks[i];
+ e = d;
+ d = c;
+ c = ROTATE_LEFT(b, 30);
+ b = a;
+ a = temp;
+ }
+
+ m_state[0] += a;
+ m_state[1] += b;
+ m_state[2] += c;
+ m_state[3] += d;
+ m_state[4] += e;
+
+ // "security" measures, as if SHA1 is secure
+ a = 0;
+ b = 0;
+ c = 0;
+ d = 0;
+ e = 0;
+ __builtin_memset(blocks, 0, 16 * sizeof(u32));
+}
+
+void SHA1::update(const u8* message, size_t length)
+{
+ for (size_t i = 0; i < length; ++i) {
+ if (m_data_length == BlockSize) {
+ transform(m_data_buffer);
+ m_bit_length += 512;
+ m_data_length = 0;
+ }
+ m_data_buffer[m_data_length++] = message[i];
+ }
+}
+
+SHA1::DigestType SHA1::digest()
+{
+ auto digest = peek();
+ reset();
+ return digest;
+}
+
+SHA1::DigestType SHA1::peek()
+{
+ DigestType digest;
+ size_t i = m_data_length;
+
+ // make a local copy of the data as we modify it
+ u8 data[BlockSize];
+ u32 state[5];
+ __builtin_memcpy(data, m_data_buffer, m_data_length);
+ __builtin_memcpy(state, m_state, 20);
+
+ if (BlockSize == m_data_length) {
+ transform(m_data_buffer);
+ m_bit_length += BlockSize * 8;
+ m_data_length = 0;
+ i = 0;
+ }
+
+ if (m_data_length < FinalBlockDataSize) {
+ m_data_buffer[i++] = 0x80;
+ while (i < FinalBlockDataSize)
+ m_data_buffer[i++] = 0x00;
+
+ } else {
+ // First, complete a block with some padding.
+ m_data_buffer[i++] = 0x80;
+ while (i < BlockSize)
+ m_data_buffer[i++] = 0x00;
+ transform(m_data_buffer);
+
+ // Then start another block with BlockSize - 8 bytes of zeros
+ __builtin_memset(m_data_buffer, 0, FinalBlockDataSize);
+ }
+
+ // append total message length
+ m_bit_length += m_data_length * 8;
+ m_data_buffer[BlockSize - 1] = m_bit_length;
+ m_data_buffer[BlockSize - 2] = m_bit_length >> 8;
+ m_data_buffer[BlockSize - 3] = m_bit_length >> 16;
+ m_data_buffer[BlockSize - 4] = m_bit_length >> 24;
+ m_data_buffer[BlockSize - 5] = m_bit_length >> 32;
+ m_data_buffer[BlockSize - 6] = m_bit_length >> 40;
+ m_data_buffer[BlockSize - 7] = m_bit_length >> 48;
+ m_data_buffer[BlockSize - 8] = m_bit_length >> 56;
+
+ transform(m_data_buffer);
+
+ for (size_t i = 0; i < 4; ++i) {
+ digest.data[i + 0] = (m_state[0] >> (24 - i * 8)) & 0x000000ff;
+ digest.data[i + 4] = (m_state[1] >> (24 - i * 8)) & 0x000000ff;
+ digest.data[i + 8] = (m_state[2] >> (24 - i * 8)) & 0x000000ff;
+ digest.data[i + 12] = (m_state[3] >> (24 - i * 8)) & 0x000000ff;
+ digest.data[i + 16] = (m_state[4] >> (24 - i * 8)) & 0x000000ff;
+ }
+ // restore the data
+ __builtin_memcpy(m_data_buffer, data, m_data_length);
+ __builtin_memcpy(m_state, state, 20);
+ return digest;
+}
+
+}
+}
diff --git a/Userland/Libraries/LibCrypto/Hash/SHA1.h b/Userland/Libraries/LibCrypto/Hash/SHA1.h
new file mode 100644
index 0000000000..98ff61e17d
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Hash/SHA1.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <LibCrypto/Hash/HashFunction.h>
+
+namespace Crypto {
+namespace Hash {
+
+namespace SHA1Constants {
+
+constexpr static u32 InitializationHashes[5] { 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 };
+
+constexpr static u32 RoundConstants[4] {
+ 0X5a827999,
+ 0X6ed9eba1,
+ 0X8f1bbcdc,
+ 0Xca62c1d6,
+};
+
+}
+
+template<size_t Bytes>
+struct SHA1Digest {
+ u8 data[Bytes];
+ constexpr static size_t Size = Bytes;
+
+ const u8* immutable_data() const { return data; }
+ size_t data_length() { return Bytes; }
+};
+
+class SHA1 final : public HashFunction<512, SHA1Digest<160 / 8>> {
+public:
+ using HashFunction::update;
+
+ SHA1()
+ {
+ reset();
+ }
+
+ virtual void update(const u8*, size_t) override;
+
+ virtual DigestType digest() override;
+ virtual DigestType peek() override;
+
+ inline static DigestType hash(const u8* data, size_t length)
+ {
+ SHA1 sha;
+ sha.update(data, length);
+ return sha.digest();
+ }
+
+ inline static DigestType hash(const ByteBuffer& buffer) { return hash(buffer.data(), buffer.size()); }
+ inline static DigestType hash(const StringView& buffer) { return hash((const u8*)buffer.characters_without_null_termination(), buffer.length()); }
+
+ virtual String class_name() const override
+ {
+ return "SHA1";
+ };
+ inline virtual void reset() override
+ {
+ m_data_length = 0;
+ m_bit_length = 0;
+ for (auto i = 0; i < 5; ++i)
+ m_state[i] = SHA1Constants::InitializationHashes[i];
+ }
+
+private:
+ inline void transform(const u8*);
+
+ u8 m_data_buffer[BlockSize];
+ size_t m_data_length { 0 };
+
+ u64 m_bit_length { 0 };
+ u32 m_state[5];
+
+ constexpr static auto FinalBlockDataSize = BlockSize - 8;
+ constexpr static auto Rounds = 80;
+};
+
+}
+}
diff --git a/Userland/Libraries/LibCrypto/Hash/SHA2.cpp b/Userland/Libraries/LibCrypto/Hash/SHA2.cpp
new file mode 100644
index 0000000000..72dbdc6d2e
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Hash/SHA2.cpp
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Types.h>
+#include <LibCrypto/Hash/SHA2.h>
+
+namespace Crypto {
+namespace Hash {
+constexpr static auto ROTRIGHT(u32 a, size_t b) { return (a >> b) | (a << (32 - b)); }
+constexpr static auto CH(u32 x, u32 y, u32 z) { return (x & y) ^ (z & ~x); }
+constexpr static auto MAJ(u32 x, u32 y, u32 z) { return (x & y) ^ (x & z) ^ (y & z); }
+constexpr static auto EP0(u32 x) { return ROTRIGHT(x, 2) ^ ROTRIGHT(x, 13) ^ ROTRIGHT(x, 22); }
+constexpr static auto EP1(u32 x) { return ROTRIGHT(x, 6) ^ ROTRIGHT(x, 11) ^ ROTRIGHT(x, 25); }
+constexpr static auto SIGN0(u32 x) { return ROTRIGHT(x, 7) ^ ROTRIGHT(x, 18) ^ (x >> 3); }
+constexpr static auto SIGN1(u32 x) { return ROTRIGHT(x, 17) ^ ROTRIGHT(x, 19) ^ (x >> 10); }
+
+constexpr static auto ROTRIGHT(u64 a, size_t b) { return (a >> b) | (a << (64 - b)); }
+constexpr static auto CH(u64 x, u64 y, u64 z) { return (x & y) ^ (z & ~x); }
+constexpr static auto MAJ(u64 x, u64 y, u64 z) { return (x & y) ^ (x & z) ^ (y & z); }
+constexpr static auto EP0(u64 x) { return ROTRIGHT(x, 28) ^ ROTRIGHT(x, 34) ^ ROTRIGHT(x, 39); }
+constexpr static auto EP1(u64 x) { return ROTRIGHT(x, 14) ^ ROTRIGHT(x, 18) ^ ROTRIGHT(x, 41); }
+constexpr static auto SIGN0(u64 x) { return ROTRIGHT(x, 1) ^ ROTRIGHT(x, 8) ^ (x >> 7); }
+constexpr static auto SIGN1(u64 x) { return ROTRIGHT(x, 19) ^ ROTRIGHT(x, 61) ^ (x >> 6); }
+
+inline void SHA256::transform(const u8* data)
+{
+ u32 m[64];
+
+ size_t i = 0;
+ for (size_t j = 0; i < 16; ++i, j += 4) {
+ m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | data[j + 3];
+ }
+
+ for (; i < BlockSize; ++i) {
+ m[i] = SIGN1(m[i - 2]) + m[i - 7] + SIGN0(m[i - 15]) + m[i - 16];
+ }
+
+ auto a = m_state[0], b = m_state[1],
+ c = m_state[2], d = m_state[3],
+ e = m_state[4], f = m_state[5],
+ g = m_state[6], h = m_state[7];
+
+ for (size_t i = 0; i < Rounds; ++i) {
+ auto temp0 = h + EP1(e) + CH(e, f, g) + SHA256Constants::RoundConstants[i] + m[i];
+ auto temp1 = EP0(a) + MAJ(a, b, c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + temp0;
+ d = c;
+ c = b;
+ b = a;
+ a = temp0 + temp1;
+ }
+
+ m_state[0] += a;
+ m_state[1] += b;
+ m_state[2] += c;
+ m_state[3] += d;
+ m_state[4] += e;
+ m_state[5] += f;
+ m_state[6] += g;
+ m_state[7] += h;
+}
+
+void SHA256::update(const u8* message, size_t length)
+{
+ for (size_t i = 0; i < length; ++i) {
+ if (m_data_length == BlockSize) {
+ transform(m_data_buffer);
+ m_bit_length += 512;
+ m_data_length = 0;
+ }
+ m_data_buffer[m_data_length++] = message[i];
+ }
+}
+
+SHA256::DigestType SHA256::digest()
+{
+ auto digest = peek();
+ reset();
+ return digest;
+}
+
+SHA256::DigestType SHA256::peek()
+{
+ DigestType digest;
+ size_t i = m_data_length;
+
+ if (BlockSize == m_data_length) {
+ transform(m_data_buffer);
+ m_bit_length += BlockSize * 8;
+ m_data_length = 0;
+ i = 0;
+ }
+
+ if (m_data_length < FinalBlockDataSize) {
+ m_data_buffer[i++] = 0x80;
+ while (i < FinalBlockDataSize)
+ m_data_buffer[i++] = 0x00;
+
+ } else {
+ // First, complete a block with some padding.
+ m_data_buffer[i++] = 0x80;
+ while (i < BlockSize)
+ m_data_buffer[i++] = 0x00;
+ transform(m_data_buffer);
+
+ // Then start another block with BlockSize - 8 bytes of zeros
+ __builtin_memset(m_data_buffer, 0, FinalBlockDataSize);
+ }
+
+ // append total message length
+ m_bit_length += m_data_length * 8;
+ m_data_buffer[BlockSize - 1] = m_bit_length;
+ m_data_buffer[BlockSize - 2] = m_bit_length >> 8;
+ m_data_buffer[BlockSize - 3] = m_bit_length >> 16;
+ m_data_buffer[BlockSize - 4] = m_bit_length >> 24;
+ m_data_buffer[BlockSize - 5] = m_bit_length >> 32;
+ m_data_buffer[BlockSize - 6] = m_bit_length >> 40;
+ m_data_buffer[BlockSize - 7] = m_bit_length >> 48;
+ m_data_buffer[BlockSize - 8] = m_bit_length >> 56;
+
+ transform(m_data_buffer);
+
+ // SHA uses big-endian and we assume little-endian
+ // FIXME: looks like a thing for AK::NetworkOrdered,
+ // but he doesn't support shifting operations
+ for (size_t i = 0; i < 4; ++i) {
+ digest.data[i + 0] = (m_state[0] >> (24 - i * 8)) & 0x000000ff;
+ digest.data[i + 4] = (m_state[1] >> (24 - i * 8)) & 0x000000ff;
+ digest.data[i + 8] = (m_state[2] >> (24 - i * 8)) & 0x000000ff;
+ digest.data[i + 12] = (m_state[3] >> (24 - i * 8)) & 0x000000ff;
+ digest.data[i + 16] = (m_state[4] >> (24 - i * 8)) & 0x000000ff;
+ digest.data[i + 20] = (m_state[5] >> (24 - i * 8)) & 0x000000ff;
+ digest.data[i + 24] = (m_state[6] >> (24 - i * 8)) & 0x000000ff;
+ digest.data[i + 28] = (m_state[7] >> (24 - i * 8)) & 0x000000ff;
+ }
+ return digest;
+}
+
+inline void SHA512::transform(const u8* data)
+{
+ u64 m[80];
+
+ size_t i = 0;
+ for (size_t j = 0; i < 16; ++i, j += 8) {
+ m[i] = ((u64)data[j] << 56) | ((u64)data[j + 1] << 48) | ((u64)data[j + 2] << 40) | ((u64)data[j + 3] << 32) | ((u64)data[j + 4] << 24) | ((u64)data[j + 5] << 16) | ((u64)data[j + 6] << 8) | (u64)data[j + 7];
+ }
+
+ for (; i < Rounds; ++i) {
+ m[i] = SIGN1(m[i - 2]) + m[i - 7] + SIGN0(m[i - 15]) + m[i - 16];
+ }
+
+ auto a = m_state[0], b = m_state[1],
+ c = m_state[2], d = m_state[3],
+ e = m_state[4], f = m_state[5],
+ g = m_state[6], h = m_state[7];
+
+ for (size_t i = 0; i < Rounds; ++i) {
+ auto temp0 = h + EP1(e) + CH(e, f, g) + SHA512Constants::RoundConstants[i] + m[i];
+ auto temp1 = EP0(a) + MAJ(a, b, c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + temp0;
+ d = c;
+ c = b;
+ b = a;
+ a = temp0 + temp1;
+ }
+
+ m_state[0] += a;
+ m_state[1] += b;
+ m_state[2] += c;
+ m_state[3] += d;
+ m_state[4] += e;
+ m_state[5] += f;
+ m_state[6] += g;
+ m_state[7] += h;
+}
+
+void SHA512::update(const u8* message, size_t length)
+{
+ for (size_t i = 0; i < length; ++i) {
+ if (m_data_length == BlockSize) {
+ transform(m_data_buffer);
+ m_bit_length += 1024;
+ m_data_length = 0;
+ }
+ m_data_buffer[m_data_length++] = message[i];
+ }
+}
+
+SHA512::DigestType SHA512::digest()
+{
+ auto digest = peek();
+ reset();
+ return digest;
+}
+
+SHA512::DigestType SHA512::peek()
+{
+ DigestType digest;
+ size_t i = m_data_length;
+
+ if (BlockSize == m_data_length) {
+ transform(m_data_buffer);
+ m_bit_length += BlockSize * 8;
+ m_data_length = 0;
+ i = 0;
+ }
+
+ if (m_data_length < FinalBlockDataSize) {
+ m_data_buffer[i++] = 0x80;
+ while (i < FinalBlockDataSize)
+ m_data_buffer[i++] = 0x00;
+
+ } else {
+ // First, complete a block with some padding.
+ m_data_buffer[i++] = 0x80;
+ while (i < BlockSize)
+ m_data_buffer[i++] = 0x00;
+ transform(m_data_buffer);
+
+ // Then start another block with BlockSize - 8 bytes of zeros
+ __builtin_memset(m_data_buffer, 0, FinalBlockDataSize);
+ }
+
+ // append total message length
+ m_bit_length += m_data_length * 8;
+ m_data_buffer[BlockSize - 1] = m_bit_length;
+ m_data_buffer[BlockSize - 2] = m_bit_length >> 8;
+ m_data_buffer[BlockSize - 3] = m_bit_length >> 16;
+ m_data_buffer[BlockSize - 4] = m_bit_length >> 24;
+ m_data_buffer[BlockSize - 5] = m_bit_length >> 32;
+ m_data_buffer[BlockSize - 6] = m_bit_length >> 40;
+ m_data_buffer[BlockSize - 7] = m_bit_length >> 48;
+ m_data_buffer[BlockSize - 8] = m_bit_length >> 56;
+
+ transform(m_data_buffer);
+
+ // SHA uses big-endian and we assume little-endian
+ // FIXME: looks like a thing for AK::NetworkOrdered,
+ // but he doesn't support shifting operations
+ for (size_t i = 0; i < 8; ++i) {
+ digest.data[i + 0] = (m_state[0] >> (56 - i * 8)) & 0x000000ff;
+ digest.data[i + 8] = (m_state[1] >> (56 - i * 8)) & 0x000000ff;
+ digest.data[i + 16] = (m_state[2] >> (56 - i * 8)) & 0x000000ff;
+ digest.data[i + 24] = (m_state[3] >> (56 - i * 8)) & 0x000000ff;
+ digest.data[i + 32] = (m_state[4] >> (56 - i * 8)) & 0x000000ff;
+ digest.data[i + 40] = (m_state[5] >> (56 - i * 8)) & 0x000000ff;
+ digest.data[i + 48] = (m_state[6] >> (56 - i * 8)) & 0x000000ff;
+ digest.data[i + 56] = (m_state[7] >> (56 - i * 8)) & 0x000000ff;
+ }
+ return digest;
+}
+}
+}
diff --git a/Userland/Libraries/LibCrypto/Hash/SHA2.h b/Userland/Libraries/LibCrypto/Hash/SHA2.h
new file mode 100644
index 0000000000..a67729e9a3
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Hash/SHA2.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <LibCrypto/Hash/HashFunction.h>
+
+namespace Crypto {
+namespace Hash {
+
+namespace SHA256Constants {
+constexpr static u32 RoundConstants[64] {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+};
+
+constexpr static u32 InitializationHashes[8] = {
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
+};
+}
+
+namespace SHA512Constants {
+constexpr static u64 RoundConstants[80] {
+ 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538,
+ 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, 0x12835b0145706fbe,
+ 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235,
+ 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
+ 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, 0x983e5152ee66dfab,
+ 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725,
+ 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed,
+ 0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
+ 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218,
+ 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, 0x19a4c116b8d2d0c8, 0x1e376c085141ab53,
+ 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373,
+ 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
+ 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, 0xca273eceea26619c,
+ 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6,
+ 0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc,
+ 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817
+};
+
+constexpr static u64 InitializationHashes[8] = {
+ 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
+ 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179
+};
+}
+
+template<size_t Bytes>
+struct SHA2Digest {
+ u8 data[Bytes];
+ constexpr static size_t Size = Bytes;
+ const u8* immutable_data() const { return data; }
+ size_t data_length() { return Bytes; }
+};
+
+// FIXME: I want template<size_t BlockSize> but the compiler gets confused
+class SHA256 final : public HashFunction<512, SHA2Digest<256 / 8>> {
+public:
+ using HashFunction::update;
+
+ SHA256()
+ {
+ reset();
+ }
+
+ virtual void update(const u8*, size_t) override;
+
+ virtual DigestType digest() override;
+ virtual DigestType peek() override;
+
+ inline static DigestType hash(const u8* data, size_t length)
+ {
+ SHA256 sha;
+ sha.update(data, length);
+ return sha.digest();
+ }
+
+ inline static DigestType hash(const ByteBuffer& buffer) { return hash(buffer.data(), buffer.size()); }
+ inline static DigestType hash(const StringView& buffer) { return hash((const u8*)buffer.characters_without_null_termination(), buffer.length()); }
+
+ virtual String class_name() const override
+ {
+ StringBuilder builder;
+ builder.append("SHA");
+ builder.appendf("%zu", this->DigestSize * 8);
+ return builder.build();
+ };
+ inline virtual void reset() override
+ {
+ m_data_length = 0;
+ m_bit_length = 0;
+ for (size_t i = 0; i < 8; ++i)
+ m_state[i] = SHA256Constants::InitializationHashes[i];
+ }
+
+private:
+ inline void transform(const u8*);
+
+ u8 m_data_buffer[BlockSize];
+ size_t m_data_length { 0 };
+
+ u64 m_bit_length { 0 };
+ u32 m_state[8];
+
+ constexpr static auto FinalBlockDataSize = BlockSize - 8;
+ constexpr static auto Rounds = 64;
+};
+
+class SHA512 final : public HashFunction<1024, SHA2Digest<512 / 8>> {
+public:
+ using HashFunction::update;
+
+ SHA512()
+ {
+ reset();
+ }
+
+ virtual void update(const u8*, size_t) override;
+
+ virtual DigestType digest() override;
+ virtual DigestType peek() override;
+
+ inline static DigestType hash(const u8* data, size_t length)
+ {
+ SHA512 sha;
+ sha.update(data, length);
+ return sha.digest();
+ }
+
+ inline static DigestType hash(const ByteBuffer& buffer) { return hash(buffer.data(), buffer.size()); }
+ inline static DigestType hash(const StringView& buffer) { return hash((const u8*)buffer.characters_without_null_termination(), buffer.length()); }
+
+ virtual String class_name() const override
+ {
+ StringBuilder builder;
+ builder.append("SHA");
+ builder.appendf("%zu", this->DigestSize * 8);
+ return builder.build();
+ };
+ inline virtual void reset() override
+ {
+ m_data_length = 0;
+ m_bit_length = 0;
+ for (size_t i = 0; i < 8; ++i)
+ m_state[i] = SHA512Constants::InitializationHashes[i];
+ }
+
+private:
+ inline void transform(const u8*);
+
+ u8 m_data_buffer[BlockSize];
+ size_t m_data_length { 0 };
+
+ u64 m_bit_length { 0 };
+ u64 m_state[8];
+
+ constexpr static auto FinalBlockDataSize = BlockSize - 8;
+ constexpr static auto Rounds = 80;
+};
+
+}
+}
diff --git a/Userland/Libraries/LibCrypto/NumberTheory/ModularFunctions.cpp b/Userland/Libraries/LibCrypto/NumberTheory/ModularFunctions.cpp
new file mode 100644
index 0000000000..9f6e49ef45
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/NumberTheory/ModularFunctions.cpp
@@ -0,0 +1,359 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCrypto/NumberTheory/ModularFunctions.h>
+
+namespace Crypto {
+namespace NumberTheory {
+
+UnsignedBigInteger ModularInverse(const UnsignedBigInteger& a_, const UnsignedBigInteger& b)
+{
+ if (b == 1)
+ return { 1 };
+
+ UnsignedBigInteger one { 1 };
+ UnsignedBigInteger temp_1;
+ UnsignedBigInteger temp_2;
+ UnsignedBigInteger temp_3;
+ UnsignedBigInteger temp_4;
+ UnsignedBigInteger temp_plus;
+ UnsignedBigInteger temp_minus;
+ UnsignedBigInteger temp_quotient;
+ UnsignedBigInteger temp_remainder;
+ UnsignedBigInteger d;
+
+ auto a = a_;
+ auto u = a;
+ if (a.words()[0] % 2 == 0) {
+ // u += b
+ UnsignedBigInteger::add_without_allocation(u, b, temp_plus);
+ u.set_to(temp_plus);
+ }
+
+ auto v = b;
+ UnsignedBigInteger x { 0 };
+
+ // d = b - 1
+ UnsignedBigInteger::subtract_without_allocation(b, one, d);
+
+ while (!(v == 1)) {
+ while (v < u) {
+ // u -= v
+ UnsignedBigInteger::subtract_without_allocation(u, v, temp_minus);
+ u.set_to(temp_minus);
+
+ // d += x
+ UnsignedBigInteger::add_without_allocation(d, x, temp_plus);
+ d.set_to(temp_plus);
+
+ while (u.words()[0] % 2 == 0) {
+ if (d.words()[0] % 2 == 1) {
+ // d += b
+ UnsignedBigInteger::add_without_allocation(d, b, temp_plus);
+ d.set_to(temp_plus);
+ }
+
+ // u /= 2
+ UnsignedBigInteger::divide_u16_without_allocation(u, 2, temp_quotient, temp_remainder);
+ u.set_to(temp_quotient);
+
+ // d /= 2
+ UnsignedBigInteger::divide_u16_without_allocation(d, 2, temp_quotient, temp_remainder);
+ d.set_to(temp_quotient);
+ }
+ }
+
+ // v -= u
+ UnsignedBigInteger::subtract_without_allocation(v, u, temp_minus);
+ v.set_to(temp_minus);
+
+ // x += d
+ UnsignedBigInteger::add_without_allocation(x, d, temp_plus);
+ x.set_to(temp_plus);
+
+ while (v.words()[0] % 2 == 0) {
+ if (x.words()[0] % 2 == 1) {
+ // x += b
+ UnsignedBigInteger::add_without_allocation(x, b, temp_plus);
+ x.set_to(temp_plus);
+ }
+
+ // v /= 2
+ UnsignedBigInteger::divide_u16_without_allocation(v, 2, temp_quotient, temp_remainder);
+ v.set_to(temp_quotient);
+
+ // x /= 2
+ UnsignedBigInteger::divide_u16_without_allocation(x, 2, temp_quotient, temp_remainder);
+ x.set_to(temp_quotient);
+ }
+ }
+
+ // x % b
+ UnsignedBigInteger::divide_without_allocation(x, b, temp_1, temp_2, temp_3, temp_4, temp_quotient, temp_remainder);
+ return temp_remainder;
+}
+
+UnsignedBigInteger ModularPower(const UnsignedBigInteger& b, const UnsignedBigInteger& e, const UnsignedBigInteger& m)
+{
+ if (m == 1)
+ return 0;
+
+ UnsignedBigInteger ep { e };
+ UnsignedBigInteger base { b };
+ UnsignedBigInteger exp { 1 };
+
+ UnsignedBigInteger temp_1;
+ UnsignedBigInteger temp_2;
+ UnsignedBigInteger temp_3;
+ UnsignedBigInteger temp_4;
+ UnsignedBigInteger temp_multiply;
+ UnsignedBigInteger temp_quotient;
+ UnsignedBigInteger temp_remainder;
+
+ while (!(ep < 1)) {
+ if (ep.words()[0] % 2 == 1) {
+ // exp = (exp * base) % m;
+ UnsignedBigInteger::multiply_without_allocation(exp, base, temp_1, temp_2, temp_3, temp_4, temp_multiply);
+ UnsignedBigInteger::divide_without_allocation(temp_multiply, m, temp_1, temp_2, temp_3, temp_4, temp_quotient, temp_remainder);
+ exp.set_to(temp_remainder);
+ }
+
+ // ep = ep / 2;
+ UnsignedBigInteger::divide_u16_without_allocation(ep, 2, temp_quotient, temp_remainder);
+ ep.set_to(temp_quotient);
+
+ // base = (base * base) % m;
+ UnsignedBigInteger::multiply_without_allocation(base, base, temp_1, temp_2, temp_3, temp_4, temp_multiply);
+ UnsignedBigInteger::divide_without_allocation(temp_multiply, m, temp_1, temp_2, temp_3, temp_4, temp_quotient, temp_remainder);
+ base.set_to(temp_remainder);
+ }
+ return exp;
+}
+
+static void GCD_without_allocation(
+ const UnsignedBigInteger& a,
+ const UnsignedBigInteger& b,
+ UnsignedBigInteger& temp_a,
+ UnsignedBigInteger& temp_b,
+ UnsignedBigInteger& temp_1,
+ UnsignedBigInteger& temp_2,
+ UnsignedBigInteger& temp_3,
+ UnsignedBigInteger& temp_4,
+ UnsignedBigInteger& temp_quotient,
+ UnsignedBigInteger& temp_remainder,
+ UnsignedBigInteger& output)
+{
+ temp_a.set_to(a);
+ temp_b.set_to(b);
+ for (;;) {
+ if (temp_a == 0) {
+ output.set_to(temp_b);
+ return;
+ }
+
+ // temp_b %= temp_a
+ UnsignedBigInteger::divide_without_allocation(temp_b, temp_a, temp_1, temp_2, temp_3, temp_4, temp_quotient, temp_remainder);
+ temp_b.set_to(temp_remainder);
+ if (temp_b == 0) {
+ output.set_to(temp_a);
+ return;
+ }
+
+ // temp_a %= temp_b
+ UnsignedBigInteger::divide_without_allocation(temp_a, temp_b, temp_1, temp_2, temp_3, temp_4, temp_quotient, temp_remainder);
+ temp_a.set_to(temp_remainder);
+ }
+}
+
+UnsignedBigInteger GCD(const UnsignedBigInteger& a, const UnsignedBigInteger& b)
+{
+ UnsignedBigInteger temp_a;
+ UnsignedBigInteger temp_b;
+ UnsignedBigInteger temp_1;
+ UnsignedBigInteger temp_2;
+ UnsignedBigInteger temp_3;
+ UnsignedBigInteger temp_4;
+ UnsignedBigInteger temp_quotient;
+ UnsignedBigInteger temp_remainder;
+ UnsignedBigInteger output;
+
+ GCD_without_allocation(a, b, temp_a, temp_b, temp_1, temp_2, temp_3, temp_4, temp_quotient, temp_remainder, output);
+
+ return output;
+}
+
+UnsignedBigInteger LCM(const UnsignedBigInteger& a, const UnsignedBigInteger& b)
+{
+ UnsignedBigInteger temp_a;
+ UnsignedBigInteger temp_b;
+ UnsignedBigInteger temp_1;
+ UnsignedBigInteger temp_2;
+ UnsignedBigInteger temp_3;
+ UnsignedBigInteger temp_4;
+ UnsignedBigInteger temp_quotient;
+ UnsignedBigInteger temp_remainder;
+ UnsignedBigInteger gcd_output;
+ UnsignedBigInteger output { 0 };
+
+ GCD_without_allocation(a, b, temp_a, temp_b, temp_1, temp_2, temp_3, temp_4, temp_quotient, temp_remainder, gcd_output);
+ if (gcd_output == 0) {
+#ifdef NT_DEBUG
+ dbgln("GCD is zero");
+#endif
+ return output;
+ }
+
+ // output = (a / gcd_output) * b
+ UnsignedBigInteger::divide_without_allocation(a, gcd_output, temp_1, temp_2, temp_3, temp_4, temp_quotient, temp_remainder);
+ UnsignedBigInteger::multiply_without_allocation(temp_quotient, b, temp_1, temp_2, temp_3, temp_4, output);
+
+#ifdef NT_DEBUG
+ dbg() << "quot: " << temp_quotient << " rem: " << temp_remainder << " out: " << output;
+#endif
+
+ return output;
+}
+
+static bool MR_primality_test(UnsignedBigInteger n, const Vector<UnsignedBigInteger, 256>& tests)
+{
+ // Written using Wikipedia:
+ // https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test#Miller%E2%80%93Rabin_test
+ ASSERT(!(n < 4));
+ auto predecessor = n.minus({ 1 });
+ auto d = predecessor;
+ size_t r = 0;
+
+ {
+ auto div_result = d.divided_by(2);
+ while (div_result.remainder == 0) {
+ d = div_result.quotient;
+ div_result = d.divided_by(2);
+ ++r;
+ }
+ }
+ if (r == 0) {
+ // n - 1 is odd, so n was even. But there is only one even prime:
+ return n == 2;
+ }
+
+ for (auto a : tests) {
+ // Technically: ASSERT(2 <= a && a <= n - 2)
+ ASSERT(a < n);
+ auto x = ModularPower(a, d, n);
+ if (x == 1 || x == predecessor)
+ continue;
+ bool skip_this_witness = false;
+ // r − 1 iterations.
+ for (size_t i = 0; i < r - 1; ++i) {
+ x = ModularPower(x, 2, n);
+ if (x == predecessor) {
+ skip_this_witness = true;
+ break;
+ }
+ }
+ if (skip_this_witness)
+ continue;
+ return false; // "composite"
+ }
+
+ return true; // "probably prime"
+}
+
+UnsignedBigInteger random_number(const UnsignedBigInteger& min, const UnsignedBigInteger& max_excluded)
+{
+ ASSERT(min < max_excluded);
+ auto range = max_excluded.minus(min);
+ UnsignedBigInteger base;
+ auto size = range.trimmed_length() * sizeof(u32) + 2;
+ // "+2" is intentional (see below).
+ // Also, if we're about to crash anyway, at least produce a nice error:
+ ASSERT(size < 8 * MiB);
+ u8 buf[size];
+ AK::fill_with_random(buf, size);
+ UnsignedBigInteger random { buf, size };
+ // At this point, `random` is a large number, in the range [0, 256^size).
+ // To get down to the actual range, we could just compute random % range.
+ // This introduces "modulo bias". However, since we added 2 to `size`,
+ // we know that the generated range is at least 65536 times as large as the
+ // required range! This means that the modulo bias is only 0.0015%, if all
+ // inputs are chosen adversarially. Let's hope this is good enough.
+ auto divmod = random.divided_by(range);
+ // The proper way to fix this is to restart if `divmod.quotient` is maximal.
+ return divmod.remainder.plus(min);
+}
+
+bool is_probably_prime(const UnsignedBigInteger& p)
+{
+ // Is it a small number?
+ if (p < 49) {
+ u32 p_value = p.words()[0];
+ // Is it a very small prime?
+ if (p_value == 2 || p_value == 3 || p_value == 5 || p_value == 7)
+ return true;
+ // Is it the multiple of a very small prime?
+ if (p_value % 2 == 0 || p_value % 3 == 0 || p_value % 5 == 0 || p_value % 7 == 0)
+ return false;
+ // Then it must be a prime, but not a very small prime, like 37.
+ return true;
+ }
+
+ Vector<UnsignedBigInteger, 256> tests;
+ // Make some good initial guesses that are guaranteed to find all primes < 2^64.
+ tests.append(UnsignedBigInteger(2));
+ tests.append(UnsignedBigInteger(3));
+ tests.append(UnsignedBigInteger(5));
+ tests.append(UnsignedBigInteger(7));
+ tests.append(UnsignedBigInteger(11));
+ tests.append(UnsignedBigInteger(13));
+ UnsignedBigInteger seventeen { 17 };
+ for (size_t i = tests.size(); i < 256; ++i) {
+ tests.append(random_number(seventeen, p.minus(2)));
+ }
+ // Miller-Rabin's "error" is 8^-k. In adversarial cases, it's 4^-k.
+ // With 200 random numbers, this would mean an error of about 2^-400.
+ // So we don't need to worry too much about the quality of the random numbers.
+
+ return MR_primality_test(p, tests);
+}
+
+UnsignedBigInteger random_big_prime(size_t bits)
+{
+ ASSERT(bits >= 33);
+ UnsignedBigInteger min = UnsignedBigInteger::from_base10("6074001000").shift_left(bits - 33);
+ UnsignedBigInteger max = UnsignedBigInteger { 1 }.shift_left(bits).minus(1);
+ for (;;) {
+ auto p = random_number(min, max);
+ if ((p.words()[0] & 1) == 0) {
+ // An even number is definitely not a large prime.
+ continue;
+ }
+ if (is_probably_prime(p))
+ return p;
+ }
+}
+
+}
+}
diff --git a/Userland/Libraries/LibCrypto/NumberTheory/ModularFunctions.h b/Userland/Libraries/LibCrypto/NumberTheory/ModularFunctions.h
new file mode 100644
index 0000000000..586a5b3e10
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/NumberTheory/ModularFunctions.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Random.h>
+#include <LibCrypto/BigInt/UnsignedBigInteger.h>
+
+//#define NT_DEBUG
+
+namespace Crypto {
+namespace NumberTheory {
+
+UnsignedBigInteger ModularInverse(const UnsignedBigInteger& a_, const UnsignedBigInteger& b);
+UnsignedBigInteger ModularPower(const UnsignedBigInteger& b, const UnsignedBigInteger& e, const UnsignedBigInteger& m);
+
+// Note: This function _will_ generate extremely huge numbers, and in doing so,
+// it will allocate and free a lot of memory!
+// Please use |ModularPower| if your use-case is modexp.
+template<typename IntegerType>
+static IntegerType Power(const IntegerType& b, const IntegerType& e)
+{
+ IntegerType ep { e };
+ IntegerType base { b };
+ IntegerType exp { 1 };
+
+ while (!(ep < IntegerType { 1 })) {
+ if (ep.words()[0] % 2 == 1)
+ exp.set_to(exp.multiplied_by(base));
+
+ // ep = ep / 2;
+ ep.set_to(ep.divided_by(IntegerType { 2 }).quotient);
+
+ // base = base * base
+ base.set_to(base.multiplied_by(base));
+ }
+
+ return exp;
+}
+
+UnsignedBigInteger GCD(const UnsignedBigInteger& a, const UnsignedBigInteger& b);
+UnsignedBigInteger LCM(const UnsignedBigInteger& a, const UnsignedBigInteger& b);
+
+UnsignedBigInteger random_number(const UnsignedBigInteger& min, const UnsignedBigInteger& max_excluded);
+bool is_probably_prime(const UnsignedBigInteger& p);
+UnsignedBigInteger random_big_prime(size_t bits);
+
+}
+}
diff --git a/Userland/Libraries/LibCrypto/PK/Code/Code.h b/Userland/Libraries/LibCrypto/PK/Code/Code.h
new file mode 100644
index 0000000000..b5a70ddb14
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/PK/Code/Code.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibCrypto/Hash/HashFunction.h>
+#include <LibCrypto/Verification.h>
+
+namespace Crypto {
+namespace PK {
+
+template<typename HashFunction>
+class Code {
+public:
+ template<typename... Args>
+ Code(Args... args)
+ : m_hasher(args...)
+ {
+ }
+
+ virtual void encode(ReadonlyBytes in, ByteBuffer& out, size_t em_bits) = 0;
+ virtual VerificationConsistency verify(ReadonlyBytes msg, ReadonlyBytes emsg, size_t em_bits) = 0;
+
+ const HashFunction& hasher() const { return m_hasher; }
+ HashFunction& hasher() { return m_hasher; }
+
+protected:
+ HashFunction m_hasher;
+};
+
+}
+}
diff --git a/Userland/Libraries/LibCrypto/PK/Code/EMSA_PSS.h b/Userland/Libraries/LibCrypto/PK/Code/EMSA_PSS.h
new file mode 100644
index 0000000000..95e6e58008
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/PK/Code/EMSA_PSS.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Random.h>
+#include <LibCrypto/PK/Code/Code.h>
+
+static constexpr u8 zeros[] { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+namespace Crypto {
+namespace PK {
+
+template<typename HashFunction, size_t SaltSize>
+class EMSA_PSS : public Code<HashFunction> {
+public:
+ template<typename... Args>
+ EMSA_PSS(Args... args)
+ : Code<HashFunction>(args...)
+ {
+ m_buffer = Bytes { m_data_buffer, sizeof(m_data_buffer) };
+ }
+
+ static constexpr auto SaltLength = SaltSize;
+
+ virtual void encode(ReadonlyBytes in, ByteBuffer& out, size_t em_bits) override
+ {
+ // FIXME: we're supposed to check if in.size() > HashFunction::input_limitation
+ // however, all of our current hash functions can hash unlimited blocks
+ auto& hash_fn = this->hasher();
+ hash_fn.update(in);
+ auto message_hash = hash_fn.digest();
+ auto hash_length = hash_fn.DigestSize;
+ auto em_length = (em_bits + 7) / 8;
+ u8 salt[SaltLength];
+
+ AK::fill_with_random(salt, SaltLength);
+
+ if (em_length < hash_length + SaltLength + 2) {
+ dbgln("Ooops...encoding error");
+ return;
+ }
+
+ m_buffer.overwrite(0, zeros, 8);
+ m_buffer.overwrite(8, message_hash.data, HashFunction::DigestSize);
+ m_buffer.overwrite(8 + HashFunction::DigestSize, salt, SaltLength);
+
+ hash_fn.update(m_buffer);
+ auto hash = hash_fn.digest();
+
+ u8 DB_data[em_length - HashFunction::DigestSize - 1];
+ auto DB = Bytes { DB_data, em_length - HashFunction::DigestSize - 1 };
+ auto DB_offset = 0;
+
+ for (size_t i = 0; i < em_length - SaltLength - HashFunction::DigestSize - 2; ++i)
+ DB[DB_offset++] = 0;
+
+ DB[DB_offset++] = 0x01;
+
+ DB.overwrite(DB_offset, salt, SaltLength);
+
+ auto mask_length = em_length - HashFunction::DigestSize - 1;
+
+ u8 DB_mask[mask_length];
+ auto DB_mask_buffer = Bytes { DB_mask, mask_length };
+ // FIXME: we should probably allow reading from u8*
+ MGF1(ReadonlyBytes { hash.data, HashFunction::DigestSize }, mask_length, DB_mask_buffer);
+
+ for (size_t i = 0; i < DB.size(); ++i)
+ DB_data[i] ^= DB_mask[i];
+
+ auto count = (8 - (em_length * 8 - em_bits));
+ DB_data[0] &= (0xff >> count) << count;
+
+ out.overwrite(0, DB.data(), DB.size());
+ out.overwrite(DB.size(), hash.data, hash_fn.DigestSize);
+ out[DB.size() + hash_fn.DigestSize] = 0xbc;
+ }
+
+ virtual VerificationConsistency verify(ReadonlyBytes msg, ReadonlyBytes emsg, size_t em_bits) override
+ {
+ auto& hash_fn = this->hasher();
+ hash_fn.update(msg);
+ auto message_hash = hash_fn.digest();
+
+ if (emsg.size() < HashFunction::DigestSize + SaltLength + 2)
+ return VerificationConsistency::Inconsistent;
+
+ if (emsg[emsg.size() - 1] != 0xbc)
+ return VerificationConsistency::Inconsistent;
+
+ auto mask_length = emsg.size() - HashFunction::DigestSize - 1;
+ auto masked_DB = emsg.slice(0, mask_length);
+ auto H = emsg.slice(mask_length, HashFunction::DigestSize);
+
+ auto length_to_check = 8 * emsg.size() - em_bits;
+ auto octet = masked_DB[0];
+ for (size_t i = 0; i < length_to_check; ++i)
+ if ((octet >> (8 - i)) & 0x01)
+ return VerificationConsistency::Inconsistent;
+
+ u8 DB_mask[mask_length];
+ auto DB_mask_buffer = Bytes { DB_mask, mask_length };
+ MGF1(H, mask_length, DB_mask_buffer);
+
+ u8 DB[mask_length];
+
+ for (size_t i = 0; i < mask_length; ++i)
+ DB[i] = masked_DB[i] ^ DB_mask[i];
+
+ DB[0] &= 0xff >> (8 - length_to_check);
+
+ auto check_octets = emsg.size() - HashFunction::DigestSize - SaltLength - 2;
+ for (size_t i = 0; i < check_octets; ++i) {
+ if (DB[i])
+ return VerificationConsistency::Inconsistent;
+ }
+
+ if (DB[check_octets + 1] != 0x01)
+ return VerificationConsistency::Inconsistent;
+
+ auto* salt = DB + mask_length - SaltLength;
+ u8 m_prime[8 + HashFunction::DigestSize + SaltLength] { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ auto m_prime_buffer = Bytes { m_prime, sizeof(m_prime) };
+
+ m_prime_buffer.overwrite(8, message_hash.data, HashFunction::DigestSize);
+ m_prime_buffer.overwrite(8 + HashFunction::DigestSize, salt, SaltLength);
+
+ hash_fn.update(m_prime_buffer);
+ auto H_prime = hash_fn.digest();
+
+ if (__builtin_memcmp(message_hash.data, H_prime.data, HashFunction::DigestSize))
+ return VerificationConsistency::Inconsistent;
+
+ return VerificationConsistency::Consistent;
+ }
+
+ void MGF1(ReadonlyBytes seed, size_t length, Bytes out)
+ {
+ auto& hash_fn = this->hasher();
+ ByteBuffer T = ByteBuffer::create_zeroed(0);
+ for (size_t counter = 0; counter < length / HashFunction::DigestSize - 1; ++counter) {
+ hash_fn.update(seed);
+ hash_fn.update((u8*)&counter, 4);
+ T.append(hash_fn.digest().data, HashFunction::DigestSize);
+ }
+ out.overwrite(0, T.data(), length);
+ }
+
+private:
+ u8 m_data_buffer[8 + HashFunction::DigestSize + SaltLength];
+ Bytes m_buffer;
+};
+
+}
+}
diff --git a/Userland/Libraries/LibCrypto/PK/PK.h b/Userland/Libraries/LibCrypto/PK/PK.h
new file mode 100644
index 0000000000..4186eb0c91
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/PK/PK.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/String.h>
+
+namespace Crypto {
+namespace PK {
+
+// FIXME: Fixing name up for grabs
+template<typename PrivKeyT, typename PubKeyT>
+class PKSystem {
+public:
+ using PublicKeyType = PubKeyT;
+ using PrivateKeyType = PrivKeyT;
+
+ PKSystem(PublicKeyType& pubkey, PrivateKeyType& privkey)
+ : m_public_key(pubkey)
+ , m_private_key(privkey)
+ {
+ }
+
+ PKSystem()
+ {
+ }
+
+ virtual void encrypt(ReadonlyBytes in, Bytes& out) = 0;
+ virtual void decrypt(ReadonlyBytes in, Bytes& out) = 0;
+
+ virtual void sign(ReadonlyBytes in, Bytes& out) = 0;
+ virtual void verify(ReadonlyBytes in, Bytes& out) = 0;
+
+ virtual String class_name() const = 0;
+
+ virtual size_t output_size() const = 0;
+
+protected:
+ PublicKeyType m_public_key;
+ PrivateKeyType m_private_key;
+};
+
+}
+}
diff --git a/Userland/Libraries/LibCrypto/PK/RSA.cpp b/Userland/Libraries/LibCrypto/PK/RSA.cpp
new file mode 100644
index 0000000000..452da97acc
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/PK/RSA.cpp
@@ -0,0 +1,329 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Random.h>
+#include <LibCrypto/ASN1/ASN1.h>
+#include <LibCrypto/ASN1/DER.h>
+#include <LibCrypto/ASN1/PEM.h>
+#include <LibCrypto/PK/RSA.h>
+
+namespace Crypto {
+namespace PK {
+
+RSA::KeyPairType RSA::parse_rsa_key(ReadonlyBytes in)
+{
+ // we are going to assign to at least one of these
+ KeyPairType keypair;
+ // TODO: move ASN parsing logic out
+ u64 t, x, y, z, tmp_oid[16];
+ u8 tmp_buf[4096] { 0 };
+ UnsignedBigInteger n, e, d;
+ ASN1::List pubkey_hash_oid[2], pubkey[2];
+
+ ASN1::set(pubkey_hash_oid[0], ASN1::Kind::ObjectIdentifier, tmp_oid, sizeof(tmp_oid) / sizeof(tmp_oid[0]));
+ ASN1::set(pubkey_hash_oid[1], ASN1::Kind::Null, nullptr, 0);
+
+ // DER is weird in that it stores pubkeys as bitstrings
+ // we must first extract that crap
+ ASN1::set(pubkey[0], ASN1::Kind::Sequence, &pubkey_hash_oid, 2);
+ ASN1::set(pubkey[1], ASN1::Kind::Null, nullptr, 0);
+
+ dbgln("we were offered {} bytes of input", in.size());
+
+ if (der_decode_sequence(in.data(), in.size(), pubkey, 2)) {
+ // yay, now we have to reassemble the bitstring to a bytestring
+ t = 0;
+ y = 0;
+ z = 0;
+ x = 0;
+ for (; x < pubkey[1].size; ++x) {
+ y = (y << 1) | tmp_buf[x];
+ if (++z == 8) {
+ tmp_buf[t++] = (u8)y;
+ y = 0;
+ z = 0;
+ }
+ }
+ // now the buffer is correct (Sequence { Integer, Integer })
+ if (!der_decode_sequence_many<2>(tmp_buf, t,
+ ASN1::Kind::Integer, 1, &n,
+ ASN1::Kind::Integer, 1, &e)) {
+ // something was fucked up
+ dbgln("bad pubkey: e={} n={}", e, n);
+ return keypair;
+ }
+ // correct public key
+ keypair.public_key.set(n, e);
+ return keypair;
+ }
+
+ // could be a private key
+ if (!der_decode_sequence_many<1>(in.data(), in.size(),
+ ASN1::Kind::Integer, 1, &n)) {
+ // that's no key
+ // that's a death star
+ dbgln("that's a death star");
+ return keypair;
+ }
+
+ if (n == 0) {
+ // it is a private key
+ UnsignedBigInteger zero;
+ if (!der_decode_sequence_many<4>(in.data(), in.size(),
+ ASN1::Kind::Integer, 1, &zero,
+ ASN1::Kind::Integer, 1, &n,
+ ASN1::Kind::Integer, 1, &e,
+ ASN1::Kind::Integer, 1, &d)) {
+ dbgln("bad privkey n={} e={} d={}", n, e, d);
+ return keypair;
+ }
+ keypair.private_key.set(n, d, e);
+ return keypair;
+ }
+ if (n == 1) {
+ // multiprime key, we don't know how to deal with this
+ dbgln("Unsupported key type");
+ return keypair;
+ }
+ // it's a broken public key
+ keypair.public_key.set(n, 65537);
+ return keypair;
+}
+
+void RSA::encrypt(ReadonlyBytes in, Bytes& out)
+{
+#ifdef CRYPTO_DEBUG
+ dbg() << "in size: " << in.size();
+#endif
+ auto in_integer = UnsignedBigInteger::import_data(in.data(), in.size());
+ if (!(in_integer < m_public_key.modulus())) {
+ dbgln("value too large for key");
+ out = {};
+ return;
+ }
+ auto exp = NumberTheory::ModularPower(in_integer, m_public_key.public_exponent(), m_public_key.modulus());
+ auto size = exp.export_data(out);
+ auto outsize = out.size();
+ if (size != outsize) {
+ dbgln("POSSIBLE RSA BUG!!! Size mismatch: {} requested but {} bytes generated", outsize, size);
+ out = out.slice(outsize - size, size);
+ }
+}
+
+void RSA::decrypt(ReadonlyBytes in, Bytes& out)
+{
+ // FIXME: Actually use the private key properly
+
+ auto in_integer = UnsignedBigInteger::import_data(in.data(), in.size());
+ auto exp = NumberTheory::ModularPower(in_integer, m_private_key.private_exponent(), m_private_key.modulus());
+ auto size = exp.export_data(out);
+
+ auto align = m_private_key.length();
+ auto aligned_size = (size + align - 1) / align * align;
+
+ for (auto i = size; i < aligned_size; ++i)
+ out[out.size() - i - 1] = 0; // zero the non-aligned values
+ out = out.slice(out.size() - aligned_size, aligned_size);
+}
+
+void RSA::sign(ReadonlyBytes in, Bytes& out)
+{
+ auto in_integer = UnsignedBigInteger::import_data(in.data(), in.size());
+ auto exp = NumberTheory::ModularPower(in_integer, m_private_key.private_exponent(), m_private_key.modulus());
+ auto size = exp.export_data(out);
+ out = out.slice(out.size() - size, size);
+}
+
+void RSA::verify(ReadonlyBytes in, Bytes& out)
+{
+ auto in_integer = UnsignedBigInteger::import_data(in.data(), in.size());
+ auto exp = NumberTheory::ModularPower(in_integer, m_public_key.public_exponent(), m_public_key.modulus());
+ auto size = exp.export_data(out);
+ out = out.slice(out.size() - size, size);
+}
+
+void RSA::import_private_key(ReadonlyBytes bytes, bool pem)
+{
+ ByteBuffer buffer;
+ if (pem) {
+ buffer = decode_pem(bytes);
+ bytes = buffer;
+ }
+
+ auto key = parse_rsa_key(bytes);
+ if (!key.private_key.length()) {
+ dbgln("We expected to see a private key, but we found none");
+ ASSERT_NOT_REACHED();
+ }
+ m_private_key = key.private_key;
+}
+
+void RSA::import_public_key(ReadonlyBytes bytes, bool pem)
+{
+ ByteBuffer buffer;
+ if (pem) {
+ buffer = decode_pem(bytes);
+ bytes = buffer;
+ }
+
+ auto key = parse_rsa_key(bytes);
+ if (!key.public_key.length()) {
+ dbgln("We expected to see a public key, but we found none");
+ ASSERT_NOT_REACHED();
+ }
+ m_public_key = key.public_key;
+}
+
+template<typename HashFunction>
+void RSA_EMSA_PSS<HashFunction>::sign(ReadonlyBytes in, Bytes& out)
+{
+ // -- encode via EMSA_PSS
+ auto mod_bits = m_rsa.private_key().modulus().trimmed_length() * sizeof(u32) * 8;
+
+ u8 EM[mod_bits];
+ auto EM_buf = Bytes { EM, mod_bits };
+ m_emsa_pss.encode(in, EM_buf, mod_bits - 1);
+
+ // -- sign via RSA
+ m_rsa.sign(EM_buf, out);
+}
+
+template<typename HashFunction>
+VerificationConsistency RSA_EMSA_PSS<HashFunction>::verify(ReadonlyBytes in)
+{
+ auto mod_bytes = m_rsa.public_key().modulus().trimmed_length() * sizeof(u32);
+ if (in.size() != mod_bytes)
+ return VerificationConsistency::Inconsistent;
+
+ u8 EM[mod_bytes];
+ auto EM_buf = Bytes { EM, mod_bytes };
+
+ // -- verify via RSA
+ m_rsa.verify(in, EM_buf);
+
+ // -- verify via EMSA_PSS
+ return m_emsa_pss.verify(in, EM, mod_bytes * 8 - 1);
+}
+
+void RSA_PKCS1_EME::encrypt(ReadonlyBytes in, Bytes& out)
+{
+ auto mod_len = (m_public_key.modulus().trimmed_length() * sizeof(u32) * 8 + 7) / 8;
+#ifdef CRYPTO_DEBUG
+ dbg() << "key size: " << mod_len;
+#endif
+ if (in.size() > mod_len - 11) {
+ dbgln("message too long :(");
+ out = out.trim(0);
+ return;
+ }
+ if (out.size() < mod_len) {
+ dbgln("output buffer too small");
+ return;
+ }
+
+ auto ps_length = mod_len - in.size() - 3;
+ u8 ps[ps_length];
+
+ // FIXME: Without this assertion, GCC refuses to compile due to a memcpy overflow(!?)
+ ASSERT(ps_length < 16384);
+
+ AK::fill_with_random(ps, ps_length);
+ // since arc4random can create zeros (shocking!)
+ // we have to go through and un-zero the zeros
+ for (size_t i = 0; i < ps_length; ++i)
+ while (!ps[i])
+ AK::fill_with_random(ps + i, 1);
+
+ u8 paddings[] { 0x00, 0x02 };
+
+ out.overwrite(0, paddings, 2);
+ out.overwrite(2, ps, ps_length);
+ out.overwrite(2 + ps_length, paddings, 1);
+ out.overwrite(3 + ps_length, in.data(), in.size());
+ out = out.trim(3 + ps_length + in.size()); // should be a single block
+
+#ifdef CRYPTO_DEBUG
+ dbg() << "padded output size: " << 3 + ps_length + in.size() << " buffer size: " << out.size();
+#endif
+
+ RSA::encrypt(out, out);
+}
+void RSA_PKCS1_EME::decrypt(ReadonlyBytes in, Bytes& out)
+{
+ auto mod_len = (m_public_key.modulus().trimmed_length() * sizeof(u32) * 8 + 7) / 8;
+ if (in.size() != mod_len) {
+ dbgln("decryption error: wrong amount of data: {}", in.size());
+ out = out.trim(0);
+ return;
+ }
+
+ RSA::decrypt(in, out);
+
+ if (out.size() < RSA::output_size()) {
+ dbgln("decryption error: not enough data after decryption: {}", out.size());
+ out = out.trim(0);
+ return;
+ }
+
+ if (out[0] != 0x00) {
+ dbgln("invalid padding byte 0 : {}", out[0]);
+ return;
+ }
+
+ if (out[1] != 0x02) {
+ dbgln("invalid padding byte 1 : {}", out[1]);
+ return;
+ }
+
+ size_t offset = 2;
+ while (offset < out.size() && out[offset])
+ ++offset;
+
+ if (offset == out.size()) {
+ dbgln("garbage data, no zero to split padding");
+ return;
+ }
+
+ ++offset;
+
+ if (offset - 3 < 8) {
+ dbgln("PS too small");
+ return;
+ }
+
+ out = out.slice(offset, out.size() - offset);
+}
+
+void RSA_PKCS1_EME::sign(ReadonlyBytes, Bytes&)
+{
+ dbgln("FIXME: RSA_PKCS_EME::sign");
+}
+void RSA_PKCS1_EME::verify(ReadonlyBytes, Bytes&)
+{
+ dbgln("FIXME: RSA_PKCS_EME::verify");
+}
+}
+}
diff --git a/Userland/Libraries/LibCrypto/PK/RSA.h b/Userland/Libraries/LibCrypto/PK/RSA.h
new file mode 100644
index 0000000000..570e0be423
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/PK/RSA.h
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Span.h>
+#include <AK/Vector.h>
+#include <LibCrypto/BigInt/UnsignedBigInteger.h>
+#include <LibCrypto/NumberTheory/ModularFunctions.h>
+#include <LibCrypto/PK/Code/EMSA_PSS.h>
+#include <LibCrypto/PK/PK.h>
+
+namespace Crypto {
+namespace PK {
+template<typename Integer = u64>
+class RSAPublicKey {
+public:
+ RSAPublicKey(const Integer& n, const Integer& e)
+ : m_modulus(n)
+ , m_public_exponent(e)
+ {
+ }
+
+ RSAPublicKey()
+ : m_modulus(0)
+ , m_public_exponent(0)
+ {
+ }
+
+ //--stuff it should do
+
+ const Integer& modulus() const { return m_modulus; }
+ const Integer& public_exponent() const { return m_public_exponent; }
+ size_t length() const { return m_length; }
+ void set_length(size_t length) { m_length = length; }
+
+ void set(const Integer& n, const Integer& e)
+ {
+ m_modulus = n;
+ m_public_exponent = e;
+ m_length = (n.trimmed_length() * sizeof(u32));
+ }
+
+private:
+ Integer m_modulus;
+ Integer m_public_exponent;
+ size_t m_length { 0 };
+};
+
+template<typename Integer = UnsignedBigInteger>
+class RSAPrivateKey {
+public:
+ RSAPrivateKey(const Integer& n, const Integer& d, const Integer& e)
+ : m_modulus(n)
+ , m_private_exponent(d)
+ , m_public_exponent(e)
+ {
+ }
+
+ RSAPrivateKey()
+ {
+ }
+
+ //--stuff it should do
+ const Integer& modulus() const { return m_modulus; }
+ const Integer& private_exponent() const { return m_private_exponent; }
+ const Integer& public_exponent() const { return m_public_exponent; }
+ size_t length() const { return m_length; }
+ void set_length(size_t length) { m_length = length; }
+
+ void set(const Integer& n, const Integer& d, const Integer& e)
+ {
+ m_modulus = n;
+ m_private_exponent = d;
+ m_public_exponent = e;
+ m_length = (n.length() * sizeof(u32));
+ }
+
+private:
+ Integer m_modulus;
+ Integer m_private_exponent;
+ Integer m_public_exponent;
+ size_t m_length { 0 };
+};
+
+template<typename PubKey, typename PrivKey>
+struct RSAKeyPair {
+ PubKey public_key;
+ PrivKey private_key;
+};
+
+using IntegerType = UnsignedBigInteger;
+class RSA : public PKSystem<RSAPrivateKey<IntegerType>, RSAPublicKey<IntegerType>> {
+ template<typename T>
+ friend class RSA_EMSA_PSS;
+
+public:
+ using KeyPairType = RSAKeyPair<PublicKeyType, PrivateKeyType>;
+
+ static KeyPairType parse_rsa_key(ReadonlyBytes);
+ static KeyPairType generate_key_pair(size_t bits = 256)
+ {
+ IntegerType e { 65537 }; // :P
+ IntegerType p, q;
+ IntegerType lambda;
+
+ do {
+ p = NumberTheory::random_big_prime(bits / 2);
+ q = NumberTheory::random_big_prime(bits / 2);
+ lambda = NumberTheory::LCM(p.minus(1), q.minus(1));
+ dbgln("checking combination p={}, q={}, lambda={}", p, q, lambda.length());
+ } while (!(NumberTheory::GCD(e, lambda) == 1));
+
+ auto n = p.multiplied_by(q);
+
+ auto d = NumberTheory::ModularInverse(e, lambda);
+ dbgln("Your keys are Pub(n={}, e={}) and Priv(n={}, d={})", n, e, n, d);
+ RSAKeyPair<PublicKeyType, PrivateKeyType> keys {
+ { n, e },
+ { n, d, e }
+ };
+ keys.public_key.set_length(bits / 2 / 8);
+ keys.private_key.set_length(bits / 2 / 8);
+ return keys;
+ }
+
+ RSA(IntegerType n, IntegerType d, IntegerType e)
+ {
+ m_public_key.set(n, e);
+ m_private_key.set(n, d, e);
+ }
+
+ RSA(PublicKeyType& pubkey, PrivateKeyType& privkey)
+ : PKSystem<RSAPrivateKey<IntegerType>, RSAPublicKey<IntegerType>>(pubkey, privkey)
+ {
+ }
+
+ RSA(const ByteBuffer& publicKeyPEM, const ByteBuffer& privateKeyPEM)
+ {
+ import_public_key(publicKeyPEM);
+ import_private_key(privateKeyPEM);
+ }
+
+ RSA(const StringView& privKeyPEM)
+ {
+ import_private_key(privKeyPEM.bytes());
+ m_public_key.set(m_private_key.modulus(), m_private_key.public_exponent());
+ }
+
+ // create our own keys
+ RSA()
+ {
+ auto pair = generate_key_pair();
+ m_public_key = pair.public_key;
+ m_private_key = pair.private_key;
+ }
+
+ virtual void encrypt(ReadonlyBytes in, Bytes& out) override;
+ virtual void decrypt(ReadonlyBytes in, Bytes& out) override;
+
+ virtual void sign(ReadonlyBytes in, Bytes& out) override;
+ virtual void verify(ReadonlyBytes in, Bytes& out) override;
+
+ virtual String class_name() const override { return "RSA"; }
+
+ virtual size_t output_size() const override { return m_public_key.length(); }
+
+ void import_public_key(ReadonlyBytes, bool pem = true);
+ void import_private_key(ReadonlyBytes, bool pem = true);
+
+ const PrivateKeyType& private_key() const { return m_private_key; }
+ const PublicKeyType& public_key() const { return m_public_key; }
+};
+
+template<typename HashFunction>
+class RSA_EMSA_PSS {
+public:
+ RSA_EMSA_PSS(RSA& rsa)
+ : m_rsa(rsa)
+ {
+ }
+
+ void sign(ReadonlyBytes in, Bytes& out);
+ VerificationConsistency verify(ReadonlyBytes in);
+
+private:
+ EMSA_PSS<HashFunction, HashFunction::DigestSize> m_emsa_pss;
+ RSA m_rsa;
+};
+
+class RSA_PKCS1_EME : public RSA {
+public:
+ // forward all constructions to RSA
+ template<typename... Args>
+ RSA_PKCS1_EME(Args... args)
+ : RSA(args...)
+ {
+ }
+
+ ~RSA_PKCS1_EME() { }
+
+ virtual void encrypt(ReadonlyBytes in, Bytes& out) override;
+ virtual void decrypt(ReadonlyBytes in, Bytes& out) override;
+
+ virtual void sign(ReadonlyBytes, Bytes&) override;
+ virtual void verify(ReadonlyBytes, Bytes&) override;
+
+ virtual String class_name() const override { return "RSA_PKCS1-EME"; }
+ virtual size_t output_size() const override { return m_public_key.length(); }
+};
+}
+}
diff --git a/Userland/Libraries/LibCrypto/Verification.h b/Userland/Libraries/LibCrypto/Verification.h
new file mode 100644
index 0000000000..33932aea53
--- /dev/null
+++ b/Userland/Libraries/LibCrypto/Verification.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace Crypto {
+
+enum class VerificationConsistency {
+ Consistent,
+ Inconsistent
+};
+
+}
diff --git a/Userland/Libraries/LibDebug/CMakeLists.txt b/Userland/Libraries/LibDebug/CMakeLists.txt
new file mode 100644
index 0000000000..a2fa57a9a5
--- /dev/null
+++ b/Userland/Libraries/LibDebug/CMakeLists.txt
@@ -0,0 +1,14 @@
+set(SOURCES
+ DebugInfo.cpp
+ DebugSession.cpp
+ Dwarf/AbbreviationsMap.cpp
+ Dwarf/CompilationUnit.cpp
+ Dwarf/DIE.cpp
+ Dwarf/DwarfInfo.cpp
+ Dwarf/Expression.cpp
+ Dwarf/LineProgram.cpp
+ StackFrameUtils.cpp
+)
+
+serenity_lib(LibDebug debug)
+target_link_libraries(LibDebug LibC LibRegex)
diff --git a/Userland/Libraries/LibDebug/DebugInfo.cpp b/Userland/Libraries/LibDebug/DebugInfo.cpp
new file mode 100644
index 0000000000..c9e773d0ac
--- /dev/null
+++ b/Userland/Libraries/LibDebug/DebugInfo.cpp
@@ -0,0 +1,371 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DebugInfo.h"
+#include <AK/LexicalPath.h>
+#include <AK/MemoryStream.h>
+#include <AK/QuickSort.h>
+#include <LibDebug/Dwarf/CompilationUnit.h>
+#include <LibDebug/Dwarf/DwarfInfo.h>
+#include <LibDebug/Dwarf/Expression.h>
+
+//#define DEBUG_SPAM
+
+namespace Debug {
+
+DebugInfo::DebugInfo(NonnullOwnPtr<const ELF::Image> elf, String source_root, FlatPtr base_address)
+ : m_elf(move(elf))
+ , m_source_root(source_root)
+ , m_base_address(base_address)
+ , m_dwarf_info(*m_elf)
+{
+ prepare_variable_scopes();
+ prepare_lines();
+}
+
+void DebugInfo::prepare_variable_scopes()
+{
+ m_dwarf_info.for_each_compilation_unit([&](const Dwarf::CompilationUnit& unit) {
+ auto root = unit.root_die();
+ parse_scopes_impl(root);
+ });
+}
+
+void DebugInfo::parse_scopes_impl(const Dwarf::DIE& die)
+{
+ die.for_each_child([&](const Dwarf::DIE& child) {
+ if (child.is_null())
+ return;
+ if (!(child.tag() == Dwarf::EntryTag::SubProgram || child.tag() == Dwarf::EntryTag::LexicalBlock))
+ return;
+
+ if (child.get_attribute(Dwarf::Attribute::Inline).has_value()) {
+#ifdef DEBUG_SPAM
+ dbgln("DWARF inlined functions are not supported");
+#endif
+ return;
+ }
+ if (child.get_attribute(Dwarf::Attribute::Ranges).has_value()) {
+#ifdef DEBUG_SPAM
+ dbgln("DWARF ranges are not supported");
+#endif
+ return;
+ }
+ auto name = child.get_attribute(Dwarf::Attribute::Name);
+
+ VariablesScope scope {};
+ scope.is_function = (child.tag() == Dwarf::EntryTag::SubProgram);
+ if (name.has_value())
+ scope.name = name.value().data.as_string;
+
+ if (!child.get_attribute(Dwarf::Attribute::LowPc).has_value()) {
+#ifdef DEBUG_SPAM
+ dbgln("DWARF: Couldn't find attribute LowPc for scope");
+#endif
+ return;
+ }
+ scope.address_low = child.get_attribute(Dwarf::Attribute::LowPc).value().data.as_u32;
+ // The attribute name HighPc is confusing. In this context, it seems to actually be a positive offset from LowPc
+ scope.address_high = scope.address_low + child.get_attribute(Dwarf::Attribute::HighPc).value().data.as_u32;
+
+ child.for_each_child([&](const Dwarf::DIE& variable_entry) {
+ if (!(variable_entry.tag() == Dwarf::EntryTag::Variable
+ || variable_entry.tag() == Dwarf::EntryTag::FormalParameter))
+ return;
+ scope.dies_of_variables.append(variable_entry);
+ });
+ m_scopes.append(scope);
+
+ parse_scopes_impl(child);
+ });
+}
+
+void DebugInfo::prepare_lines()
+{
+ auto section = elf().lookup_section(".debug_line");
+ if (section.is_undefined())
+ return;
+
+ InputMemoryStream stream { section.bytes() };
+
+ Vector<Dwarf::LineProgram::LineInfo> all_lines;
+ while (!stream.eof()) {
+ Dwarf::LineProgram program(stream);
+ all_lines.append(program.lines());
+ }
+
+ String serenity_slash("serenity/");
+
+ for (auto& line_info : all_lines) {
+ String file_path = line_info.file;
+ if (file_path.contains("Toolchain/") || file_path.contains("libgcc"))
+ continue;
+ if (file_path.contains(serenity_slash)) {
+ auto start_index = file_path.index_of(serenity_slash).value() + serenity_slash.length();
+ file_path = file_path.substring(start_index, file_path.length() - start_index);
+ }
+ if (file_path.starts_with("./") && !m_source_root.is_null()) {
+ file_path = LexicalPath::canonicalized_path(String::formatted("{}/{}", m_source_root, file_path));
+ }
+ m_sorted_lines.append({ line_info.address, file_path, line_info.line });
+ }
+ quick_sort(m_sorted_lines, [](auto& a, auto& b) {
+ return a.address < b.address;
+ });
+}
+
+Optional<DebugInfo::SourcePosition> DebugInfo::get_source_position(u32 target_address) const
+{
+ if (m_sorted_lines.is_empty())
+ return {};
+ if (target_address < m_sorted_lines[0].address)
+ return {};
+
+ // TODO: We can do a binray search here
+ for (size_t i = 0; i < m_sorted_lines.size() - 1; ++i) {
+ if (m_sorted_lines[i + 1].address > target_address) {
+ return SourcePosition::from_line_info(m_sorted_lines[i]);
+ }
+ }
+ return {};
+}
+
+Optional<DebugInfo::SourcePositionAndAddress> DebugInfo::get_address_from_source_position(const String& file, size_t line) const
+{
+ String file_path = file;
+ if (!file_path.starts_with("/"))
+ file_path = String::format("/%s", file_path.characters());
+
+ constexpr char SERENITY_LIBS_PREFIX[] = "/usr/src/serenity";
+ if (file.starts_with(SERENITY_LIBS_PREFIX)) {
+ file_path = file.substring(sizeof(SERENITY_LIBS_PREFIX), file.length() - sizeof(SERENITY_LIBS_PREFIX));
+ file_path = String::format("../%s", file_path.characters());
+ }
+
+ Optional<SourcePositionAndAddress> result;
+ for (const auto& line_entry : m_sorted_lines) {
+ if (!line_entry.file.ends_with(file_path))
+ continue;
+
+ if (line_entry.line > line)
+ continue;
+
+ // We look for the source position that is closest to the desired position, and is not after it.
+ // For example, get_address_of_source_position("main.cpp", 73) could return the address for an instruction whose location is ("main.cpp", 72)
+ // as there might not be an instruction mapped for "main.cpp", 73.
+ if (!result.has_value() || (line_entry.line > result.value().line)) {
+ result = SourcePositionAndAddress { line_entry.file, line_entry.line, line_entry.address };
+ }
+ }
+ return result;
+}
+
+NonnullOwnPtrVector<DebugInfo::VariableInfo> DebugInfo::get_variables_in_current_scope(const PtraceRegisters& regs) const
+{
+ NonnullOwnPtrVector<DebugInfo::VariableInfo> variables;
+
+ // TODO: We can store the scopes in a better data structure
+ for (const auto& scope : m_scopes) {
+ if (regs.eip - m_base_address < scope.address_low || regs.eip - m_base_address >= scope.address_high)
+ continue;
+
+ for (const auto& die_entry : scope.dies_of_variables) {
+ auto variable_info = create_variable_info(die_entry, regs);
+ if (!variable_info)
+ continue;
+ variables.append(variable_info.release_nonnull());
+ }
+ }
+ return variables;
+}
+
+static Optional<Dwarf::DIE> parse_variable_type_die(const Dwarf::DIE& variable_die, DebugInfo::VariableInfo& variable_info)
+{
+ auto type_die_offset = variable_die.get_attribute(Dwarf::Attribute::Type);
+ if (!type_die_offset.has_value())
+ return {};
+
+ ASSERT(type_die_offset.value().type == Dwarf::DIE::AttributeValue::Type::DieReference);
+
+ auto type_die = variable_die.get_die_at_offset(type_die_offset.value().data.as_u32);
+ auto type_name = type_die.get_attribute(Dwarf::Attribute::Name);
+ if (type_name.has_value()) {
+ variable_info.type_name = type_name.value().data.as_string;
+ } else {
+ dbgln("Unnamed DWARF type at offset: {}", type_die.offset());
+ variable_info.name = "[Unnamed Type]";
+ }
+
+ return type_die;
+}
+
+static void parse_variable_location(const Dwarf::DIE& variable_die, DebugInfo::VariableInfo& variable_info, const PtraceRegisters& regs)
+{
+ auto location_info = variable_die.get_attribute(Dwarf::Attribute::Location);
+ if (!location_info.has_value()) {
+ location_info = variable_die.get_attribute(Dwarf::Attribute::MemberLocation);
+ }
+
+ if (!location_info.has_value())
+ return;
+
+ switch (location_info.value().type) {
+ case Dwarf::DIE::AttributeValue::Type::UnsignedNumber:
+ variable_info.location_type = DebugInfo::VariableInfo::LocationType::Address;
+ variable_info.location_data.address = location_info.value().data.as_u32;
+ break;
+ case Dwarf::DIE::AttributeValue::Type::DwarfExpression: {
+ auto expression_bytes = ReadonlyBytes { location_info.value().data.as_raw_bytes.bytes, location_info.value().data.as_raw_bytes.length };
+ auto value = Dwarf::Expression::evaluate(expression_bytes, regs);
+
+ if (value.type != Dwarf::Expression::Type::None) {
+ ASSERT(value.type == Dwarf::Expression::Type::UnsignedIntetger);
+ variable_info.location_type = DebugInfo::VariableInfo::LocationType::Address;
+ variable_info.location_data.address = value.data.as_u32;
+ }
+ break;
+ }
+ default:
+ dbgln("Warninig: unhandled Dwarf location type: {}", (int)location_info.value().type);
+ }
+}
+
+OwnPtr<DebugInfo::VariableInfo> DebugInfo::create_variable_info(const Dwarf::DIE& variable_die, const PtraceRegisters& regs) const
+{
+ ASSERT(variable_die.tag() == Dwarf::EntryTag::Variable
+ || variable_die.tag() == Dwarf::EntryTag::Member
+ || variable_die.tag() == Dwarf::EntryTag::FormalParameter
+ || variable_die.tag() == Dwarf::EntryTag::EnumerationType
+ || variable_die.tag() == Dwarf::EntryTag::Enumerator
+ || variable_die.tag() == Dwarf::EntryTag::StructureType);
+
+ if (variable_die.tag() == Dwarf::EntryTag::FormalParameter
+ && !variable_die.get_attribute(Dwarf::Attribute::Name).has_value()) {
+ // We don't want to display info for unused parameters
+ return {};
+ }
+
+ NonnullOwnPtr<VariableInfo> variable_info = make<VariableInfo>();
+ variable_info->name = variable_die.get_attribute(Dwarf::Attribute::Name).value().data.as_string;
+
+ auto type_die = parse_variable_type_die(variable_die, *variable_info);
+
+ if (variable_die.tag() == Dwarf::EntryTag::Enumerator) {
+ auto constant = variable_die.get_attribute(Dwarf::Attribute::ConstValue);
+ ASSERT(constant.has_value());
+ switch (constant.value().type) {
+ case Dwarf::DIE::AttributeValue::Type::UnsignedNumber:
+ variable_info->constant_data.as_u32 = constant.value().data.as_u32;
+ break;
+ case Dwarf::DIE::AttributeValue::Type::SignedNumber:
+ variable_info->constant_data.as_i32 = constant.value().data.as_i32;
+ break;
+ case Dwarf::DIE::AttributeValue::Type::String:
+ variable_info->constant_data.as_string = constant.value().data.as_string;
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ } else {
+ parse_variable_location(variable_die, *variable_info, regs);
+ }
+
+ if (type_die.has_value()) {
+ OwnPtr<VariableInfo> type_info;
+ if (type_die.value().tag() == Dwarf::EntryTag::EnumerationType || type_die.value().tag() == Dwarf::EntryTag::StructureType) {
+ type_info = create_variable_info(type_die.value(), regs);
+ }
+
+ type_die.value().for_each_child([&](const Dwarf::DIE& member) {
+ if (member.is_null())
+ return;
+ auto member_variable = create_variable_info(member, regs);
+ ASSERT(member_variable);
+
+ if (type_die.value().tag() == Dwarf::EntryTag::EnumerationType) {
+ member_variable->parent = type_info.ptr();
+ type_info->members.append(member_variable.release_nonnull());
+ } else {
+ if (variable_info->location_type == DebugInfo::VariableInfo::LocationType::None) {
+ return;
+ }
+ ASSERT(variable_info->location_type == DebugInfo::VariableInfo::LocationType::Address);
+
+ if (member_variable->location_type == DebugInfo::VariableInfo::LocationType::Address)
+ member_variable->location_data.address += variable_info->location_data.address;
+
+ member_variable->parent = variable_info.ptr();
+ variable_info->members.append(member_variable.release_nonnull());
+ }
+ });
+
+ if (type_info) {
+ variable_info->type = move(type_info);
+ variable_info->type->type_tag = type_die.value().tag();
+ }
+ }
+
+ return variable_info;
+}
+
+String DebugInfo::name_of_containing_function(u32 address) const
+{
+ auto function = get_containing_function(address);
+ if (!function.has_value())
+ return {};
+ return function.value().name;
+}
+
+Optional<DebugInfo::VariablesScope> DebugInfo::get_containing_function(u32 address) const
+{
+ for (const auto& scope : m_scopes) {
+ if (!scope.is_function || address < scope.address_low || address >= scope.address_high)
+ continue;
+ return scope;
+ }
+ return {};
+}
+
+Vector<DebugInfo::SourcePosition> DebugInfo::source_lines_in_scope(const VariablesScope& scope) const
+{
+ Vector<DebugInfo::SourcePosition> source_lines;
+ for (const auto& line : m_sorted_lines) {
+ if (line.address < scope.address_low)
+ continue;
+
+ if (line.address >= scope.address_high)
+ break;
+ source_lines.append(SourcePosition::from_line_info(line));
+ }
+ return source_lines;
+}
+
+DebugInfo::SourcePosition DebugInfo::SourcePosition::from_line_info(const Dwarf::LineProgram::LineInfo& line)
+{
+ return { line.file, line.line, { line.address } };
+}
+
+}
diff --git a/Userland/Libraries/LibDebug/DebugInfo.h b/Userland/Libraries/LibDebug/DebugInfo.h
new file mode 100644
index 0000000000..8c73d7dee4
--- /dev/null
+++ b/Userland/Libraries/LibDebug/DebugInfo.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullOwnPtrVector.h>
+#include <AK/NonnullRefPtr.h>
+#include <AK/Optional.h>
+#include <AK/Vector.h>
+#include <LibDebug/Dwarf/DIE.h>
+#include <LibDebug/Dwarf/DwarfInfo.h>
+#include <LibDebug/Dwarf/LineProgram.h>
+#include <LibELF/Image.h>
+#include <sys/arch/i386/regs.h>
+
+namespace Debug {
+
+class DebugInfo {
+public:
+ explicit DebugInfo(NonnullOwnPtr<const ELF::Image>, String source_root = {}, FlatPtr base_address = 0);
+
+ const ELF::Image& elf() const { return *m_elf; }
+
+ struct SourcePosition {
+ FlyString file_path;
+ size_t line_number { 0 };
+ Optional<u32> address_of_first_statement;
+
+ SourcePosition()
+ : SourcePosition(String::empty(), 0)
+ {
+ }
+ SourcePosition(String file_path, size_t line_number)
+ : file_path(file_path)
+ , line_number(line_number)
+ {
+ }
+ SourcePosition(String file_path, size_t line_number, u32 address_of_first_statement)
+ : file_path(file_path)
+ , line_number(line_number)
+ , address_of_first_statement(address_of_first_statement)
+ {
+ }
+
+ bool operator==(const SourcePosition& other) const { return file_path == other.file_path && line_number == other.line_number; }
+ bool operator!=(const SourcePosition& other) const { return !(*this == other); }
+
+ static SourcePosition from_line_info(const Dwarf::LineProgram::LineInfo&);
+ };
+
+ struct VariableInfo {
+ enum class LocationType {
+ None,
+ Address,
+ Register,
+ };
+ String name;
+ String type_name;
+ LocationType location_type { LocationType::None };
+ union {
+ u32 address;
+ } location_data { 0 };
+
+ union {
+ u32 as_u32;
+ u32 as_i32;
+ const char* as_string;
+ } constant_data { 0 };
+
+ Dwarf::EntryTag type_tag;
+ OwnPtr<VariableInfo> type;
+ NonnullOwnPtrVector<VariableInfo> members;
+ VariableInfo* parent { nullptr };
+
+ bool is_enum_type() const { return type && type->type_tag == Dwarf::EntryTag::EnumerationType; }
+ };
+
+ struct VariablesScope {
+ bool is_function { false };
+ String name;
+ u32 address_low { 0 };
+ u32 address_high { 0 }; // Non-inclusive - the lowest address after address_low that's not in this scope
+ Vector<Dwarf::DIE> dies_of_variables;
+ };
+
+ NonnullOwnPtrVector<VariableInfo> get_variables_in_current_scope(const PtraceRegisters&) const;
+
+ Optional<SourcePosition> get_source_position(u32 address) const;
+
+ struct SourcePositionAndAddress {
+ String file;
+ size_t line;
+ FlatPtr address;
+ };
+
+ Optional<SourcePositionAndAddress> get_address_from_source_position(const String& file, size_t line) const;
+
+ template<typename Callback>
+ void for_each_source_position(Callback callback) const
+ {
+ FlyString previous_file = "";
+ size_t previous_line = 0;
+ for (const auto& line_info : m_sorted_lines) {
+ if (line_info.file == previous_file && line_info.line == previous_line)
+ continue;
+ previous_file = line_info.file;
+ previous_line = line_info.line;
+ callback({ line_info.file, line_info.line, line_info.address });
+ }
+ }
+
+ String name_of_containing_function(u32 address) const;
+ Vector<SourcePosition> source_lines_in_scope(const VariablesScope&) const;
+ Optional<VariablesScope> get_containing_function(u32 address) const;
+
+private:
+ void prepare_variable_scopes();
+ void prepare_lines();
+ void parse_scopes_impl(const Dwarf::DIE& die);
+ OwnPtr<VariableInfo> create_variable_info(const Dwarf::DIE& variable_die, const PtraceRegisters&) const;
+
+ NonnullOwnPtr<const ELF::Image> m_elf;
+ String m_source_root;
+ FlatPtr m_base_address { 0 };
+ Dwarf::DwarfInfo m_dwarf_info;
+
+ Vector<VariablesScope> m_scopes;
+ Vector<Dwarf::LineProgram::LineInfo> m_sorted_lines;
+};
+
+}
diff --git a/Userland/Libraries/LibDebug/DebugSession.cpp b/Userland/Libraries/LibDebug/DebugSession.cpp
new file mode 100644
index 0000000000..82871d070c
--- /dev/null
+++ b/Userland/Libraries/LibDebug/DebugSession.cpp
@@ -0,0 +1,432 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DebugSession.h"
+#include <AK/JsonObject.h>
+#include <AK/JsonValue.h>
+#include <AK/LexicalPath.h>
+#include <AK/Optional.h>
+#include <LibCore/File.h>
+#include <LibRegex/Regex.h>
+#include <stdlib.h>
+
+namespace Debug {
+
+DebugSession::DebugSession(pid_t pid, String source_root)
+ : m_debuggee_pid(pid)
+ , m_source_root(source_root)
+
+{
+}
+
+DebugSession::~DebugSession()
+{
+ if (m_is_debuggee_dead)
+ return;
+
+ for (const auto& bp : m_breakpoints) {
+ disable_breakpoint(bp.key);
+ }
+ m_breakpoints.clear();
+
+ if (ptrace(PT_DETACH, m_debuggee_pid, 0, 0) < 0) {
+ perror("PT_DETACH");
+ }
+}
+
+OwnPtr<DebugSession> DebugSession::exec_and_attach(const String& command, String source_root)
+{
+ auto pid = fork();
+
+ if (pid < 0) {
+ perror("fork");
+ exit(1);
+ }
+
+ if (!pid) {
+ if (ptrace(PT_TRACE_ME, 0, 0, 0) < 0) {
+ perror("PT_TRACE_ME");
+ exit(1);
+ }
+
+ auto parts = command.split(' ');
+ ASSERT(!parts.is_empty());
+ const char** args = (const char**)calloc(parts.size() + 1, sizeof(const char*));
+ for (size_t i = 0; i < parts.size(); i++) {
+ args[i] = parts[i].characters();
+ }
+ const char** envp = (const char**)calloc(2, sizeof(const char*));
+ // This causes loader to stop on a breakpoint before jumping to the entry point of the program.
+ envp[0] = "_LOADER_BREAKPOINT=1";
+ int rc = execvpe(args[0], const_cast<char**>(args), const_cast<char**>(envp));
+ if (rc < 0) {
+ perror("execvp");
+ }
+ ASSERT_NOT_REACHED();
+ }
+
+ if (waitpid(pid, nullptr, WSTOPPED) != pid) {
+ perror("waitpid");
+ return {};
+ }
+
+ if (ptrace(PT_ATTACH, pid, 0, 0) < 0) {
+ perror("PT_ATTACH");
+ return {};
+ }
+
+ // We want to continue until the exit from the 'execve' sycsall.
+ // This ensures that when we start debugging the process
+ // it executes the target image, and not the forked image of the tracing process.
+ // NOTE: we only need to do this when we are debugging a new process (i.e not attaching to a process that's already running!)
+
+ if (waitpid(pid, nullptr, WSTOPPED) != pid) {
+ perror("wait_pid");
+ return {};
+ }
+
+ auto debug_session = adopt_own(*new DebugSession(pid, source_root));
+
+ // Continue until breakpoint before entry point of main program
+ int wstatus = debug_session->continue_debuggee_and_wait();
+ if (WSTOPSIG(wstatus) != SIGTRAP) {
+ dbgln("expected SIGTRAP");
+ return {};
+ }
+
+ // At this point, libraries should have been loaded
+ debug_session->update_loaded_libs();
+
+ return move(debug_session);
+}
+
+bool DebugSession::poke(u32* address, u32 data)
+{
+ if (ptrace(PT_POKE, m_debuggee_pid, (void*)address, data) < 0) {
+ perror("PT_POKE");
+ return false;
+ }
+ return true;
+}
+
+Optional<u32> DebugSession::peek(u32* address) const
+{
+ Optional<u32> result;
+ int rc = ptrace(PT_PEEK, m_debuggee_pid, (void*)address, 0);
+ if (errno == 0)
+ result = static_cast<u32>(rc);
+ return result;
+}
+
+bool DebugSession::insert_breakpoint(void* address)
+{
+ // We insert a software breakpoint by
+ // patching the first byte of the instruction at 'address'
+ // with the breakpoint instruction (int3)
+
+ if (m_breakpoints.contains(address))
+ return false;
+
+ auto original_bytes = peek(reinterpret_cast<u32*>(address));
+
+ if (!original_bytes.has_value())
+ return false;
+
+ ASSERT((original_bytes.value() & 0xff) != BREAKPOINT_INSTRUCTION);
+
+ BreakPoint breakpoint { address, original_bytes.value(), BreakPointState::Disabled };
+
+ m_breakpoints.set(address, breakpoint);
+
+ enable_breakpoint(breakpoint.address);
+
+ return true;
+}
+
+bool DebugSession::disable_breakpoint(void* address)
+{
+ auto breakpoint = m_breakpoints.get(address);
+ ASSERT(breakpoint.has_value());
+ if (!poke(reinterpret_cast<u32*>(reinterpret_cast<char*>(breakpoint.value().address)), breakpoint.value().original_first_word))
+ return false;
+
+ auto bp = m_breakpoints.get(breakpoint.value().address).value();
+ bp.state = BreakPointState::Disabled;
+ m_breakpoints.set(bp.address, bp);
+ return true;
+}
+
+bool DebugSession::enable_breakpoint(void* address)
+{
+ auto breakpoint = m_breakpoints.get(address);
+ ASSERT(breakpoint.has_value());
+
+ ASSERT(breakpoint.value().state == BreakPointState::Disabled);
+
+ if (!poke(reinterpret_cast<u32*>(breakpoint.value().address), (breakpoint.value().original_first_word & ~(uint32_t)0xff) | BREAKPOINT_INSTRUCTION))
+ return false;
+
+ auto bp = m_breakpoints.get(breakpoint.value().address).value();
+ bp.state = BreakPointState::Enabled;
+ m_breakpoints.set(bp.address, bp);
+ return true;
+}
+
+bool DebugSession::remove_breakpoint(void* address)
+{
+ if (!disable_breakpoint(address))
+ return false;
+
+ m_breakpoints.remove(address);
+ return true;
+}
+
+bool DebugSession::breakpoint_exists(void* address) const
+{
+ return m_breakpoints.contains(address);
+}
+
+PtraceRegisters DebugSession::get_registers() const
+{
+ PtraceRegisters regs;
+ if (ptrace(PT_GETREGS, m_debuggee_pid, &regs, 0) < 0) {
+ perror("PT_GETREGS");
+ ASSERT_NOT_REACHED();
+ }
+ return regs;
+}
+
+void DebugSession::set_registers(const PtraceRegisters& regs)
+{
+ if (ptrace(PT_SETREGS, m_debuggee_pid, reinterpret_cast<void*>(&const_cast<PtraceRegisters&>(regs)), 0) < 0) {
+ perror("PT_SETREGS");
+ ASSERT_NOT_REACHED();
+ }
+}
+
+void DebugSession::continue_debuggee(ContinueType type)
+{
+ int command = (type == ContinueType::FreeRun) ? PT_CONTINUE : PT_SYSCALL;
+ if (ptrace(command, m_debuggee_pid, 0, 0) < 0) {
+ perror("continue");
+ ASSERT_NOT_REACHED();
+ }
+}
+
+int DebugSession::continue_debuggee_and_wait(ContinueType type)
+{
+ continue_debuggee(type);
+ int wstatus = 0;
+ if (waitpid(m_debuggee_pid, &wstatus, WSTOPPED | WEXITED) != m_debuggee_pid) {
+ perror("waitpid");
+ ASSERT_NOT_REACHED();
+ }
+ return wstatus;
+}
+
+void* DebugSession::single_step()
+{
+ // Single stepping works by setting the x86 TRAP flag bit in the eflags register.
+ // This flag causes the cpu to enter single-stepping mode, which causes
+ // Interrupt 1 (debug interrupt) to be emitted after every instruction.
+ // To single step the program, we set the TRAP flag and continue the debuggee.
+ // After the debuggee has stopped, we clear the TRAP flag.
+
+ auto regs = get_registers();
+ constexpr u32 TRAP_FLAG = 0x100;
+ regs.eflags |= TRAP_FLAG;
+ set_registers(regs);
+
+ continue_debuggee();
+
+ if (waitpid(m_debuggee_pid, 0, WSTOPPED) != m_debuggee_pid) {
+ perror("waitpid");
+ ASSERT_NOT_REACHED();
+ }
+
+ regs = get_registers();
+ regs.eflags &= ~(TRAP_FLAG);
+ set_registers(regs);
+ return (void*)regs.eip;
+}
+
+void DebugSession::detach()
+{
+ for (auto& breakpoint : m_breakpoints.keys()) {
+ remove_breakpoint(breakpoint);
+ }
+ continue_debuggee();
+}
+
+Optional<DebugSession::InsertBreakpointAtSymbolResult> DebugSession::insert_breakpoint(const String& symbol_name)
+{
+ Optional<InsertBreakpointAtSymbolResult> result;
+ for_each_loaded_library([this, symbol_name, &result](auto& lib) {
+ // The loader contains its own definitions for LibC symbols, so we don't want to include it in the search.
+ if (lib.name == "Loader.so")
+ return IterationDecision::Continue;
+
+ auto symbol = lib.debug_info->elf().find_demangled_function(symbol_name);
+ if (!symbol.has_value())
+ return IterationDecision::Continue;
+
+ auto breakpoint_address = symbol.value().value() + lib.base_address;
+ bool rc = this->insert_breakpoint(reinterpret_cast<void*>(breakpoint_address));
+ if (!rc)
+ return IterationDecision::Break;
+
+ result = InsertBreakpointAtSymbolResult { lib.name, breakpoint_address };
+ return IterationDecision::Break;
+ });
+ return result;
+}
+
+Optional<DebugSession::InsertBreakpointAtSourcePositionResult> DebugSession::insert_breakpoint(const String& file_name, size_t line_number)
+{
+ auto address_and_source_position = get_address_from_source_position(file_name, line_number);
+ if (!address_and_source_position.has_value())
+ return {};
+
+ auto address = address_and_source_position.value().address;
+ bool rc = this->insert_breakpoint(reinterpret_cast<void*>(address));
+ if (!rc)
+ return {};
+
+ auto lib = library_at(address);
+ ASSERT(lib);
+
+ return InsertBreakpointAtSourcePositionResult { lib->name, address_and_source_position.value().file, address_and_source_position.value().line, address };
+}
+
+void DebugSession::update_loaded_libs()
+{
+ auto file = Core::File::construct(String::format("/proc/%u/vm", m_debuggee_pid));
+ bool rc = file->open(Core::IODevice::ReadOnly);
+ ASSERT(rc);
+
+ auto file_contents = file->read_all();
+ auto json = JsonValue::from_string(file_contents);
+ ASSERT(json.has_value());
+
+ auto vm_entries = json.value().as_array();
+ Regex<PosixExtended> re("(.+): \\.text");
+
+ auto get_path_to_object = [&re](const String& vm_name) -> Optional<String> {
+ if (vm_name == "/usr/lib/Loader.so")
+ return vm_name;
+ RegexResult result;
+ auto rc = re.search(vm_name, result);
+ if (!rc)
+ return {};
+ auto lib_name = result.capture_group_matches.at(0).at(0).view.u8view().to_string();
+ if (lib_name.starts_with("/"))
+ return lib_name;
+ return String::format("/usr/lib/%s", lib_name.characters());
+ };
+
+ vm_entries.for_each([&](auto& entry) {
+ // TODO: check that region is executable
+ auto vm_name = entry.as_object().get("name").as_string();
+
+ auto object_path = get_path_to_object(vm_name);
+ if (!object_path.has_value())
+ return IterationDecision::Continue;
+
+ String lib_name = object_path.value();
+ if (lib_name.ends_with(".so"))
+ lib_name = LexicalPath(object_path.value()).basename();
+
+ // FIXME: DebugInfo currently cannot parse the debug information of libgcc_s.so
+ if (lib_name == "libgcc_s.so")
+ return IterationDecision::Continue;
+
+ if (m_loaded_libraries.contains(lib_name))
+ return IterationDecision::Continue;
+
+ auto file_or_error = MappedFile ::map(object_path.value());
+ if (file_or_error.is_error())
+ return IterationDecision::Continue;
+
+ FlatPtr base_address = entry.as_object().get("address").as_u32();
+ auto debug_info = make<DebugInfo>(make<ELF::Image>(file_or_error.value()->bytes()), m_source_root, base_address);
+ auto lib = make<LoadedLibrary>(lib_name, file_or_error.release_value(), move(debug_info), base_address);
+ m_loaded_libraries.set(lib_name, move(lib));
+
+ return IterationDecision::Continue;
+ });
+}
+
+const DebugSession::LoadedLibrary* DebugSession::library_at(FlatPtr address) const
+{
+ const LoadedLibrary* result = nullptr;
+ for_each_loaded_library([&result, address](const auto& lib) {
+ if (address >= lib.base_address && address < lib.base_address + lib.debug_info->elf().size()) {
+ result = &lib;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+ return result;
+}
+
+Optional<DebugSession::SymbolicationResult> DebugSession::symbolicate(FlatPtr address) const
+{
+ auto* lib = library_at(address);
+ if (!lib)
+ return {};
+ //FIXME: ELF::Image symlicate() API should return String::empty() if symbol is not found (It currently returns ??)
+ auto symbol = lib->debug_info->elf().symbolicate(address - lib->base_address);
+ return { { lib->name, symbol } };
+}
+
+Optional<DebugInfo::SourcePositionAndAddress> DebugSession::get_address_from_source_position(const String& file, size_t line) const
+{
+ Optional<DebugInfo::SourcePositionAndAddress> result;
+ for_each_loaded_library([this, file, line, &result](auto& lib) {
+ // The loader contains its own definitions for LibC symbols, so we don't want to include it in the search.
+ if (lib.name == "Loader.so")
+ return IterationDecision::Continue;
+
+ auto source_position_and_address = lib.debug_info->get_address_from_source_position(file, line);
+ if (!source_position_and_address.has_value())
+ return IterationDecision::Continue;
+
+ result = source_position_and_address;
+ result.value().address += lib.base_address;
+ return IterationDecision::Break;
+ });
+ return result;
+}
+
+Optional<DebugInfo::SourcePosition> DebugSession::get_source_position(FlatPtr address) const
+{
+ auto* lib = library_at(address);
+ if (!lib)
+ return {};
+ return lib->debug_info->get_source_position(address - lib->base_address);
+}
+
+}
diff --git a/Userland/Libraries/LibDebug/DebugSession.h b/Userland/Libraries/LibDebug/DebugSession.h
new file mode 100644
index 0000000000..f30174a740
--- /dev/null
+++ b/Userland/Libraries/LibDebug/DebugSession.h
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Demangle.h>
+#include <AK/HashMap.h>
+#include <AK/MappedFile.h>
+#include <AK/NonnullRefPtr.h>
+#include <AK/Optional.h>
+#include <AK/OwnPtr.h>
+#include <AK/String.h>
+#include <LibC/sys/arch/i386/regs.h>
+#include <LibDebug/DebugInfo.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+namespace Debug {
+
+class DebugSession {
+public:
+ static OwnPtr<DebugSession> exec_and_attach(const String& command, String source_root = {});
+
+ ~DebugSession();
+
+ int pid() const { return m_debuggee_pid; }
+
+ bool poke(u32* address, u32 data);
+ Optional<u32> peek(u32* address) const;
+
+ enum class BreakPointState {
+ Enabled,
+ Disabled,
+ };
+
+ struct BreakPoint {
+ void* address { nullptr };
+ u32 original_first_word { 0 };
+ BreakPointState state { BreakPointState::Disabled };
+ };
+
+ struct InsertBreakpointAtSymbolResult {
+ String library_name;
+ FlatPtr address { 0 };
+ };
+
+ Optional<InsertBreakpointAtSymbolResult> insert_breakpoint(const String& symbol_name);
+
+ struct InsertBreakpointAtSourcePositionResult {
+ String library_name;
+ String file_name;
+ size_t line_number { 0 };
+ FlatPtr address { 0 };
+ };
+
+ Optional<InsertBreakpointAtSourcePositionResult> insert_breakpoint(const String& file_name, size_t line_number);
+
+ bool insert_breakpoint(void* address);
+ bool disable_breakpoint(void* address);
+ bool enable_breakpoint(void* address);
+ bool remove_breakpoint(void* address);
+ bool breakpoint_exists(void* address) const;
+
+ void dump_breakpoints()
+ {
+ for (auto addr : m_breakpoints.keys()) {
+ dbgln("{}", addr);
+ }
+ }
+
+ PtraceRegisters get_registers() const;
+ void set_registers(const PtraceRegisters&);
+
+ enum class ContinueType {
+ FreeRun,
+ Syscall,
+ };
+ void continue_debuggee(ContinueType type = ContinueType::FreeRun);
+
+ // Returns the wstatus result of waitpid()
+ int continue_debuggee_and_wait(ContinueType type = ContinueType::FreeRun);
+
+ // Returns the new eip
+ void* single_step();
+
+ void detach();
+
+ enum DesiredInitialDebugeeState {
+ Running,
+ Stopped
+ };
+ template<typename Callback>
+ void run(DesiredInitialDebugeeState, Callback);
+
+ enum DebugDecision {
+ Continue,
+ SingleStep,
+ ContinueBreakAtSyscall,
+ Detach,
+ Kill,
+ };
+
+ enum DebugBreakReason {
+ Breakpoint,
+ Syscall,
+ Exited,
+ };
+
+ struct LoadedLibrary {
+ String name;
+ NonnullRefPtr<MappedFile> file;
+ NonnullOwnPtr<DebugInfo> debug_info;
+ FlatPtr base_address;
+
+ LoadedLibrary(const String& name, NonnullRefPtr<MappedFile> file, NonnullOwnPtr<DebugInfo>&& debug_info, FlatPtr base_address)
+ : name(name)
+ , file(move(file))
+ , debug_info(move(debug_info))
+ , base_address(base_address)
+ {
+ }
+ };
+
+ template<typename Func>
+ void for_each_loaded_library(Func f) const
+ {
+ for (const auto& lib_name : m_loaded_libraries.keys()) {
+ const auto& lib = *m_loaded_libraries.get(lib_name).value();
+ if (f(lib) == IterationDecision::Break)
+ break;
+ }
+ }
+
+ const LoadedLibrary* library_at(FlatPtr address) const;
+
+ struct SymbolicationResult {
+ String library_name;
+ String symbol;
+ };
+ Optional<SymbolicationResult> symbolicate(FlatPtr address) const;
+
+ Optional<DebugInfo::SourcePositionAndAddress> get_address_from_source_position(const String& file, size_t line) const;
+
+ Optional<DebugInfo::SourcePosition> get_source_position(FlatPtr address) const;
+
+private:
+ explicit DebugSession(pid_t, String source_root);
+
+ // x86 breakpoint instruction "int3"
+ static constexpr u8 BREAKPOINT_INSTRUCTION = 0xcc;
+
+ void update_loaded_libs();
+
+ int m_debuggee_pid { -1 };
+ String m_source_root;
+ bool m_is_debuggee_dead { false };
+
+ HashMap<void*, BreakPoint> m_breakpoints;
+
+ // Maps from base address to loaded library
+ HashMap<String, NonnullOwnPtr<LoadedLibrary>> m_loaded_libraries;
+};
+
+template<typename Callback>
+void DebugSession::run(DesiredInitialDebugeeState initial_debugee_state, Callback callback)
+{
+
+ enum class State {
+ FirstIteration,
+ FreeRun,
+ Syscall,
+ ConsecutiveBreakpoint,
+ SingleStep,
+ };
+
+ State state { State::FirstIteration };
+
+ auto do_continue_and_wait = [&]() {
+ int wstatus = continue_debuggee_and_wait((state == State::Syscall) ? ContinueType::Syscall : ContinueType::FreeRun);
+
+ // FIXME: This check actually only checks whether the debuggee
+ // stopped because it hit a breakpoint/syscall/is in single stepping mode or not
+ if (WSTOPSIG(wstatus) != SIGTRAP) {
+ callback(DebugBreakReason::Exited, Optional<PtraceRegisters>());
+ m_is_debuggee_dead = true;
+ return true;
+ }
+ return false;
+ };
+
+ for (;;) {
+ if ((state == State::FirstIteration && initial_debugee_state == DesiredInitialDebugeeState::Running) || state == State::FreeRun || state == State::Syscall) {
+ if (do_continue_and_wait())
+ break;
+ }
+ if (state == State::FirstIteration)
+ state = State::FreeRun;
+
+ auto regs = get_registers();
+ Optional<BreakPoint> current_breakpoint;
+
+ if (state == State::FreeRun || state == State::Syscall) {
+ current_breakpoint = m_breakpoints.get((void*)((u32)regs.eip - 1));
+ if (current_breakpoint.has_value())
+ state = State::FreeRun;
+ } else {
+ current_breakpoint = m_breakpoints.get((void*)regs.eip);
+ }
+
+ if (current_breakpoint.has_value()) {
+ // We want to make the breakpoint transparent to the user of the debugger.
+ // To achieive this, we perform two rollbacks:
+ // 1. Set regs.eip to point at the actual address of the instruction we breaked on.
+ // regs.eip currently points to one byte after the address of the original instruction,
+ // because the cpu has just executed the INT3 we patched into the instruction.
+ // 2. We restore the original first byte of the instruction,
+ // because it was patched with INT3.
+ regs.eip = reinterpret_cast<u32>(current_breakpoint.value().address);
+ set_registers(regs);
+ disable_breakpoint(current_breakpoint.value().address);
+ }
+
+ DebugBreakReason reason = (state == State::Syscall && !current_breakpoint.has_value()) ? DebugBreakReason::Syscall : DebugBreakReason::Breakpoint;
+
+ DebugDecision decision = callback(reason, regs);
+
+ if (reason == DebugBreakReason::Syscall) {
+ // skip the exit from the syscall
+ if (do_continue_and_wait())
+ break;
+ }
+
+ if (decision == DebugDecision::Continue) {
+ state = State::FreeRun;
+ } else if (decision == DebugDecision::ContinueBreakAtSyscall) {
+ state = State::Syscall;
+ }
+
+ bool did_single_step = false;
+
+ // Re-enable the breakpoint if it wasn't removed by the user
+ if (current_breakpoint.has_value() && m_breakpoints.contains(current_breakpoint.value().address)) {
+ // The current breakpoint was removed to make it transparent to the user.
+ // We now want to re-enable it - the code execution flow could hit it again.
+ // To re-enable the breakpoint, we first perform a single step and execute the
+ // instruction of the breakpoint, and then redo the INT3 patch in its first byte.
+
+ // If the user manually inserted a breakpoint at were we breaked at originally,
+ // we need to disable that breakpoint because we want to singlestep over it to execute the
+ // instruction we breaked on (we re-enable it again later anyways).
+ if (m_breakpoints.contains(current_breakpoint.value().address) && m_breakpoints.get(current_breakpoint.value().address).value().state == BreakPointState::Enabled) {
+ disable_breakpoint(current_breakpoint.value().address);
+ }
+ auto stopped_address = single_step();
+ enable_breakpoint(current_breakpoint.value().address);
+ did_single_step = true;
+ // If there is another breakpoint after the current one,
+ // Then we are already on it (because of single_step)
+ auto breakpoint_at_next_instruction = m_breakpoints.get(stopped_address);
+ if (breakpoint_at_next_instruction.has_value()
+ && breakpoint_at_next_instruction.value().state == BreakPointState::Enabled) {
+ state = State::ConsecutiveBreakpoint;
+ }
+ }
+
+ if (decision == DebugDecision::SingleStep) {
+ state = State::SingleStep;
+ }
+
+ if (decision == DebugDecision::Detach) {
+ detach();
+ break;
+ }
+ if (decision == DebugDecision::Kill) {
+ ASSERT_NOT_REACHED(); // TODO: implement
+ }
+
+ if (state == State::SingleStep && !did_single_step) {
+ single_step();
+ }
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibDebug/Dwarf/AbbreviationsMap.cpp b/Userland/Libraries/LibDebug/Dwarf/AbbreviationsMap.cpp
new file mode 100644
index 0000000000..865cf56943
--- /dev/null
+++ b/Userland/Libraries/LibDebug/Dwarf/AbbreviationsMap.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "AbbreviationsMap.h"
+#include "DwarfInfo.h"
+
+#include <AK/MemoryStream.h>
+
+namespace Debug::Dwarf {
+
+AbbreviationsMap::AbbreviationsMap(const DwarfInfo& dwarf_info, u32 offset)
+ : m_dwarf_info(dwarf_info)
+ , m_offset(offset)
+{
+ populate_map();
+}
+
+void AbbreviationsMap::populate_map()
+{
+ InputMemoryStream abbreviation_stream(m_dwarf_info.abbreviation_data());
+ abbreviation_stream.discard_or_error(m_offset);
+
+ while (!abbreviation_stream.eof()) {
+ size_t abbreviation_code = 0;
+ abbreviation_stream.read_LEB128_unsigned(abbreviation_code);
+ // An abbreviation code of 0 marks the end of the
+ // abbreviations for a given compilation unit
+ if (abbreviation_code == 0)
+ break;
+
+ size_t tag {};
+ abbreviation_stream.read_LEB128_unsigned(tag);
+
+ u8 has_children = 0;
+ abbreviation_stream >> has_children;
+
+ AbbreviationEntry abbrevation_entry {};
+ abbrevation_entry.tag = static_cast<EntryTag>(tag);
+ abbrevation_entry.has_children = (has_children == 1);
+
+ AttributeSpecification current_attribute_specification {};
+ do {
+ size_t attribute_value = 0;
+ size_t form_value = 0;
+ abbreviation_stream.read_LEB128_unsigned(attribute_value);
+ abbreviation_stream.read_LEB128_unsigned(form_value);
+
+ current_attribute_specification.attribute = static_cast<Attribute>(attribute_value);
+ current_attribute_specification.form = static_cast<AttributeDataForm>(form_value);
+
+ if (current_attribute_specification.attribute != Attribute::None) {
+ abbrevation_entry.attribute_specifications.append(current_attribute_specification);
+ }
+ } while (current_attribute_specification.attribute != Attribute::None || current_attribute_specification.form != AttributeDataForm::None);
+
+ m_entries.set((u32)abbreviation_code, move(abbrevation_entry));
+ }
+}
+
+Optional<AbbreviationsMap::AbbreviationEntry> AbbreviationsMap::get(u32 code) const
+{
+ return m_entries.get(code);
+}
+
+}
diff --git a/Userland/Libraries/LibDebug/Dwarf/AbbreviationsMap.h b/Userland/Libraries/LibDebug/Dwarf/AbbreviationsMap.h
new file mode 100644
index 0000000000..ce5da7408a
--- /dev/null
+++ b/Userland/Libraries/LibDebug/Dwarf/AbbreviationsMap.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "DwarfTypes.h"
+#include <AK/HashMap.h>
+#include <AK/Optional.h>
+#include <AK/Types.h>
+
+namespace Debug::Dwarf {
+
+class DwarfInfo;
+
+class AbbreviationsMap {
+public:
+ AbbreviationsMap(const DwarfInfo& dwarf_info, u32 offset);
+
+ struct AbbreviationEntry {
+
+ EntryTag tag;
+ bool has_children;
+
+ Vector<AttributeSpecification> attribute_specifications;
+ };
+
+ Optional<AbbreviationEntry> get(u32 code) const;
+
+private:
+ void populate_map();
+
+ const DwarfInfo& m_dwarf_info;
+ u32 m_offset { 0 };
+ HashMap<u32, AbbreviationEntry> m_entries;
+};
+
+}
diff --git a/Userland/Libraries/LibDebug/Dwarf/CompilationUnit.cpp b/Userland/Libraries/LibDebug/Dwarf/CompilationUnit.cpp
new file mode 100644
index 0000000000..664d68e957
--- /dev/null
+++ b/Userland/Libraries/LibDebug/Dwarf/CompilationUnit.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "CompilationUnit.h"
+#include "DIE.h"
+
+namespace Debug::Dwarf {
+
+CompilationUnit::CompilationUnit(const DwarfInfo& dwarf_info, u32 offset, const CompilationUnitHeader& header)
+ : m_dwarf_info(dwarf_info)
+ , m_offset(offset)
+ , m_header(header)
+ , m_abbreviations(dwarf_info, header.abbrev_offset)
+{
+}
+
+DIE CompilationUnit::root_die() const
+{
+ return DIE(*this, m_offset + sizeof(CompilationUnitHeader));
+}
+
+}
diff --git a/Userland/Libraries/LibDebug/Dwarf/CompilationUnit.h b/Userland/Libraries/LibDebug/Dwarf/CompilationUnit.h
new file mode 100644
index 0000000000..811ce3bc37
--- /dev/null
+++ b/Userland/Libraries/LibDebug/Dwarf/CompilationUnit.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "AbbreviationsMap.h"
+#include <AK/Types.h>
+
+namespace Debug::Dwarf {
+
+class DwarfInfo;
+class DIE;
+
+class CompilationUnit {
+public:
+ CompilationUnit(const DwarfInfo& dwarf_info, u32 offset, const CompilationUnitHeader&);
+
+ u32 offset() const { return m_offset; }
+ u32 size() const { return m_header.length + sizeof(u32); }
+
+ DIE root_die() const;
+
+ const DwarfInfo& dwarf_info() const { return m_dwarf_info; }
+ const AbbreviationsMap& abbreviations_map() const { return m_abbreviations; }
+
+private:
+ const DwarfInfo& m_dwarf_info;
+ u32 m_offset { 0 };
+ CompilationUnitHeader m_header;
+ AbbreviationsMap m_abbreviations;
+};
+
+}
diff --git a/Userland/Libraries/LibDebug/Dwarf/DIE.cpp b/Userland/Libraries/LibDebug/Dwarf/DIE.cpp
new file mode 100644
index 0000000000..96ad0875d6
--- /dev/null
+++ b/Userland/Libraries/LibDebug/Dwarf/DIE.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DIE.h"
+#include "CompilationUnit.h"
+#include "DwarfInfo.h"
+#include <AK/ByteBuffer.h>
+#include <AK/MemoryStream.h>
+
+namespace Debug::Dwarf {
+
+DIE::DIE(const CompilationUnit& unit, u32 offset)
+ : m_compilation_unit(unit)
+ , m_offset(offset)
+{
+ InputMemoryStream stream(m_compilation_unit.dwarf_info().debug_info_data());
+ stream.discard_or_error(m_offset);
+ stream.read_LEB128_unsigned(m_abbreviation_code);
+ m_data_offset = stream.offset();
+
+ if (m_abbreviation_code == 0) {
+ // An abbreviation code of 0 ( = null DIE entry) means the end of a chain of siblings
+ m_tag = EntryTag::None;
+ } else {
+ auto abbreviation_info = m_compilation_unit.abbreviations_map().get(m_abbreviation_code);
+ ASSERT(abbreviation_info.has_value());
+
+ m_tag = abbreviation_info.value().tag;
+ m_has_children = abbreviation_info.value().has_children;
+
+ // We iterate the attributes data only to calculate this DIE's size
+ for (auto& attribute_spec : abbreviation_info.value().attribute_specifications) {
+ get_attribute_value(attribute_spec.form, stream);
+ }
+ }
+ m_size = stream.offset() - m_offset;
+}
+
+DIE::AttributeValue DIE::get_attribute_value(AttributeDataForm form,
+ InputMemoryStream& debug_info_stream) const
+{
+ AttributeValue value;
+
+ auto assign_raw_bytes_value = [&](size_t length) {
+ value.data.as_raw_bytes.length = length;
+ value.data.as_raw_bytes.bytes = reinterpret_cast<const u8*>(m_compilation_unit.dwarf_info().debug_info_data().data()
+ + debug_info_stream.offset());
+
+ debug_info_stream.discard_or_error(length);
+ };
+
+ switch (form) {
+ case AttributeDataForm::StringPointer: {
+ u32 offset;
+ debug_info_stream >> offset;
+ value.type = AttributeValue::Type::String;
+
+ auto strings_data = m_compilation_unit.dwarf_info().debug_strings_data();
+ value.data.as_string = reinterpret_cast<const char*>(strings_data.data() + offset);
+ break;
+ }
+ case AttributeDataForm::Data1: {
+ u8 data;
+ debug_info_stream >> data;
+ value.type = AttributeValue::Type::UnsignedNumber;
+ value.data.as_u32 = data;
+ break;
+ }
+ case AttributeDataForm::Data2: {
+ u16 data;
+ debug_info_stream >> data;
+ value.type = AttributeValue::Type::UnsignedNumber;
+ value.data.as_u32 = data;
+ break;
+ }
+ case AttributeDataForm::Addr: {
+ u32 address;
+ debug_info_stream >> address;
+ value.type = AttributeValue::Type::UnsignedNumber;
+ value.data.as_u32 = address;
+ break;
+ }
+ case AttributeDataForm::SData: {
+ ssize_t data;
+ debug_info_stream.read_LEB128_signed(data);
+ value.type = AttributeValue::Type::SignedNumber;
+ value.data.as_i32 = data;
+ break;
+ }
+ case AttributeDataForm::SecOffset: {
+ u32 data;
+ debug_info_stream >> data;
+ value.type = AttributeValue::Type::SecOffset;
+ value.data.as_u32 = data;
+ break;
+ }
+ case AttributeDataForm::Data4: {
+ u32 data;
+ debug_info_stream >> data;
+ value.type = AttributeValue::Type::UnsignedNumber;
+ value.data.as_u32 = data;
+ break;
+ }
+ case AttributeDataForm::Ref4: {
+ u32 data;
+ debug_info_stream >> data;
+ value.type = AttributeValue::Type::DieReference;
+ value.data.as_u32 = data + m_compilation_unit.offset();
+ break;
+ }
+ case AttributeDataForm::FlagPresent: {
+ value.type = AttributeValue::Type::Boolean;
+ value.data.as_bool = true;
+ break;
+ }
+ case AttributeDataForm::ExprLoc: {
+ size_t length;
+ debug_info_stream.read_LEB128_unsigned(length);
+ value.type = AttributeValue::Type::DwarfExpression;
+ assign_raw_bytes_value(length);
+ break;
+ }
+ case AttributeDataForm::String: {
+ String str;
+ u32 str_offset = debug_info_stream.offset();
+ debug_info_stream >> str;
+ value.type = AttributeValue::Type::String;
+ value.data.as_string = reinterpret_cast<const char*>(str_offset + m_compilation_unit.dwarf_info().debug_info_data().data());
+ break;
+ }
+ case AttributeDataForm::Block1: {
+ value.type = AttributeValue::Type::RawBytes;
+ u8 length;
+ debug_info_stream >> length;
+ assign_raw_bytes_value(length);
+ break;
+ }
+ case AttributeDataForm::Block2: {
+ value.type = AttributeValue::Type::RawBytes;
+ u16 length;
+ debug_info_stream >> length;
+ assign_raw_bytes_value(length);
+ break;
+ }
+ case AttributeDataForm::Block4: {
+ value.type = AttributeValue::Type::RawBytes;
+ u32 length;
+ debug_info_stream >> length;
+ assign_raw_bytes_value(length);
+ break;
+ }
+ case AttributeDataForm::Block: {
+ value.type = AttributeValue::Type::RawBytes;
+ size_t length;
+ debug_info_stream.read_LEB128_unsigned(length);
+ assign_raw_bytes_value(length);
+ break;
+ }
+ default:
+ dbgln("Unimplemented AttributeDataForm: {}", (u32)form);
+ ASSERT_NOT_REACHED();
+ }
+ return value;
+}
+
+Optional<DIE::AttributeValue> DIE::get_attribute(const Attribute& attribute) const
+{
+ InputMemoryStream stream { m_compilation_unit.dwarf_info().debug_info_data() };
+ stream.discard_or_error(m_data_offset);
+
+ auto abbreviation_info = m_compilation_unit.abbreviations_map().get(m_abbreviation_code);
+ ASSERT(abbreviation_info.has_value());
+
+ for (const auto& attribute_spec : abbreviation_info.value().attribute_specifications) {
+ auto value = get_attribute_value(attribute_spec.form, stream);
+ if (attribute_spec.attribute == attribute) {
+ return value;
+ }
+ }
+ return {};
+}
+
+void DIE::for_each_child(Function<void(const DIE& child)> callback) const
+{
+ if (!m_has_children)
+ return;
+
+ NonnullOwnPtr<DIE> current_child = make<DIE>(m_compilation_unit, m_offset + m_size);
+ while (true) {
+ callback(*current_child);
+ if (current_child->is_null())
+ break;
+ if (!current_child->has_children()) {
+ current_child = make<DIE>(m_compilation_unit, current_child->offset() + current_child->size());
+ continue;
+ }
+
+ auto sibling = current_child->get_attribute(Attribute::Sibling);
+ u32 sibling_offset = 0;
+ if (sibling.has_value()) {
+ sibling_offset = sibling.value().data.as_u32;
+ }
+
+ if (!sibling.has_value()) {
+ // NOTE: According to the spec, the compiler doesn't have to supply the sibling information.
+ // When it doesn't, we have to recursively iterate the current child's children to find where they end
+ current_child->for_each_child([&](const DIE& sub_child) {
+ sibling_offset = sub_child.offset() + sub_child.size();
+ });
+ }
+ current_child = make<DIE>(m_compilation_unit, sibling_offset);
+ }
+}
+
+DIE DIE::get_die_at_offset(u32 offset) const
+{
+ ASSERT(offset >= m_compilation_unit.offset() && offset < m_compilation_unit.offset() + m_compilation_unit.size());
+ return DIE(m_compilation_unit, offset);
+}
+
+}
diff --git a/Userland/Libraries/LibDebug/Dwarf/DIE.h b/Userland/Libraries/LibDebug/Dwarf/DIE.h
new file mode 100644
index 0000000000..3ba68eafa8
--- /dev/null
+++ b/Userland/Libraries/LibDebug/Dwarf/DIE.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "CompilationUnit.h"
+#include "DwarfTypes.h"
+#include <AK/Function.h>
+#include <AK/NonnullOwnPtr.h>
+#include <AK/Optional.h>
+#include <AK/Types.h>
+
+namespace Debug::Dwarf {
+
+class CompilationUnit;
+
+// DIE = Debugging Information Entry
+class DIE {
+public:
+ DIE(const CompilationUnit&, u32 offset);
+
+ struct AttributeValue {
+ enum class Type : u8 {
+ UnsignedNumber,
+ SignedNumber,
+ String,
+ DieReference, // Reference to another DIE in the same compilation unit
+ Boolean,
+ DwarfExpression,
+ SecOffset,
+ RawBytes,
+ } type;
+
+ union {
+ u32 as_u32;
+ i32 as_i32;
+ const char* as_string; // points to bytes in the memory mapped elf image
+ bool as_bool;
+ struct {
+ u32 length;
+ const u8* bytes; // points to bytes in the memory mapped elf image
+ } as_raw_bytes;
+ } data {};
+ };
+
+ u32 offset() const { return m_offset; }
+ u32 size() const { return m_size; }
+ bool has_children() const { return m_has_children; }
+ EntryTag tag() const { return m_tag; }
+
+ Optional<AttributeValue> get_attribute(const Attribute&) const;
+
+ void for_each_child(Function<void(const DIE& child)> callback) const;
+
+ bool is_null() const { return m_tag == EntryTag::None; }
+
+ DIE get_die_at_offset(u32 offset) const;
+
+private:
+ AttributeValue get_attribute_value(AttributeDataForm form,
+ InputMemoryStream& debug_info_stream) const;
+
+ const CompilationUnit& m_compilation_unit;
+ u32 m_offset { 0 };
+ u32 m_data_offset { 0 };
+ size_t m_abbreviation_code { 0 };
+ EntryTag m_tag { EntryTag::None };
+ bool m_has_children { false };
+ u32 m_size { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibDebug/Dwarf/DwarfInfo.cpp b/Userland/Libraries/LibDebug/Dwarf/DwarfInfo.cpp
new file mode 100644
index 0000000000..09717cfc71
--- /dev/null
+++ b/Userland/Libraries/LibDebug/Dwarf/DwarfInfo.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DwarfInfo.h"
+
+#include <AK/MemoryStream.h>
+
+namespace Debug::Dwarf {
+
+DwarfInfo::DwarfInfo(const ELF::Image& elf)
+ : m_elf(elf)
+{
+ m_debug_info_data = section_data(".debug_info");
+ m_abbreviation_data = section_data(".debug_abbrev");
+ m_debug_strings_data = section_data(".debug_str");
+
+ populate_compilation_units();
+}
+
+ReadonlyBytes DwarfInfo::section_data(const String& section_name) const
+{
+ auto section = m_elf.lookup_section(section_name);
+ if (section.is_undefined())
+ return {};
+ return section.bytes();
+}
+
+void DwarfInfo::populate_compilation_units()
+{
+ if (!m_debug_info_data.data())
+ return;
+
+ InputMemoryStream stream { m_debug_info_data };
+ while (!stream.eof()) {
+ auto unit_offset = stream.offset();
+ CompilationUnitHeader compilation_unit_header {};
+
+ stream >> Bytes { &compilation_unit_header, sizeof(compilation_unit_header) };
+ ASSERT(compilation_unit_header.address_size == sizeof(u32));
+ ASSERT(compilation_unit_header.version <= 4);
+
+ u32 length_after_header = compilation_unit_header.length - (sizeof(CompilationUnitHeader) - offsetof(CompilationUnitHeader, version));
+ m_compilation_units.empend(*this, unit_offset, compilation_unit_header);
+ stream.discard_or_error(length_after_header);
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibDebug/Dwarf/DwarfInfo.h b/Userland/Libraries/LibDebug/Dwarf/DwarfInfo.h
new file mode 100644
index 0000000000..59496e0e3b
--- /dev/null
+++ b/Userland/Libraries/LibDebug/Dwarf/DwarfInfo.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "CompilationUnit.h"
+#include "DwarfTypes.h"
+#include <AK/ByteBuffer.h>
+#include <AK/NonnullRefPtr.h>
+#include <AK/RefCounted.h>
+#include <AK/String.h>
+#include <LibELF/Image.h>
+
+namespace Debug::Dwarf {
+
+class DwarfInfo {
+public:
+ explicit DwarfInfo(const ELF::Image&);
+
+ ReadonlyBytes debug_info_data() const { return m_debug_info_data; }
+ ReadonlyBytes abbreviation_data() const { return m_abbreviation_data; }
+ ReadonlyBytes debug_strings_data() const { return m_debug_strings_data; }
+
+ template<typename Callback>
+ void for_each_compilation_unit(Callback) const;
+
+private:
+ void populate_compilation_units();
+
+ ReadonlyBytes section_data(const String& section_name) const;
+
+ const ELF::Image& m_elf;
+ ReadonlyBytes m_debug_info_data;
+ ReadonlyBytes m_abbreviation_data;
+ ReadonlyBytes m_debug_strings_data;
+
+ Vector<Dwarf::CompilationUnit> m_compilation_units;
+};
+
+template<typename Callback>
+void DwarfInfo::for_each_compilation_unit(Callback callback) const
+{
+ for (const auto& unit : m_compilation_units) {
+ callback(unit);
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibDebug/Dwarf/DwarfTypes.h b/Userland/Libraries/LibDebug/Dwarf/DwarfTypes.h
new file mode 100644
index 0000000000..7bda8f971a
--- /dev/null
+++ b/Userland/Libraries/LibDebug/Dwarf/DwarfTypes.h
@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+
+namespace Debug::Dwarf {
+
+struct [[gnu::packed]] CompilationUnitHeader {
+ u32 length;
+ u16 version;
+ u32 abbrev_offset;
+ u8 address_size;
+};
+
+enum class EntryTag : u16 {
+ None = 0,
+ ArrayType = 0x1,
+ ClassType = 0x2,
+ EntryPoint = 0x3,
+ EnumerationType = 0x4,
+ FormalParameter = 0x5,
+ ImportedDeclaration = 0x8,
+ Label = 0xa,
+ LexicalBlock = 0xb,
+ Member = 0xd,
+ PointerType = 0xf,
+ ReferenceType = 0x10,
+ CompileUnit = 0x11,
+ StringType = 0x12,
+ StructureType = 0x13,
+ SubroutineType = 0x15,
+ TypeDef = 0x16,
+ UnionType = 0x17,
+ UnspecifiedParameters = 0x18,
+ Variant = 0x19,
+ CommonBlock = 0x1a,
+ CommonInclusion = 0x1b,
+ Inheritance = 0x1c,
+ InlinedSubroutine = 0x1d,
+ Module = 0x1e,
+ PtrToMemberType = 0x1f,
+ SetType = 0x20,
+ SubRangeType = 0x21,
+ WithStmt = 0x22,
+ AccessDeclaration = 0x23,
+ BaseType = 0x24,
+ CatchBlock = 0x25,
+ ConstType = 0x26,
+ Constant = 0x27,
+ Enumerator = 0x28,
+ FileType = 0x29,
+ Friend = 0x2a,
+ NameList = 0x2b,
+ NameListItem = 0x2c,
+ PackedType = 0x2d,
+ SubProgram = 0x2e,
+ TemplateTypeParam = 0x2f,
+ TemplateValueParam = 0x30,
+ ThrownType = 0x31,
+ TryBlock = 0x32,
+ VariantPart = 0x33,
+ Variable = 0x34,
+ VolatileType = 0x35,
+ DwarfProcedure = 0x36,
+ RestrictType = 0x37,
+ InterfaceType = 0x38,
+ Namespace = 0x39,
+ ImportedModule = 0x3a,
+ UnspecifiedType = 0x3b,
+ PartialUnit = 0x3c,
+ ImportedUnit = 0x3d,
+ MutableType = 0x3e,
+ Condition = 0x3f,
+ SharedTyped = 0x40,
+ TypeUnit = 0x41,
+ RValueReferenceType = 0x42,
+ TemplateAlias = 0x43,
+ CoArrayType = 0x44,
+ GenericSubRange = 0x45,
+ DynamicType = 0x46,
+ AtomicType = 0x47,
+ CallSite = 0x48,
+ CallSiteParameter = 0x49,
+ SkeletonUnit = 0x4a,
+ ImmutableType = 0x4b,
+ LoUser = 0x4080,
+ HiUser = 0xffff,
+};
+
+enum class Attribute : u16 {
+ None = 0,
+ Sibling = 0x1,
+ Location = 0x2,
+ Name = 0x3,
+ Ordering = 0x9,
+ ByteSize = 0xb,
+ BitOffset = 0xc,
+ BitSize = 0xd,
+ StmtList = 0x10,
+ LowPc = 0x11,
+ HighPc = 0x12,
+ Language = 0x13,
+ Discr = 0x15,
+ DiscrValue = 0x16,
+ Visibility = 0x17,
+ Import = 0x18,
+ StringLength = 0x19,
+ CommonReference = 0x1a,
+ CompDir = 0x1b,
+ ConstValue = 0x1c,
+ ContainingType = 0x1d,
+ DefaultValue = 0x1e,
+ Inline = 0x20,
+ IsOptional = 0x21,
+ LowerBound = 0x22,
+ Producer = 0x25,
+ Prototyped = 0x27,
+ ReturnAddr = 0x2a,
+ StartScope = 0x2c,
+ BitStride = 0x2e,
+ UpperBound = 0x2f,
+ AbstractOrigin = 0x31,
+ Accessibility = 0x32,
+ AddressClass = 0x33,
+ Artificial = 0x34,
+ BaseTypes = 0x35,
+ CallingConvention = 0x36,
+ Count = 0x37,
+ MemberLocation = 0x38,
+ DeclColumn = 0x39,
+ DeclFile = 0x3a,
+ DeclLine = 0x3b,
+ Declaration = 0x3c,
+ DiscrList = 0x3d,
+ Encoding = 0x3e,
+ External = 0x3f,
+ FrameBase = 0x40,
+ Friend = 0x41,
+ IdentifierCase = 0x43,
+ MacroInfo = 0x43,
+ NameListItem = 0x44,
+ Priority = 0x45,
+ Segment = 0x46,
+ Specification = 0x47,
+ StaticLink = 0x48,
+ Type = 0x49,
+ UseLocation = 0x4a,
+ VariableParameter = 0x4b,
+ Virtuality = 0x4c,
+ VtableElemLocation = 0x4d,
+ Allocated = 0x4e,
+ Associated = 0x4f,
+ DataLocation = 0x50,
+ ByteStride = 0x51,
+ EntryPC = 0x52,
+ UseUTF8 = 0x53,
+ Extension = 0x54,
+ Ranges = 0x55,
+ Trampoline = 0x56,
+ CallColumn = 0x57,
+ CallFile = 0x58,
+ CallLine = 0x59,
+ Description = 0x5a,
+ BinaryScale = 0x5b,
+ DecimalScale = 0x5c,
+ Small = 0x5d,
+ DecimalSign = 0x5e,
+ DigitCount = 0x5f,
+ PictureString = 0x60,
+ Mutable = 0x61,
+ ThreadsScaled = 0x62,
+ Explicit = 0x63,
+ ObjectPointer = 0x64,
+ Endianity = 0x65,
+ Elemental = 0x66,
+ Pure = 0x67,
+ Recursive = 0x68,
+ Signature = 0x69,
+ MainSubprogram = 0x6a,
+ DataBitOffset = 0x6b,
+ ConstExpr = 0x6c,
+ EnumClass = 0x6d,
+ LinkageName = 0x6e,
+ StringLengthBitSize = 0x6f,
+ StringLengthByteSize = 0x70,
+ Rank = 0x71,
+ StrOffsetsBase = 0x72,
+ AddrBase = 0x73,
+ RngListsBase = 0x74,
+ DWOName = 0x76,
+ Reference = 0x77,
+ RValueReference = 0x78,
+ Macros = 0x79,
+ CallAllCalls = 0x7a,
+ CallAllSourceCalls = 0x7b,
+ CallAllTailCalls = 0x7c,
+ CallReturnPC = 0x7d,
+ CallValue = 0x7e,
+ CallOrigin = 0x7f,
+ CallParameter = 0x80,
+ CallPC = 0x81,
+ CallTailCall = 0x82,
+ CallTarget = 0x83,
+ CallTargetClobbered = 0x84,
+ CallDataLocation = 0x85,
+ CallDataValue = 0x86,
+ NoReturn = 0x87,
+ Alignment = 0x88,
+ ExportSymbols = 0x89,
+ Deleted = 0x8a,
+ Defaulted = 0x8b,
+ LocListsBase = 0x8c,
+ LoUser = 0x2000,
+ HiUser = 0x3fff,
+};
+
+enum class AttributeDataForm : u8 {
+ None = 0,
+ Addr = 0x1,
+ Block2 = 0x3,
+ Block4 = 0x4,
+ Data2 = 0x5,
+ Data4 = 0x6,
+ Data8 = 0x7,
+ String = 0x8,
+ Block = 0x9,
+ Block1 = 0xa,
+ Data1 = 0xb,
+ Flag = 0xc,
+ SData = 0xd,
+ StringPointer = 0xe,
+ UData = 0xf,
+ RefAddr = 0x10,
+ Ref1 = 0x11,
+ Ref2 = 0x12,
+ Ref4 = 0x13,
+ Ref8 = 0x14,
+ RefUData = 0x15,
+ Indirect = 0x16,
+ SecOffset = 0x17,
+ ExprLoc = 0x18,
+ FlagPresent = 0x19,
+ StrX = 0x1a,
+ AddrX = 0x1b,
+ RefSup4 = 0x1c,
+ StrPSup = 0x1d,
+ Data16 = 0x1e,
+ LineStrP = 0x1f,
+ RefSig8 = 0x20,
+ ImplicitConst = 0x21,
+ LocListX = 0x22,
+ RngListX = 0x23,
+ RefSup8 = 0x24,
+ StrX1 = 0x25,
+ StrX2 = 0x26,
+ StrX3 = 0x27,
+ StrX4 = 0x28,
+ AddrX1 = 0x29,
+ AddrX2 = 0x2a,
+ AddrX3 = 0x2b,
+ AddrX4 = 0x2c
+};
+
+struct [[gnu::packed]] AttributeSpecification {
+ Attribute attribute;
+ AttributeDataForm form;
+};
+
+}
diff --git a/Userland/Libraries/LibDebug/Dwarf/Expression.cpp b/Userland/Libraries/LibDebug/Dwarf/Expression.cpp
new file mode 100644
index 0000000000..fe50d1aed3
--- /dev/null
+++ b/Userland/Libraries/LibDebug/Dwarf/Expression.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "Expression.h"
+
+#include <AK/MemoryStream.h>
+
+#include <sys/arch/i386/regs.h>
+
+namespace Debug::Dwarf::Expression {
+
+Value evaluate(ReadonlyBytes bytes, const PtraceRegisters& regs)
+{
+ InputMemoryStream stream(bytes);
+
+ while (!stream.eof()) {
+ u8 opcode = 0;
+ stream >> opcode;
+
+ switch (static_cast<Operations>(opcode)) {
+ case Operations::RegEbp: {
+ ssize_t offset = 0;
+ stream.read_LEB128_signed(offset);
+ return Value { Type::UnsignedIntetger, regs.ebp + offset };
+ }
+
+ case Operations::FbReg: {
+ ssize_t offset = 0;
+ stream.read_LEB128_signed(offset);
+ return Value { Type::UnsignedIntetger, regs.ebp + 2 * sizeof(size_t) + offset };
+ }
+
+ default:
+ dbgln("DWARF expr addr: {}", (const void*)bytes.data());
+ dbgln("unsupported opcode: {}", (u8)opcode);
+ ASSERT_NOT_REACHED();
+ }
+ }
+ ASSERT_NOT_REACHED();
+}
+
+}
diff --git a/Userland/Libraries/LibDebug/Dwarf/Expression.h b/Userland/Libraries/LibDebug/Dwarf/Expression.h
new file mode 100644
index 0000000000..ef13830353
--- /dev/null
+++ b/Userland/Libraries/LibDebug/Dwarf/Expression.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "AK/ByteBuffer.h"
+#include "AK/Types.h"
+
+struct PtraceRegisters;
+
+namespace Debug::Dwarf::Expression {
+
+enum class Type {
+ None,
+ UnsignedIntetger,
+ Register,
+};
+
+struct Value {
+ Type type;
+ union {
+ u32 as_u32;
+ } data { 0 };
+};
+
+enum class Operations : u8 {
+ RegEbp = 0x75,
+ FbReg = 0x91,
+};
+
+Value evaluate(ReadonlyBytes, const PtraceRegisters&);
+
+}
diff --git a/Userland/Libraries/LibDebug/Dwarf/LineProgram.cpp b/Userland/Libraries/LibDebug/Dwarf/LineProgram.cpp
new file mode 100644
index 0000000000..2bc89a885f
--- /dev/null
+++ b/Userland/Libraries/LibDebug/Dwarf/LineProgram.cpp
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "LineProgram.h"
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+
+//#define DWARF_DEBUG
+
+namespace Debug::Dwarf {
+
+LineProgram::LineProgram(InputMemoryStream& stream)
+ : m_stream(stream)
+{
+ m_unit_offset = m_stream.offset();
+ parse_unit_header();
+ parse_source_directories();
+ parse_source_files();
+ run_program();
+}
+
+void LineProgram::parse_unit_header()
+{
+ m_stream >> Bytes { &m_unit_header, sizeof(m_unit_header) };
+
+ ASSERT(m_unit_header.version == DWARF_VERSION);
+ ASSERT(m_unit_header.opcode_base == SPECIAL_OPCODES_BASE);
+
+#ifdef DWARF_DEBUG
+ dbgln("unit length: {}", m_unit_header.length);
+#endif
+}
+
+void LineProgram::parse_source_directories()
+{
+ m_source_directories.append(".");
+
+ while (m_stream.peek_or_error()) {
+ String directory;
+ m_stream >> directory;
+#ifdef DWARF_DEBUG
+ dbgln("directory: {}", directory);
+#endif
+ m_source_directories.append(move(directory));
+ }
+ m_stream.handle_recoverable_error();
+ m_stream.discard_or_error(1);
+ ASSERT(!m_stream.has_any_error());
+}
+
+void LineProgram::parse_source_files()
+{
+ m_source_files.append({ ".", 0 });
+ while (!m_stream.eof() && m_stream.peek_or_error()) {
+ String file_name;
+ m_stream >> file_name;
+ size_t directory_index = 0;
+ m_stream.read_LEB128_unsigned(directory_index);
+ size_t _unused = 0;
+ m_stream.read_LEB128_unsigned(_unused); // skip modification time
+ m_stream.read_LEB128_unsigned(_unused); // skip file size
+#ifdef DWARF_DEBUG
+ dbgln("file: {}, directory index: {}", file_name, directory_index);
+#endif
+ m_source_files.append({ file_name, directory_index });
+ }
+ m_stream.discard_or_error(1);
+ ASSERT(!m_stream.has_any_error());
+}
+
+void LineProgram::append_to_line_info()
+{
+#ifdef DWARF_DEBUG
+ dbgln("appending line info: {:p}, {}:{}", m_address, m_source_files[m_file_index].name, m_line);
+#endif
+ if (!m_is_statement)
+ return;
+
+ String directory = m_source_directories[m_source_files[m_file_index].directory_index];
+
+ StringBuilder full_path(directory.length() + m_source_files[m_file_index].name.length() + 1);
+ full_path.append(directory);
+ full_path.append('/');
+ full_path.append(m_source_files[m_file_index].name);
+
+ m_lines.append({ m_address, full_path.to_string(), m_line });
+}
+
+void LineProgram::reset_registers()
+{
+ m_address = 0;
+ m_line = 1;
+ m_file_index = 1;
+ m_is_statement = m_unit_header.default_is_stmt == 1;
+}
+
+void LineProgram::handle_extended_opcode()
+{
+ size_t length = 0;
+ m_stream.read_LEB128_unsigned(length);
+
+ u8 sub_opcode = 0;
+ m_stream >> sub_opcode;
+
+ switch (sub_opcode) {
+ case ExtendedOpcodes::EndSequence: {
+ append_to_line_info();
+ reset_registers();
+ break;
+ }
+ case ExtendedOpcodes::SetAddress: {
+ ASSERT(length == sizeof(size_t) + 1);
+ m_stream >> m_address;
+#ifdef DWARF_DEBUG
+ dbgln("SetAddress: {:p}", m_address);
+#endif
+ break;
+ }
+ case ExtendedOpcodes::SetDiscriminator: {
+#ifdef DWARF_DEBUG
+ dbgln("SetDiscriminator");
+#endif
+ m_stream.discard_or_error(1);
+ break;
+ }
+ default:
+#ifdef DWARF_DEBUG
+ dbgln("offset: {:p}", m_stream.offset());
+#endif
+ ASSERT_NOT_REACHED();
+ }
+}
+void LineProgram::handle_standard_opcode(u8 opcode)
+{
+ switch (opcode) {
+ case StandardOpcodes::Copy: {
+ append_to_line_info();
+ break;
+ }
+ case StandardOpcodes::AdvancePc: {
+ size_t operand = 0;
+ m_stream.read_LEB128_unsigned(operand);
+ size_t delta = operand * m_unit_header.min_instruction_length;
+#ifdef DWARF_DEBUG
+ dbgln("AdvancePC by: {} to: {:p}", delta, m_address + delta);
+#endif
+ m_address += delta;
+ break;
+ }
+ case StandardOpcodes::SetFile: {
+ size_t new_file_index = 0;
+ m_stream.read_LEB128_unsigned(new_file_index);
+#ifdef DWARF_DEBUG
+ dbgln("SetFile: new file index: {}", new_file_index);
+#endif
+ m_file_index = new_file_index;
+ break;
+ }
+ case StandardOpcodes::SetColumn: {
+ // not implemented
+#ifdef DWARF_DEBUG
+ dbgln("SetColumn");
+#endif
+ size_t new_column;
+ m_stream.read_LEB128_unsigned(new_column);
+
+ break;
+ }
+ case StandardOpcodes::AdvanceLine: {
+ ssize_t line_delta;
+ m_stream.read_LEB128_signed(line_delta);
+ ASSERT(line_delta >= 0 || m_line >= (size_t)(-line_delta));
+ m_line += line_delta;
+#ifdef DWARF_DEBUG
+ dbgln("AdvanceLine: {}", m_line);
+#endif
+ break;
+ }
+ case StandardOpcodes::NegateStatement: {
+#ifdef DWARF_DEBUG
+ dbgln("NegateStatement");
+#endif
+ m_is_statement = !m_is_statement;
+ break;
+ }
+ case StandardOpcodes::ConstAddPc: {
+ u8 adjusted_opcode = 255 - SPECIAL_OPCODES_BASE;
+ ssize_t address_increment = (adjusted_opcode / m_unit_header.line_range) * m_unit_header.min_instruction_length;
+ address_increment *= m_unit_header.min_instruction_length;
+#ifdef DWARF_DEBUG
+ dbgln("ConstAddPc: advance pc by: {} to: {}", address_increment, (m_address + address_increment));
+#endif
+ m_address += address_increment;
+ break;
+ }
+ case StandardOpcodes::SetIsa: {
+ size_t isa;
+ m_stream.read_LEB128_unsigned(isa);
+ dbgln("SetIsa: {}", isa);
+ break;
+ }
+ default:
+ dbgln("Unhandled LineProgram opcode {}", opcode);
+ ASSERT_NOT_REACHED();
+ }
+}
+void LineProgram::handle_sepcial_opcode(u8 opcode)
+{
+ u8 adjusted_opcode = opcode - SPECIAL_OPCODES_BASE;
+ ssize_t address_increment = (adjusted_opcode / m_unit_header.line_range) * m_unit_header.min_instruction_length;
+ ssize_t line_increment = m_unit_header.line_base + (adjusted_opcode % m_unit_header.line_range);
+
+ m_address += address_increment;
+ m_line += line_increment;
+
+#ifdef DWARF_DEBUG
+ dbgln("Special adjusted_opcode: {}, address_increment: {}, line_increment: {}", adjusted_opcode, address_increment, line_increment);
+ dbg() << "Address is now:" << (void*)m_address << ", and line is: " << m_source_files[m_file_index].name << ":" << m_line;
+#endif
+
+ append_to_line_info();
+}
+
+void LineProgram::run_program()
+{
+ reset_registers();
+
+ while ((size_t)m_stream.offset() < m_unit_offset + sizeof(u32) + m_unit_header.length) {
+ u8 opcode = 0;
+ m_stream >> opcode;
+
+#ifdef DWARF_DEBUG
+ dbg() << (void*)(m_stream.offset() - 1) << ": opcode: " << opcode;
+#endif
+
+ if (opcode == 0) {
+ handle_extended_opcode();
+ } else if (opcode >= 1 && opcode <= 12) {
+ handle_standard_opcode(opcode);
+ } else {
+ handle_sepcial_opcode(opcode);
+ }
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibDebug/Dwarf/LineProgram.h b/Userland/Libraries/LibDebug/Dwarf/LineProgram.h
new file mode 100644
index 0000000000..5599cae555
--- /dev/null
+++ b/Userland/Libraries/LibDebug/Dwarf/LineProgram.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <AK/MemoryStream.h>
+#include <AK/Vector.h>
+
+namespace Debug::Dwarf {
+
+class LineProgram {
+public:
+ explicit LineProgram(InputMemoryStream& stream);
+
+ struct LineInfo {
+ u32 address { 0 };
+ FlyString file;
+ size_t line { 0 };
+ };
+
+ const Vector<LineInfo>& lines() const { return m_lines; }
+
+private:
+ void parse_unit_header();
+ void parse_source_directories();
+ void parse_source_files();
+ void run_program();
+
+ void append_to_line_info();
+ void reset_registers();
+
+ void handle_extended_opcode();
+ void handle_standard_opcode(u8 opcode);
+ void handle_sepcial_opcode(u8 opcode);
+
+ struct [[gnu::packed]] UnitHeader32 {
+ u32 length;
+ u16 version;
+ u32 header_length;
+ u8 min_instruction_length;
+ u8 default_is_stmt;
+ i8 line_base;
+ u8 line_range;
+ u8 opcode_base;
+ u8 std_opcode_lengths[12];
+ };
+
+ enum StandardOpcodes {
+ Copy = 1,
+ AdvancePc,
+ AdvanceLine,
+ SetFile,
+ SetColumn,
+ NegateStatement,
+ SetBasicBlock,
+ ConstAddPc,
+ FixAdvancePc,
+ SetProlougeEnd,
+ SetEpilogueBegin,
+ SetIsa
+ };
+
+ enum ExtendedOpcodes {
+ EndSequence = 1,
+ SetAddress,
+ DefineFile,
+ SetDiscriminator,
+ };
+
+ struct FileEntry {
+ FlyString name;
+ size_t directory_index { 0 };
+ };
+
+ static constexpr u16 DWARF_VERSION = 3;
+ static constexpr u8 SPECIAL_OPCODES_BASE = 13;
+
+ InputMemoryStream& m_stream;
+
+ size_t m_unit_offset { 0 };
+ UnitHeader32 m_unit_header {};
+ Vector<String> m_source_directories;
+ Vector<FileEntry> m_source_files;
+
+ // The registers of the "line program" virtual machine
+ u32 m_address { 0 };
+ size_t m_line { 0 };
+ size_t m_file_index { 0 };
+ bool m_is_statement { false };
+
+ Vector<LineInfo> m_lines;
+};
+
+}
diff --git a/Userland/Libraries/LibDebug/StackFrameUtils.cpp b/Userland/Libraries/LibDebug/StackFrameUtils.cpp
new file mode 100644
index 0000000000..6b68237924
--- /dev/null
+++ b/Userland/Libraries/LibDebug/StackFrameUtils.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "StackFrameUtils.h"
+
+namespace Debug::StackFrameUtils {
+
+Optional<StackFrameInfo> get_info(const DebugSession& session, FlatPtr current_ebp)
+{
+ auto return_address = session.peek(reinterpret_cast<u32*>(current_ebp + sizeof(FlatPtr)));
+ auto next_ebp = session.peek(reinterpret_cast<u32*>(current_ebp));
+ if (!return_address.has_value() || !next_ebp.has_value())
+ return {};
+
+ StackFrameInfo info = { return_address.value(), next_ebp.value() };
+ return info;
+}
+
+}
diff --git a/Userland/Libraries/LibDebug/StackFrameUtils.h b/Userland/Libraries/LibDebug/StackFrameUtils.h
new file mode 100644
index 0000000000..557992d79e
--- /dev/null
+++ b/Userland/Libraries/LibDebug/StackFrameUtils.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Optional.h>
+#include <AK/Types.h>
+
+#include "LibDebug/DebugSession.h"
+
+namespace Debug::StackFrameUtils {
+
+struct StackFrameInfo {
+ FlatPtr return_address;
+ FlatPtr next_ebp;
+};
+
+Optional<StackFrameInfo> get_info(const DebugSession&, FlatPtr current_ebp);
+
+}
diff --git a/Userland/Libraries/LibDesktop/AppFile.cpp b/Userland/Libraries/LibDesktop/AppFile.cpp
new file mode 100644
index 0000000000..41958328f5
--- /dev/null
+++ b/Userland/Libraries/LibDesktop/AppFile.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <AK/Vector.h>
+#include <LibCore/ConfigFile.h>
+#include <LibCore/DirIterator.h>
+#include <LibDesktop/AppFile.h>
+
+namespace Desktop {
+
+NonnullRefPtr<AppFile> AppFile::get_for_app(const StringView& app_name)
+{
+ auto path = String::formatted("{}/{}.af", APP_FILES_DIRECTORY, app_name);
+ return open(path);
+}
+
+NonnullRefPtr<AppFile> AppFile::open(const StringView& path)
+{
+ return adopt(*new AppFile(path));
+}
+
+void AppFile::for_each(Function<void(NonnullRefPtr<AppFile>)> callback, const StringView& directory)
+{
+ Core::DirIterator di(directory, Core::DirIterator::SkipDots);
+ if (di.has_error())
+ return;
+ while (di.has_next()) {
+ auto name = di.next_path();
+ if (!name.ends_with(".af"))
+ continue;
+ auto path = String::formatted("{}/{}", directory, name);
+ auto af = AppFile::open(path);
+ if (!af->is_valid())
+ continue;
+ callback(af);
+ }
+}
+
+AppFile::AppFile(const StringView& path)
+ : m_config(Core::ConfigFile::open(path))
+ , m_valid(validate())
+{
+}
+
+AppFile::~AppFile()
+{
+}
+
+bool AppFile::validate() const
+{
+ if (m_config->read_entry("App", "Name").trim_whitespace().is_empty())
+ return false;
+ if (m_config->read_entry("App", "Executable").trim_whitespace().is_empty())
+ return false;
+ return true;
+}
+
+String AppFile::name() const
+{
+ auto name = m_config->read_entry("App", "Name").trim_whitespace();
+ ASSERT(!name.is_empty());
+ return name;
+}
+
+String AppFile::executable() const
+{
+ auto executable = m_config->read_entry("App", "Executable").trim_whitespace();
+ ASSERT(!executable.is_empty());
+ return executable;
+}
+
+String AppFile::category() const
+{
+ return m_config->read_entry("App", "Category").trim_whitespace();
+}
+
+Vector<String> AppFile::launcher_file_types() const
+{
+ Vector<String> file_types;
+ for (auto& entry : m_config->read_entry("Launcher", "FileTypes").split(',')) {
+ entry = entry.trim_whitespace();
+ if (!entry.is_empty())
+ file_types.append(entry);
+ }
+ return file_types;
+}
+
+Vector<String> AppFile::launcher_protocols() const
+{
+ Vector<String> protocols;
+ for (auto& entry : m_config->read_entry("Launcher", "Protocols").split(',')) {
+ entry = entry.trim_whitespace();
+ if (!entry.is_empty())
+ protocols.append(entry);
+ }
+ return protocols;
+}
+
+}
diff --git a/Userland/Libraries/LibDesktop/AppFile.h b/Userland/Libraries/LibDesktop/AppFile.h
new file mode 100644
index 0000000000..7bfeabd3a3
--- /dev/null
+++ b/Userland/Libraries/LibDesktop/AppFile.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibCore/ConfigFile.h>
+#include <LibGUI/FileIconProvider.h>
+#include <LibGUI/Icon.h>
+
+namespace Desktop {
+
+class AppFile : public RefCounted<AppFile> {
+public:
+ static constexpr const char* APP_FILES_DIRECTORY = "/res/apps";
+ static NonnullRefPtr<AppFile> get_for_app(const StringView& app_name);
+ static NonnullRefPtr<AppFile> open(const StringView& path);
+ static void for_each(Function<void(NonnullRefPtr<AppFile>)>, const StringView& directory = APP_FILES_DIRECTORY);
+ ~AppFile();
+
+ bool is_valid() const { return m_valid; }
+ String file_name() const { return m_config->file_name(); }
+
+ String name() const;
+ String executable() const;
+ String category() const;
+ Vector<String> launcher_file_types() const;
+ Vector<String> launcher_protocols() const;
+
+ GUI::Icon icon() const { return GUI::FileIconProvider::icon_for_path(executable()); };
+
+private:
+ explicit AppFile(const StringView& path);
+
+ bool validate() const;
+
+ RefPtr<Core::ConfigFile> m_config;
+ bool m_valid { false };
+};
+
+}
diff --git a/Userland/Libraries/LibDesktop/CMakeLists.txt b/Userland/Libraries/LibDesktop/CMakeLists.txt
new file mode 100644
index 0000000000..598bb952c8
--- /dev/null
+++ b/Userland/Libraries/LibDesktop/CMakeLists.txt
@@ -0,0 +1,12 @@
+set(SOURCES
+ AppFile.cpp
+ Launcher.cpp
+)
+
+set(GENERATED_SOURCES
+ ../../Services/LaunchServer/LaunchClientEndpoint.h
+ ../../Services/LaunchServer/LaunchServerEndpoint.h
+)
+
+serenity_lib(LibDesktop desktop)
+target_link_libraries(LibDesktop LibCore LibIPC LibGUI)
diff --git a/Userland/Libraries/LibDesktop/Launcher.cpp b/Userland/Libraries/LibDesktop/Launcher.cpp
new file mode 100644
index 0000000000..04e170745f
--- /dev/null
+++ b/Userland/Libraries/LibDesktop/Launcher.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/JsonObject.h>
+#include <AK/URL.h>
+#include <LaunchServer/LaunchClientEndpoint.h>
+#include <LaunchServer/LaunchServerEndpoint.h>
+#include <LibDesktop/Launcher.h>
+#include <LibIPC/ServerConnection.h>
+#include <stdlib.h>
+
+namespace Desktop {
+
+auto Launcher::Details::from_details_str(const String& details_str) -> NonnullRefPtr<Details>
+{
+ auto details = adopt(*new Details);
+ auto json = JsonValue::from_string(details_str);
+ ASSERT(json.has_value());
+ auto obj = json.value().as_object();
+ details->executable = obj.get("executable").to_string();
+ details->name = obj.get("name").to_string();
+ if (auto type_value = obj.get_ptr("type")) {
+ auto type_str = type_value->to_string();
+ if (type_str == "app")
+ details->launcher_type = LauncherType::Application;
+ else if (type_str == "userpreferred")
+ details->launcher_type = LauncherType::UserPreferred;
+ else if (type_str == "userdefault")
+ details->launcher_type = LauncherType::UserDefault;
+ }
+ return details;
+}
+
+class LaunchServerConnection : public IPC::ServerConnection<LaunchClientEndpoint, LaunchServerEndpoint>
+ , public LaunchClientEndpoint {
+ C_OBJECT(LaunchServerConnection)
+public:
+ virtual void handshake() override
+ {
+ auto response = send_sync<Messages::LaunchServer::Greet>();
+ set_my_client_id(response->client_id());
+ }
+
+private:
+ LaunchServerConnection()
+ : IPC::ServerConnection<LaunchClientEndpoint, LaunchServerEndpoint>(*this, "/tmp/portal/launch")
+ {
+ }
+ virtual void handle(const Messages::LaunchClient::Dummy&) override { }
+};
+
+static LaunchServerConnection& connection()
+{
+ static auto connection = LaunchServerConnection::construct();
+ return connection;
+}
+
+bool Launcher::add_allowed_url(const URL& url)
+{
+ auto response = connection().send_sync<Messages::LaunchServer::AddAllowedURL>(url);
+ if (!response) {
+ dbgln("Launcher::add_allowed_url: Failed");
+ return false;
+ }
+ return true;
+}
+
+bool Launcher::add_allowed_handler_with_any_url(const String& handler)
+{
+ auto response = connection().send_sync<Messages::LaunchServer::AddAllowedHandlerWithAnyURL>(handler);
+ if (!response) {
+ dbgln("Launcher::add_allowed_handler_with_any_url: Failed");
+ return false;
+ }
+ return true;
+}
+
+bool Launcher::add_allowed_handler_with_only_specific_urls(const String& handler, const Vector<URL>& urls)
+{
+ auto response = connection().send_sync<Messages::LaunchServer::AddAllowedHandlerWithOnlySpecificURLs>(handler, urls);
+ if (!response) {
+ dbgln("Launcher::add_allowed_handler_with_only_specific_urls: Failed");
+ return false;
+ }
+ return true;
+}
+
+bool Launcher::seal_allowlist()
+{
+ auto response = connection().send_sync<Messages::LaunchServer::SealAllowlist>();
+ if (!response) {
+ dbgln("Launcher::seal_allowlist: Failed");
+ return false;
+ }
+ return true;
+}
+
+bool Launcher::open(const URL& url, const String& handler_name)
+{
+ return connection().send_sync<Messages::LaunchServer::OpenURL>(url, handler_name)->response();
+}
+
+bool Launcher::open(const URL& url, const Details& details)
+{
+ ASSERT(details.launcher_type != LauncherType::Application); // Launcher should not be used to execute arbitrary applications
+ return open(url, details.executable);
+}
+
+Vector<String> Launcher::get_handlers_for_url(const URL& url)
+{
+ return connection().send_sync<Messages::LaunchServer::GetHandlersForURL>(url.to_string())->handlers();
+}
+
+auto Launcher::get_handlers_with_details_for_url(const URL& url) -> NonnullRefPtrVector<Details>
+{
+ auto details = connection().send_sync<Messages::LaunchServer::GetHandlersWithDetailsForURL>(url.to_string())->handlers_details();
+ NonnullRefPtrVector<Details> handlers_with_details;
+ for (auto& value : details) {
+ handlers_with_details.append(Details::from_details_str(value));
+ }
+ return handlers_with_details;
+}
+
+}
diff --git a/Userland/Libraries/LibDesktop/Launcher.h b/Userland/Libraries/LibDesktop/Launcher.h
new file mode 100644
index 0000000000..130ca155fc
--- /dev/null
+++ b/Userland/Libraries/LibDesktop/Launcher.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/HashMap.h>
+#include <AK/NonnullRefPtrVector.h>
+#include <AK/RefCounted.h>
+#include <AK/String.h>
+
+namespace Desktop {
+
+class Launcher {
+public:
+ enum class LauncherType {
+ Default = 0,
+ Application,
+ UserPreferred,
+ UserDefault
+ };
+
+ struct Details : public RefCounted<Details> {
+ String name;
+ String executable;
+ LauncherType launcher_type { LauncherType::Default };
+
+ static NonnullRefPtr<Details> from_details_str(const String&);
+ };
+
+ [[nodiscard]] static bool add_allowed_url(const URL&);
+ [[nodiscard]] static bool add_allowed_handler_with_any_url(const String& handler);
+ [[nodiscard]] static bool add_allowed_handler_with_only_specific_urls(const String& handler, const Vector<URL>&);
+ [[nodiscard]] static bool seal_allowlist();
+ static bool open(const URL&, const String& handler_name = {});
+ static bool open(const URL&, const Details& details);
+ static Vector<String> get_handlers_for_url(const URL&);
+ static NonnullRefPtrVector<Details> get_handlers_with_details_for_url(const URL&);
+};
+
+}
diff --git a/Userland/Libraries/LibDiff/CMakeLists.txt b/Userland/Libraries/LibDiff/CMakeLists.txt
new file mode 100644
index 0000000000..fe81fedda9
--- /dev/null
+++ b/Userland/Libraries/LibDiff/CMakeLists.txt
@@ -0,0 +1,8 @@
+
+set(SOURCES
+ Hunks.cpp
+ Format.cpp
+)
+
+serenity_lib(LibDiff diff)
+target_link_libraries(LibDiff LibC)
diff --git a/Userland/Libraries/LibDiff/Format.cpp b/Userland/Libraries/LibDiff/Format.cpp
new file mode 100644
index 0000000000..38766b89cb
--- /dev/null
+++ b/Userland/Libraries/LibDiff/Format.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Format.h"
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/Vector.h>
+
+namespace Diff {
+String generate_only_additions(const String& text)
+{
+ auto lines = text.split('\n', true); // Keep empty
+ StringBuilder builder;
+ builder.appendf("@@ -0,0 +1,%zu @@\n", lines.size());
+ for (const auto& line : lines) {
+ builder.appendf("+%s\n", line.characters());
+ }
+ return builder.to_string();
+}
+};
diff --git a/Userland/Libraries/LibDiff/Format.h b/Userland/Libraries/LibDiff/Format.h
new file mode 100644
index 0000000000..73cbbcf407
--- /dev/null
+++ b/Userland/Libraries/LibDiff/Format.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+
+namespace Diff {
+String generate_only_additions(const String&);
+};
diff --git a/Userland/Libraries/LibDiff/Hunks.cpp b/Userland/Libraries/LibDiff/Hunks.cpp
new file mode 100644
index 0000000000..d806b2b386
--- /dev/null
+++ b/Userland/Libraries/LibDiff/Hunks.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Hunks.h"
+
+// #define DEBUG_HUNKS
+
+namespace Diff {
+Vector<Hunk> parse_hunks(const String& diff)
+{
+ Vector<String> diff_lines = diff.split('\n');
+ if (diff_lines.is_empty())
+ return {};
+
+ Vector<Hunk> hunks;
+
+ size_t line_index = 0;
+ HunkLocation current_location {};
+
+ // Skip to first hunk
+ while (diff_lines[line_index][0] != '@') {
+ ++line_index;
+ }
+
+ while (line_index < diff_lines.size()) {
+ if (diff_lines[line_index][0] == '@') {
+ current_location = parse_hunk_location(diff_lines[line_index]);
+ ++line_index;
+ continue;
+ }
+ if (diff_lines[line_index][0] == ' ') {
+ current_location.apply_offset(1, HunkLocation::LocationType::Both);
+ ++line_index;
+ continue;
+ }
+ Hunk hunk {};
+ hunk.original_start_line = current_location.original_start_line;
+ hunk.target_start_line = current_location.target_start_line;
+
+ while (line_index < diff_lines.size() && diff_lines[line_index][0] == '-') {
+ hunk.removed_lines.append(diff_lines[line_index].substring(1, diff_lines[line_index].length() - 1));
+ current_location.apply_offset(1, HunkLocation::LocationType::Original);
+ ++line_index;
+ }
+ while (line_index < diff_lines.size() && diff_lines[line_index][0] == '+') {
+ hunk.added_lines.append(diff_lines[line_index].substring(1, diff_lines[line_index].length() - 1));
+ current_location.apply_offset(1, HunkLocation::LocationType::Target);
+ ++line_index;
+ }
+
+ while (line_index < diff_lines.size() && diff_lines[line_index][0] == ' ') {
+ current_location.apply_offset(1, HunkLocation::LocationType::Both);
+ ++line_index;
+ }
+ hunks.append(hunk);
+ }
+
+#ifdef DEBUG_HUNKS
+ for (const auto& hunk : hunks) {
+ dbgln("Hunk location:");
+ dbg() << "orig: " << hunk.original_start_line;
+ dbg() << "target: " << hunk.target_start_line;
+ dbgln("removed:");
+ for (const auto& line : hunk.removed_lines) {
+ dbg() << "- " << line;
+ }
+ dbgln("added:");
+ for (const auto& line : hunk.added_lines) {
+ dbg() << "+ " << line;
+ }
+ }
+#endif
+
+ return hunks;
+}
+
+HunkLocation parse_hunk_location(const String& location_line)
+{
+ size_t char_index = 0;
+ struct StartAndLength {
+ size_t start { 0 };
+ size_t length { 0 };
+ };
+ auto parse_start_and_length_pair = [](const String& raw) {
+ auto index_of_separator = raw.index_of(",").value();
+ auto start = raw.substring(0, index_of_separator);
+ auto length = raw.substring(index_of_separator + 1, raw.length() - index_of_separator - 1);
+ auto res = StartAndLength { start.to_uint().value() - 1, length.to_uint().value() - 1 };
+ return res;
+ };
+ while (char_index < location_line.length() && location_line[char_index++] != '-') {
+ }
+ ASSERT(char_index < location_line.length());
+
+ size_t original_location_start_index = char_index;
+
+ while (char_index < location_line.length() && location_line[char_index++] != ' ') {
+ }
+ ASSERT(char_index < location_line.length() && location_line[char_index] == '+');
+ size_t original_location_end_index = char_index - 2;
+
+ size_t target_location_start_index = char_index + 1;
+
+ char_index += 1;
+ while (char_index < location_line.length() && location_line[char_index++] != ' ') {
+ }
+ ASSERT(char_index < location_line.length());
+
+ size_t target_location_end_index = char_index - 2;
+
+ auto original_pair = parse_start_and_length_pair(location_line.substring(original_location_start_index, original_location_end_index - original_location_start_index + 1));
+ auto target_pair = parse_start_and_length_pair(location_line.substring(target_location_start_index, target_location_end_index - target_location_start_index + 1));
+ return { original_pair.start, original_pair.length, target_pair.start, target_pair.length };
+}
+
+void HunkLocation::apply_offset(size_t offset, HunkLocation::LocationType type)
+{
+ if (type == LocationType::Original || type == LocationType::Both) {
+ original_start_line += offset;
+ original_length -= offset;
+ }
+ if (type == LocationType::Target || type == LocationType::Both) {
+ target_start_line += offset;
+ target_length -= offset;
+ }
+}
+
+};
diff --git a/Userland/Libraries/LibDiff/Hunks.h b/Userland/Libraries/LibDiff/Hunks.h
new file mode 100644
index 0000000000..a24dd2e72c
--- /dev/null
+++ b/Userland/Libraries/LibDiff/Hunks.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/Vector.h>
+
+namespace Diff {
+
+struct HunkLocation {
+ size_t original_start_line { 0 };
+ size_t original_length { 0 };
+ size_t target_start_line { 0 };
+ size_t target_length { 0 };
+
+ enum class LocationType {
+ Original,
+ Target,
+ Both
+ };
+ void apply_offset(size_t offset, LocationType);
+};
+
+struct Hunk {
+ size_t original_start_line { 0 };
+ size_t target_start_line { 0 };
+ Vector<String> removed_lines;
+ Vector<String> added_lines;
+};
+
+Vector<Hunk> parse_hunks(const String& diff);
+HunkLocation parse_hunk_location(const String& location_line);
+};
diff --git a/Userland/Libraries/LibELF/Arch/i386/plt_trampoline.S b/Userland/Libraries/LibELF/Arch/i386/plt_trampoline.S
new file mode 100644
index 0000000000..6eb5e96a5f
--- /dev/null
+++ b/Userland/Libraries/LibELF/Arch/i386/plt_trampoline.S
@@ -0,0 +1,58 @@
+/*-
+ * Copyright (c) 1998, 2002 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas and by Charles M. Hannum.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * This asm method is copied from NetBSD. We changed the internal method that
+ * gets called and the name, but it's still essentially the same as
+ * _rtld_bind_start from libexec/ld.elf_so/arch/i386/rtld_start.S
+ */
+
+.align 4
+ .globl _plt_trampoline
+ .hidden _plt_trampoline
+ .type _plt_trampoline,@function
+_plt_trampoline: # (obj, reloff)
+ pushf # save registers
+ pushl %eax
+ pushl %ecx
+ pushl %edx
+
+ pushl 20(%esp) # Copy of reloff
+ pushl 20(%esp) # Copy of obj
+ call _fixup_plt_entry # Call the binder
+ addl $8,%esp # pop binder args
+ movl %eax,20(%esp) # Store function to be called in obj
+
+ popl %edx
+ popl %ecx
+ popl %eax
+ popf
+
+ leal 4(%esp),%esp # Discard reloff, do not change eflags
+ ret
diff --git a/Userland/Libraries/LibELF/AuxiliaryVector.h b/Userland/Libraries/LibELF/AuxiliaryVector.h
new file mode 100644
index 0000000000..24a5f0924c
--- /dev/null
+++ b/Userland/Libraries/LibELF/AuxiliaryVector.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/Types.h>
+
+/* Auxiliary Vector types, from Intel386 ABI ver 1.0 section 2.3.3 */
+typedef struct
+{
+ long a_type; /* Note: Extended to long from int, for ease of comaptibility w/64 bit */
+ union {
+ long a_val;
+ void* a_ptr;
+ void (*a_fnc)(); /* In spec, not used */
+ } a_un;
+} auxv_t;
+
+// clang-format off
+#define AT_NULL 0 /* No length, last entry's a_type has this value */
+#define AT_IGNORE 1 /* Entry has no meaning, a_un undefined */
+#define AT_EXECFD 2 /* a_val contains a file descriptor of the main program image */
+#define AT_PHDR 3 /* a_ptr contains pointer to program header table of main program image */
+#define AT_PHENT 4 /* a_val holds size of program header table entries */
+#define AT_PHNUM 5 /* a_val holds number of program header table entries */
+#define AT_PAGESZ 6 /* a_val gives system page size in bytes */
+#define AT_BASE 7 /* a_ptr holds base address that Loader was loaded into memory */
+#define AT_FLAGS 8 /* a_val holds 1 bit flags. Undefined flags are 0 */
+#define AT_ENTRY 9 /* a_ptr holds entry point of the main program */
+#define AT_NOTELF 10 /* a_val non-zero if the program is not ELF */
+#define AT_UID 11 /* a_val holds real user id of process */
+#define AT_EUID 12 /* a_val holds effective user id of process */
+#define AT_GID 13 /* a_val holds real group id of process */
+#define AT_EGID 14 /* a_val holds effective group id of process */
+#define AT_PLATFORM 15 /* a_val points to a string containing platform name */
+#define AT_HWCAP 16 /* a_val contains bitmask of CPU features. Equivalent to CPUID 1.EDX*/
+#define AT_CLKTCK 17 /* a_val contains frequence at which times() increments. (Re: Spec. What is times()?) */
+#define AT_SECURE 23 /* a_val holds 1 if program in secure mode (e.g. suid). Otherwise 0 */
+#define AT_BASE_PLATFORM 24 /* a_ptr points to a string identifying base platform name, which might be different from platform (e.g x86_64 when in i386 compat) */
+#define AT_RANDOM 25 /* a_ptr points to 16 securely generated random bytes */
+#define AT_HWCAP2 26 /* a_val holds extended hw feature mask. Currently 0 */
+#define AT_EXECFN 31 /* a_ptr points to file name of executed program */
+#define AT_EXE_BASE 32 /* a_ptr holds base address where main program was loaded into memory */
+#define AT_EXE_SIZE 33 /* a_val holds the size of the main program in memory */
+// clang-format on
+
+namespace ELF {
+
+struct AuxiliaryValue {
+ enum Type {
+ Null = AT_NULL,
+ Ignore = AT_IGNORE,
+ ExecFileDescriptor = AT_EXECFD,
+ Phdr = AT_PHDR,
+ Phent = AT_PHENT,
+ Phnum = AT_PHNUM,
+ PageSize = AT_PAGESZ,
+ BaseAddress = AT_BASE,
+ Flags = AT_FLAGS,
+ Entry = AT_ENTRY,
+ NotELF = AT_NOTELF,
+ Uid = AT_UID,
+ EUid = AT_EUID,
+ Gid = AT_GID,
+ EGid = AT_EGID,
+ Platform = AT_PLATFORM,
+ HwCap = AT_HWCAP,
+ ClockTick = AT_CLKTCK,
+ Secure = AT_SECURE,
+ BasePlatform = AT_BASE_PLATFORM,
+ Random = AT_RANDOM,
+ HwCap2 = AT_HWCAP2,
+ ExecFilename = AT_EXECFN,
+ ExeBaseAddress = AT_EXE_BASE,
+ ExeSize = AT_EXE_SIZE
+ };
+
+ AuxiliaryValue(Type type, long val)
+ {
+ auxv.a_type = type;
+ auxv.a_un.a_val = val;
+ }
+ AuxiliaryValue(Type type, void* ptr)
+ {
+ auxv.a_type = type;
+ auxv.a_un.a_ptr = (void*)ptr;
+ }
+ AuxiliaryValue(Type type, String string)
+ {
+ auxv.a_type = type;
+ auxv.a_un.a_ptr = nullptr;
+ optional_string = string;
+ }
+
+ auxv_t auxv {};
+ String optional_string;
+};
+
+}
diff --git a/Userland/Libraries/LibELF/CMakeLists.txt b/Userland/Libraries/LibELF/CMakeLists.txt
new file mode 100644
index 0000000000..3c0959e7e9
--- /dev/null
+++ b/Userland/Libraries/LibELF/CMakeLists.txt
@@ -0,0 +1 @@
+serenity_install_sources(Libraries/LibELF)
diff --git a/Userland/Libraries/LibELF/CoreDump.h b/Userland/Libraries/LibELF/CoreDump.h
new file mode 100644
index 0000000000..4978430306
--- /dev/null
+++ b/Userland/Libraries/LibELF/CoreDump.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <LibC/sys/arch/i386/regs.h>
+
+namespace ELF::Core {
+
+struct [[gnu::packed]] NotesEntryHeader {
+ enum Type : u8 {
+ Null = 0, // Terminates segment
+ ProcessInfo,
+ ThreadInfo,
+ MemoryRegionInfo,
+ Metadata,
+ };
+ Type type;
+};
+
+struct [[gnu::packed]] NotesEntry {
+ NotesEntryHeader header;
+ char data[];
+};
+
+struct [[gnu::packed]] ProcessInfo {
+ NotesEntryHeader header;
+ int pid;
+ u8 termination_signal;
+ char executable_path[]; // Null terminated
+};
+
+struct [[gnu::packed]] ThreadInfo {
+ NotesEntryHeader header;
+ int tid;
+ PtraceRegisters regs;
+};
+
+struct [[gnu::packed]] MemoryRegionInfo {
+ NotesEntryHeader header;
+ uint32_t region_start;
+ uint32_t region_end;
+ uint16_t program_header_index;
+ char region_name[]; // Null terminated
+
+ String object_name() const
+ {
+ StringView memory_region_name { region_name };
+ if (memory_region_name.contains("Loader.so"))
+ return "Loader.so";
+ if (!memory_region_name.contains(":"))
+ return {};
+ return memory_region_name.substring_view(0, memory_region_name.find_first_of(":").value()).to_string();
+ }
+};
+
+struct [[gnu::packed]] Metadata {
+ NotesEntryHeader header;
+ char json_data[]; // Null terminated
+};
+
+}
diff --git a/Userland/Libraries/LibELF/DynamicLinker.cpp b/Userland/Libraries/LibELF/DynamicLinker.cpp
new file mode 100644
index 0000000000..3513645d07
--- /dev/null
+++ b/Userland/Libraries/LibELF/DynamicLinker.cpp
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * Copyright (c) 2021, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/HashMap.h>
+#include <AK/HashTable.h>
+#include <AK/LexicalPath.h>
+#include <AK/LogStream.h>
+#include <AK/ScopeGuard.h>
+#include <LibC/mman.h>
+#include <LibC/stdio.h>
+#include <LibC/sys/internals.h>
+#include <LibC/unistd.h>
+#include <LibCore/File.h>
+#include <LibELF/AuxiliaryVector.h>
+#include <LibELF/DynamicLinker.h>
+#include <LibELF/DynamicLoader.h>
+#include <LibELF/DynamicObject.h>
+#include <LibELF/Image.h>
+#include <LibELF/exec_elf.h>
+#include <dlfcn.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+// #define DYNAMIC_LOAD_VERBOSE
+
+#ifdef DYNAMIC_LOAD_VERBOSE
+# define VERBOSE(fmt, ...) dbgprintf(fmt, ##__VA_ARGS__)
+#else
+# define VERBOSE(fmt, ...) \
+ do { \
+ } while (0)
+#endif
+#define TLS_VERBOSE(fmt, ...) dbgprintf(fmt, ##__VA_ARGS__)
+
+namespace ELF {
+
+namespace {
+HashMap<String, NonnullRefPtr<ELF::DynamicLoader>> g_loaders;
+HashMap<String, NonnullRefPtr<ELF::DynamicObject>> g_loaded_objects;
+Vector<NonnullRefPtr<ELF::DynamicObject>> g_global_objects;
+
+using MainFunction = int (*)(int, char**, char**);
+using LibCExitFunction = void (*)(int);
+
+size_t g_current_tls_offset = 0;
+size_t g_total_tls_size = 0;
+char** g_envp = nullptr;
+LibCExitFunction g_libc_exit = nullptr;
+
+bool g_allowed_to_check_environment_variables { false };
+bool g_do_breakpoint_trap_before_entry { false };
+}
+
+DynamicObject::SymbolLookupResult DynamicLinker::lookup_global_symbol(const char* symbol_name)
+{
+ DynamicObject::SymbolLookupResult weak_result = {};
+ for (auto& lib : g_global_objects) {
+ auto res = lib->lookup_symbol(symbol_name);
+ if (res.found) {
+ if (res.bind == STB_GLOBAL) {
+ return res;
+ } else if (res.bind == STB_WEAK && !weak_result.found) {
+ weak_result = res;
+ }
+ // We don't want to allow local symbols to be pulled in to other modules
+ }
+ }
+ return weak_result;
+}
+
+static void map_library(const String& name, int fd)
+{
+ struct stat lib_stat;
+ int rc = fstat(fd, &lib_stat);
+ ASSERT(!rc);
+
+ auto loader = ELF::DynamicLoader::construct(name.characters(), fd, lib_stat.st_size);
+ loader->set_tls_offset(g_current_tls_offset);
+
+ g_loaders.set(name, loader);
+
+ g_current_tls_offset += loader->tls_size();
+}
+
+static void map_library(const String& name)
+{
+ // TODO: Do we want to also look for libs in other paths too?
+ String path = String::format("/usr/lib/%s", name.characters());
+ int fd = open(path.characters(), O_RDONLY);
+ ASSERT(fd >= 0);
+ map_library(name, fd);
+}
+
+static String get_library_name(const StringView& path)
+{
+ return LexicalPath(path).basename();
+}
+
+static Vector<String> get_dependencies(const String& name)
+{
+ auto lib = g_loaders.get(name).value();
+ Vector<String> dependencies;
+
+ lib->for_each_needed_library([&dependencies, &name](auto needed_name) {
+ if (name == needed_name)
+ return IterationDecision::Continue;
+ dependencies.append(needed_name);
+ return IterationDecision::Continue;
+ });
+ return dependencies;
+}
+
+static void map_dependencies(const String& name)
+{
+ VERBOSE("mapping dependencies for: %s\n", name.characters());
+
+ for (const auto& needed_name : get_dependencies(name)) {
+ VERBOSE("needed library: %s\n", needed_name.characters());
+ String library_name = get_library_name(needed_name);
+
+ if (!g_loaders.contains(library_name)) {
+ map_library(library_name);
+ map_dependencies(library_name);
+ }
+ }
+ VERBOSE("mapped dependencies for %s\n", name.characters());
+}
+
+static void allocate_tls()
+{
+ size_t total_tls_size = 0;
+ for (const auto& data : g_loaders) {
+ VERBOSE("%s: TLS Size: %zu\n", data.key.characters(), data.value->tls_size());
+ total_tls_size += data.value->tls_size();
+ }
+ if (total_tls_size) {
+ [[maybe_unused]] void* tls_address = ::allocate_tls(total_tls_size);
+ VERBOSE("from userspace, tls_address: %p\n", tls_address);
+ }
+ g_total_tls_size = total_tls_size;
+}
+
+static void initialize_libc(DynamicObject& libc)
+{
+ // Traditionally, `_start` of the main program initializes libc.
+ // However, since some libs use malloc() and getenv() in global constructors,
+ // we have to initialize libc just after it is loaded.
+ // Also, we can't just mark `__libc_init` with "__attribute__((constructor))"
+ // because it uses getenv() internally, so `environ` has to be initialized before we call `__libc_init`.
+ auto res = libc.lookup_symbol("environ");
+ ASSERT(res.found);
+ *((char***)res.address) = g_envp;
+
+ res = libc.lookup_symbol("__environ_is_malloced");
+ ASSERT(res.found);
+ *((bool*)res.address) = false;
+
+ res = libc.lookup_symbol("exit");
+ ASSERT(res.found);
+ g_libc_exit = (LibCExitFunction)res.address;
+
+ res = libc.lookup_symbol("__libc_init");
+ ASSERT(res.found);
+ typedef void libc_init_func();
+ ((libc_init_func*)res.address)();
+}
+
+static void load_elf(const String& name)
+{
+ VERBOSE("load_elf: %s\n", name.characters());
+ auto loader = g_loaders.get(name).value();
+ VERBOSE("a1\n");
+ for (const auto& needed_name : get_dependencies(name)) {
+ VERBOSE("needed library: %s\n", needed_name.characters());
+ String library_name = get_library_name(needed_name);
+ if (!g_loaded_objects.contains(library_name)) {
+ load_elf(library_name);
+ }
+ }
+
+ auto dynamic_object = loader->load_from_image(RTLD_GLOBAL | RTLD_LAZY, g_total_tls_size);
+ ASSERT(dynamic_object);
+
+ g_loaded_objects.set(name, *dynamic_object);
+ g_global_objects.append(*dynamic_object);
+
+ VERBOSE("load_elf: done %s\n", name.characters());
+}
+
+static NonnullRefPtr<DynamicLoader> commit_elf(const String& name)
+{
+ auto loader = g_loaders.get(name).value();
+ for (const auto& needed_name : get_dependencies(name)) {
+ String library_name = get_library_name(needed_name);
+ if (g_loaders.contains(library_name)) {
+ commit_elf(library_name);
+ }
+ }
+
+ auto object = loader->load_stage_3(RTLD_GLOBAL | RTLD_LAZY, g_total_tls_size);
+ ASSERT(object);
+ if (name == "libc.so") {
+ initialize_libc(*object);
+ }
+ g_loaders.remove(name);
+ return loader;
+}
+
+static void read_environment_variables()
+{
+ for (char** env = g_envp; *env; ++env) {
+ if (StringView { *env } == "_LOADER_BREAKPOINT=1") {
+ g_do_breakpoint_trap_before_entry = true;
+ }
+ }
+}
+
+void ELF::DynamicLinker::linker_main(String&& main_program_name, int main_program_fd, bool is_secure, int argc, char** argv, char** envp)
+{
+ g_envp = envp;
+
+ g_allowed_to_check_environment_variables = !is_secure;
+ if (g_allowed_to_check_environment_variables)
+ read_environment_variables();
+
+ map_library(main_program_name, main_program_fd);
+ map_dependencies(main_program_name);
+
+ VERBOSE("loaded all dependencies");
+ for ([[maybe_unused]] auto& lib : g_loaders) {
+ VERBOSE("%s - tls size: %zu, tls offset: %zu\n", lib.key.characters(), lib.value->tls_size(), lib.value->tls_offset());
+ }
+
+ allocate_tls();
+
+ load_elf(main_program_name);
+ auto main_program_lib = commit_elf(main_program_name);
+
+ FlatPtr entry_point = reinterpret_cast<FlatPtr>(main_program_lib->image().entry().as_ptr());
+ if (main_program_lib->is_dynamic())
+ entry_point += reinterpret_cast<FlatPtr>(main_program_lib->text_segment_load_address().as_ptr());
+
+ VERBOSE("entry point: %p\n", (void*)entry_point);
+ g_loaders.clear();
+
+ MainFunction main_function = (MainFunction)(entry_point);
+ VERBOSE("jumping to main program entry point: %p\n", main_function);
+ if (g_do_breakpoint_trap_before_entry) {
+ asm("int3");
+ }
+ int rc = main_function(argc, argv, envp);
+ VERBOSE("rc: %d\n", rc);
+ if (g_libc_exit != nullptr) {
+ g_libc_exit(rc);
+ } else {
+ _exit(rc);
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+}
diff --git a/Userland/Libraries/LibELF/DynamicLinker.h b/Userland/Libraries/LibELF/DynamicLinker.h
new file mode 100644
index 0000000000..d476443976
--- /dev/null
+++ b/Userland/Libraries/LibELF/DynamicLinker.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2021 (c), the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Result.h>
+#include <AK/Vector.h>
+#include <LibELF/DynamicObject.h>
+
+namespace ELF {
+
+class DynamicLinker {
+public:
+ static DynamicObject::SymbolLookupResult lookup_global_symbol(const char* symbol);
+ [[noreturn]] static void linker_main(String&& main_program_name, int fd, bool is_secure, int argc, char** argv, char** envp);
+
+private:
+ DynamicLinker() = delete;
+ ~DynamicLinker() = delete;
+};
+
+}
diff --git a/Userland/Libraries/LibELF/DynamicLoader.cpp b/Userland/Libraries/LibELF/DynamicLoader.cpp
new file mode 100644
index 0000000000..e7c56a370c
--- /dev/null
+++ b/Userland/Libraries/LibELF/DynamicLoader.cpp
@@ -0,0 +1,530 @@
+/*
+ * Copyright (c) 2019-2020, Andrew Kaster <andrewdkaster@gmail.com>
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibELF/DynamicLoader.h>
+#include <LibELF/Validation.h>
+
+#include <assert.h>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+
+#ifndef DYNAMIC_LOAD_DEBUG
+# define DYNAMIC_LOAD_DEBUG
+#endif
+
+// #define DYNAMIC_LOAD_VERBOSE
+
+#ifdef DYNAMIC_LOAD_VERBOSE
+# define VERBOSE(fmt, ...) dbgprintf(fmt, ##__VA_ARGS__)
+#else
+# define VERBOSE(fmt, ...) \
+ do { \
+ } while (0)
+#endif
+
+#ifndef __serenity__
+static void* mmap_with_name(void* addr, size_t length, int prot, int flags, int fd, off_t offset, const char*)
+{
+ return mmap(addr, length, prot, flags, fd, offset);
+}
+#endif
+
+namespace ELF {
+
+static bool s_always_bind_now = false;
+
+NonnullRefPtr<DynamicLoader> DynamicLoader::construct(const char* filename, int fd, size_t size)
+{
+ return adopt(*new DynamicLoader(filename, fd, size));
+}
+
+void* DynamicLoader::do_mmap(int fd, size_t size, const String& name)
+{
+ if (size < sizeof(Elf32_Ehdr))
+ return MAP_FAILED;
+
+ String file_mmap_name = String::format("ELF_DYN: %s", name.characters());
+ return mmap_with_name(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0, file_mmap_name.characters());
+}
+
+DynamicLoader::DynamicLoader(const char* filename, int fd, size_t size)
+ : m_filename(filename)
+ , m_file_size(size)
+ , m_image_fd(fd)
+ , m_file_mapping(do_mmap(m_image_fd, m_file_size, m_filename))
+ , m_elf_image((u8*)m_file_mapping, m_file_size)
+{
+
+ if (m_file_mapping == MAP_FAILED) {
+ m_valid = false;
+ return;
+ }
+
+ m_tls_size = calculate_tls_size();
+
+ m_valid = validate();
+}
+
+RefPtr<DynamicObject> DynamicLoader::dynamic_object_from_image() const
+{
+ VirtualAddress dynamic_section_address;
+
+ m_elf_image.for_each_program_header([&dynamic_section_address](auto program_header) {
+ if (program_header.type() == PT_DYNAMIC) {
+ dynamic_section_address = VirtualAddress(program_header.raw_data());
+ }
+ return IterationDecision::Continue;
+ });
+ ASSERT(!dynamic_section_address.is_null());
+
+ return ELF::DynamicObject::construct(VirtualAddress(m_elf_image.base_address()), dynamic_section_address);
+}
+
+size_t DynamicLoader::calculate_tls_size() const
+{
+ size_t tls_size = 0;
+ m_elf_image.for_each_program_header([&tls_size](auto program_header) {
+ if (program_header.type() == PT_TLS) {
+ tls_size = program_header.size_in_memory();
+ }
+ return IterationDecision::Continue;
+ });
+ return tls_size;
+}
+
+bool DynamicLoader::validate()
+{
+ auto* elf_header = (Elf32_Ehdr*)m_file_mapping;
+ return validate_elf_header(*elf_header, m_file_size) && validate_program_headers(*elf_header, m_file_size, (u8*)m_file_mapping, m_file_size, &m_program_interpreter);
+}
+
+DynamicLoader::~DynamicLoader()
+{
+ if (MAP_FAILED != m_file_mapping)
+ munmap(m_file_mapping, m_file_size);
+ close(m_image_fd);
+}
+
+void* DynamicLoader::symbol_for_name(const char* name)
+{
+ auto symbol = m_dynamic_object->hash_section().lookup_symbol(name);
+
+ if (symbol.is_undefined())
+ return nullptr;
+
+ return m_dynamic_object->base_address().offset(symbol.value()).as_ptr();
+}
+
+RefPtr<DynamicObject> DynamicLoader::load_from_image(unsigned flags, size_t total_tls_size)
+{
+
+ m_valid = m_elf_image.is_valid();
+
+ if (!m_valid) {
+ dbgprintf("DynamicLoader::load_from_image failed: image is invalid\n");
+ return nullptr;
+ }
+
+#ifdef DYNAMIC_LOAD_VERBOSE
+ // m_image->dump();
+#endif
+
+ load_program_headers();
+
+ m_dynamic_object = DynamicObject::construct(m_text_segment_load_address, m_dynamic_section_address);
+ m_dynamic_object->set_tls_offset(m_tls_offset);
+ m_dynamic_object->set_tls_size(m_tls_size);
+
+ auto rc = load_stage_2(flags, total_tls_size);
+ if (!rc) {
+ dbgprintf("DynamicLoader::load_from_image failed at load_stage_2\n");
+ return nullptr;
+ }
+ return m_dynamic_object;
+}
+
+bool DynamicLoader::load_stage_2(unsigned flags, size_t total_tls_size)
+{
+ ASSERT(flags & RTLD_GLOBAL);
+
+#ifdef DYNAMIC_LOAD_DEBUG
+ m_dynamic_object->dump();
+#endif
+
+ if (m_dynamic_object->has_text_relocations()) {
+ ASSERT(m_text_segment_load_address.get() != 0);
+
+#ifndef AK_OS_MACOS
+ // Remap this text region as private.
+ if (mremap(m_text_segment_load_address.as_ptr(), m_text_segment_size, m_text_segment_size, MAP_PRIVATE) == MAP_FAILED) {
+ perror("mremap .text: MAP_PRIVATE");
+ return false;
+ }
+#endif
+
+ if (0 > mprotect(m_text_segment_load_address.as_ptr(), m_text_segment_size, PROT_READ | PROT_WRITE)) {
+ perror("mprotect .text: PROT_READ | PROT_WRITE"); // FIXME: dlerror?
+ return false;
+ }
+ }
+ do_main_relocations(total_tls_size);
+ return true;
+}
+
+void DynamicLoader::do_main_relocations(size_t total_tls_size)
+{
+ auto do_single_relocation = [&](ELF::DynamicObject::Relocation relocation) {
+ switch (do_relocation(total_tls_size, relocation)) {
+ case RelocationResult::Failed:
+ dbgln("Loader.so: {} unresolved symbol '{}'", m_filename, relocation.symbol().name());
+ ASSERT_NOT_REACHED();
+ break;
+ case RelocationResult::ResolveLater:
+ m_unresolved_relocations.append(relocation);
+ break;
+ case RelocationResult::Success:
+ break;
+ }
+ return IterationDecision::Continue;
+ };
+ m_dynamic_object->relocation_section().for_each_relocation(do_single_relocation);
+ m_dynamic_object->plt_relocation_section().for_each_relocation(do_single_relocation);
+}
+
+RefPtr<DynamicObject> DynamicLoader::load_stage_3(unsigned flags, size_t total_tls_size)
+{
+
+ do_lazy_relocations(total_tls_size);
+ if (flags & RTLD_LAZY) {
+ setup_plt_trampoline();
+ }
+
+ // Clean up our setting of .text to PROT_READ | PROT_WRITE
+ if (m_dynamic_object->has_text_relocations()) {
+ if (0 > mprotect(m_text_segment_load_address.as_ptr(), m_text_segment_size, PROT_READ | PROT_EXEC)) {
+ perror("mprotect .text: PROT_READ | PROT_EXEC"); // FIXME: dlerror?
+ return nullptr;
+ }
+ }
+
+ call_object_init_functions();
+
+ VERBOSE("Loaded %s\n", m_filename.characters());
+ return m_dynamic_object;
+}
+
+void DynamicLoader::do_lazy_relocations(size_t total_tls_size)
+{
+ for (const auto& relocation : m_unresolved_relocations) {
+ if (auto res = do_relocation(total_tls_size, relocation); res != RelocationResult::Success) {
+ dbgln("Loader.so: {} unresolved symbol '{}'", m_filename, relocation.symbol().name());
+ ASSERT_NOT_REACHED();
+ }
+ }
+}
+
+void DynamicLoader::load_program_headers()
+{
+ Vector<ProgramHeaderRegion> program_headers;
+
+ ProgramHeaderRegion* text_region_ptr = nullptr;
+ ProgramHeaderRegion* data_region_ptr = nullptr;
+ ProgramHeaderRegion* tls_region_ptr = nullptr;
+ VirtualAddress dynamic_region_desired_vaddr;
+
+ m_elf_image.for_each_program_header([&](const Image::ProgramHeader& program_header) {
+ ProgramHeaderRegion new_region;
+ new_region.set_program_header(program_header.raw_header());
+ program_headers.append(move(new_region));
+ auto& region = program_headers.last();
+ if (region.is_tls_template())
+ tls_region_ptr = &region;
+ else if (region.is_load()) {
+ if (region.is_executable())
+ text_region_ptr = &region;
+ else
+ data_region_ptr = &region;
+ } else if (region.is_dynamic()) {
+ dynamic_region_desired_vaddr = region.desired_load_address();
+ }
+ return IterationDecision::Continue;
+ });
+
+ ASSERT(text_region_ptr && data_region_ptr);
+
+ // Process regions in order: .text, .data, .tls
+ auto* region = text_region_ptr;
+ void* requested_load_address = m_elf_image.is_dynamic() ? nullptr : region->desired_load_address().as_ptr();
+
+ ASSERT(!region->is_writable());
+
+ void* text_segment_begin = mmap_with_name(
+ requested_load_address,
+ region->required_load_size(),
+ region->mmap_prot(),
+ MAP_SHARED,
+ m_image_fd,
+ region->offset(),
+ String::format("%s: .text", m_filename.characters()).characters());
+ if (MAP_FAILED == text_segment_begin) {
+ ASSERT_NOT_REACHED();
+ }
+ ASSERT(requested_load_address == nullptr || requested_load_address == text_segment_begin);
+ m_text_segment_size = region->required_load_size();
+ m_text_segment_load_address = VirtualAddress { (FlatPtr)text_segment_begin };
+
+ if (m_elf_image.is_dynamic())
+ m_dynamic_section_address = dynamic_region_desired_vaddr.offset(m_text_segment_load_address.get());
+ else
+ m_dynamic_section_address = dynamic_region_desired_vaddr;
+
+ region = data_region_ptr;
+ void* data_segment_begin = mmap_with_name(
+ (u8*)text_segment_begin + m_text_segment_size,
+ region->required_load_size(),
+ region->mmap_prot(),
+ MAP_ANONYMOUS | MAP_PRIVATE,
+ 0,
+ 0,
+ String::format("%s: .data", m_filename.characters()).characters());
+ if (MAP_FAILED == data_segment_begin) {
+ ASSERT_NOT_REACHED();
+ }
+ VirtualAddress data_segment_actual_addr;
+ if (m_elf_image.is_dynamic()) {
+ data_segment_actual_addr = region->desired_load_address().offset((FlatPtr)text_segment_begin);
+ } else {
+ data_segment_actual_addr = region->desired_load_address();
+ }
+ memcpy(data_segment_actual_addr.as_ptr(), (u8*)m_file_mapping + region->offset(), region->size_in_image());
+ // FIXME: Initialize the values in the TLS section. Currently, it is zeroed.
+}
+
+DynamicLoader::RelocationResult DynamicLoader::do_relocation(size_t total_tls_size, ELF::DynamicObject::Relocation relocation)
+{
+ VERBOSE("Relocation symbol: %s, type: %d\n", relocation.symbol().name(), relocation.type());
+ FlatPtr* patch_ptr = nullptr;
+ if (is_dynamic())
+ patch_ptr = (FlatPtr*)(m_dynamic_object->base_address().as_ptr() + relocation.offset());
+ else
+ patch_ptr = (FlatPtr*)(FlatPtr)relocation.offset();
+
+ // VERBOSE("dynamic object name: %s\n", dynamic_object.object_name());
+ VERBOSE("dynamic object base address: %p\n", m_dynamic_object->base_address());
+ VERBOSE("relocation offset: 0x%x\n", relocation.offset());
+ VERBOSE("patch_ptr: %p\n", patch_ptr);
+ switch (relocation.type()) {
+ case R_386_NONE:
+ // Apparently most loaders will just skip these?
+ // Seems if the 'link editor' generates one something is funky with your code
+ VERBOSE("None relocation. No symbol, no nothing.\n");
+ break;
+ case R_386_32: {
+ auto symbol = relocation.symbol();
+ VERBOSE("Absolute relocation: name: '%s', value: %p\n", symbol.name(), symbol.value());
+ auto res = lookup_symbol(symbol);
+ if (!res.found) {
+ if (symbol.bind() == STB_WEAK) {
+ return RelocationResult::ResolveLater;
+ }
+ dbgln("ERROR: symbol not found: {}.", symbol.name());
+ ASSERT_NOT_REACHED();
+ }
+ u32 symbol_address = res.address;
+ *patch_ptr += symbol_address;
+ VERBOSE(" Symbol address: %p\n", *patch_ptr);
+ break;
+ }
+ case R_386_PC32: {
+ auto symbol = relocation.symbol();
+ VERBOSE("PC-relative relocation: '%s', value: %p\n", symbol.name(), symbol.value());
+ auto res = lookup_symbol(symbol);
+ ASSERT(res.found);
+ u32 relative_offset = (res.address - (FlatPtr)(m_dynamic_object->base_address().as_ptr() + relocation.offset()));
+ *patch_ptr += relative_offset;
+ VERBOSE(" Symbol address: %p\n", *patch_ptr);
+ break;
+ }
+ case R_386_GLOB_DAT: {
+ auto symbol = relocation.symbol();
+ VERBOSE("Global data relocation: '%s', value: %p\n", symbol.name(), symbol.value());
+ auto res = lookup_symbol(symbol);
+ if (!res.found) {
+ // We do not support these
+ // TODO: Can we tell gcc not to generate the piece of code that uses these?
+ // (--disable-tm-clone-registry flag in gcc conifugraion?)
+ if (!strcmp(symbol.name(), "__deregister_frame_info") || !strcmp(symbol.name(), "_ITM_registerTMCloneTable")
+ || !strcmp(symbol.name(), "_ITM_deregisterTMCloneTable") || !strcmp(symbol.name(), "__register_frame_info")) {
+ break;
+ }
+
+ if (symbol.bind() == STB_WEAK) {
+ return RelocationResult::ResolveLater;
+ }
+
+ // Symbol not found
+ return RelocationResult::Failed;
+ }
+ VERBOSE("was symbol found? %d, address: 0x%x\n", res.found, res.address);
+ VERBOSE("object: %s\n", m_filename.characters());
+
+ u32 symbol_location = res.address;
+ ASSERT(symbol_location != (FlatPtr)m_dynamic_object->base_address().as_ptr());
+ *patch_ptr = symbol_location;
+ VERBOSE(" Symbol address: %p\n", *patch_ptr);
+ break;
+ }
+ case R_386_RELATIVE: {
+ // FIXME: According to the spec, R_386_relative ones must be done first.
+ // We could explicitly do them first using m_number_of_relocatoins from DT_RELCOUNT
+ // However, our compiler is nice enough to put them at the front of the relocations for us :)
+ VERBOSE("Load address relocation at offset %X\n", relocation.offset());
+ VERBOSE(" patch ptr == %p, adding load base address (%p) to it and storing %p\n", *patch_ptr, m_dynamic_object->base_address().as_ptr(), *patch_ptr + m_dynamic_object->base_address().as_ptr());
+ *patch_ptr += (FlatPtr)m_dynamic_object->base_address().as_ptr(); // + addend for RelA (addend for Rel is stored at addr)
+ break;
+ }
+ case R_386_TLS_TPOFF32:
+ case R_386_TLS_TPOFF: {
+ VERBOSE("Relocation type: R_386_TLS_TPOFF at offset %X\n", relocation.offset());
+ auto symbol = relocation.symbol();
+ // For some reason, LibC has a R_386_TLS_TPOFF that refers to the undefined symbol.. huh
+ if (relocation.symbol_index() == 0)
+ break;
+ VERBOSE("Symbol index: %d\n", symbol.index());
+ VERBOSE("Symbol is_undefined?: %d\n", symbol.is_undefined());
+ VERBOSE("TLS relocation: '%s', value: %p\n", symbol.name(), symbol.value());
+ auto res = lookup_symbol(symbol);
+ if (!res.found)
+ break;
+ ASSERT(res.found);
+ u32 symbol_value = res.value;
+ VERBOSE("symbol value: %d\n", symbol_value);
+ const auto dynamic_object_of_symbol = res.dynamic_object;
+ ASSERT(dynamic_object_of_symbol);
+ size_t offset_of_tls_end = dynamic_object_of_symbol->tls_offset().value() + dynamic_object_of_symbol->tls_size().value();
+ // size_t offset_of_tls_end = tls_offset() + tls_size();
+ VERBOSE("patch ptr: 0x%x\n", patch_ptr);
+ VERBOSE("tls end offset: %d, total tls size: %d\n", offset_of_tls_end, total_tls_size);
+ *patch_ptr = (offset_of_tls_end - total_tls_size - symbol_value - sizeof(Elf32_Addr));
+ VERBOSE("*patch ptr: %d\n", (i32)*patch_ptr);
+ break;
+ }
+ case R_386_JMP_SLOT: {
+ // FIXME: Or BIND_NOW flag passed in?
+ if (m_dynamic_object->must_bind_now() || s_always_bind_now) {
+ // Eagerly BIND_NOW the PLT entries, doing all the symbol looking goodness
+ // The patch method returns the address for the LAZY fixup path, but we don't need it here
+ VERBOSE("patching plt reloaction: 0x%x\n", relocation.offset_in_section());
+ [[maybe_unused]] auto rc = m_dynamic_object->patch_plt_entry(relocation.offset_in_section());
+ } else {
+ u8* relocation_address = relocation.address().as_ptr();
+
+ if (m_elf_image.is_dynamic())
+ *(u32*)relocation_address += (FlatPtr)m_dynamic_object->base_address().as_ptr();
+ }
+ break;
+ }
+ default:
+ // Raise the alarm! Someone needs to implement this relocation type
+ VERBOSE("Found a new exciting relocation type %d\n", relocation.type());
+ // printf("DynamicLoader: Found unknown relocation type %d\n", relocation.type());
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ return RelocationResult::Success;
+}
+
+// Defined in <arch>/plt_trampoline.S
+extern "C" void _plt_trampoline(void) __attribute__((visibility("hidden")));
+
+void DynamicLoader::setup_plt_trampoline()
+{
+ ASSERT(m_dynamic_object);
+ VirtualAddress got_address = m_dynamic_object->plt_got_base_address();
+
+ FlatPtr* got_ptr = (FlatPtr*)got_address.as_ptr();
+ got_ptr[1] = (FlatPtr)m_dynamic_object.ptr();
+ got_ptr[2] = (FlatPtr)&_plt_trampoline;
+
+ VERBOSE("Set GOT PLT entries at %p: [0] = %p [1] = %p, [2] = %p\n", got_ptr, (void*)got_ptr[0], (void*)got_ptr[1], (void*)got_ptr[2]);
+}
+
+// Called from our ASM routine _plt_trampoline.
+// Tell the compiler that it might be called from other places:
+extern "C" Elf32_Addr _fixup_plt_entry(DynamicObject* object, u32 relocation_offset);
+extern "C" Elf32_Addr _fixup_plt_entry(DynamicObject* object, u32 relocation_offset)
+{
+ return object->patch_plt_entry(relocation_offset);
+}
+
+void DynamicLoader::call_object_init_functions()
+{
+ typedef void (*InitFunc)();
+
+ if (m_dynamic_object->has_init_section()) {
+ auto init_function = (InitFunc)(m_dynamic_object->init_section().address().as_ptr());
+
+ VERBOSE("Calling DT_INIT at %p\n", init_function);
+ (init_function)();
+ }
+
+ if (m_dynamic_object->has_init_array_section()) {
+ auto init_array_section = m_dynamic_object->init_array_section();
+
+ InitFunc* init_begin = (InitFunc*)(init_array_section.address().as_ptr());
+ InitFunc* init_end = init_begin + init_array_section.entry_count();
+ while (init_begin != init_end) {
+ // Android sources claim that these can be -1, to be ignored.
+ // 0 definitely shows up. Apparently 0/-1 are valid? Confusing.
+ if (!*init_begin || ((FlatPtr)*init_begin == (FlatPtr)-1))
+ continue;
+ VERBOSE("Calling DT_INITARRAY entry at %p\n", *init_begin);
+ (*init_begin)();
+ ++init_begin;
+ }
+ }
+}
+
+u32 DynamicLoader::ProgramHeaderRegion::mmap_prot() const
+{
+ int prot = 0;
+ prot |= is_executable() ? PROT_EXEC : 0;
+ prot |= is_readable() ? PROT_READ : 0;
+ prot |= is_writable() ? PROT_WRITE : 0;
+ return prot;
+}
+
+DynamicObject::SymbolLookupResult DynamicLoader::lookup_symbol(const ELF::DynamicObject::Symbol& symbol) const
+{
+ return m_dynamic_object->lookup_symbol(symbol);
+}
+
+} // end namespace ELF
diff --git a/Userland/Libraries/LibELF/DynamicLoader.h b/Userland/Libraries/LibELF/DynamicLoader.h
new file mode 100644
index 0000000000..6c9f984296
--- /dev/null
+++ b/Userland/Libraries/LibELF/DynamicLoader.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2019-2020, Andrew Kaster <andrewdkaster@gmail.com>
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Assertions.h>
+#include <AK/OwnPtr.h>
+#include <AK/RefCounted.h>
+#include <AK/String.h>
+#include <LibELF/DynamicObject.h>
+#include <LibELF/Image.h>
+#include <LibELF/exec_elf.h>
+#include <sys/mman.h>
+
+namespace ELF {
+
+#define ALIGN_ROUND_UP(x, align) ((((size_t)(x)) + align - 1) & (~(align - 1)))
+
+class DynamicLoader : public RefCounted<DynamicLoader> {
+public:
+ static NonnullRefPtr<DynamicLoader> construct(const char* filename, int fd, size_t file_size);
+
+ ~DynamicLoader();
+
+ bool is_valid() const { return m_valid; }
+
+ // Load a full ELF image from file into the current process and create an DynamicObject
+ // from the SHT_DYNAMIC in the file.
+ RefPtr<DynamicObject> load_from_image(unsigned flags, size_t total_tls_size);
+
+ // Stage 2 of loading: dynamic object loading and primary relocations
+ bool load_stage_2(unsigned flags, size_t total_tls_size);
+
+ // Stage 3 of loading: lazy relocations and initializers
+ RefPtr<DynamicObject> load_stage_3(unsigned flags, size_t total_tls_size);
+ // Intended for use by dlsym or other internal methods
+ void* symbol_for_name(const char*);
+
+ void dump();
+
+ // Requested program interpreter from program headers. May be empty string
+ StringView program_interpreter() const { return m_program_interpreter; }
+
+ VirtualAddress text_segment_load_addresss() const { return m_text_segment_load_address; }
+
+ void set_tls_offset(size_t offset) { m_tls_offset = offset; };
+ size_t tls_size() const { return m_tls_size; }
+ size_t tls_offset() const { return m_tls_offset; }
+ const ELF::Image& image() const { return m_elf_image; }
+
+ template<typename F>
+ void for_each_needed_library(F) const;
+
+ VirtualAddress text_segment_load_address() const { return m_text_segment_load_address; }
+ bool is_dynamic() const { return m_elf_image.is_dynamic(); }
+
+private:
+ class ProgramHeaderRegion {
+ public:
+ void set_program_header(const Elf32_Phdr& header) { m_program_header = header; }
+
+ // Information from ELF Program header
+ u32 type() const { return m_program_header.p_type; }
+ u32 flags() const { return m_program_header.p_flags; }
+ u32 offset() const { return m_program_header.p_offset; }
+ VirtualAddress desired_load_address() const { return VirtualAddress(m_program_header.p_vaddr); }
+ u32 size_in_memory() const { return m_program_header.p_memsz; }
+ u32 size_in_image() const { return m_program_header.p_filesz; }
+ u32 alignment() const { return m_program_header.p_align; }
+ u32 mmap_prot() const;
+ bool is_readable() const { return flags() & PF_R; }
+ bool is_writable() const { return flags() & PF_W; }
+ bool is_executable() const { return flags() & PF_X; }
+ bool is_tls_template() const { return type() == PT_TLS; }
+ bool is_load() const { return type() == PT_LOAD; }
+ bool is_dynamic() const { return type() == PT_DYNAMIC; }
+
+ u32 required_load_size() { return ALIGN_ROUND_UP(m_program_header.p_memsz, m_program_header.p_align); }
+
+ private:
+ Elf32_Phdr m_program_header; // Explicitly a copy of the PHDR in the image
+ };
+
+ static void* do_mmap(int fd, size_t size, const String& name);
+ RefPtr<DynamicObject> dynamic_object_from_image() const;
+
+ explicit DynamicLoader(const char* filename, int fd, size_t file_size);
+
+ // Stage 1
+ void load_program_headers();
+
+ // Stage 2
+ void do_main_relocations(size_t total_tls_size);
+
+ // Stage 3
+ void do_lazy_relocations(size_t total_tls_size);
+ void setup_plt_trampoline();
+ void call_object_init_functions();
+
+ bool validate();
+
+ enum class RelocationResult : uint8_t {
+ Failed = 0,
+ Success = 1,
+ ResolveLater = 2,
+ };
+ RelocationResult do_relocation(size_t total_tls_size, DynamicObject::Relocation relocation);
+ size_t calculate_tls_size() const;
+
+ DynamicObject::SymbolLookupResult lookup_symbol(const ELF::DynamicObject::Symbol& symbol) const;
+
+ String m_filename;
+ String m_program_interpreter;
+ size_t m_file_size { 0 };
+ int m_image_fd { -1 };
+ void* m_file_mapping { MAP_FAILED };
+ ELF::Image m_elf_image;
+ bool m_valid { true };
+
+ RefPtr<DynamicObject> m_dynamic_object;
+
+ VirtualAddress m_text_segment_load_address;
+ size_t m_text_segment_size { 0 };
+
+ VirtualAddress m_tls_segment_address;
+ VirtualAddress m_dynamic_section_address;
+
+ size_t m_tls_offset { 0 };
+ size_t m_tls_size { 0 };
+
+ Vector<DynamicObject::Relocation> m_unresolved_relocations;
+};
+
+template<typename F>
+void DynamicLoader::for_each_needed_library(F func) const
+{
+ dynamic_object_from_image()->for_each_needed_library(move(func));
+}
+
+} // end namespace ELF
diff --git a/Userland/Libraries/LibELF/DynamicObject.cpp b/Userland/Libraries/LibELF/DynamicObject.cpp
new file mode 100644
index 0000000000..39b06beba2
--- /dev/null
+++ b/Userland/Libraries/LibELF/DynamicObject.cpp
@@ -0,0 +1,527 @@
+/*
+ * Copyright (c) 2019-2020, Andrew Kaster <andrewdkaster@gmail.com>
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <LibELF/DynamicLinker.h>
+#include <LibELF/DynamicObject.h>
+#include <LibELF/exec_elf.h>
+#include <stdio.h>
+#include <string.h>
+
+// #define DYNAMIC_OBJECT_VERBOSE
+
+#ifdef DYNAMIC_OBJECT_VERBOSE
+# define VERBOSE(fmt, ...) dbgprintf(fmt, ##__VA_ARGS__)
+#else
+# define VERBOSE(fmt, ...) \
+ do { \
+ } while (0)
+#endif
+
+namespace ELF {
+
+static const char* name_for_dtag(Elf32_Sword d_tag);
+
+DynamicObject::DynamicObject(VirtualAddress base_address, VirtualAddress dynamic_section_addresss)
+ : m_base_address(base_address)
+ , m_dynamic_address(dynamic_section_addresss)
+{
+ Elf32_Ehdr* header = (Elf32_Ehdr*)base_address.as_ptr();
+ Elf32_Phdr* pheader = (Elf32_Phdr*)(base_address.as_ptr() + header->e_phoff);
+ m_elf_base_address = VirtualAddress(pheader->p_vaddr - pheader->p_offset);
+ if (header->e_type == ET_DYN)
+ m_is_elf_dynamic = true;
+ else
+ m_is_elf_dynamic = false;
+
+ parse();
+}
+
+DynamicObject::~DynamicObject()
+{
+}
+
+void DynamicObject::dump() const
+{
+ StringBuilder builder;
+ builder.append("\nd_tag tag_name value\n");
+ size_t num_dynamic_sections = 0;
+
+ for_each_dynamic_entry([&](const DynamicObject::DynamicEntry& entry) {
+ String name_field = String::format("(%s)", name_for_dtag(entry.tag()));
+ builder.appendf("0x%08X %-17s0x%X\n", entry.tag(), name_field.characters(), entry.val());
+ num_dynamic_sections++;
+ return IterationDecision::Continue;
+ });
+
+ if (m_has_soname)
+ builder.appendf("DT_SONAME: %s\n", soname()); // FIXME: Valdidate that this string is null terminated?
+
+ VERBOSE("Dynamic section at address %p contains %zu entries:\n", m_dynamic_address.as_ptr(), num_dynamic_sections);
+ VERBOSE("%s", builder.to_string().characters());
+}
+
+void DynamicObject::parse()
+{
+ for_each_dynamic_entry([&](const DynamicEntry& entry) {
+ switch (entry.tag()) {
+ case DT_INIT:
+ m_init_offset = entry.ptr() - (FlatPtr)m_elf_base_address.as_ptr();
+ break;
+ case DT_FINI:
+ m_fini_offset = entry.ptr() - (FlatPtr)m_elf_base_address.as_ptr();
+ break;
+ case DT_INIT_ARRAY:
+ m_init_array_offset = entry.ptr() - (FlatPtr)m_elf_base_address.as_ptr();
+ break;
+ case DT_INIT_ARRAYSZ:
+ m_init_array_size = entry.val();
+ break;
+ case DT_FINI_ARRAY:
+ m_fini_array_offset = entry.ptr() - (FlatPtr)m_elf_base_address.as_ptr();
+ break;
+ case DT_FINI_ARRAYSZ:
+ m_fini_array_size = entry.val();
+ break;
+ case DT_HASH:
+ // Use SYSV hash only if GNU hash is not available
+ if (m_hash_type == HashType::SYSV) {
+ m_hash_table_offset = entry.ptr() - (FlatPtr)m_elf_base_address.as_ptr();
+ }
+ break;
+ case DT_GNU_HASH:
+ m_hash_type = HashType::GNU;
+ m_hash_table_offset = entry.ptr() - (FlatPtr)m_elf_base_address.as_ptr();
+ break;
+ case DT_SYMTAB:
+ m_symbol_table_offset = entry.ptr() - (FlatPtr)m_elf_base_address.as_ptr();
+ break;
+ case DT_STRTAB:
+ m_string_table_offset = entry.ptr() - (FlatPtr)m_elf_base_address.as_ptr();
+ break;
+ case DT_STRSZ:
+ m_size_of_string_table = entry.val();
+ break;
+ case DT_SYMENT:
+ m_size_of_symbol_table_entry = entry.val();
+ break;
+ case DT_PLTGOT:
+ m_procedure_linkage_table_offset = entry.ptr() - (FlatPtr)m_elf_base_address.as_ptr();
+ break;
+ case DT_PLTRELSZ:
+ m_size_of_plt_relocation_entry_list = entry.val();
+ break;
+ case DT_PLTREL:
+ m_procedure_linkage_table_relocation_type = entry.val();
+ ASSERT(m_procedure_linkage_table_relocation_type & (DT_REL | DT_RELA));
+ break;
+ case DT_JMPREL:
+ m_plt_relocation_offset_location = entry.ptr() - (FlatPtr)m_elf_base_address.as_ptr();
+ break;
+ case DT_RELA:
+ case DT_REL:
+ m_relocation_table_offset = entry.ptr() - (FlatPtr)m_elf_base_address.as_ptr();
+ break;
+ case DT_RELASZ:
+ case DT_RELSZ:
+ m_size_of_relocation_table = entry.val();
+ break;
+ case DT_RELAENT:
+ case DT_RELENT:
+ m_size_of_relocation_entry = entry.val();
+ break;
+ case DT_RELACOUNT:
+ case DT_RELCOUNT:
+ m_number_of_relocations = entry.val();
+ break;
+ case DT_FLAGS:
+ m_dt_flags = entry.val();
+ break;
+ case DT_TEXTREL:
+ m_dt_flags |= DF_TEXTREL; // This tag seems to exist for legacy reasons only?
+ break;
+ case DT_SONAME:
+ m_soname_index = entry.val();
+ m_has_soname = true;
+ break;
+ case DT_DEBUG:
+ break;
+ case DT_FLAGS_1:
+ break;
+ case DT_NEEDED:
+ // We handle these in for_each_needed_library
+ break;
+ default:
+ dbgprintf("DynamicObject: DYNAMIC tag handling not implemented for DT_%s\n", name_for_dtag(entry.tag()));
+ printf("DynamicObject: DYNAMIC tag handling not implemented for DT_%s\n", name_for_dtag(entry.tag()));
+ ASSERT_NOT_REACHED(); // FIXME: Maybe just break out here and return false?
+ break;
+ }
+ return IterationDecision::Continue;
+ });
+
+ if (!m_size_of_relocation_entry) {
+ // TODO: FIXME, this shouldn't be hardcoded
+ // The reason we need this here is that for some reason, when there only PLT relocations, the compiler
+ // doesn't insert a 'PLTRELSZ' entry to the dynamic section
+ m_size_of_relocation_entry = sizeof(Elf32_Rel);
+ }
+
+ auto hash_section_address = hash_section().address().as_ptr();
+ // TODO: consider base address - it might not be zero
+ auto num_hash_chains = ((u32*)hash_section_address)[1];
+ m_symbol_count = num_hash_chains;
+}
+
+const DynamicObject::Relocation DynamicObject::RelocationSection::relocation(unsigned index) const
+{
+ ASSERT(index < entry_count());
+ unsigned offset_in_section = index * entry_size();
+ auto relocation_address = (Elf32_Rel*)address().offset(offset_in_section).as_ptr();
+ return Relocation(m_dynamic, *relocation_address, offset_in_section);
+}
+
+const DynamicObject::Relocation DynamicObject::RelocationSection::relocation_at_offset(unsigned offset) const
+{
+ ASSERT(offset <= (m_section_size_bytes - m_entry_size));
+ auto relocation_address = (Elf32_Rel*)address().offset(offset).as_ptr();
+ return Relocation(m_dynamic, *relocation_address, offset);
+}
+
+const DynamicObject::Symbol DynamicObject::symbol(unsigned index) const
+{
+ auto symbol_section = Section(*this, m_symbol_table_offset, (m_symbol_count * m_size_of_symbol_table_entry), m_size_of_symbol_table_entry, "DT_SYMTAB");
+ auto symbol_entry = (Elf32_Sym*)symbol_section.address().offset(index * symbol_section.entry_size()).as_ptr();
+ return Symbol(*this, index, *symbol_entry);
+}
+
+const DynamicObject::Section DynamicObject::init_section() const
+{
+ return Section(*this, m_init_offset, sizeof(void (*)()), sizeof(void (*)()), "DT_INIT");
+}
+
+const DynamicObject::Section DynamicObject::fini_section() const
+{
+ return Section(*this, m_fini_offset, sizeof(void (*)()), sizeof(void (*)()), "DT_FINI");
+}
+
+const DynamicObject::Section DynamicObject::init_array_section() const
+{
+ return Section(*this, m_init_array_offset, m_init_array_size, sizeof(void (*)()), "DT_INIT_ARRAY");
+}
+
+const DynamicObject::Section DynamicObject::fini_array_section() const
+{
+ return Section(*this, m_fini_array_offset, m_fini_array_size, sizeof(void (*)()), "DT_FINI_ARRAY");
+}
+
+const DynamicObject::HashSection DynamicObject::hash_section() const
+{
+ const char* section_name = m_hash_type == HashType::SYSV ? "DT_HASH" : "DT_GNU_HASH";
+ return HashSection(Section(*this, m_hash_table_offset, 0, 0, section_name), m_hash_type);
+}
+
+const DynamicObject::RelocationSection DynamicObject::relocation_section() const
+{
+ return RelocationSection(Section(*this, m_relocation_table_offset, m_size_of_relocation_table, m_size_of_relocation_entry, "DT_REL"));
+}
+
+const DynamicObject::RelocationSection DynamicObject::plt_relocation_section() const
+{
+ return RelocationSection(Section(*this, m_plt_relocation_offset_location, m_size_of_plt_relocation_entry_list, m_size_of_relocation_entry, "DT_JMPREL"));
+}
+
+u32 DynamicObject::HashSection::calculate_elf_hash(const char* name) const
+{
+ // SYSV ELF hash algorithm
+ // Note that the GNU HASH algorithm has less collisions
+
+ uint32_t hash = 0;
+
+ while (*name != '\0') {
+ hash = hash << 4;
+ hash += *name;
+ name++;
+
+ const uint32_t top_nibble_of_hash = hash & 0xF0000000U;
+ hash ^= top_nibble_of_hash >> 24;
+ hash &= ~top_nibble_of_hash;
+ }
+
+ return hash;
+}
+
+u32 DynamicObject::HashSection::calculate_gnu_hash(const char* name) const
+{
+ // GNU ELF hash algorithm
+ u32 hash = 5381;
+
+ for (; *name != '\0'; ++name) {
+ hash = hash * 33 + *name;
+ }
+
+ return hash;
+}
+
+const DynamicObject::Symbol DynamicObject::HashSection::lookup_symbol(const char* name) const
+{
+ return (this->*(m_lookup_function))(name);
+}
+
+const DynamicObject::Symbol DynamicObject::HashSection::lookup_elf_symbol(const char* name) const
+{
+ u32 hash_value = calculate_elf_hash(name);
+
+ u32* hash_table_begin = (u32*)address().as_ptr();
+
+ size_t num_buckets = hash_table_begin[0];
+
+ // This is here for completeness, but, since we're using the fact that every chain
+ // will end at chain 0 (which means 'not found'), we don't need to check num_chains.
+ // Interestingly, num_chains is required to be num_symbols
+
+ // size_t num_chains = hash_table_begin[1];
+
+ u32* buckets = &hash_table_begin[2];
+ u32* chains = &buckets[num_buckets];
+
+ for (u32 i = buckets[hash_value % num_buckets]; i; i = chains[i]) {
+ auto symbol = m_dynamic.symbol(i);
+ if (strcmp(name, symbol.name()) == 0) {
+ VERBOSE("Returning SYSV dynamic symbol with index %u for %s: %p\n", i, symbol.name(), symbol.address().as_ptr());
+ return symbol;
+ }
+ }
+ return Symbol::create_undefined(m_dynamic);
+}
+
+const DynamicObject::Symbol DynamicObject::HashSection::lookup_gnu_symbol(const char* name) const
+{
+ // Algorithm reference: https://ent-voy.blogspot.com/2011/02/
+ // TODO: Handle 64bit bloomwords for ELF_CLASS64
+ using BloomWord = u32;
+ constexpr size_t bloom_word_size = sizeof(BloomWord) * 8;
+
+ const u32* hash_table_begin = (u32*)address().as_ptr();
+
+ const size_t num_buckets = hash_table_begin[0];
+ const size_t num_omitted_symbols = hash_table_begin[1];
+ const u32 num_maskwords = hash_table_begin[2];
+ // This works because num_maskwords is required to be a power of 2
+ const u32 num_maskwords_bitmask = num_maskwords - 1;
+ const u32 shift2 = hash_table_begin[3];
+
+ const BloomWord* bloom_words = &hash_table_begin[4];
+ const u32* const buckets = &bloom_words[num_maskwords];
+ const u32* const chains = &buckets[num_buckets];
+
+ BloomWord hash1 = calculate_gnu_hash(name);
+ BloomWord hash2 = hash1 >> shift2;
+ const BloomWord bitmask = (1 << (hash1 % bloom_word_size)) | (1 << (hash2 % bloom_word_size));
+
+ if ((bloom_words[(hash1 / bloom_word_size) & num_maskwords_bitmask] & bitmask) != bitmask) {
+ return Symbol::create_undefined(m_dynamic);
+ }
+
+ size_t current_sym = buckets[hash1 % num_buckets];
+ if (current_sym == 0) {
+ return Symbol::create_undefined(m_dynamic);
+ }
+ const u32* current_chain = &chains[current_sym - num_omitted_symbols];
+
+ for (hash1 &= ~1;; ++current_sym) {
+ hash2 = *(current_chain++);
+ const auto symbol = m_dynamic.symbol(current_sym);
+ if ((hash1 == (hash2 & ~1)) && strcmp(name, symbol.name()) == 0) {
+ VERBOSE("Returning GNU dynamic symbol with index %zu for %s: %p\n", current_sym, symbol.name(), symbol.address().as_ptr());
+ return symbol;
+ }
+ if (hash2 & 1) {
+ break;
+ }
+ }
+
+ return Symbol::create_undefined(m_dynamic);
+}
+
+const char* DynamicObject::symbol_string_table_string(Elf32_Word index) const
+{
+ return (const char*)base_address().offset(m_string_table_offset + index).as_ptr();
+}
+
+DynamicObject::InitializationFunction DynamicObject::init_section_function() const
+{
+ ASSERT(has_init_section());
+ return (InitializationFunction)init_section().address().as_ptr();
+}
+
+static const char* name_for_dtag(Elf32_Sword d_tag)
+{
+ switch (d_tag) {
+ case DT_NULL:
+ return "NULL"; /* marks end of _DYNAMIC array */
+ case DT_NEEDED:
+ return "NEEDED"; /* string table offset of needed lib */
+ case DT_PLTRELSZ:
+ return "PLTRELSZ"; /* size of relocation entries in PLT */
+ case DT_PLTGOT:
+ return "PLTGOT"; /* address PLT/GOT */
+ case DT_HASH:
+ return "HASH"; /* address of symbol hash table */
+ case DT_STRTAB:
+ return "STRTAB"; /* address of string table */
+ case DT_SYMTAB:
+ return "SYMTAB"; /* address of symbol table */
+ case DT_RELA:
+ return "RELA"; /* address of relocation table */
+ case DT_RELASZ:
+ return "RELASZ"; /* size of relocation table */
+ case DT_RELAENT:
+ return "RELAENT"; /* size of relocation entry */
+ case DT_STRSZ:
+ return "STRSZ"; /* size of string table */
+ case DT_SYMENT:
+ return "SYMENT"; /* size of symbol table entry */
+ case DT_INIT:
+ return "INIT"; /* address of initialization func. */
+ case DT_FINI:
+ return "FINI"; /* address of termination function */
+ case DT_SONAME:
+ return "SONAME"; /* string table offset of shared obj */
+ case DT_RPATH:
+ return "RPATH"; /* string table offset of library search path */
+ case DT_SYMBOLIC:
+ return "SYMBOLIC"; /* start sym search in shared obj. */
+ case DT_REL:
+ return "REL"; /* address of rel. tbl. w addends */
+ case DT_RELSZ:
+ return "RELSZ"; /* size of DT_REL relocation table */
+ case DT_RELENT:
+ return "RELENT"; /* size of DT_REL relocation entry */
+ case DT_PLTREL:
+ return "PLTREL"; /* PLT referenced relocation entry */
+ case DT_DEBUG:
+ return "DEBUG"; /* bugger */
+ case DT_TEXTREL:
+ return "TEXTREL"; /* Allow rel. mod. to unwritable seg */
+ case DT_JMPREL:
+ return "JMPREL"; /* add. of PLT's relocation entries */
+ case DT_BIND_NOW:
+ return "BIND_NOW"; /* Bind now regardless of env setting */
+ case DT_INIT_ARRAY:
+ return "INIT_ARRAY"; /* address of array of init func */
+ case DT_FINI_ARRAY:
+ return "FINI_ARRAY"; /* address of array of term func */
+ case DT_INIT_ARRAYSZ:
+ return "INIT_ARRAYSZ"; /* size of array of init func */
+ case DT_FINI_ARRAYSZ:
+ return "FINI_ARRAYSZ"; /* size of array of term func */
+ case DT_RUNPATH:
+ return "RUNPATH"; /* strtab offset of lib search path */
+ case DT_FLAGS:
+ return "FLAGS"; /* Set of DF_* flags */
+ case DT_ENCODING:
+ return "ENCODING"; /* further DT_* follow encoding rules */
+ case DT_PREINIT_ARRAY:
+ return "PREINIT_ARRAY"; /* address of array of preinit func */
+ case DT_PREINIT_ARRAYSZ:
+ return "PREINIT_ARRAYSZ"; /* size of array of preinit func */
+ case DT_LOOS:
+ return "LOOS"; /* reserved range for OS */
+ case DT_HIOS:
+ return "HIOS"; /* specific dynamic array tags */
+ case DT_LOPROC:
+ return "LOPROC"; /* reserved range for processor */
+ case DT_HIPROC:
+ return "HIPROC"; /* specific dynamic array tags */
+ case DT_GNU_HASH:
+ return "GNU_HASH"; /* address of GNU hash table */
+ case DT_RELACOUNT:
+ return "RELACOUNT"; /* if present, number of RELATIVE */
+ case DT_RELCOUNT:
+ return "RELCOUNT"; /* relocs, which must come first */
+ case DT_FLAGS_1:
+ return "FLAGS_1";
+ default:
+ return "??";
+ }
+}
+
+DynamicObject::SymbolLookupResult DynamicObject::lookup_symbol(const char* name) const
+{
+ auto res = hash_section().lookup_symbol(name);
+ if (res.is_undefined())
+ return {};
+ return SymbolLookupResult { true, res.value(), (FlatPtr)res.address().as_ptr(), res.bind(), this };
+}
+
+NonnullRefPtr<DynamicObject> DynamicObject::construct(VirtualAddress base_address, VirtualAddress dynamic_section_address)
+{
+ return adopt(*new DynamicObject(base_address, dynamic_section_address));
+}
+
+// offset is in PLT relocation table
+Elf32_Addr DynamicObject::patch_plt_entry(u32 relocation_offset)
+{
+ auto relocation = plt_relocation_section().relocation_at_offset(relocation_offset);
+
+ ASSERT(relocation.type() == R_386_JMP_SLOT);
+
+ auto sym = relocation.symbol();
+
+ u8* relocation_address = relocation.address().as_ptr();
+ auto res = lookup_symbol(sym);
+
+ if (!res.found) {
+ dbgln("did not find symbol: {} ", sym.name());
+ ASSERT_NOT_REACHED();
+ }
+
+ u32 symbol_location = res.address;
+
+ VERBOSE("DynamicLoader: Jump slot relocation: putting %s (%p) into PLT at %p\n", sym.name(), (void*)symbol_location, (void*)relocation_address);
+
+ *(u32*)relocation_address = symbol_location;
+
+ return symbol_location;
+}
+
+DynamicObject::SymbolLookupResult DynamicObject::lookup_symbol(const ELF::DynamicObject::Symbol& symbol) const
+{
+ VERBOSE("looking up symbol: %s\n", symbol.name());
+ if (symbol.is_undefined() || symbol.bind() == STB_WEAK)
+ return DynamicLinker::lookup_global_symbol(symbol.name());
+
+ if (!symbol.is_undefined()) {
+ VERBOSE("symbol is defined in its object\n");
+ return { true, symbol.value(), (FlatPtr)symbol.address().as_ptr(), symbol.bind(), &symbol.object() };
+ }
+ return DynamicLinker::lookup_global_symbol(symbol.name());
+}
+
+} // end namespace ELF
diff --git a/Userland/Libraries/LibELF/DynamicObject.h b/Userland/Libraries/LibELF/DynamicObject.h
new file mode 100644
index 0000000000..7b03c43c6d
--- /dev/null
+++ b/Userland/Libraries/LibELF/DynamicObject.h
@@ -0,0 +1,414 @@
+/*
+ * Copyright (c) 2019-2020, Andrew Kaster <andrewdkaster@gmail.com>
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Assertions.h>
+#include <AK/RefCounted.h>
+#include <Kernel/VirtualAddress.h>
+#include <LibELF/exec_elf.h>
+
+namespace ELF {
+
+class DynamicObject : public RefCounted<DynamicObject> {
+public:
+ static NonnullRefPtr<DynamicObject> construct(VirtualAddress base_address, VirtualAddress dynamic_section_address);
+
+ ~DynamicObject();
+ void dump() const;
+
+ class DynamicEntry;
+ class Section;
+ class RelocationSection;
+ class Symbol;
+ class Relocation;
+ class HashSection;
+
+ class DynamicEntry {
+ public:
+ DynamicEntry(const Elf32_Dyn& dyn)
+ : m_dyn(dyn)
+ {
+ }
+
+ ~DynamicEntry() { }
+
+ Elf32_Sword tag() const { return m_dyn.d_tag; }
+ Elf32_Addr ptr() const { return m_dyn.d_un.d_ptr; }
+ Elf32_Word val() const { return m_dyn.d_un.d_val; }
+
+ private:
+ const Elf32_Dyn& m_dyn;
+ };
+
+ class Symbol {
+ public:
+ Symbol(const DynamicObject& dynamic, unsigned index, const Elf32_Sym& sym)
+ : m_dynamic(dynamic)
+ , m_sym(sym)
+ , m_index(index)
+ {
+ if (section_index() == 0)
+ m_is_undefined = true;
+ }
+
+ Symbol(const Symbol& other)
+ : m_dynamic(other.m_dynamic)
+ , m_sym(other.m_sym)
+ , m_index(other.m_index)
+ , m_is_undefined(other.m_is_undefined)
+ {
+ }
+
+ static Symbol create_undefined(const DynamicObject& dynamic)
+ {
+ auto s = Symbol(dynamic, 0, {});
+ s.m_is_undefined = true;
+ return s;
+ }
+
+ ~Symbol() { }
+
+ const char* name() const { return m_dynamic.symbol_string_table_string(m_sym.st_name); }
+ unsigned section_index() const { return m_sym.st_shndx; }
+ unsigned value() const { return m_sym.st_value; }
+ unsigned size() const { return m_sym.st_size; }
+ unsigned index() const { return m_index; }
+ unsigned type() const { return ELF32_ST_TYPE(m_sym.st_info); }
+ unsigned bind() const { return ELF32_ST_BIND(m_sym.st_info); }
+
+ bool is_undefined() const
+ {
+ return m_is_undefined;
+ }
+ VirtualAddress address() const
+ {
+ if (m_dynamic.elf_is_dynamic())
+ return m_dynamic.base_address().offset(value());
+ return VirtualAddress { value() };
+ }
+ const DynamicObject& object() const { return m_dynamic; }
+
+ private:
+ const DynamicObject& m_dynamic;
+ const Elf32_Sym& m_sym;
+ const unsigned m_index;
+ bool m_is_undefined { false };
+ };
+
+ class Section {
+ public:
+ Section(const DynamicObject& dynamic, unsigned section_offset, unsigned section_size_bytes, unsigned entry_size, const char* name)
+ : m_dynamic(dynamic)
+ , m_section_offset(section_offset)
+ , m_section_size_bytes(section_size_bytes)
+ , m_entry_size(entry_size)
+ , m_name(name)
+ {
+ }
+ ~Section() { }
+
+ const char* name() const { return m_name; }
+ unsigned offset() const { return m_section_offset; }
+ unsigned size() const { return m_section_size_bytes; }
+ unsigned entry_size() const { return m_entry_size; }
+ unsigned entry_count() const
+ {
+ return !entry_size() ? 0 : size() / entry_size();
+ }
+ VirtualAddress address() const
+ {
+ return m_dynamic.base_address().offset(m_section_offset);
+ }
+
+ protected:
+ friend class RelocationSection;
+ friend class HashSection;
+ const DynamicObject& m_dynamic;
+ unsigned m_section_offset;
+ unsigned m_section_size_bytes;
+ unsigned m_entry_size;
+ const char* m_name { nullptr };
+ };
+
+ class RelocationSection : public Section {
+ public:
+ RelocationSection(const Section& section)
+ : Section(section.m_dynamic, section.m_section_offset, section.m_section_size_bytes, section.m_entry_size, section.m_name)
+ {
+ }
+ unsigned relocation_count() const { return entry_count(); }
+ const Relocation relocation(unsigned index) const;
+ const Relocation relocation_at_offset(unsigned offset) const;
+ template<typename F>
+ void for_each_relocation(F) const;
+ };
+
+ class Relocation {
+ public:
+ Relocation(const DynamicObject& dynamic, const Elf32_Rel& rel, unsigned offset_in_section)
+ : m_dynamic(dynamic)
+ , m_rel(rel)
+ , m_offset_in_section(offset_in_section)
+ {
+ }
+
+ ~Relocation() { }
+
+ unsigned offset_in_section() const { return m_offset_in_section; }
+ unsigned offset() const { return m_rel.r_offset; }
+ unsigned type() const { return ELF32_R_TYPE(m_rel.r_info); }
+ unsigned symbol_index() const { return ELF32_R_SYM(m_rel.r_info); }
+ const Symbol symbol() const { return m_dynamic.symbol(symbol_index()); }
+ VirtualAddress address() const
+ {
+ if (m_dynamic.elf_is_dynamic())
+ return m_dynamic.base_address().offset(offset());
+ return VirtualAddress { offset() };
+ }
+
+ private:
+ const DynamicObject& m_dynamic;
+ const Elf32_Rel& m_rel;
+ const unsigned m_offset_in_section;
+ };
+
+ enum class HashType {
+ SYSV,
+ GNU
+ };
+
+ class HashSection : public Section {
+ public:
+ HashSection(const Section& section, HashType hash_type)
+ : Section(section.m_dynamic, section.m_section_offset, section.m_section_size_bytes, section.m_entry_size, section.m_name)
+ {
+ switch (hash_type) {
+ case HashType::SYSV:
+ m_lookup_function = &HashSection::lookup_elf_symbol;
+ break;
+ case HashType::GNU:
+ m_lookup_function = &HashSection::lookup_gnu_symbol;
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ }
+
+ const Symbol lookup_symbol(const char*) const;
+
+ private:
+ u32 calculate_elf_hash(const char* name) const;
+ u32 calculate_gnu_hash(const char* name) const;
+
+ const DynamicObject::Symbol lookup_elf_symbol(const char* name) const;
+ const DynamicObject::Symbol lookup_gnu_symbol(const char* name) const;
+
+ typedef const DynamicObject::Symbol (HashSection::*LookupFunction)(const char*) const;
+ LookupFunction m_lookup_function;
+ };
+
+ unsigned symbol_count() const { return m_symbol_count; }
+
+ const Symbol symbol(unsigned) const;
+
+ typedef void (*InitializationFunction)();
+
+ bool has_init_section() const { return m_init_offset != 0; }
+ bool has_init_array_section() const { return m_init_array_offset != 0; }
+ const Section init_section() const;
+ InitializationFunction init_section_function() const;
+ const Section fini_section() const;
+ const Section init_array_section() const;
+ const Section fini_array_section() const;
+
+ const HashSection hash_section() const;
+
+ const RelocationSection relocation_section() const;
+ const RelocationSection plt_relocation_section() const;
+
+ bool should_process_origin() const { return m_dt_flags & DF_ORIGIN; }
+ bool requires_symbolic_symbol_resolution() const { return m_dt_flags & DF_SYMBOLIC; }
+ // Text relocations meaning: we need to edit the .text section which is normally mapped PROT_READ
+ bool has_text_relocations() const { return m_dt_flags & DF_TEXTREL; }
+ bool must_bind_now() const { return m_dt_flags & DF_BIND_NOW; }
+ bool has_static_thread_local_storage() const { return m_dt_flags & DF_STATIC_TLS; }
+
+ VirtualAddress plt_got_base_address() const { return m_base_address.offset(m_procedure_linkage_table_offset); }
+ VirtualAddress base_address() const { return m_base_address; }
+
+ const char* soname() const { return m_has_soname ? symbol_string_table_string(m_soname_index) : nullptr; }
+
+ Optional<FlatPtr> tls_offset() const { return m_tls_offset; }
+ Optional<FlatPtr> tls_size() const { return m_tls_size; }
+ void set_tls_offset(FlatPtr offset) { m_tls_offset = offset; }
+ void set_tls_size(FlatPtr size) { m_tls_size = size; }
+
+ template<typename F>
+ void for_each_needed_library(F) const;
+
+ template<typename F>
+ void for_each_initialization_array_function(F f) const;
+
+ struct SymbolLookupResult {
+ bool found { false };
+ FlatPtr value { 0 };
+ FlatPtr address { 0 };
+ unsigned bind { STB_LOCAL };
+ const ELF::DynamicObject* dynamic_object { nullptr }; // The object in which the symbol is defined
+ };
+ SymbolLookupResult lookup_symbol(const char* name) const;
+
+ // Will be called from _fixup_plt_entry, as part of the PLT trampoline
+ Elf32_Addr patch_plt_entry(u32 relocation_offset);
+
+ SymbolLookupResult lookup_symbol(const ELF::DynamicObject::Symbol& symbol) const;
+ using SymbolLookupFunction = DynamicObject::SymbolLookupResult (*)(const char*);
+ SymbolLookupFunction m_global_symbol_lookup_func { nullptr };
+
+ bool elf_is_dynamic() const { return m_is_elf_dynamic; }
+
+private:
+ explicit DynamicObject(VirtualAddress base_address, VirtualAddress dynamic_section_address);
+
+ const char* symbol_string_table_string(Elf32_Word) const;
+ void parse();
+
+ template<typename F>
+ void for_each_symbol(F) const;
+
+ template<typename F>
+ void for_each_dynamic_entry(F) const;
+
+ VirtualAddress m_base_address;
+ VirtualAddress m_dynamic_address;
+ VirtualAddress m_elf_base_address;
+
+ unsigned m_symbol_count { 0 };
+
+ // Begin Section information collected from DT_* entries
+ FlatPtr m_init_offset { 0 };
+ FlatPtr m_fini_offset { 0 };
+
+ FlatPtr m_init_array_offset { 0 };
+ size_t m_init_array_size { 0 };
+ FlatPtr m_fini_array_offset { 0 };
+ size_t m_fini_array_size { 0 };
+
+ FlatPtr m_hash_table_offset { 0 };
+ HashType m_hash_type { HashType::SYSV };
+
+ FlatPtr m_string_table_offset { 0 };
+ size_t m_size_of_string_table { 0 };
+ FlatPtr m_symbol_table_offset { 0 };
+ size_t m_size_of_symbol_table_entry { 0 };
+
+ Elf32_Sword m_procedure_linkage_table_relocation_type { -1 };
+ FlatPtr m_plt_relocation_offset_location { 0 }; // offset of PLT relocations, at end of relocations
+ size_t m_size_of_plt_relocation_entry_list { 0 };
+ FlatPtr m_procedure_linkage_table_offset { 0 };
+
+ // NOTE: We'll only ever either RELA or REL entries, not both (thank god)
+ // NOTE: The x86 ABI will only ever genrerate REL entries.
+ size_t m_number_of_relocations { 0 };
+ size_t m_size_of_relocation_entry { 0 };
+ size_t m_size_of_relocation_table { 0 };
+ FlatPtr m_relocation_table_offset { 0 };
+ bool m_is_elf_dynamic { false };
+
+ // DT_FLAGS
+ Elf32_Word m_dt_flags { 0 };
+
+ bool m_has_soname { false };
+ Elf32_Word m_soname_index { 0 }; // Index into dynstr table for SONAME
+
+ Optional<FlatPtr> m_tls_offset;
+ Optional<FlatPtr> m_tls_size;
+ // End Section information from DT_* entries
+};
+
+template<typename F>
+inline void DynamicObject::RelocationSection::for_each_relocation(F func) const
+{
+ for (unsigned i = 0; i < relocation_count(); ++i) {
+ const auto reloc = relocation(i);
+ if (reloc.type() == 0)
+ continue;
+ if (func(reloc) == IterationDecision::Break)
+ break;
+ }
+}
+
+template<typename F>
+inline void DynamicObject::for_each_symbol(F func) const
+{
+ for (unsigned i = 0; i < symbol_count(); ++i) {
+ if (func(symbol(i)) == IterationDecision::Break)
+ break;
+ }
+}
+
+template<typename F>
+inline void DynamicObject::for_each_dynamic_entry(F func) const
+{
+ auto* dyns = reinterpret_cast<const Elf32_Dyn*>(m_dynamic_address.as_ptr());
+ for (unsigned i = 0;; ++i) {
+ auto&& dyn = DynamicEntry(dyns[i]);
+ if (dyn.tag() == DT_NULL)
+ break;
+ if (func(dyn) == IterationDecision::Break)
+ break;
+ }
+}
+template<typename F>
+inline void DynamicObject::for_each_needed_library(F func) const
+{
+ for_each_dynamic_entry([func, this](auto entry) {
+ if (entry.tag() != DT_NEEDED)
+ return IterationDecision::Continue;
+ Elf32_Word offset = entry.val();
+ const char* name = (const char*)(m_base_address.offset(m_string_table_offset).offset(offset)).as_ptr();
+ if (func(StringView(name)) == IterationDecision::Break)
+ return IterationDecision::Break;
+ return IterationDecision::Continue;
+ });
+}
+
+template<typename F>
+void DynamicObject::for_each_initialization_array_function(F f) const
+{
+ if (!has_init_array_section())
+ return;
+ FlatPtr init_array = (FlatPtr)init_array_section().address().as_ptr();
+ for (size_t i = 0; i < (m_init_array_size / sizeof(void*)); ++i) {
+ InitializationFunction current = ((InitializationFunction*)(init_array))[i];
+ f(current);
+ }
+}
+
+} // end namespace ELF
diff --git a/Userland/Libraries/LibELF/Image.cpp b/Userland/Libraries/LibELF/Image.cpp
new file mode 100644
index 0000000000..27bf519353
--- /dev/null
+++ b/Userland/Libraries/LibELF/Image.cpp
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Demangle.h>
+#include <AK/Memory.h>
+#include <AK/QuickSort.h>
+#include <AK/StringBuilder.h>
+#include <AK/StringView.h>
+#include <LibELF/Image.h>
+#include <LibELF/Validation.h>
+
+//#define ELF_IMAGE_DEBUG
+
+namespace ELF {
+
+Image::Image(ReadonlyBytes bytes, bool verbose_logging)
+ : m_buffer(bytes.data())
+ , m_size(bytes.size())
+ , m_verbose_logging(verbose_logging)
+{
+ parse();
+}
+
+Image::Image(const u8* buffer, size_t size, bool verbose_logging)
+ : Image(ReadonlyBytes { buffer, size }, verbose_logging)
+{
+}
+
+Image::~Image()
+{
+}
+
+#ifdef ELF_IMAGE_DEBUG
+static const char* object_file_type_to_string(Elf32_Half type)
+{
+ switch (type) {
+ case ET_NONE:
+ return "None";
+ case ET_REL:
+ return "Relocatable";
+ case ET_EXEC:
+ return "Executable";
+ case ET_DYN:
+ return "Shared object";
+ case ET_CORE:
+ return "Core";
+ default:
+ return "(?)";
+ }
+}
+#endif
+
+StringView Image::section_index_to_string(unsigned index) const
+{
+ ASSERT(m_valid);
+ if (index == SHN_UNDEF)
+ return "Undefined";
+ if (index >= SHN_LORESERVE)
+ return "Reserved";
+ return section(index).name();
+}
+
+unsigned Image::symbol_count() const
+{
+ ASSERT(m_valid);
+ if (!section_count())
+ return 0;
+ return section(m_symbol_table_section_index).entry_count();
+}
+
+void Image::dump() const
+{
+#ifdef ELF_IMAGE_DEBUG
+ dbgln("ELF::Image({:p}) {{", this);
+ dbgln(" is_valid: {}", is_valid());
+
+ if (!is_valid()) {
+ dbgln("}}");
+ return;
+ }
+
+ dbgln(" type: {}", object_file_type_to_string(header().e_type));
+ dbgln(" machine: {}", header().e_machine);
+ dbgln(" entry: {:x}", header().e_entry);
+ dbgln(" shoff: {}", header().e_shoff);
+ dbgln(" shnum: {}", header().e_shnum);
+ dbgln(" phoff: {}", header().e_phoff);
+ dbgln(" phnum: {}", header().e_phnum);
+ dbgln(" shstrndx: {}", header().e_shstrndx);
+
+ for_each_program_header([&](const ProgramHeader& program_header) {
+ dbgln(" Program Header {}: {{", program_header.index());
+ dbgln(" type: {:x}", program_header.type());
+ dbgln(" offset: {:x}", program_header.offset());
+ dbgln(" flags: {:x}", program_header.flags());
+ dbgln(" }}");
+ return IterationDecision::Continue;
+ });
+
+ for (unsigned i = 0; i < header().e_shnum; ++i) {
+ auto& section = this->section(i);
+ dbgln(" Section {}: {{", i);
+ dbgln(" name: {}", section.name());
+ dbgln(" type: {:x}", section.type());
+ dbgln(" offset: {:x}", section.offset());
+ dbgln(" size: {}", section.size());
+ dbgln(" ");
+ dbgln(" }}");
+ }
+
+ dbgln("Symbol count: {} (table is {})", symbol_count(), m_symbol_table_section_index);
+ for (unsigned i = 1; i < symbol_count(); ++i) {
+ auto& sym = symbol(i);
+ dbgln("Symbol @{}:", i);
+ dbgln(" Name: {}", sym.name());
+ dbgln(" In section: {}", section_index_to_string(sym.section_index()));
+ dbgln(" Value: {}", sym.value());
+ dbgln(" Size: {}", sym.size());
+ }
+
+ dbgln("}}");
+#endif
+}
+
+unsigned Image::section_count() const
+{
+ ASSERT(m_valid);
+ return header().e_shnum;
+}
+
+unsigned Image::program_header_count() const
+{
+ ASSERT(m_valid);
+ return header().e_phnum;
+}
+
+bool Image::parse()
+{
+ if (m_size < sizeof(Elf32_Ehdr) || !validate_elf_header(header(), m_size, m_verbose_logging)) {
+ if (m_verbose_logging)
+ dbgln("ELF::Image::parse(): ELF Header not valid");
+ return m_valid = false;
+ }
+
+ if (!validate_program_headers(header(), m_size, m_buffer, m_size, nullptr, m_verbose_logging)) {
+ if (m_verbose_logging)
+ dbgln("ELF::Image::parse(): ELF Program Headers not valid");
+ return m_valid = false;
+ }
+
+ m_valid = true;
+
+ // First locate the string tables.
+ for (unsigned i = 0; i < section_count(); ++i) {
+ auto& sh = section_header(i);
+ if (sh.sh_type == SHT_SYMTAB) {
+ if (m_symbol_table_section_index && m_symbol_table_section_index != i)
+ return m_valid = false;
+ m_symbol_table_section_index = i;
+ }
+ if (sh.sh_type == SHT_STRTAB && i != header().e_shstrndx) {
+ if (section_header_table_string(sh.sh_name) == ELF_STRTAB)
+ m_string_table_section_index = i;
+ }
+ }
+
+ // Then create a name-to-index map.
+ for (unsigned i = 0; i < section_count(); ++i) {
+ auto& section = this->section(i);
+ m_sections.set(section.name(), move(i));
+ }
+
+ return m_valid;
+}
+
+StringView Image::table_string(unsigned table_index, unsigned offset) const
+{
+ ASSERT(m_valid);
+ auto& sh = section_header(table_index);
+ if (sh.sh_type != SHT_STRTAB)
+ return nullptr;
+ size_t computed_offset = sh.sh_offset + offset;
+ if (computed_offset >= m_size) {
+ if (m_verbose_logging)
+ dbgln("SHENANIGANS! Image::table_string() computed offset outside image.");
+ return {};
+ }
+ size_t max_length = m_size - computed_offset;
+ size_t length = strnlen(raw_data(sh.sh_offset + offset), max_length);
+ return { raw_data(sh.sh_offset + offset), length };
+}
+
+StringView Image::section_header_table_string(unsigned offset) const
+{
+ ASSERT(m_valid);
+ return table_string(header().e_shstrndx, offset);
+}
+
+StringView Image::table_string(unsigned offset) const
+{
+ ASSERT(m_valid);
+ return table_string(m_string_table_section_index, offset);
+}
+
+const char* Image::raw_data(unsigned offset) const
+{
+ ASSERT(offset < m_size); // Callers must check indices into raw_data()'s result are also in bounds.
+ return reinterpret_cast<const char*>(m_buffer) + offset;
+}
+
+const Elf32_Ehdr& Image::header() const
+{
+ ASSERT(m_size >= sizeof(Elf32_Ehdr));
+ return *reinterpret_cast<const Elf32_Ehdr*>(raw_data(0));
+}
+
+const Elf32_Phdr& Image::program_header_internal(unsigned index) const
+{
+ ASSERT(m_valid);
+ ASSERT(index < header().e_phnum);
+ return *reinterpret_cast<const Elf32_Phdr*>(raw_data(header().e_phoff + (index * sizeof(Elf32_Phdr))));
+}
+
+const Elf32_Shdr& Image::section_header(unsigned index) const
+{
+ ASSERT(m_valid);
+ ASSERT(index < header().e_shnum);
+ return *reinterpret_cast<const Elf32_Shdr*>(raw_data(header().e_shoff + (index * header().e_shentsize)));
+}
+
+const Image::Symbol Image::symbol(unsigned index) const
+{
+ ASSERT(m_valid);
+ ASSERT(index < symbol_count());
+ auto* raw_syms = reinterpret_cast<const Elf32_Sym*>(raw_data(section(m_symbol_table_section_index).offset()));
+ return Symbol(*this, index, raw_syms[index]);
+}
+
+const Image::Section Image::section(unsigned index) const
+{
+ ASSERT(m_valid);
+ ASSERT(index < section_count());
+ return Section(*this, index);
+}
+
+const Image::ProgramHeader Image::program_header(unsigned index) const
+{
+ ASSERT(m_valid);
+ ASSERT(index < program_header_count());
+ return ProgramHeader(*this, index);
+}
+
+FlatPtr Image::program_header_table_offset() const
+{
+ return header().e_phoff;
+}
+
+const Image::Relocation Image::RelocationSection::relocation(unsigned index) const
+{
+ ASSERT(index < relocation_count());
+ auto* rels = reinterpret_cast<const Elf32_Rel*>(m_image.raw_data(offset()));
+ return Relocation(m_image, rels[index]);
+}
+
+const Image::RelocationSection Image::Section::relocations() const
+{
+ StringBuilder builder;
+ builder.append(".rel");
+ builder.append(name());
+
+ auto relocation_section = m_image.lookup_section(builder.to_string());
+ if (relocation_section.type() != SHT_REL)
+ return static_cast<const RelocationSection>(m_image.section(0));
+
+#ifdef ELF_IMAGE_DEBUG
+ dbgln("Found relocations for {} in {}", name(), relocation_section.name());
+#endif
+ return static_cast<const RelocationSection>(relocation_section);
+}
+
+const Image::Section Image::lookup_section(const String& name) const
+{
+ ASSERT(m_valid);
+ if (auto it = m_sections.find(name); it != m_sections.end())
+ return section((*it).value);
+ return section(0);
+}
+
+StringView Image::Symbol::raw_data() const
+{
+ auto& section = this->section();
+ return { section.raw_data() + (value() - section.address()), size() };
+}
+
+Optional<Image::Symbol> Image::find_demangled_function(const String& name) const
+{
+ Optional<Image::Symbol> found;
+ for_each_symbol([&](const Image::Symbol symbol) {
+ if (symbol.type() != STT_FUNC)
+ return IterationDecision::Continue;
+ if (symbol.is_undefined())
+ return IterationDecision::Continue;
+ auto demangled = demangle(symbol.name());
+ auto index_of_paren = demangled.index_of("(");
+ if (index_of_paren.has_value()) {
+ demangled = demangled.substring(0, index_of_paren.value());
+ }
+ if (demangled != name)
+ return IterationDecision::Continue;
+ found = symbol;
+ return IterationDecision::Break;
+ });
+ return found;
+}
+
+Optional<Image::Symbol> Image::find_symbol(u32 address, u32* out_offset) const
+{
+ auto symbol_count = this->symbol_count();
+ if (!symbol_count)
+ return {};
+
+ SortedSymbol* sorted_symbols = nullptr;
+ if (m_sorted_symbols.is_empty()) {
+ m_sorted_symbols.ensure_capacity(symbol_count);
+ for_each_symbol([this](auto& symbol) {
+ m_sorted_symbols.append({ symbol.value(), symbol.name(), {}, symbol });
+ return IterationDecision::Continue;
+ });
+ quick_sort(m_sorted_symbols, [](auto& a, auto& b) {
+ return a.address < b.address;
+ });
+ }
+ sorted_symbols = m_sorted_symbols.data();
+
+ for (size_t i = 0; i < symbol_count; ++i) {
+ if (sorted_symbols[i].address > address) {
+ if (i == 0)
+ return {};
+ auto& symbol = sorted_symbols[i - 1];
+ if (out_offset)
+ *out_offset = address - symbol.address;
+ return symbol.symbol;
+ }
+ }
+ return {};
+}
+
+String Image::symbolicate(u32 address, u32* out_offset) const
+{
+ auto symbol_count = this->symbol_count();
+ if (!symbol_count) {
+ if (out_offset)
+ *out_offset = 0;
+ return "??";
+ }
+ SortedSymbol* sorted_symbols = nullptr;
+
+ if (m_sorted_symbols.is_empty()) {
+ m_sorted_symbols.ensure_capacity(symbol_count);
+ for_each_symbol([this](auto& symbol) {
+ m_sorted_symbols.append({ symbol.value(), symbol.name(), {}, symbol });
+ return IterationDecision::Continue;
+ });
+ quick_sort(m_sorted_symbols, [](auto& a, auto& b) {
+ return a.address < b.address;
+ });
+ }
+ sorted_symbols = m_sorted_symbols.data();
+
+ for (size_t i = 0; i < symbol_count; ++i) {
+ if (sorted_symbols[i].address > address) {
+ if (i == 0) {
+ if (out_offset)
+ *out_offset = 0;
+ return "!!";
+ }
+ auto& symbol = sorted_symbols[i - 1];
+
+ auto& demangled_name = symbol.demangled_name;
+ if (demangled_name.is_null()) {
+ demangled_name = demangle(symbol.name);
+ }
+
+ if (out_offset) {
+ *out_offset = address - symbol.address;
+ return demangled_name;
+ }
+ return String::format("%s +0x%x", demangled_name.characters(), address - symbol.address);
+ }
+ }
+ if (out_offset)
+ *out_offset = 0;
+ return "??";
+}
+
+} // end namespace ELF
diff --git a/Userland/Libraries/LibELF/Image.h b/Userland/Libraries/LibELF/Image.h
new file mode 100644
index 0000000000..3250cf4deb
--- /dev/null
+++ b/Userland/Libraries/LibELF/Image.h
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/HashMap.h>
+#include <AK/OwnPtr.h>
+#include <AK/String.h>
+#include <Kernel/VirtualAddress.h>
+#include <LibELF/exec_elf.h>
+
+namespace ELF {
+
+class Image {
+public:
+ explicit Image(ReadonlyBytes, bool verbose_logging = true);
+ explicit Image(const u8*, size_t, bool verbose_logging = true);
+
+ ~Image();
+ void dump() const;
+ bool is_valid() const { return m_valid; }
+ bool parse();
+
+ bool is_within_image(const void* address, size_t size) const
+ {
+ if (address < m_buffer)
+ return false;
+ if (((const u8*)address + size) > m_buffer + m_size)
+ return false;
+ return true;
+ }
+
+ class Section;
+ class RelocationSection;
+ class Symbol;
+ class Relocation;
+
+ class Symbol {
+ public:
+ Symbol(const Image& image, unsigned index, const Elf32_Sym& sym)
+ : m_image(image)
+ , m_sym(sym)
+ , m_index(index)
+ {
+ }
+
+ ~Symbol() { }
+
+ StringView name() const { return m_image.table_string(m_sym.st_name); }
+ unsigned section_index() const { return m_sym.st_shndx; }
+ unsigned value() const { return m_sym.st_value; }
+ unsigned size() const { return m_sym.st_size; }
+ unsigned index() const { return m_index; }
+ unsigned type() const { return ELF32_ST_TYPE(m_sym.st_info); }
+ unsigned bind() const { return ELF32_ST_BIND(m_sym.st_info); }
+ const Section section() const { return m_image.section(section_index()); }
+ bool is_undefined() const { return section_index() == 0; }
+ StringView raw_data() const;
+
+ private:
+ const Image& m_image;
+ const Elf32_Sym& m_sym;
+ const unsigned m_index;
+ };
+
+ class ProgramHeader {
+ public:
+ ProgramHeader(const Image& image, unsigned program_header_index)
+ : m_image(image)
+ , m_program_header(image.program_header_internal(program_header_index))
+ , m_program_header_index(program_header_index)
+ {
+ }
+ ~ProgramHeader() { }
+
+ unsigned index() const { return m_program_header_index; }
+ u32 type() const { return m_program_header.p_type; }
+ u32 flags() const { return m_program_header.p_flags; }
+ u32 offset() const { return m_program_header.p_offset; }
+ VirtualAddress vaddr() const { return VirtualAddress(m_program_header.p_vaddr); }
+ u32 size_in_memory() const { return m_program_header.p_memsz; }
+ u32 size_in_image() const { return m_program_header.p_filesz; }
+ u32 alignment() const { return m_program_header.p_align; }
+ bool is_readable() const { return flags() & PF_R; }
+ bool is_writable() const { return flags() & PF_W; }
+ bool is_executable() const { return flags() & PF_X; }
+ const char* raw_data() const { return m_image.raw_data(m_program_header.p_offset); }
+ Elf32_Phdr raw_header() const { return m_program_header; }
+
+ private:
+ const Image& m_image;
+ const Elf32_Phdr& m_program_header;
+ unsigned m_program_header_index { 0 };
+ };
+
+ class Section {
+ public:
+ Section(const Image& image, unsigned sectionIndex)
+ : m_image(image)
+ , m_section_header(image.section_header(sectionIndex))
+ , m_section_index(sectionIndex)
+ {
+ }
+ ~Section() { }
+
+ StringView name() const { return m_image.section_header_table_string(m_section_header.sh_name); }
+ unsigned type() const { return m_section_header.sh_type; }
+ unsigned offset() const { return m_section_header.sh_offset; }
+ unsigned size() const { return m_section_header.sh_size; }
+ unsigned entry_size() const { return m_section_header.sh_entsize; }
+ unsigned entry_count() const { return !entry_size() ? 0 : size() / entry_size(); }
+ u32 address() const { return m_section_header.sh_addr; }
+ const char* raw_data() const { return m_image.raw_data(m_section_header.sh_offset); }
+ ReadonlyBytes bytes() const { return { raw_data(), size() }; }
+ bool is_undefined() const { return m_section_index == SHN_UNDEF; }
+ const RelocationSection relocations() const;
+ u32 flags() const { return m_section_header.sh_flags; }
+ bool is_writable() const { return flags() & SHF_WRITE; }
+ bool is_executable() const { return flags() & PF_X; }
+
+ protected:
+ friend class RelocationSection;
+ const Image& m_image;
+ const Elf32_Shdr& m_section_header;
+ unsigned m_section_index;
+ };
+
+ class RelocationSection : public Section {
+ public:
+ RelocationSection(const Section& section)
+ : Section(section.m_image, section.m_section_index)
+ {
+ }
+ unsigned relocation_count() const { return entry_count(); }
+ const Relocation relocation(unsigned index) const;
+ template<typename F>
+ void for_each_relocation(F) const;
+ };
+
+ class Relocation {
+ public:
+ Relocation(const Image& image, const Elf32_Rel& rel)
+ : m_image(image)
+ , m_rel(rel)
+ {
+ }
+
+ ~Relocation() { }
+
+ unsigned offset() const { return m_rel.r_offset; }
+ unsigned type() const { return ELF32_R_TYPE(m_rel.r_info); }
+ unsigned symbol_index() const { return ELF32_R_SYM(m_rel.r_info); }
+ const Symbol symbol() const { return m_image.symbol(symbol_index()); }
+
+ private:
+ const Image& m_image;
+ const Elf32_Rel& m_rel;
+ };
+
+ unsigned symbol_count() const;
+ unsigned section_count() const;
+ unsigned program_header_count() const;
+
+ const Symbol symbol(unsigned) const;
+ const Section section(unsigned) const;
+ const ProgramHeader program_header(unsigned const) const;
+ FlatPtr program_header_table_offset() const;
+
+ template<typename F>
+ void for_each_section(F) const;
+ template<typename F>
+ void for_each_section_of_type(unsigned, F) const;
+ template<typename F>
+ void for_each_symbol(F) const;
+ template<typename F>
+ void for_each_program_header(F) const;
+
+ // NOTE: Returns section(0) if section with name is not found.
+ // FIXME: I don't love this API.
+ const Section lookup_section(const String& name) const;
+
+ bool is_executable() const { return header().e_type == ET_EXEC; }
+ bool is_relocatable() const { return header().e_type == ET_REL; }
+ bool is_dynamic() const { return header().e_type == ET_DYN; }
+
+ VirtualAddress entry() const { return VirtualAddress(header().e_entry); }
+ FlatPtr base_address() const { return (FlatPtr)m_buffer; }
+ size_t size() const { return m_size; }
+
+ Optional<Symbol> find_demangled_function(const String& name) const;
+
+ bool has_symbols() const { return symbol_count(); }
+ String symbolicate(u32 address, u32* offset = nullptr) const;
+ Optional<Image::Symbol> find_symbol(u32 address, u32* offset = nullptr) const;
+
+private:
+ const char* raw_data(unsigned offset) const;
+ const Elf32_Ehdr& header() const;
+ const Elf32_Shdr& section_header(unsigned) const;
+ const Elf32_Phdr& program_header_internal(unsigned) const;
+ StringView table_string(unsigned offset) const;
+ StringView section_header_table_string(unsigned offset) const;
+ StringView section_index_to_string(unsigned index) const;
+ StringView table_string(unsigned table_index, unsigned offset) const;
+
+ const u8* m_buffer { nullptr };
+ size_t m_size { 0 };
+ bool m_verbose_logging { true };
+ HashMap<String, unsigned> m_sections;
+ bool m_valid { false };
+ unsigned m_symbol_table_section_index { 0 };
+ unsigned m_string_table_section_index { 0 };
+
+ struct SortedSymbol {
+ u32 address;
+ StringView name;
+ String demangled_name;
+ Optional<Image::Symbol> symbol;
+ };
+
+ mutable Vector<SortedSymbol> m_sorted_symbols;
+};
+
+template<typename F>
+inline void Image::for_each_section(F func) const
+{
+ auto section_count = this->section_count();
+ for (unsigned i = 0; i < section_count; ++i)
+ func(section(i));
+}
+
+template<typename F>
+inline void Image::for_each_section_of_type(unsigned type, F func) const
+{
+ auto section_count = this->section_count();
+ for (unsigned i = 0; i < section_count; ++i) {
+ auto& section = this->section(i);
+ if (section.type() == type) {
+ if (func(section) == IterationDecision::Break)
+ break;
+ }
+ }
+}
+
+template<typename F>
+inline void Image::RelocationSection::for_each_relocation(F func) const
+{
+ auto relocation_count = this->relocation_count();
+ for (unsigned i = 0; i < relocation_count; ++i) {
+ if (func(relocation(i)) == IterationDecision::Break)
+ break;
+ }
+}
+
+template<typename F>
+inline void Image::for_each_symbol(F func) const
+{
+ auto symbol_count = this->symbol_count();
+ for (unsigned i = 0; i < symbol_count; ++i) {
+ if (func(symbol(i)) == IterationDecision::Break)
+ break;
+ }
+}
+
+template<typename F>
+inline void Image::for_each_program_header(F func) const
+{
+ auto program_header_count = this->program_header_count();
+ for (unsigned i = 0; i < program_header_count; ++i) {
+ if (func(program_header(i)) == IterationDecision::Break)
+ return;
+ }
+}
+
+} // end namespace ELF
diff --git a/Userland/Libraries/LibELF/Validation.cpp b/Userland/Libraries/LibELF/Validation.cpp
new file mode 100644
index 0000000000..1700a48b1e
--- /dev/null
+++ b/Userland/Libraries/LibELF/Validation.cpp
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2020, Andrew Kaster <andrewdkaster@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/String.h>
+#include <LibELF/Validation.h>
+#include <LibELF/exec_elf.h>
+
+namespace ELF {
+
+bool validate_elf_header(const Elf32_Ehdr& elf_header, size_t file_size, bool verbose)
+{
+ if (!IS_ELF(elf_header)) {
+ if (verbose)
+ dbgln("File is not an ELF file.");
+ return false;
+ }
+
+ if (ELFCLASS32 != elf_header.e_ident[EI_CLASS]) {
+ if (verbose)
+ dbgln("File is not a 32 bit ELF file.");
+ return false;
+ }
+
+ if (ELFDATA2LSB != elf_header.e_ident[EI_DATA]) {
+ if (verbose)
+ dbgln("File is not a little endian ELF file.");
+ return false;
+ }
+
+ if (EV_CURRENT != elf_header.e_ident[EI_VERSION]) {
+ if (verbose)
+ dbgln("File has unrecognized ELF version ({}), expected ({})!", elf_header.e_ident[EI_VERSION], EV_CURRENT);
+ return false;
+ }
+
+ if (ELFOSABI_SYSV != elf_header.e_ident[EI_OSABI]) {
+ if (verbose)
+ dbgln("File has unknown OS ABI ({}), expected SYSV(0)!", elf_header.e_ident[EI_OSABI]);
+ return false;
+ }
+
+ if (0 != elf_header.e_ident[EI_ABIVERSION]) {
+ if (verbose)
+ dbgln("File has unknown SYSV ABI version ({})!", elf_header.e_ident[EI_ABIVERSION]);
+ return false;
+ }
+
+ if (EM_386 != elf_header.e_machine) {
+ if (verbose)
+ dbgln("File has unknown machine ({}), expected i386 (3)!", elf_header.e_machine);
+ return false;
+ }
+
+ if (ET_EXEC != elf_header.e_type && ET_DYN != elf_header.e_type && ET_REL != elf_header.e_type && ET_CORE != elf_header.e_type) {
+ if (verbose)
+ dbgln("File has unloadable ELF type ({}), expected REL (1), EXEC (2), DYN (3) or CORE(4)!", elf_header.e_type);
+ return false;
+ }
+
+ if (EV_CURRENT != elf_header.e_version) {
+ if (verbose)
+ dbgln("File has unrecognized ELF version ({}), expected ({})!", elf_header.e_version, EV_CURRENT);
+ return false;
+ }
+
+ if (sizeof(Elf32_Ehdr) != elf_header.e_ehsize) {
+ if (verbose)
+ dbgln("File has incorrect ELF header size..? ({}), expected ({})!", elf_header.e_ehsize, sizeof(Elf32_Ehdr));
+ return false;
+ }
+
+ if (elf_header.e_phoff < elf_header.e_ehsize || (elf_header.e_shnum != SHN_UNDEF && elf_header.e_shoff < elf_header.e_ehsize)) {
+ if (verbose) {
+ dbgln("SHENANIGANS! program header offset ({}) or section header offset ({}) overlap with ELF header!",
+ elf_header.e_phoff, elf_header.e_shoff);
+ }
+ return false;
+ }
+
+ if (elf_header.e_phoff > file_size || elf_header.e_shoff > file_size) {
+ if (verbose) {
+ dbgln("SHENANIGANS! program header offset ({}) or section header offset ({}) are past the end of the file!",
+ elf_header.e_phoff, elf_header.e_shoff);
+ }
+ return false;
+ }
+
+ if (elf_header.e_phnum == 0 && elf_header.e_phoff != 0) {
+ if (verbose)
+ dbgln("SHENANIGANS! File has no program headers, but it does have a program header offset ({})!", elf_header.e_phoff);
+ return false;
+ }
+
+ if (elf_header.e_phnum != 0 && elf_header.e_phoff != elf_header.e_ehsize) {
+ if (verbose) {
+ dbgln("File does not have program headers directly after the ELF header? program header offset ({}), expected ({}).",
+ elf_header.e_phoff, elf_header.e_ehsize);
+ }
+ return false;
+ }
+
+ if (0 != elf_header.e_flags) {
+ if (verbose)
+ dbgln("File has incorrect ELF header flags...? ({}), expected ({}).", elf_header.e_flags, 0);
+ return false;
+ }
+
+ if (0 != elf_header.e_phnum && sizeof(Elf32_Phdr) != elf_header.e_phentsize) {
+ if (verbose)
+ dbgln("File has incorrect program header size..? ({}), expected ({}).", elf_header.e_phentsize, sizeof(Elf32_Phdr));
+ return false;
+ }
+
+ if (sizeof(Elf32_Shdr) != elf_header.e_shentsize) {
+ if (verbose)
+ dbgln("File has incorrect section header size..? ({}), expected ({}).", elf_header.e_shentsize, sizeof(Elf32_Shdr));
+ return false;
+ }
+
+ size_t end_of_last_program_header = elf_header.e_phoff + (elf_header.e_phnum * elf_header.e_phentsize);
+ if (end_of_last_program_header > file_size) {
+ if (verbose)
+ dbgln("SHENANIGANS! End of last program header ({}) is past the end of the file!", end_of_last_program_header);
+ return false;
+ }
+
+ if (elf_header.e_shoff != SHN_UNDEF && elf_header.e_shoff < end_of_last_program_header) {
+ if (verbose) {
+ dbgln("SHENANIGANS! Section header table begins at file offset {}, which is within program headers [ {} - {} ]!",
+ elf_header.e_shoff, elf_header.e_phoff, end_of_last_program_header);
+ }
+ return false;
+ }
+
+ size_t end_of_last_section_header = elf_header.e_shoff + (elf_header.e_shnum * elf_header.e_shentsize);
+ if (end_of_last_section_header > file_size) {
+ if (verbose)
+ dbgln("SHENANIGANS! End of last section header ({}) is past the end of the file!", end_of_last_section_header);
+ return false;
+ }
+
+ if (elf_header.e_shstrndx != SHN_UNDEF && elf_header.e_shstrndx >= elf_header.e_shnum) {
+ if (verbose)
+ dbgln("SHENANIGANS! Section header string table index ({}) is not a valid index given we have {} section headers!", elf_header.e_shstrndx, elf_header.e_shnum);
+ return false;
+ }
+
+ return true;
+}
+
+bool validate_program_headers(const Elf32_Ehdr& elf_header, size_t file_size, const u8* buffer, size_t buffer_size, String* interpreter_path, bool verbose)
+{
+ // Can we actually parse all the program headers in the given buffer?
+ size_t end_of_last_program_header = elf_header.e_phoff + (elf_header.e_phnum * elf_header.e_phentsize);
+ if (end_of_last_program_header > buffer_size) {
+ if (verbose)
+ dbgln("Unable to parse program headers from buffer, buffer too small! Buffer size: {}, End of program headers {}",
+ buffer_size, end_of_last_program_header);
+ return false;
+ }
+
+ if (file_size < buffer_size) {
+ dbgln("We somehow read more from a file than was in the file in the first place!");
+ ASSERT_NOT_REACHED();
+ }
+
+ size_t num_program_headers = elf_header.e_phnum;
+ auto program_header_begin = (const Elf32_Phdr*)&(buffer[elf_header.e_phoff]);
+
+ for (size_t header_index = 0; header_index < num_program_headers; ++header_index) {
+ auto& program_header = program_header_begin[header_index];
+
+ if (program_header.p_filesz > program_header.p_memsz) {
+ if (verbose)
+ dbgln("Program header ({}) has p_filesz ({}) larger than p_memsz ({})", header_index, program_header.p_filesz, program_header.p_memsz);
+ return false;
+ }
+
+ if (program_header.p_memsz <= 0 && (program_header.p_type == PT_TLS || program_header.p_type == PT_LOAD)) {
+ if (verbose)
+ dbgln("Program header ({}) has invalid size in memory ({})", header_index, program_header.p_memsz);
+ return false;
+ }
+
+ if (program_header.p_type == PT_LOAD && program_header.p_align != PAGE_SIZE) {
+ if (elf_header.e_type != ET_CORE) {
+ if (verbose)
+ dbgln("Program header ({}) with p_type PT_LOAD has p_align ({}) not equal to page size ({})", header_index, program_header.p_align, PAGE_SIZE);
+ return false;
+ }
+ }
+
+ switch (program_header.p_type) {
+ case PT_INTERP:
+ // We checked above that file_size was >= buffer size. We only care about buffer size anyway, we're trying to read this!
+ if (program_header.p_offset + program_header.p_filesz > buffer_size) {
+ if (verbose)
+ dbgln("Found PT_INTERP header ({}), but the .interp section was not within the buffer :(", header_index);
+ return false;
+ }
+ if (interpreter_path)
+ *interpreter_path = String((const char*)&buffer[program_header.p_offset], program_header.p_filesz - 1);
+ break;
+ case PT_LOAD:
+ case PT_DYNAMIC:
+ case PT_NOTE:
+ case PT_PHDR:
+ case PT_TLS:
+ if (program_header.p_offset + program_header.p_filesz > file_size) {
+ if (verbose)
+ dbgln("SHENANIGANS! Program header {} segment leaks beyond end of file!", header_index);
+ return false;
+ }
+ if ((program_header.p_flags & PF_X) && (program_header.p_flags & PF_W)) {
+ if (verbose)
+ dbgln("SHENANIGANS! Program header {} segment is marked write and execute", header_index);
+ return false;
+ }
+ break;
+ case PT_GNU_STACK:
+ if (program_header.p_flags & PF_X) {
+ if (verbose)
+ dbgln("Possible shenanigans! Validating an ELF with executable stack.");
+ }
+ break;
+ case PT_GNU_RELRO:
+ if ((program_header.p_flags & PF_X) && (program_header.p_flags & PF_W)) {
+ if (verbose)
+ dbgln("SHENANIGANS! Program header {} segment is marked write and execute", header_index);
+ return false;
+ }
+ break;
+ default:
+ // Not handling other program header types in other code so... let's not surprise them
+ if (verbose)
+ dbgln("Found program header ({}) of unrecognized type {}!", header_index, program_header.p_type);
+ return false;
+ }
+ }
+ return true;
+}
+
+} // end namespace ELF
diff --git a/Userland/Libraries/LibELF/Validation.h b/Userland/Libraries/LibELF/Validation.h
new file mode 100644
index 0000000000..fe49c80b82
--- /dev/null
+++ b/Userland/Libraries/LibELF/Validation.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020, Andrew Kaster <andrewdkaster@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibELF/exec_elf.h>
+
+namespace ELF {
+
+bool validate_elf_header(const Elf32_Ehdr& elf_header, size_t file_size, bool verbose = true);
+bool validate_program_headers(const Elf32_Ehdr& elf_header, size_t file_size, const u8* buffer, size_t buffer_size, String* interpreter_path, bool verbose = true);
+
+} // end namespace ELF
diff --git a/Userland/Libraries/LibELF/exec_elf.h b/Userland/Libraries/LibELF/exec_elf.h
new file mode 100644
index 0000000000..45a0e9401f
--- /dev/null
+++ b/Userland/Libraries/LibELF/exec_elf.h
@@ -0,0 +1,792 @@
+/* $OpenBSD: exec_elf.h,v 1.83 2019/01/22 23:23:18 jsg Exp $ */
+/*
+ * Copyright (c) 1995, 1996 Erik Theisen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This is the ELF ABI header file
+ * formerly known as "elf_abi.h".
+ */
+
+#ifndef _SYS_EXEC_ELF_H_
+#define _SYS_EXEC_ELF_H_
+
+#include <AK/Types.h>
+
+typedef uint8_t Elf_Byte;
+
+typedef uint32_t Elf32_Addr; /* Unsigned program address */
+typedef uint32_t Elf32_Off; /* Unsigned file offset */
+typedef int32_t Elf32_Sword; /* Signed large integer */
+typedef uint32_t Elf32_Word; /* Unsigned large integer */
+typedef uint16_t Elf32_Half; /* Unsigned medium integer */
+typedef uint64_t Elf32_Lword;
+
+typedef uint64_t Elf64_Addr;
+typedef uint64_t Elf64_Off;
+typedef int32_t Elf64_Shalf;
+
+#ifdef __alpha__
+typedef int64_t Elf64_Sword;
+typedef uint64_t Elf64_Word;
+#else
+typedef int32_t Elf64_Sword;
+typedef uint32_t Elf64_Word;
+#endif
+
+typedef int64_t Elf64_Sxword;
+typedef uint64_t Elf64_Xword;
+typedef uint64_t Elf64_Lword;
+
+typedef uint32_t Elf64_Half;
+typedef uint16_t Elf64_Quarter;
+
+/*
+ * e_ident[] identification indexes
+ * See http://www.sco.com/developers/gabi/latest/ch4.eheader.html
+ */
+#define EI_MAG0 0 /* file ID */
+#define EI_MAG1 1 /* file ID */
+#define EI_MAG2 2 /* file ID */
+#define EI_MAG3 3 /* file ID */
+#define EI_CLASS 4 /* file class */
+#define EI_DATA 5 /* data encoding */
+#define EI_VERSION 6 /* ELF header version */
+#define EI_OSABI 7 /* OS/ABI ID */
+#define EI_ABIVERSION 8 /* ABI version */
+#define EI_PAD 9 /* start of pad bytes */
+#define EI_NIDENT 16 /* Gfx::Size of e_ident[] */
+
+/* e_ident[] magic number */
+#define ELFMAG0 0x7f /* e_ident[EI_MAG0] */
+#define ELFMAG1 'E' /* e_ident[EI_MAG1] */
+#define ELFMAG2 'L' /* e_ident[EI_MAG2] */
+#define ELFMAG3 'F' /* e_ident[EI_MAG3] */
+#define ELFMAG "\177ELF" /* magic */
+#define SELFMAG 4 /* size of magic */
+
+/* e_ident[] file class */
+#define ELFCLASSNONE 0 /* invalid */
+#define ELFCLASS32 1 /* 32-bit objs */
+#define ELFCLASS64 2 /* 64-bit objs */
+#define ELFCLASSNUM 3 /* number of classes */
+
+/* e_ident[] data encoding */
+#define ELFDATANONE 0 /* invalid */
+#define ELFDATA2LSB 1 /* Little-Endian */
+#define ELFDATA2MSB 2 /* Big-Endian */
+#define ELFDATANUM 3 /* number of data encode defines */
+
+/* e_ident[] Operating System/ABI */
+#define ELFOSABI_SYSV 0 /* UNIX System V ABI */
+#define ELFOSABI_HPUX 1 /* HP-UX operating system */
+#define ELFOSABI_NETBSD 2 /* NetBSD */
+#define ELFOSABI_LINUX 3 /* GNU/Linux */
+#define ELFOSABI_HURD 4 /* GNU/Hurd */
+#define ELFOSABI_86OPEN 5 /* 86Open common IA32 ABI */
+#define ELFOSABI_SOLARIS 6 /* Solaris */
+#define ELFOSABI_MONTEREY 7 /* Monterey */
+#define ELFOSABI_IRIX 8 /* IRIX */
+#define ELFOSABI_FREEBSD 9 /* FreeBSD */
+#define ELFOSABI_TRU64 10 /* TRU64 UNIX */
+#define ELFOSABI_MODESTO 11 /* Novell Modesto */
+#define ELFOSABI_OPENBSD 12 /* OpenBSD */
+#define ELFOSABI_ARM 97 /* ARM */
+#define ELFOSABI_STANDALONE 255 /* Standalone (embedded) application */
+
+/* e_ident */
+#define IS_ELF(ehdr) ((ehdr).e_ident[EI_MAG0] == ELFMAG0 && (ehdr).e_ident[EI_MAG1] == ELFMAG1 && (ehdr).e_ident[EI_MAG2] == ELFMAG2 && (ehdr).e_ident[EI_MAG3] == ELFMAG3)
+
+/* ELF Header */
+typedef struct elfhdr {
+ unsigned char e_ident[EI_NIDENT]; /* ELF Identification */
+ Elf32_Half e_type; /* object file type */
+ Elf32_Half e_machine; /* machine */
+ Elf32_Word e_version; /* object file version */
+ Elf32_Addr e_entry; /* virtual entry point */
+ Elf32_Off e_phoff; /* program header table offset */
+ Elf32_Off e_shoff; /* section header table offset */
+ Elf32_Word e_flags; /* processor-specific flags */
+ Elf32_Half e_ehsize; /* ELF header size */
+ Elf32_Half e_phentsize; /* program header entry size */
+ Elf32_Half e_phnum; /* number of program header entries */
+ Elf32_Half e_shentsize; /* section header entry size */
+ Elf32_Half e_shnum; /* number of section header entries */
+ Elf32_Half e_shstrndx; /* section header table's "section
+ header string table" entry offset */
+} Elf32_Ehdr;
+
+typedef struct {
+ unsigned char e_ident[EI_NIDENT]; /* Id bytes */
+ Elf64_Quarter e_type; /* file type */
+ Elf64_Quarter e_machine; /* machine type */
+ Elf64_Half e_version; /* version number */
+ Elf64_Addr e_entry; /* entry point */
+ Elf64_Off e_phoff; /* Program hdr offset */
+ Elf64_Off e_shoff; /* Section hdr offset */
+ Elf64_Half e_flags; /* Processor flags */
+ Elf64_Quarter e_ehsize; /* sizeof ehdr */
+ Elf64_Quarter e_phentsize; /* Program header entry size */
+ Elf64_Quarter e_phnum; /* Number of program headers */
+ Elf64_Quarter e_shentsize; /* Section header entry size */
+ Elf64_Quarter e_shnum; /* Number of section headers */
+ Elf64_Quarter e_shstrndx; /* String table index */
+} Elf64_Ehdr;
+
+/* e_type */
+#define ET_NONE 0 /* No file type */
+#define ET_REL 1 /* relocatable file */
+#define ET_EXEC 2 /* executable file */
+#define ET_DYN 3 /* shared object file */
+#define ET_CORE 4 /* core file */
+#define ET_NUM 5 /* number of types */
+#define ET_LOPROC 0xff00 /* reserved range for processor */
+#define ET_HIPROC 0xffff /* specific e_type */
+
+/* e_machine */
+#define EM_NONE 0 /* No Machine */
+#define EM_M32 1 /* AT&T WE 32100 */
+#define EM_SPARC 2 /* SPARC */
+#define EM_386 3 /* Intel 80386 */
+#define EM_68K 4 /* Motorola 68000 */
+#define EM_88K 5 /* Motorola 88000 */
+#define EM_486 6 /* Intel 80486 - unused? */
+#define EM_860 7 /* Intel 80860 */
+#define EM_MIPS 8 /* MIPS R3000 Big-Endian only */
+/*
+ * Don't know if EM_MIPS_RS4_BE,
+ * EM_SPARC64, EM_PARISC,
+ * or EM_PPC are ABI compliant
+ */
+#define EM_MIPS_RS4_BE 10 /* MIPS R4000 Big-Endian */
+#define EM_SPARC64 11 /* SPARC v9 64-bit unofficial */
+#define EM_PARISC 15 /* HPPA */
+#define EM_SPARC32PLUS 18 /* Enhanced instruction set SPARC */
+#define EM_PPC 20 /* PowerPC */
+#define EM_PPC64 21 /* PowerPC 64 */
+#define EM_ARM 40 /* Advanced RISC Machines ARM */
+#define EM_ALPHA 41 /* DEC ALPHA */
+#define EM_SH 42 /* Hitachi/Renesas Super-H */
+#define EM_SPARCV9 43 /* SPARC version 9 */
+#define EM_IA_64 50 /* Intel IA-64 Processor */
+#define EM_AMD64 62 /* AMD64 architecture */
+#define EM_X86_64 EM_AMD64
+#define EM_VAX 75 /* DEC VAX */
+#define EM_AARCH64 183 /* ARM 64-bit architecture (AArch64) */
+
+/* Non-standard */
+#define EM_ALPHA_EXP 0x9026 /* DEC ALPHA */
+#define EM__LAST__ (EM_ALPHA_EXP + 1)
+
+#define EM_NUM 22 /* number of machine types */
+
+/* Version */
+#define EV_NONE 0 /* Invalid */
+#define EV_CURRENT 1 /* Current */
+#define EV_NUM 2 /* number of versions */
+
+/* Magic for e_phnum: get real value from sh_info of first section header */
+#define PN_XNUM 0xffff
+
+/* Section Header */
+typedef struct {
+ Elf32_Word sh_name; /* name - index into section header
+ string table section */
+ Elf32_Word sh_type; /* type */
+ Elf32_Word sh_flags; /* flags */
+ Elf32_Addr sh_addr; /* address */
+ Elf32_Off sh_offset; /* file offset */
+ Elf32_Word sh_size; /* section size */
+ Elf32_Word sh_link; /* section header table index link */
+ Elf32_Word sh_info; /* extra information */
+ Elf32_Word sh_addralign; /* address alignment */
+ Elf32_Word sh_entsize; /* section entry size */
+} Elf32_Shdr;
+
+typedef struct {
+ Elf64_Half sh_name; /* section name */
+ Elf64_Half sh_type; /* section type */
+ Elf64_Xword sh_flags; /* section flags */
+ Elf64_Addr sh_addr; /* virtual address */
+ Elf64_Off sh_offset; /* file offset */
+ Elf64_Xword sh_size; /* section size */
+ Elf64_Half sh_link; /* link to another */
+ Elf64_Half sh_info; /* misc info */
+ Elf64_Xword sh_addralign; /* memory alignment */
+ Elf64_Xword sh_entsize; /* table entry size */
+} Elf64_Shdr;
+
+/* Special Section Indexes */
+#define SHN_UNDEF 0 /* undefined */
+#define SHN_LORESERVE 0xff00 /* lower bounds of reserved indexes */
+#define SHN_LOPROC 0xff00 /* reserved range for processor */
+#define SHN_HIPROC 0xff1f /* specific section indexes */
+#define SHN_ABS 0xfff1 /* absolute value */
+#define SHN_COMMON 0xfff2 /* common symbol */
+#define SHN_XINDEX 0xffff /* Escape -- index stored elsewhere. */
+#define SHN_HIRESERVE 0xffff /* upper bounds of reserved indexes */
+
+/* sh_type */
+#define SHT_NULL 0 /* inactive */
+#define SHT_PROGBITS 1 /* program defined information */
+#define SHT_SYMTAB 2 /* symbol table section */
+#define SHT_STRTAB 3 /* string table section */
+#define SHT_RELA 4 /* relocation section with addends*/
+#define SHT_HASH 5 /* symbol hash table section */
+#define SHT_DYNAMIC 6 /* dynamic section */
+#define SHT_NOTE 7 /* note section */
+#define SHT_NOBITS 8 /* no space section */
+#define SHT_REL 9 /* relation section without addends */
+#define SHT_SHLIB 10 /* reserved - purpose unknown */
+#define SHT_DYNSYM 11 /* dynamic symbol table section */
+#define SHT_NUM 12 /* number of section types */
+#define SHT_INIT_ARRAY 14 /* pointers to init functions */
+#define SHT_FINI_ARRAY 15 /* pointers to termination functions */
+#define SHT_PREINIT_ARRAY 16 /* ptrs to funcs called before init */
+#define SHT_GROUP 17 /* defines a section group */
+#define SHT_SYMTAB_SHNDX 18 /* Section indexes (see SHN_XINDEX). */
+#define SHT_LOOS 0x60000000 /* reserved range for OS specific */
+#define SHT_SUNW_dof 0x6ffffff4 /* used by dtrace */
+#define SHT_GNU_LIBLIST 0x6ffffff7 /* libraries to be prelinked */
+#define SHT_SUNW_move 0x6ffffffa /* inf for partially init'ed symbols */
+#define SHT_SUNW_syminfo 0x6ffffffc /* ad symbol information */
+#define SHT_SUNW_verdef 0x6ffffffd /* symbol versioning inf */
+#define SHT_SUNW_verneed 0x6ffffffe /* symbol versioning req */
+#define SHT_SUNW_versym 0x6fffffff /* symbol versioning table */
+#define SHT_HIOS 0x6fffffff /* section header types */
+#define SHT_LOPROC 0x70000000 /* reserved range for processor */
+#define SHT_HIPROC 0x7fffffff /* specific section header types */
+#define SHT_LOUSER 0x80000000 /* reserved range for application */
+#define SHT_HIUSER 0xffffffff /* specific indexes */
+
+#define SHT_GNU_HASH 0x6ffffff6 /* GNU-style hash table section */
+
+/* Section names */
+#define ELF_BSS ".bss" /* uninitialized data */
+#define ELF_DATA ".data" /* initialized data */
+#define ELF_CTF ".SUNW_ctf" /* CTF data */
+#define ELF_DEBUG ".debug" /* debug */
+#define ELF_DYNAMIC ".dynamic" /* dynamic linking information */
+#define ELF_DYNSTR ".dynstr" /* dynamic string table */
+#define ELF_DYNSYM ".dynsym" /* dynamic symbol table */
+#define ELF_FINI ".fini" /* termination code */
+#define ELF_GOT ".got" /* global offset table */
+#define ELF_HASH ".hash" /* symbol hash table */
+#define ELF_INIT ".init" /* initialization code */
+#define ELF_REL_DATA ".rel.data" /* relocation data */
+#define ELF_REL_FINI ".rel.fini" /* relocation termination code */
+#define ELF_REL_INIT ".rel.init" /* relocation initialization code */
+#define ELF_REL_DYN ".rel.dyn" /* relocation dynamic link info */
+#define ELF_REL_RODATA ".rel.rodata" /* relocation read-only data */
+#define ELF_REL_TEXT ".rel.text" /* relocation code */
+#define ELF_RODATA ".rodata" /* read-only data */
+#define ELF_SHSTRTAB ".shstrtab" /* section header string table */
+#define ELF_STRTAB ".strtab" /* string table */
+#define ELF_SYMTAB ".symtab" /* symbol table */
+#define ELF_TEXT ".text" /* code */
+#define ELF_OPENBSDRANDOMDATA ".openbsd.randomdata" /* constant randomdata */
+
+/* Section Attribute Flags - sh_flags */
+#define SHF_WRITE 0x1 /* Writable */
+#define SHF_ALLOC 0x2 /* occupies memory */
+#define SHF_EXECINSTR 0x4 /* executable */
+#define SHF_MERGE 0x10 /* may be merged */
+#define SHF_STRINGS 0x20 /* contains strings */
+#define SHF_INFO_LINK 0x40 /* sh_info holds section index */
+#define SHF_LINK_ORDER 0x80 /* ordering requirements */
+#define SHF_OS_NONCONFORMING 0x100 /* OS-specific processing required */
+#define SHF_GROUP 0x200 /* member of section group */
+#define SHF_TLS 0x400 /* thread local storage */
+#define SHF_COMPRESSED 0x800 /* contains compressed data */
+#define SHF_MASKOS 0x0ff00000 /* OS-specific semantics */
+#define SHF_MASKPROC 0xf0000000 /* reserved bits for processor */
+ /* specific section attributes */
+
+/* Symbol Table Entry */
+typedef struct elf32_sym {
+ Elf32_Word st_name; /* name - index into string table */
+ Elf32_Addr st_value; /* symbol value */
+ Elf32_Word st_size; /* symbol size */
+ unsigned char st_info; /* type and binding */
+ unsigned char st_other; /* 0 - no defined meaning */
+ Elf32_Half st_shndx; /* section header index */
+} Elf32_Sym;
+
+typedef struct {
+ Elf64_Half st_name; /* Symbol name index in str table */
+ Elf_Byte st_info; /* type / binding attrs */
+ Elf_Byte st_other; /* unused */
+ Elf64_Quarter st_shndx; /* section index of symbol */
+ Elf64_Xword st_value; /* value of symbol */
+ Elf64_Xword st_size; /* size of symbol */
+} Elf64_Sym;
+
+/* Symbol table index */
+#define STN_UNDEF 0 /* undefined */
+
+/* Extract symbol info - st_info */
+#define ELF32_ST_BIND(x) ((x) >> 4)
+#define ELF32_ST_TYPE(x) (((unsigned int)x) & 0xf)
+#define ELF32_ST_INFO(b, t) (((b) << 4) + ((t)&0xf))
+
+#define ELF64_ST_BIND(x) ((x) >> 4)
+#define ELF64_ST_TYPE(x) (((unsigned int)x) & 0xf)
+#define ELF64_ST_INFO(b, t) (((b) << 4) + ((t)&0xf))
+
+/* Symbol Binding - ELF32_ST_BIND - st_info */
+#define STB_LOCAL 0 /* Local symbol */
+#define STB_GLOBAL 1 /* Global symbol */
+#define STB_WEAK 2 /* like global - lower precedence */
+#define STB_NUM 3 /* number of symbol bindings */
+#define STB_LOPROC 13 /* reserved range for processor */
+#define STB_HIPROC 15 /* specific symbol bindings */
+
+/* Symbol type - ELF32_ST_TYPE - st_info */
+#define STT_NOTYPE 0 /* not specified */
+#define STT_OBJECT 1 /* data object */
+#define STT_FUNC 2 /* function */
+#define STT_SECTION 3 /* section */
+#define STT_FILE 4 /* file */
+#define STT_TLS 6 /* thread local storage */
+#define STT_LOPROC 13 /* reserved range for processor */
+#define STT_HIPROC 15 /* specific symbol types */
+
+/* Extract symbol visibility - st_other */
+#define ELF_ST_VISIBILITY(v) ((v)&0x3)
+#define ELF32_ST_VISIBILITY ELF_ST_VISIBILITY
+#define ELF64_ST_VISIBILITY ELF_ST_VISIBILITY
+
+#define STV_DEFAULT 0 /* Visibility set by binding type */
+#define STV_INTERNAL 1 /* OS specific version of STV_HIDDEN */
+#define STV_HIDDEN 2 /* can only be seen inside own .so */
+#define STV_PROTECTED 3 /* HIDDEN inside, DEFAULT outside */
+
+/* Relocation entry with implicit addend */
+typedef struct {
+ Elf32_Addr r_offset; /* offset of relocation */
+ Elf32_Word r_info; /* symbol table index and type */
+} Elf32_Rel;
+
+/* Relocation entry with explicit addend */
+typedef struct {
+ Elf32_Addr r_offset; /* offset of relocation */
+ Elf32_Word r_info; /* symbol table index and type */
+ Elf32_Sword r_addend;
+} Elf32_Rela;
+
+/* Extract relocation info - r_info */
+#define ELF32_R_SYM(i) ((i) >> 8)
+#define ELF32_R_TYPE(i) ((unsigned char)(i))
+#define ELF32_R_INFO(s, t) (((s) << 8) + (unsigned char)(t))
+
+typedef struct {
+ Elf64_Xword r_offset; /* where to do it */
+ Elf64_Xword r_info; /* index & type of relocation */
+} Elf64_Rel;
+
+typedef struct {
+ Elf64_Xword r_offset; /* where to do it */
+ Elf64_Xword r_info; /* index & type of relocation */
+ Elf64_Sxword r_addend; /* adjustment value */
+} Elf64_Rela;
+
+#define ELF64_R_SYM(info) ((info) >> 32)
+#define ELF64_R_TYPE(info) ((info)&0xFFFFFFFF)
+#define ELF64_R_INFO(s, t) (((s) << 32) + (uint32_t)(t))
+
+#if defined(__mips64__) && defined(__MIPSEL__)
+/*
+ * The 64-bit MIPS ELF ABI uses a slightly different relocation format
+ * than the regular ELF ABI: the r_info field is split into several
+ * pieces (see gnu/usr.bin/binutils-2.17/include/elf/mips.h for details).
+ */
+# undef ELF64_R_SYM
+# undef ELF64_R_TYPE
+# undef ELF64_R_INFO
+# define ELF64_R_TYPE(info) ((uint64_t)swap32((info) >> 32))
+# define ELF64_R_SYM(info) ((info)&0xFFFFFFFF)
+# define ELF64_R_INFO(s, t) (((uint64_t)swap32(t) << 32) + (uint32_t)(s))
+#endif /* __mips64__ && __MIPSEL__ */
+
+/* Program Header */
+typedef struct {
+ Elf32_Word p_type; /* segment type */
+ Elf32_Off p_offset; /* segment offset */
+ Elf32_Addr p_vaddr; /* virtual address of segment */
+ Elf32_Addr p_paddr; /* physical address - ignored? */
+ Elf32_Word p_filesz; /* number of bytes in file for seg. */
+ Elf32_Word p_memsz; /* number of bytes in mem. for seg. */
+ Elf32_Word p_flags; /* flags */
+ Elf32_Word p_align; /* memory alignment */
+} Elf32_Phdr;
+
+typedef struct {
+ Elf64_Half p_type; /* entry type */
+ Elf64_Half p_flags; /* flags */
+ Elf64_Off p_offset; /* offset */
+ Elf64_Addr p_vaddr; /* virtual address */
+ Elf64_Addr p_paddr; /* physical address */
+ Elf64_Xword p_filesz; /* file size */
+ Elf64_Xword p_memsz; /* memory size */
+ Elf64_Xword p_align; /* memory & file alignment */
+} Elf64_Phdr;
+
+/* Segment types - p_type */
+#define PT_NULL 0 /* unused */
+#define PT_LOAD 1 /* loadable segment */
+#define PT_DYNAMIC 2 /* dynamic linking section */
+#define PT_INTERP 3 /* the RTLD */
+#define PT_NOTE 4 /* auxiliary information */
+#define PT_SHLIB 5 /* reserved - purpose undefined */
+#define PT_PHDR 6 /* program header */
+#define PT_TLS 7 /* thread local storage */
+#define PT_LOOS 0x60000000 /* reserved range for OS */
+#define PT_HIOS 0x6fffffff /* specific segment types */
+#define PT_LOPROC 0x70000000 /* reserved range for processor */
+#define PT_HIPROC 0x7fffffff /* specific segment types */
+
+#define PT_GNU_EH_FRAME 0x6474e550 /* Exception handling info */
+#define PT_GNU_RELRO 0x6474e552 /* Read-only after relocation */
+#define PT_GNU_STACK 0x6474e551 /* Stack permissions info */
+
+#define PT_OPENBSD_RANDOMIZE 0x65a3dbe6 /* fill with random data */
+#define PT_OPENBSD_WXNEEDED 0x65a3dbe7 /* program performs W^X violations */
+#define PT_OPENBSD_BOOTDATA 0x65a41be6 /* section for boot arguments */
+
+/* Segment flags - p_flags */
+#define PF_X 0x1 /* Executable */
+#define PF_W 0x2 /* Writable */
+#define PF_R 0x4 /* Readable */
+#define PF_MASKPROC 0xf0000000 /* reserved bits for processor */
+ /* specific segment flags */
+
+/* Dynamic structure */
+typedef struct {
+ Elf32_Sword d_tag; /* controls meaning of d_val */
+ union {
+ Elf32_Word d_val; /* Multiple meanings - see d_tag */
+ Elf32_Addr d_ptr; /* program virtual address */
+ } d_un;
+} Elf32_Dyn;
+
+typedef struct {
+ Elf64_Xword d_tag; /* controls meaning of d_val */
+ union {
+ Elf64_Addr d_ptr;
+ Elf64_Xword d_val;
+ } d_un;
+} Elf64_Dyn;
+
+/* Dynamic Array Tags - d_tag */
+#define DT_NULL 0 /* marks end of _DYNAMIC array */
+#define DT_NEEDED 1 /* string table offset of needed lib */
+#define DT_PLTRELSZ 2 /* size of relocation entries in PLT */
+#define DT_PLTGOT 3 /* address PLT/GOT */
+#define DT_HASH 4 /* address of symbol hash table */
+#define DT_STRTAB 5 /* address of string table */
+#define DT_SYMTAB 6 /* address of symbol table */
+#define DT_RELA 7 /* address of relocation table */
+#define DT_RELASZ 8 /* size of relocation table */
+#define DT_RELAENT 9 /* size of relocation entry */
+#define DT_STRSZ 10 /* size of string table */
+#define DT_SYMENT 11 /* size of symbol table entry */
+#define DT_INIT 12 /* address of initialization func. */
+#define DT_FINI 13 /* address of termination function */
+#define DT_SONAME 14 /* string table offset of shared obj */
+#define DT_RPATH 15 /* string table offset of library \
+ search path */
+#define DT_SYMBOLIC 16 /* start sym search in shared obj. */
+#define DT_REL 17 /* address of rel. tbl. w addends */
+#define DT_RELSZ 18 /* size of DT_REL relocation table */
+#define DT_RELENT 19 /* size of DT_REL relocation entry */
+#define DT_PLTREL 20 /* PLT referenced relocation entry */
+#define DT_DEBUG 21 /* bugger */
+#define DT_TEXTREL 22 /* Allow rel. mod. to unwritable seg */
+#define DT_JMPREL 23 /* add. of PLT's relocation entries */
+#define DT_BIND_NOW 24 /* Bind now regardless of env setting */
+#define DT_INIT_ARRAY 25 /* address of array of init func */
+#define DT_FINI_ARRAY 26 /* address of array of term func */
+#define DT_INIT_ARRAYSZ 27 /* size of array of init func */
+#define DT_FINI_ARRAYSZ 28 /* size of array of term func */
+#define DT_RUNPATH 29 /* strtab offset of lib search path */
+#define DT_FLAGS 30 /* Set of DF_* flags */
+#define DT_ENCODING 31 /* further DT_* follow encoding rules */
+#define DT_PREINIT_ARRAY 32 /* address of array of preinit func */
+#define DT_PREINIT_ARRAYSZ 33 /* size of array of preinit func */
+#define DT_LOOS 0x6000000d /* reserved range for OS */
+#define DT_HIOS 0x6ffff000 /* specific dynamic array tags */
+#define DT_LOPROC 0x70000000 /* reserved range for processor */
+#define DT_HIPROC 0x7fffffff /* specific dynamic array tags */
+
+/* some other useful tags */
+#define DT_GNU_HASH 0x6ffffef5 /* address of GNU hash table */
+#define DT_RELACOUNT 0x6ffffff9 /* if present, number of RELATIVE */
+#define DT_RELCOUNT 0x6ffffffa /* relocs, which must come first */
+#define DT_FLAGS_1 0x6ffffffb
+
+/* Dynamic Flags - DT_FLAGS .dynamic entry */
+#define DF_ORIGIN 0x00000001
+#define DF_SYMBOLIC 0x00000002
+#define DF_TEXTREL 0x00000004
+#define DF_BIND_NOW 0x00000008
+#define DF_STATIC_TLS 0x00000010
+
+/* Dynamic Flags - DT_FLAGS_1 .dynamic entry */
+#define DF_1_NOW 0x00000001
+#define DF_1_GLOBAL 0x00000002
+#define DF_1_GROUP 0x00000004
+#define DF_1_NODELETE 0x00000008
+#define DF_1_LOADFLTR 0x00000010
+#define DF_1_INITFIRST 0x00000020
+#define DF_1_NOOPEN 0x00000040
+#define DF_1_ORIGIN 0x00000080
+#define DF_1_DIRECT 0x00000100
+#define DF_1_TRANS 0x00000200
+#define DF_1_INTERPOSE 0x00000400
+#define DF_1_NODEFLIB 0x00000800
+#define DF_1_NODUMP 0x00001000
+#define DF_1_CONLFAT 0x00002000
+
+/*
+ * Note header
+ */
+typedef struct {
+ Elf32_Word n_namesz;
+ Elf32_Word n_descsz;
+ Elf32_Word n_type;
+} Elf32_Nhdr;
+
+typedef struct {
+ Elf64_Half n_namesz;
+ Elf64_Half n_descsz;
+ Elf64_Half n_type;
+} Elf64_Nhdr;
+
+/*
+ * Note Definitions
+ */
+typedef struct {
+ Elf32_Word namesz;
+ Elf32_Word descsz;
+ Elf32_Word type;
+} Elf32_Note;
+
+typedef struct {
+ Elf64_Half namesz;
+ Elf64_Half descsz;
+ Elf64_Half type;
+} Elf64_Note;
+
+/* Values for n_type. */
+#define NT_PRSTATUS 1 /* Process status. */
+#define NT_FPREGSET 2 /* Floating point registers. */
+#define NT_PRPSINFO 3 /* Process state info. */
+
+/*
+ * OpenBSD-specific core file information.
+ *
+ * OpenBSD ELF core files use notes to provide information about
+ * the process's state. The note name is "OpenBSD" for information
+ * that is global to the process, and "OpenBSD@nn", where "nn" is the
+ * thread ID of the thread that the information belongs to (such as
+ * register state).
+ *
+ * We use the following note identifiers:
+ *
+ * NT_OPENBSD_PROCINFO
+ * Note is a "elfcore_procinfo" structure.
+ * NT_OPENBSD_AUXV
+ * Note is a a bunch of Auxiliary Vectors, terminated by
+ * an AT_NULL entry.
+ * NT_OPENBSD_REGS
+ * Note is a "reg" structure.
+ * NT_OPENBSD_FPREGS
+ * Note is a "fpreg" structure.
+ *
+ * Please try to keep the members of the "elfcore_procinfo" structure
+ * nicely aligned, and if you add elements, add them to the end and
+ * bump the version.
+ */
+
+#define NT_OPENBSD_PROCINFO 10
+#define NT_OPENBSD_AUXV 11
+
+#define NT_OPENBSD_REGS 20
+#define NT_OPENBSD_FPREGS 21
+#define NT_OPENBSD_XFPREGS 22
+#define NT_OPENBSD_WCOOKIE 23
+
+struct elfcore_procinfo {
+ /* Version 1 fields start here. */
+ uint32_t cpi_version; /* netbsd_elfcore_procinfo version */
+#define ELFCORE_PROCINFO_VERSION 1
+ uint32_t cpi_cpisize; /* sizeof(netbsd_elfcore_procinfo) */
+ uint32_t cpi_signo; /* killing signal */
+ uint32_t cpi_sigcode; /* signal code */
+ uint32_t cpi_sigpend; /* pending signals */
+ uint32_t cpi_sigmask; /* blocked signals */
+ uint32_t cpi_sigignore; /* ignored signals */
+ uint32_t cpi_sigcatch; /* signals being caught by user */
+ int32_t cpi_pid; /* process ID */
+ int32_t cpi_ppid; /* parent process ID */
+ int32_t cpi_pgrp; /* process group ID */
+ int32_t cpi_sid; /* session ID */
+ uint32_t cpi_ruid; /* real user ID */
+ uint32_t cpi_euid; /* effective user ID */
+ uint32_t cpi_svuid; /* saved user ID */
+ uint32_t cpi_rgid; /* real group ID */
+ uint32_t cpi_egid; /* effective group ID */
+ uint32_t cpi_svgid; /* saved group ID */
+ int8_t cpi_name[32]; /* copy of pr->ps_comm */
+};
+
+/*
+ * XXX - these _KERNEL items aren't part of the ABI!
+ */
+#if defined(_KERNEL) || defined(_DYN_LOADER)
+
+# define ELF32_NO_ADDR ((uint32_t)~0) /* Indicates addr. not yet filled in */
+
+typedef struct {
+ Elf32_Sword au_id; /* 32-bit id */
+ Elf32_Word au_v; /* 32-bit value */
+} Aux32Info;
+
+# define ELF64_NO_ADDR ((uint64_t)~0) /* Indicates addr. not yet filled in */
+
+typedef struct {
+ Elf64_Shalf au_id; /* 32-bit id */
+ Elf64_Xword au_v; /* 64-bit value */
+} Aux64Info;
+
+enum AuxID {
+ AUX_null = 0,
+ AUX_ignore = 1,
+ AUX_execfd = 2,
+ AUX_phdr = 3, /* &phdr[0] */
+ AUX_phent = 4, /* sizeof(phdr[0]) */
+ AUX_phnum = 5, /* # phdr entries */
+ AUX_pagesz = 6, /* PAGESIZE */
+ AUX_base = 7, /* ld.so base addr */
+ AUX_flags = 8, /* processor flags */
+ AUX_entry = 9, /* a.out entry */
+ AUX_sun_uid = 2000, /* euid */
+ AUX_sun_ruid = 2001, /* ruid */
+ AUX_sun_gid = 2002, /* egid */
+ AUX_sun_rgid = 2003 /* rgid */
+};
+
+struct elf_args {
+ u_long arg_entry; /* program entry point */
+ u_long arg_interp; /* Interpreter load address */
+ u_long arg_phaddr; /* program header address */
+ u_long arg_phentsize; /* Gfx::Size of program header */
+ u_long arg_phnum; /* Number of program headers */
+};
+
+#endif
+
+#if !defined(ELFSIZE) && defined(ARCH_ELFSIZE)
+# define ELFSIZE ARCH_ELFSIZE
+#endif
+
+#if defined(ELFSIZE)
+# define CONCAT(x, y) __CONCAT(x, y)
+# define ELFNAME(x) CONCAT(elf, CONCAT(ELFSIZE, CONCAT(_, x)))
+# define ELFDEFNNAME(x) CONCAT(ELF, CONCAT(ELFSIZE, CONCAT(_, x)))
+#endif
+
+#if defined(ELFSIZE) && (ELFSIZE == 32)
+# define Elf_Ehdr Elf32_Ehdr
+# define Elf_Phdr Elf32_Phdr
+# define Elf_Shdr Elf32_Shdr
+# define Elf_Sym Elf32_Sym
+# define Elf_Rel Elf32_Rel
+# define Elf_RelA Elf32_Rela
+# define Elf_Dyn Elf32_Dyn
+# define Elf_Half Elf32_Half
+# define Elf_Word Elf32_Word
+# define Elf_Sword Elf32_Sword
+# define Elf_Addr Elf32_Addr
+# define Elf_Off Elf32_Off
+# define Elf_Nhdr Elf32_Nhdr
+# define Elf_Note Elf32_Note
+
+# define ELF_R_SYM ELF32_R_SYM
+# define ELF_R_TYPE ELF32_R_TYPE
+# define ELF_R_INFO ELF32_R_INFO
+# define ELFCLASS ELFCLASS32
+
+# define ELF_ST_BIND ELF32_ST_BIND
+# define ELF_ST_TYPE ELF32_ST_TYPE
+# define ELF_ST_INFO ELF32_ST_INFO
+
+# define ELF_NO_ADDR ELF32_NO_ADDR
+# define AuxInfo Aux32Info
+#elif defined(ELFSIZE) && (ELFSIZE == 64)
+# define Elf_Ehdr Elf64_Ehdr
+# define Elf_Phdr Elf64_Phdr
+# define Elf_Shdr Elf64_Shdr
+# define Elf_Sym Elf64_Sym
+# define Elf_Rel Elf64_Rel
+# define Elf_RelA Elf64_Rela
+# define Elf_Dyn Elf64_Dyn
+# define Elf_Half Elf64_Half
+# define Elf_Word Elf64_Word
+# define Elf_Sword Elf64_Sword
+# define Elf_Addr Elf64_Addr
+# define Elf_Off Elf64_Off
+# define Elf_Nhdr Elf64_Nhdr
+# define Elf_Note Elf64_Note
+
+# define ELF_R_SYM ELF64_R_SYM
+# define ELF_R_TYPE ELF64_R_TYPE
+# define ELF_R_INFO ELF64_R_INFO
+# define ELFCLASS ELFCLASS64
+
+# define ELF_ST_BIND ELF64_ST_BIND
+# define ELF_ST_TYPE ELF64_ST_TYPE
+# define ELF_ST_INFO ELF64_ST_INFO
+
+# define ELF_NO_ADDR ELF64_NO_ADDR
+# define AuxInfo Aux64Info
+#endif
+
+#define ELF_TARG_VER 1 /* The ver for which this code is intended */
+
+/* Relocation types */
+#define R_386_NONE 0
+#define R_386_32 1 /* Symbol + Addend */
+#define R_386_PC32 2 /* Symbol + Addend - Section offset */
+#define R_386_GOT32 3 /* Used by build-time linker to create GOT entry */
+#define R_386_PLT32 4 /* Used by build-time linker to create PLT entry */
+#define R_386_COPY 5 /* https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter4-10454.html#chapter4-84604 */
+#define R_386_GLOB_DAT 6 /* Relation b/w GOT entry and symbol */
+#define R_386_JMP_SLOT 7 /* Fixed up by dynamic loader */
+#define R_386_RELATIVE 8 /* Base address + Addned */
+#define R_386_TLS_TPOFF 14 /* Negative offset into the static TLS storage */
+#define R_386_TLS_TPOFF32 37
+
+#endif /* _SYS_EXEC_ELF_H_ */
diff --git a/Userland/Libraries/LibGUI/AboutDialog.cpp b/Userland/Libraries/LibGUI/AboutDialog.cpp
new file mode 100644
index 0000000000..7e08782ab2
--- /dev/null
+++ b/Userland/Libraries/LibGUI/AboutDialog.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibCore/ConfigFile.h>
+#include <LibGUI/AboutDialog.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/ImageWidget.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/Widget.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/FontDatabase.h>
+
+namespace GUI {
+
+AboutDialog::AboutDialog(const StringView& name, const Gfx::Bitmap* icon, Window* parent_window)
+ : Dialog(parent_window)
+ , m_name(name)
+ , m_icon(icon)
+{
+ resize(413, 205);
+ set_title(String::formatted("About {}", m_name));
+ set_resizable(false);
+
+ if (parent_window)
+ set_icon(parent_window->icon());
+
+ auto& widget = set_main_widget<Widget>();
+ widget.set_fill_with_background_color(true);
+ widget.set_layout<VerticalBoxLayout>();
+ widget.layout()->set_spacing(0);
+
+ auto& banner_image = widget.add<GUI::ImageWidget>();
+ banner_image.load_from_file("/res/graphics/brand-banner.png");
+
+ auto& content_container = widget.add<Widget>();
+ content_container.set_layout<HorizontalBoxLayout>();
+
+ auto& left_container = content_container.add<Widget>();
+ left_container.set_fixed_width(60);
+ left_container.set_layout<VerticalBoxLayout>();
+ left_container.layout()->set_margins({ 0, 12, 0, 0 });
+
+ if (icon) {
+ auto& icon_wrapper = left_container.add<Widget>();
+ icon_wrapper.set_fixed_size(32, 48);
+ icon_wrapper.set_layout<VerticalBoxLayout>();
+
+ auto& icon_image = icon_wrapper.add<ImageWidget>();
+ icon_image.set_bitmap(m_icon);
+ }
+
+ auto& right_container = content_container.add<Widget>();
+ right_container.set_layout<VerticalBoxLayout>();
+ right_container.layout()->set_margins({ 0, 12, 12, 8 });
+
+ auto make_label = [&](const StringView& text, bool bold = false) {
+ auto& label = right_container.add<Label>(text);
+ label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
+ label.set_fixed_height(14);
+ if (bold)
+ label.set_font(Gfx::FontDatabase::default_bold_font());
+ };
+ make_label(m_name, true);
+ // If we are displaying a dialog for an application, insert 'SerenityOS' below the application name
+ if (m_name != "SerenityOS")
+ make_label("SerenityOS");
+ make_label(version_string());
+ make_label("Copyright \xC2\xA9 the SerenityOS developers, 2018-2021");
+
+ right_container.layout()->add_spacer();
+
+ auto& button_container = right_container.add<Widget>();
+ button_container.set_fixed_height(23);
+ button_container.set_layout<HorizontalBoxLayout>();
+ button_container.layout()->add_spacer();
+ auto& ok_button = button_container.add<Button>("OK");
+ ok_button.set_fixed_size(80, 23);
+ ok_button.on_click = [this](auto) {
+ done(Dialog::ExecOK);
+ };
+}
+
+AboutDialog::~AboutDialog()
+{
+}
+
+String AboutDialog::version_string() const
+{
+ auto version_config = Core::ConfigFile::open("/res/version.ini");
+ auto major_version = version_config->read_entry("Version", "Major", "0");
+ auto minor_version = version_config->read_entry("Version", "Minor", "0");
+ auto git_version = version_config->read_entry("Version", "Git", "");
+
+ StringBuilder builder;
+ builder.append("Version ");
+ builder.append(major_version);
+ builder.append('.');
+ builder.append(minor_version);
+
+ if (git_version != "") {
+ builder.append(".g");
+ builder.append(git_version);
+ }
+
+ return builder.to_string();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/AboutDialog.h b/Userland/Libraries/LibGUI/AboutDialog.h
new file mode 100644
index 0000000000..a3528324f2
--- /dev/null
+++ b/Userland/Libraries/LibGUI/AboutDialog.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Dialog.h>
+
+namespace GUI {
+
+class AboutDialog final : public Dialog {
+ C_OBJECT(AboutDialog)
+public:
+ virtual ~AboutDialog() override;
+
+ static void show(const StringView& name, const Gfx::Bitmap* icon = nullptr, Window* parent_window = nullptr, const Gfx::Bitmap* window_icon = nullptr)
+ {
+ auto dialog = AboutDialog::construct(name, icon, parent_window);
+ if (window_icon)
+ dialog->set_icon(window_icon);
+ dialog->exec();
+ }
+
+private:
+ AboutDialog(const StringView& name, const Gfx::Bitmap* icon = nullptr, Window* parent_window = nullptr);
+ String version_string() const;
+
+ String m_name;
+ RefPtr<Gfx::Bitmap> m_icon;
+};
+}
diff --git a/Userland/Libraries/LibGUI/AbstractButton.cpp b/Userland/Libraries/LibGUI/AbstractButton.cpp
new file mode 100644
index 0000000000..f2846cb78b
--- /dev/null
+++ b/Userland/Libraries/LibGUI/AbstractButton.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/JsonObject.h>
+#include <LibCore/Timer.h>
+#include <LibGUI/AbstractButton.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+AbstractButton::AbstractButton(String text)
+{
+ set_text(move(text));
+
+ set_focus_policy(GUI::FocusPolicy::StrongFocus);
+ set_background_role(Gfx::ColorRole::Button);
+ set_foreground_role(Gfx::ColorRole::ButtonText);
+
+ m_auto_repeat_timer = add<Core::Timer>();
+ m_auto_repeat_timer->on_timeout = [this] {
+ click();
+ };
+
+ REGISTER_STRING_PROPERTY("text", text, set_text);
+ REGISTER_BOOL_PROPERTY("checked", is_checked, set_checked);
+ REGISTER_BOOL_PROPERTY("checkable", is_checkable, set_checkable);
+ REGISTER_BOOL_PROPERTY("exclusive", is_exclusive, set_exclusive);
+}
+
+AbstractButton::~AbstractButton()
+{
+}
+
+void AbstractButton::set_text(String text)
+{
+ if (m_text == text)
+ return;
+ m_text = move(text);
+ update();
+}
+
+void AbstractButton::set_checked(bool checked)
+{
+ if (m_checked == checked)
+ return;
+ m_checked = checked;
+
+ if (is_exclusive() && checked && parent_widget()) {
+ bool sibling_had_focus = false;
+ parent_widget()->for_each_child_of_type<AbstractButton>([&](auto& sibling) {
+ if (!sibling.is_exclusive())
+ return IterationDecision::Continue;
+ if (window() && window()->focused_widget() == &sibling)
+ sibling_had_focus = true;
+ if (!sibling.is_checked())
+ return IterationDecision::Continue;
+ sibling.m_checked = false;
+ sibling.update();
+ if (sibling.on_checked)
+ sibling.on_checked(false);
+ return IterationDecision::Continue;
+ });
+ m_checked = true;
+ if (sibling_had_focus)
+ set_focus(true);
+ }
+
+ update();
+ if (on_checked)
+ on_checked(checked);
+}
+
+void AbstractButton::set_checkable(bool checkable)
+{
+ if (m_checkable == checkable)
+ return;
+ m_checkable = checkable;
+ update();
+}
+
+void AbstractButton::mousemove_event(MouseEvent& event)
+{
+ bool is_over = rect().contains(event.position());
+ m_hovered = is_over;
+ if (event.buttons() & MouseButton::Left) {
+ bool being_pressed = is_over;
+ if (being_pressed != m_being_pressed) {
+ m_being_pressed = being_pressed;
+ if (m_auto_repeat_interval) {
+ if (!m_being_pressed)
+ m_auto_repeat_timer->stop();
+ else
+ m_auto_repeat_timer->start(m_auto_repeat_interval);
+ }
+ update();
+ }
+ }
+ Widget::mousemove_event(event);
+}
+
+void AbstractButton::mousedown_event(MouseEvent& event)
+{
+ if (event.button() == MouseButton::Left) {
+ m_being_pressed = true;
+ update();
+
+ if (m_auto_repeat_interval) {
+ click();
+ m_auto_repeat_timer->start(m_auto_repeat_interval);
+ }
+ }
+ Widget::mousedown_event(event);
+}
+
+void AbstractButton::mouseup_event(MouseEvent& event)
+{
+ if (event.button() == MouseButton::Left) {
+ bool was_auto_repeating = m_auto_repeat_timer->is_active();
+ m_auto_repeat_timer->stop();
+ bool was_being_pressed = m_being_pressed;
+ m_being_pressed = false;
+ update();
+ if (was_being_pressed && !was_auto_repeating)
+ click(event.modifiers());
+ }
+ Widget::mouseup_event(event);
+}
+
+void AbstractButton::enter_event(Core::Event&)
+{
+ m_hovered = true;
+ update();
+}
+
+void AbstractButton::leave_event(Core::Event&)
+{
+ m_hovered = false;
+ update();
+}
+
+void AbstractButton::keydown_event(KeyEvent& event)
+{
+ if (event.key() == KeyCode::Key_Return || event.key() == KeyCode::Key_Space) {
+ click(event.modifiers());
+ event.accept();
+ return;
+ }
+ Widget::keydown_event(event);
+}
+
+void AbstractButton::paint_text(Painter& painter, const Gfx::IntRect& rect, const Gfx::Font& font, Gfx::TextAlignment text_alignment)
+{
+ auto clipped_rect = rect.intersected(this->rect());
+
+ if (!is_enabled()) {
+ painter.draw_text(clipped_rect.translated(1, 1), text(), font, text_alignment, Color::White, Gfx::TextElision::Right);
+ painter.draw_text(clipped_rect, text(), font, text_alignment, Color::from_rgb(0x808080), Gfx::TextElision::Right);
+ return;
+ }
+
+ if (text().is_empty())
+ return;
+ painter.draw_text(clipped_rect, text(), font, text_alignment, palette().color(foreground_role()), Gfx::TextElision::Right);
+}
+
+void AbstractButton::change_event(Event& event)
+{
+ if (event.type() == Event::Type::EnabledChange) {
+ if (!is_enabled()) {
+ bool was_being_pressed = m_being_pressed;
+ m_being_pressed = false;
+ if (was_being_pressed)
+ update();
+ }
+ }
+ Widget::change_event(event);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/AbstractButton.h b/Userland/Libraries/LibGUI/AbstractButton.h
new file mode 100644
index 0000000000..cf869aab94
--- /dev/null
+++ b/Userland/Libraries/LibGUI/AbstractButton.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class AbstractButton : public Widget {
+ C_OBJECT_ABSTRACT(AbstractButton);
+
+public:
+ virtual ~AbstractButton() override;
+
+ Function<void(bool)> on_checked;
+
+ void set_text(String);
+ const String& text() const { return m_text; }
+
+ bool is_exclusive() const { return m_exclusive; }
+ void set_exclusive(bool b) { m_exclusive = b; }
+
+ bool is_checked() const { return m_checked; }
+ void set_checked(bool);
+
+ bool is_checkable() const { return m_checkable; }
+ void set_checkable(bool);
+
+ bool is_hovered() const { return m_hovered; }
+ bool is_being_pressed() const { return m_being_pressed; }
+
+ virtual void click(unsigned modifiers = 0) = 0;
+ virtual bool is_uncheckable() const { return true; }
+
+ int auto_repeat_interval() const { return m_auto_repeat_interval; }
+ void set_auto_repeat_interval(int interval) { m_auto_repeat_interval = interval; }
+
+protected:
+ explicit AbstractButton(String = {});
+
+ virtual void mousedown_event(MouseEvent&) override;
+ virtual void mousemove_event(MouseEvent&) override;
+ virtual void mouseup_event(MouseEvent&) override;
+ virtual void keydown_event(KeyEvent&) override;
+ virtual void enter_event(Core::Event&) override;
+ virtual void leave_event(Core::Event&) override;
+ virtual void change_event(Event&) override;
+
+ void paint_text(Painter&, const Gfx::IntRect&, const Gfx::Font&, Gfx::TextAlignment);
+
+private:
+ String m_text;
+ bool m_checked { false };
+ bool m_checkable { false };
+ bool m_hovered { false };
+ bool m_being_pressed { false };
+ bool m_exclusive { false };
+
+ int m_auto_repeat_interval { 0 };
+ RefPtr<Core::Timer> m_auto_repeat_timer;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/AbstractSlider.cpp b/Userland/Libraries/LibGUI/AbstractSlider.cpp
new file mode 100644
index 0000000000..99400b44d2
--- /dev/null
+++ b/Userland/Libraries/LibGUI/AbstractSlider.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/StdLibExtras.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/Slider.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+namespace GUI {
+
+AbstractSlider::AbstractSlider(Orientation orientation)
+ : m_orientation(orientation)
+{
+ REGISTER_INT_PROPERTY("value", value, set_value);
+ REGISTER_INT_PROPERTY("min", min, set_min);
+ REGISTER_INT_PROPERTY("max", max, set_max);
+ REGISTER_INT_PROPERTY("step", step, set_step);
+ REGISTER_INT_PROPERTY("page_step", page_step, set_page_step);
+ REGISTER_ENUM_PROPERTY("orientation", this->orientation, set_orientation, Orientation,
+ { Orientation::Horizontal, "Horizontal" },
+ { Orientation::Vertical, "Vertical" });
+}
+
+AbstractSlider::~AbstractSlider()
+{
+}
+
+void AbstractSlider::set_orientation(Orientation value)
+{
+ if (m_orientation == value)
+ return;
+ m_orientation = value;
+ update();
+}
+
+void AbstractSlider::set_page_step(int page_step)
+{
+ m_page_step = AK::max(0, page_step);
+}
+
+void AbstractSlider::set_range(int min, int max)
+{
+ ASSERT(min <= max);
+ if (m_min == min && m_max == max)
+ return;
+ m_min = min;
+ m_max = max;
+ m_value = clamp(m_value, m_min, m_max);
+ update();
+}
+
+void AbstractSlider::set_value(int value)
+{
+ value = clamp(value, m_min, m_max);
+ if (m_value == value)
+ return;
+ m_value = value;
+ update();
+
+ if (on_change)
+ on_change(m_value);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/AbstractSlider.h b/Userland/Libraries/LibGUI/AbstractSlider.h
new file mode 100644
index 0000000000..56dcfadaa3
--- /dev/null
+++ b/Userland/Libraries/LibGUI/AbstractSlider.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class AbstractSlider : public Widget {
+ C_OBJECT_ABSTRACT(AbstractSlider);
+
+public:
+ virtual ~AbstractSlider() override;
+
+ void set_orientation(Orientation value);
+ Orientation orientation() const { return m_orientation; }
+
+ int value() const { return m_value; }
+ int min() const { return m_min; }
+ int max() const { return m_max; }
+ int step() const { return m_step; }
+ int page_step() const { return m_page_step; }
+
+ void set_range(int min, int max);
+ void set_value(int);
+
+ void set_min(int min) { set_range(min, max()); }
+ void set_max(int max) { set_range(min(), max); }
+ void set_step(int step) { m_step = step; }
+ void set_page_step(int page_step);
+
+ Function<void(int)> on_change;
+
+protected:
+ explicit AbstractSlider(Orientation = Orientation::Vertical);
+
+private:
+ void set_knob_hovered(bool);
+
+ int m_value { 0 };
+ int m_min { 0 };
+ int m_max { 0 };
+ int m_step { 1 };
+ int m_page_step { 10 };
+ Orientation m_orientation { Orientation::Horizontal };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/AbstractTableView.cpp b/Userland/Libraries/LibGUI/AbstractTableView.cpp
new file mode 100644
index 0000000000..129189db36
--- /dev/null
+++ b/Userland/Libraries/LibGUI/AbstractTableView.cpp
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <AK/Vector.h>
+#include <LibGUI/AbstractTableView.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/HeaderView.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+AbstractTableView::AbstractTableView()
+{
+ set_selection_behavior(SelectionBehavior::SelectRows);
+ m_corner_button = add<Button>();
+ m_corner_button->move_to_back();
+ m_corner_button->set_background_role(Gfx::ColorRole::ThreedShadow1);
+ m_corner_button->set_fill_with_background_color(true);
+ m_column_header = add<HeaderView>(*this, Gfx::Orientation::Horizontal);
+ m_column_header->move_to_back();
+ m_row_header = add<HeaderView>(*this, Gfx::Orientation::Vertical);
+ m_row_header->move_to_back();
+ m_row_header->set_visible(false);
+ set_should_hide_unnecessary_scrollbars(true);
+}
+
+AbstractTableView::~AbstractTableView()
+{
+}
+
+void AbstractTableView::select_all()
+{
+ selection().clear();
+ for (int item_index = 0; item_index < item_count(); ++item_index) {
+ auto index = model()->index(item_index);
+ selection().add(index);
+ }
+}
+
+void AbstractTableView::update_column_sizes()
+{
+ if (!model())
+ return;
+
+ auto& model = *this->model();
+ int column_count = model.column_count();
+ int row_count = model.row_count();
+
+ for (int column = 0; column < column_count; ++column) {
+ if (!column_header().is_section_visible(column))
+ continue;
+ int header_width = m_column_header->font().width(model.column_name(column));
+ if (column == m_key_column && model.is_column_sortable(column))
+ header_width += font().width(" \xE2\xAC\x86"); // UPWARDS BLACK ARROW
+ int column_width = header_width;
+ for (int row = 0; row < row_count; ++row) {
+ auto cell_data = model.index(row, column).data();
+ int cell_width = 0;
+ if (cell_data.is_icon()) {
+ cell_width = row_height();
+ } else if (cell_data.is_bitmap()) {
+ cell_width = cell_data.as_bitmap().width();
+ } else if (cell_data.is_valid()) {
+ cell_width = font().width(cell_data.to_string());
+ }
+ column_width = max(column_width, cell_width);
+ }
+ column_header().set_section_size(column, max(m_column_header->section_size(column), column_width));
+ }
+}
+
+void AbstractTableView::update_row_sizes()
+{
+ if (!model())
+ return;
+
+ auto& model = *this->model();
+ int row_count = model.row_count();
+
+ for (int row = 0; row < row_count; ++row) {
+ if (!column_header().is_section_visible(row))
+ continue;
+ row_header().set_section_size(row, row_height());
+ }
+}
+
+void AbstractTableView::update_content_size()
+{
+ if (!model())
+ return set_content_size({});
+
+ int content_width = 0;
+ int column_count = model()->column_count();
+
+ for (int i = 0; i < column_count; ++i) {
+ if (column_header().is_section_visible(i))
+ content_width += column_width(i) + horizontal_padding() * 2;
+ }
+ int content_height = item_count() * row_height();
+
+ set_content_size({ content_width, content_height });
+ set_size_occupied_by_fixed_elements({ row_header().width(), column_header().height() });
+ layout_headers();
+}
+
+TableCellPaintingDelegate* AbstractTableView::column_painting_delegate(int column) const
+{
+ // FIXME: This should return a const pointer I think..
+ return const_cast<TableCellPaintingDelegate*>(m_column_painting_delegate.get(column).value_or(nullptr));
+}
+
+void AbstractTableView::set_column_painting_delegate(int column, OwnPtr<TableCellPaintingDelegate> delegate)
+{
+ if (!delegate)
+ m_column_painting_delegate.remove(column);
+ else
+ m_column_painting_delegate.set(column, move(delegate));
+}
+
+int AbstractTableView::column_width(int column_index) const
+{
+ if (!model())
+ return 0;
+ return m_column_header->section_size(column_index);
+}
+
+void AbstractTableView::set_column_width(int column, int width)
+{
+ column_header().set_section_size(column, width);
+}
+
+Gfx::TextAlignment AbstractTableView::column_header_alignment(int column_index) const
+{
+ if (!model())
+ return Gfx::TextAlignment::CenterLeft;
+ return m_column_header->section_alignment(column_index);
+}
+
+void AbstractTableView::set_column_header_alignment(int column, Gfx::TextAlignment alignment)
+{
+ column_header().set_section_alignment(column, alignment);
+}
+
+void AbstractTableView::mousedown_event(MouseEvent& event)
+{
+ if (!model())
+ return AbstractView::mousedown_event(event);
+
+ if (event.button() != MouseButton::Left)
+ return AbstractView::mousedown_event(event);
+
+ bool is_toggle;
+ auto index = index_at_event_position(event.position(), is_toggle);
+
+ if (index.is_valid() && is_toggle && model()->row_count(index)) {
+ toggle_index(index);
+ return;
+ }
+
+ AbstractView::mousedown_event(event);
+}
+
+ModelIndex AbstractTableView::index_at_event_position(const Gfx::IntPoint& position, bool& is_toggle) const
+{
+ is_toggle = false;
+ if (!model())
+ return {};
+
+ auto adjusted_position = this->adjusted_position(position);
+ for (int row = 0, row_count = model()->row_count(); row < row_count; ++row) {
+ if (!row_rect(row).contains(adjusted_position))
+ continue;
+ for (int column = 0, column_count = model()->column_count(); column < column_count; ++column) {
+ if (!content_rect(row, column).contains(adjusted_position))
+ continue;
+ return model()->index(row, column);
+ }
+ return model()->index(row, 0);
+ }
+ return {};
+}
+
+ModelIndex AbstractTableView::index_at_event_position(const Gfx::IntPoint& position) const
+{
+ bool is_toggle;
+ auto index = index_at_event_position(position, is_toggle);
+ return is_toggle ? ModelIndex() : index;
+}
+
+int AbstractTableView::item_count() const
+{
+ if (!model())
+ return 0;
+ return model()->row_count();
+}
+
+void AbstractTableView::move_cursor_relative(int vertical_steps, int horizontal_steps, SelectionUpdate selection_update)
+{
+ if (!model())
+ return;
+ auto& model = *this->model();
+ ModelIndex new_index;
+ if (cursor_index().is_valid()) {
+ new_index = model.index(cursor_index().row() + vertical_steps, cursor_index().column() + horizontal_steps);
+ } else {
+ new_index = model.index(0, 0);
+ }
+ set_cursor(new_index, selection_update);
+}
+
+void AbstractTableView::scroll_into_view(const ModelIndex& index, bool scroll_horizontally, bool scroll_vertically)
+{
+ Gfx::IntRect rect;
+ switch (selection_behavior()) {
+ case SelectionBehavior::SelectItems:
+ rect = content_rect(index);
+ break;
+ case SelectionBehavior::SelectRows:
+ rect = row_rect(index.row());
+ break;
+ }
+ ScrollableWidget::scroll_into_view(rect, scroll_horizontally, scroll_vertically);
+}
+
+void AbstractTableView::context_menu_event(ContextMenuEvent& event)
+{
+ if (!model())
+ return;
+
+ bool is_toggle;
+ auto index = index_at_event_position(event.position(), is_toggle);
+ if (index.is_valid()) {
+ if (!selection().contains(index))
+ selection().set(index);
+ } else {
+ selection().clear();
+ }
+ if (on_context_menu_request)
+ on_context_menu_request(index, event);
+}
+
+Gfx::IntRect AbstractTableView::content_rect(int row, int column) const
+{
+ auto row_rect = this->row_rect(row);
+ int x = 0;
+ for (int i = 0; i < column; ++i)
+ x += column_width(i) + horizontal_padding() * 2;
+
+ return { row_rect.x() + x, row_rect.y(), column_width(column) + horizontal_padding() * 2, row_height() };
+}
+
+Gfx::IntRect AbstractTableView::content_rect(const ModelIndex& index) const
+{
+ return content_rect(index.row(), index.column());
+}
+
+Gfx::IntRect AbstractTableView::row_rect(int item_index) const
+{
+ return { row_header().is_visible() ? row_header().width() : 0,
+ (column_header().is_visible() ? column_header().height() : 0) + (item_index * row_height()),
+ max(content_size().width(), width()),
+ row_height() };
+}
+
+Gfx::IntPoint AbstractTableView::adjusted_position(const Gfx::IntPoint& position) const
+{
+ return position.translated(horizontal_scrollbar().value() - frame_thickness(), vertical_scrollbar().value() - frame_thickness());
+}
+
+void AbstractTableView::model_did_update(unsigned flags)
+{
+ AbstractView::model_did_update(flags);
+ update_row_sizes();
+ update_column_sizes();
+ update_content_size();
+ update();
+}
+
+void AbstractTableView::resize_event(ResizeEvent& event)
+{
+ AbstractView::resize_event(event);
+ layout_headers();
+}
+
+void AbstractTableView::header_did_change_section_size(Badge<HeaderView>, Gfx::Orientation, int, int)
+{
+ update_content_size();
+ update();
+}
+
+void AbstractTableView::header_did_change_section_visibility(Badge<HeaderView>, Gfx::Orientation, int, bool)
+{
+ update_content_size();
+ update();
+}
+
+void AbstractTableView::set_column_hidden(int column, bool hidden)
+{
+ column_header().set_section_visible(column, !hidden);
+}
+
+void AbstractTableView::set_column_headers_visible(bool visible)
+{
+ column_header().set_visible(visible);
+}
+
+void AbstractTableView::did_scroll()
+{
+ AbstractView::did_scroll();
+ layout_headers();
+}
+
+void AbstractTableView::layout_headers()
+{
+ if (column_header().is_visible()) {
+ int row_header_width = row_header().is_visible() ? row_header().width() : 0;
+ int vertical_scrollbar_width = vertical_scrollbar().is_visible() ? vertical_scrollbar().width() : 0;
+
+ int x = frame_thickness() + row_header_width - horizontal_scrollbar().value();
+ int y = frame_thickness();
+ int width = AK::max(content_width(), rect().width() - frame_thickness() * 2 - row_header_width - vertical_scrollbar_width);
+
+ column_header().set_relative_rect(x, y, width, column_header().min_size().height());
+ }
+
+ if (row_header().is_visible()) {
+ int column_header_height = column_header().is_visible() ? column_header().height() : 0;
+ int horizontal_scrollbar_height = horizontal_scrollbar().is_visible() ? horizontal_scrollbar().height() : 0;
+
+ int x = frame_thickness();
+ int y = frame_thickness() + column_header_height - vertical_scrollbar().value();
+ int height = AK::max(content_height(), rect().height() - frame_thickness() * 2 - column_header_height - horizontal_scrollbar_height);
+
+ row_header().set_relative_rect(x, y, row_header().min_size().width(), height);
+ }
+
+ if (row_header().is_visible() && column_header().is_visible()) {
+ m_corner_button->set_relative_rect(frame_thickness(), frame_thickness(), row_header().width(), column_header().height());
+ m_corner_button->set_visible(true);
+ } else {
+ m_corner_button->set_visible(false);
+ }
+}
+
+void AbstractTableView::keydown_event(KeyEvent& event)
+{
+ if (is_tab_key_navigation_enabled()) {
+ if (event.modifiers() == KeyModifier::Mod_Shift && event.key() == KeyCode::Key_Tab) {
+ move_cursor(CursorMovement::Left, SelectionUpdate::Set);
+ event.accept();
+ return;
+ }
+ if (!event.modifiers() && event.key() == KeyCode::Key_Tab) {
+ move_cursor(CursorMovement::Right, SelectionUpdate::Set);
+ event.accept();
+ return;
+ }
+ }
+
+ AbstractView::keydown_event(event);
+}
+
+int AbstractTableView::horizontal_padding() const
+{
+ return font().glyph_height() / 2;
+}
+
+int AbstractTableView::row_height() const
+{
+ return font().glyph_height() + 6;
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/AbstractTableView.h b/Userland/Libraries/LibGUI/AbstractTableView.h
new file mode 100644
index 0000000000..b64e4b3bcb
--- /dev/null
+++ b/Userland/Libraries/LibGUI/AbstractTableView.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/AbstractView.h>
+
+namespace GUI {
+
+class TableCellPaintingDelegate {
+public:
+ virtual ~TableCellPaintingDelegate() { }
+
+ virtual void paint(Painter&, const Gfx::IntRect&, const Gfx::Palette&, const ModelIndex&) = 0;
+};
+
+class AbstractTableView : public AbstractView {
+public:
+ int row_height() const;
+
+ bool alternating_row_colors() const { return m_alternating_row_colors; }
+ void set_alternating_row_colors(bool b) { m_alternating_row_colors = b; }
+ bool highlight_selected_rows() const { return m_highlight_selected_rows; }
+ void set_highlight_selected_rows(bool b) { m_highlight_selected_rows = b; }
+
+ bool column_headers_visible() const;
+ void set_column_headers_visible(bool);
+
+ void set_column_hidden(int, bool);
+
+ int column_width(int column) const;
+ void set_column_width(int column, int width);
+
+ Gfx::TextAlignment column_header_alignment(int column) const;
+ void set_column_header_alignment(int column, Gfx::TextAlignment);
+
+ void set_column_painting_delegate(int column, OwnPtr<TableCellPaintingDelegate>);
+
+ int horizontal_padding() const;
+
+ Gfx::IntPoint adjusted_position(const Gfx::IntPoint&) const;
+
+ virtual Gfx::IntRect content_rect(const ModelIndex&) const override;
+ Gfx::IntRect content_rect(int row, int column) const;
+ Gfx::IntRect row_rect(int item_index) const;
+
+ virtual void scroll_into_view(const ModelIndex&, bool scroll_horizontally = true, bool scroll_vertically = true) override;
+ void scroll_into_view(const ModelIndex& index, Orientation orientation)
+ {
+ scroll_into_view(index, orientation == Gfx::Orientation::Horizontal, orientation == Gfx::Orientation::Vertical);
+ }
+
+ virtual ModelIndex index_at_event_position(const Gfx::IntPoint&, bool& is_toggle) const;
+ virtual ModelIndex index_at_event_position(const Gfx::IntPoint&) const override;
+
+ virtual void select_all() override;
+
+ void header_did_change_section_visibility(Badge<HeaderView>, Gfx::Orientation, int section, bool visible);
+ void header_did_change_section_size(Badge<HeaderView>, Gfx::Orientation, int section, int size);
+
+ virtual void did_scroll() override;
+
+ HeaderView& column_header() { return *m_column_header; }
+ const HeaderView& column_header() const { return *m_column_header; }
+
+ HeaderView& row_header() { return *m_row_header; }
+ const HeaderView& row_header() const { return *m_row_header; }
+
+ virtual void model_did_update(unsigned flags) override;
+
+protected:
+ virtual ~AbstractTableView() override;
+ AbstractTableView();
+
+ virtual void mousedown_event(MouseEvent&) override;
+ virtual void context_menu_event(ContextMenuEvent&) override;
+ virtual void keydown_event(KeyEvent&) override;
+ virtual void resize_event(ResizeEvent&) override;
+
+ virtual void toggle_index(const ModelIndex&) { }
+
+ void update_content_size();
+ virtual void update_column_sizes();
+ virtual void update_row_sizes();
+ virtual int item_count() const;
+
+ TableCellPaintingDelegate* column_painting_delegate(int column) const;
+
+ void move_cursor_relative(int vertical_steps, int horizontal_steps, SelectionUpdate);
+
+private:
+ void layout_headers();
+
+ RefPtr<HeaderView> m_column_header;
+ RefPtr<HeaderView> m_row_header;
+ RefPtr<Button> m_corner_button;
+
+ HashMap<int, OwnPtr<TableCellPaintingDelegate>> m_column_painting_delegate;
+
+ bool m_alternating_row_colors { true };
+ bool m_highlight_selected_rows { true };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/AbstractView.cpp b/Userland/Libraries/LibGUI/AbstractView.cpp
new file mode 100644
index 0000000000..701733cb07
--- /dev/null
+++ b/Userland/Libraries/LibGUI/AbstractView.cpp
@@ -0,0 +1,765 @@
+/*
+ * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <AK/Utf8View.h>
+#include <AK/Vector.h>
+#include <LibCore/Timer.h>
+#include <LibGUI/AbstractView.h>
+#include <LibGUI/DragOperation.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/ModelEditingDelegate.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGUI/TextBox.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+AbstractView::AbstractView()
+ : m_sort_order(SortOrder::Ascending)
+ , m_selection(*this)
+{
+ set_focus_policy(GUI::FocusPolicy::StrongFocus);
+}
+
+AbstractView::~AbstractView()
+{
+ if (m_searching_timer)
+ m_searching_timer->stop();
+ if (m_model)
+ m_model->unregister_view({}, *this);
+}
+
+void AbstractView::set_model(RefPtr<Model> model)
+{
+ if (model == m_model)
+ return;
+ if (m_model)
+ m_model->unregister_view({}, *this);
+ m_model = move(model);
+ if (m_model)
+ m_model->register_view({}, *this);
+ model_did_update(GUI::Model::InvalidateAllIndexes);
+ scroll_to_top();
+}
+
+void AbstractView::model_did_update(unsigned int flags)
+{
+ if (!model() || (flags & GUI::Model::InvalidateAllIndexes)) {
+ stop_editing();
+ m_edit_index = {};
+ m_hovered_index = {};
+ m_cursor_index = {};
+ m_drop_candidate_index = {};
+ clear_selection();
+ } else {
+ // FIXME: These may no longer point to whatever they did before,
+ // but let's be optimistic until we can be sure about it.
+ if (!model()->is_valid(m_edit_index)) {
+ stop_editing();
+ m_edit_index = {};
+ }
+ if (!model()->is_valid(m_hovered_index))
+ m_hovered_index = {};
+ if (!model()->is_valid(m_cursor_index))
+ m_cursor_index = {};
+ if (!model()->is_valid(m_drop_candidate_index))
+ m_drop_candidate_index = {};
+ selection().remove_matching([this](auto& index) { return !model()->is_valid(index); });
+ }
+}
+
+void AbstractView::clear_selection()
+{
+ m_selection.clear();
+}
+
+void AbstractView::set_selection(const ModelIndex& new_index)
+{
+ m_selection.set(new_index);
+}
+
+void AbstractView::add_selection(const ModelIndex& new_index)
+{
+ m_selection.add(new_index);
+}
+
+void AbstractView::remove_selection(const ModelIndex& new_index)
+{
+ m_selection.remove(new_index);
+}
+
+void AbstractView::toggle_selection(const ModelIndex& new_index)
+{
+ m_selection.toggle(new_index);
+}
+
+void AbstractView::did_update_selection()
+{
+ if (!model() || selection().first() != m_edit_index)
+ stop_editing();
+ if (model() && on_selection && selection().first().is_valid())
+ on_selection(selection().first());
+}
+
+void AbstractView::did_scroll()
+{
+ update_edit_widget_position();
+}
+
+void AbstractView::update_edit_widget_position()
+{
+ if (!m_edit_widget)
+ return;
+ m_edit_widget->set_relative_rect(m_edit_widget_content_rect.translated(-horizontal_scrollbar().value(), -vertical_scrollbar().value()));
+}
+
+void AbstractView::begin_editing(const ModelIndex& index)
+{
+ ASSERT(is_editable());
+ ASSERT(model());
+ if (m_edit_index == index)
+ return;
+ if (!model()->is_editable(index))
+ return;
+ if (m_edit_widget) {
+ remove_child(*m_edit_widget);
+ m_edit_widget = nullptr;
+ }
+ m_edit_index = index;
+
+ ASSERT(aid_create_editing_delegate);
+ m_editing_delegate = aid_create_editing_delegate(index);
+ m_editing_delegate->bind(*model(), index);
+ m_editing_delegate->set_value(index.data());
+ m_edit_widget = m_editing_delegate->widget();
+ add_child(*m_edit_widget);
+ m_edit_widget->move_to_back();
+ m_edit_widget_content_rect = content_rect(index).translated(frame_thickness(), frame_thickness());
+ update_edit_widget_position();
+ m_edit_widget->set_focus(true);
+ m_editing_delegate->will_begin_editing();
+ m_editing_delegate->on_commit = [this] {
+ ASSERT(model());
+ model()->set_data(m_edit_index, m_editing_delegate->value());
+ stop_editing();
+ };
+ m_editing_delegate->on_rollback = [this] {
+ ASSERT(model());
+ stop_editing();
+ };
+}
+
+void AbstractView::stop_editing()
+{
+ bool take_back_focus = false;
+ m_edit_index = {};
+ if (m_edit_widget) {
+ take_back_focus = m_edit_widget->is_focused();
+ remove_child(*m_edit_widget);
+ m_edit_widget = nullptr;
+ }
+ if (take_back_focus)
+ set_focus(true);
+}
+
+void AbstractView::activate(const ModelIndex& index)
+{
+ if (on_activation)
+ on_activation(index);
+}
+
+void AbstractView::activate_selected()
+{
+ if (!on_activation)
+ return;
+
+ selection().for_each_index([this](auto& index) {
+ on_activation(index);
+ });
+}
+
+void AbstractView::notify_selection_changed(Badge<ModelSelection>)
+{
+ did_update_selection();
+ if (on_selection_change)
+ on_selection_change();
+ update();
+}
+
+NonnullRefPtr<Gfx::Font> AbstractView::font_for_index(const ModelIndex& index) const
+{
+ if (!model())
+ return font();
+
+ auto font_data = index.data(ModelRole::Font);
+ if (font_data.is_font())
+ return font_data.as_font();
+
+ return font();
+}
+
+void AbstractView::mousedown_event(MouseEvent& event)
+{
+ ScrollableWidget::mousedown_event(event);
+
+ if (!model())
+ return;
+
+ if (event.button() == MouseButton::Left)
+ m_left_mousedown_position = event.position();
+
+ auto index = index_at_event_position(event.position());
+ m_might_drag = false;
+
+ if (!index.is_valid()) {
+ clear_selection();
+ } else if (event.modifiers() & Mod_Ctrl) {
+ set_cursor(index, SelectionUpdate::Ctrl);
+ } else if (event.modifiers() & Mod_Shift) {
+ set_cursor(index, SelectionUpdate::Shift);
+ } else if (event.button() == MouseButton::Left && m_selection.contains(index) && !m_model->drag_data_type().is_null()) {
+ // We might be starting a drag, so don't throw away other selected items yet.
+ m_might_drag = true;
+ } else if (event.button() == MouseButton::Right) {
+ set_cursor(index, SelectionUpdate::ClearIfNotSelected);
+ } else {
+ set_cursor(index, SelectionUpdate::Set);
+ m_might_drag = true;
+ }
+
+ update();
+}
+
+void AbstractView::set_hovered_index(const ModelIndex& index)
+{
+ if (m_hovered_index == index)
+ return;
+ auto old_index = m_hovered_index;
+ m_hovered_index = index;
+ did_change_hovered_index(old_index, index);
+ update();
+}
+
+void AbstractView::leave_event(Core::Event& event)
+{
+ ScrollableWidget::leave_event(event);
+ set_hovered_index({});
+}
+
+void AbstractView::mousemove_event(MouseEvent& event)
+{
+ if (!model())
+ return ScrollableWidget::mousemove_event(event);
+
+ auto hovered_index = index_at_event_position(event.position());
+ set_hovered_index(hovered_index);
+
+ auto data_type = m_model->drag_data_type();
+ if (data_type.is_null())
+ return ScrollableWidget::mousemove_event(event);
+
+ if (!m_might_drag)
+ return ScrollableWidget::mousemove_event(event);
+
+ if (!(event.buttons() & MouseButton::Left) || m_selection.is_empty()) {
+ m_might_drag = false;
+ return ScrollableWidget::mousemove_event(event);
+ }
+
+ auto diff = event.position() - m_left_mousedown_position;
+ auto distance_travelled_squared = diff.x() * diff.x() + diff.y() * diff.y();
+ constexpr int drag_distance_threshold = 5;
+
+ if (distance_travelled_squared <= drag_distance_threshold)
+ return ScrollableWidget::mousemove_event(event);
+
+ ASSERT(!data_type.is_null());
+
+ if (m_is_dragging)
+ return;
+
+ // An event might sneak in between us constructing the drag operation and the
+ // event loop exec at the end of `drag_operation->exec()' if the user is fast enough.
+ // Prevent this by just ignoring later drag initiations (until the current drag operation ends).
+ TemporaryChange dragging { m_is_dragging, true };
+
+ dbgln("Initiate drag!");
+ auto drag_operation = DragOperation::construct();
+
+ drag_operation->set_mime_data(m_model->mime_data(m_selection));
+
+ auto outcome = drag_operation->exec();
+
+ switch (outcome) {
+ case DragOperation::Outcome::Accepted:
+ dbgln("Drag was accepted!");
+ break;
+ case DragOperation::Outcome::Cancelled:
+ dbgln("Drag was cancelled!");
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+}
+
+void AbstractView::mouseup_event(MouseEvent& event)
+{
+ ScrollableWidget::mouseup_event(event);
+
+ if (!model())
+ return;
+
+ if (m_might_drag) {
+ // We were unsure about unselecting items other than the current one
+ // in mousedown_event(), because we could be seeing a start of a drag.
+ // Since we're here, it was not that; so fix up the selection now.
+ auto index = index_at_event_position(event.position());
+ if (index.is_valid())
+ set_selection(index);
+ else
+ clear_selection();
+ m_might_drag = false;
+ update();
+ }
+
+ if (activates_on_selection())
+ activate_selected();
+}
+
+void AbstractView::doubleclick_event(MouseEvent& event)
+{
+ if (!model())
+ return;
+
+ if (event.button() != MouseButton::Left)
+ return;
+
+ m_might_drag = false;
+
+ auto index = index_at_event_position(event.position());
+
+ if (!index.is_valid()) {
+ clear_selection();
+ return;
+ }
+
+ if (!m_selection.contains(index))
+ set_selection(index);
+
+ if (is_editable() && edit_triggers() & EditTrigger::DoubleClicked)
+ begin_editing(cursor_index());
+ else
+ activate(cursor_index());
+}
+
+void AbstractView::context_menu_event(ContextMenuEvent& event)
+{
+ if (!model())
+ return;
+
+ auto index = index_at_event_position(event.position());
+
+ if (index.is_valid())
+ add_selection(index);
+ else
+ clear_selection();
+
+ if (on_context_menu_request)
+ on_context_menu_request(index, event);
+}
+
+void AbstractView::drop_event(DropEvent& event)
+{
+ event.accept();
+
+ if (!model())
+ return;
+
+ auto index = index_at_event_position(event.position());
+ if (on_drop)
+ on_drop(index, event);
+}
+
+void AbstractView::set_selection_mode(SelectionMode selection_mode)
+{
+ if (m_selection_mode == selection_mode)
+ return;
+ m_selection_mode = selection_mode;
+
+ if (m_selection_mode == SelectionMode::NoSelection)
+ m_selection.clear();
+ else if (m_selection_mode != SelectionMode::SingleSelection && m_selection.size() > 1) {
+ auto first_selected = m_selection.first();
+ m_selection.clear();
+ m_selection.set(first_selected);
+ }
+
+ update();
+}
+
+void AbstractView::set_key_column_and_sort_order(int column, SortOrder sort_order)
+{
+ m_key_column = column;
+ m_sort_order = sort_order;
+
+ if (model())
+ model()->sort(column, sort_order);
+
+ update();
+}
+
+void AbstractView::set_cursor(ModelIndex index, SelectionUpdate selection_update, bool scroll_cursor_into_view)
+{
+ if (!model() || !index.is_valid() || selection_mode() == SelectionMode::NoSelection) {
+ m_cursor_index = {};
+ cancel_searching();
+ return;
+ }
+
+ if (!m_cursor_index.is_valid() || model()->parent_index(m_cursor_index) != model()->parent_index(index))
+ cancel_searching();
+
+ if (selection_mode() == SelectionMode::SingleSelection && (selection_update == SelectionUpdate::Ctrl || selection_update == SelectionUpdate::Shift))
+ selection_update = SelectionUpdate::Set;
+
+ if (model()->is_valid(index)) {
+ if (selection_update == SelectionUpdate::Set)
+ set_selection(index);
+ else if (selection_update == SelectionUpdate::Ctrl)
+ toggle_selection(index);
+ else if (selection_update == SelectionUpdate::ClearIfNotSelected) {
+ if (!m_selection.contains(index))
+ clear_selection();
+ } else if (selection_update == SelectionUpdate::Shift) {
+ // Toggle all from cursor to new index.
+ auto min_row = min(cursor_index().row(), index.row());
+ auto max_row = max(cursor_index().row(), index.row());
+ auto min_column = min(cursor_index().column(), index.column());
+ auto max_column = max(cursor_index().column(), index.column());
+
+ for (auto row = min_row; row <= max_row; ++row) {
+ for (auto column = min_column; column <= max_column; ++column) {
+ auto new_index = model()->index(row, column);
+ if (new_index.is_valid())
+ toggle_selection(new_index);
+ }
+ }
+
+ // Finally toggle the cursor index again to make it go back to its current state.
+ toggle_selection(cursor_index());
+ }
+
+ // FIXME: Support the other SelectionUpdate types
+
+ auto old_cursor_index = m_cursor_index;
+ m_cursor_index = index;
+ did_change_cursor_index(old_cursor_index, m_cursor_index);
+
+ if (scroll_cursor_into_view)
+ scroll_into_view(index, true, true);
+ update();
+ }
+}
+
+void AbstractView::set_edit_triggers(unsigned triggers)
+{
+ m_edit_triggers = triggers;
+}
+
+void AbstractView::hide_event(HideEvent& event)
+{
+ stop_editing();
+ ScrollableWidget::hide_event(event);
+}
+
+void AbstractView::keydown_event(KeyEvent& event)
+{
+ if (event.key() == KeyCode::Key_F2) {
+ if (is_editable() && edit_triggers() & EditTrigger::EditKeyPressed) {
+ begin_editing(cursor_index());
+ event.accept();
+ return;
+ }
+ }
+
+ if (event.key() == KeyCode::Key_Return) {
+ activate_selected();
+ event.accept();
+ return;
+ }
+
+ SelectionUpdate selection_update = SelectionUpdate::Set;
+ if (event.modifiers() == KeyModifier::Mod_Shift) {
+ selection_update = SelectionUpdate::Shift;
+ }
+
+ if (event.key() == KeyCode::Key_Left) {
+ move_cursor(CursorMovement::Left, selection_update);
+ event.accept();
+ return;
+ }
+ if (event.key() == KeyCode::Key_Right) {
+ move_cursor(CursorMovement::Right, selection_update);
+ event.accept();
+ return;
+ }
+ if (event.key() == KeyCode::Key_Up) {
+ move_cursor(CursorMovement::Up, selection_update);
+ event.accept();
+ return;
+ }
+ if (event.key() == KeyCode::Key_Down) {
+ move_cursor(CursorMovement::Down, selection_update);
+ event.accept();
+ return;
+ }
+ if (event.key() == KeyCode::Key_Home) {
+ move_cursor(CursorMovement::Home, selection_update);
+ event.accept();
+ return;
+ }
+ if (event.key() == KeyCode::Key_End) {
+ move_cursor(CursorMovement::End, selection_update);
+ event.accept();
+ return;
+ }
+ if (event.key() == KeyCode::Key_PageUp) {
+ move_cursor(CursorMovement::PageUp, selection_update);
+ event.accept();
+ return;
+ }
+ if (event.key() == KeyCode::Key_PageDown) {
+ move_cursor(CursorMovement::PageDown, selection_update);
+ event.accept();
+ return;
+ }
+
+ if (is_searchable()) {
+ if (event.key() == KeyCode::Key_Backspace) {
+ if (is_searching()) {
+ //if (event.modifiers() == Mod_Ctrl) {
+ // TODO: delete last word
+ //}
+ Utf8View view(m_searching);
+ size_t n_code_points = view.length();
+ if (n_code_points > 1) {
+ n_code_points--;
+ StringBuilder sb;
+ for (auto it = view.begin(); it != view.end(); ++it) {
+ if (n_code_points == 0)
+ break;
+ n_code_points--;
+ sb.append_code_point(*it);
+ }
+ do_search(sb.to_string());
+ start_searching_timer();
+ } else {
+ cancel_searching();
+ }
+
+ event.accept();
+ return;
+ }
+ } else if (event.key() == KeyCode::Key_Escape) {
+ if (is_searching()) {
+ cancel_searching();
+
+ event.accept();
+ return;
+ }
+ } else if (event.key() != KeyCode::Key_Tab && !event.ctrl() && !event.alt() && event.code_point() != 0) {
+ StringBuilder sb;
+ sb.append(m_searching);
+ sb.append_code_point(event.code_point());
+ do_search(sb.to_string());
+ start_searching_timer();
+
+ event.accept();
+ return;
+ }
+ }
+
+ ScrollableWidget::keydown_event(event);
+}
+
+void AbstractView::cancel_searching()
+{
+ m_searching = nullptr;
+ if (m_searching_timer)
+ m_searching_timer->stop();
+ if (m_highlighted_search_index.is_valid()) {
+ m_highlighted_search_index = {};
+ update();
+ }
+}
+
+void AbstractView::start_searching_timer()
+{
+ if (!m_searching_timer) {
+ m_searching_timer = add<Core::Timer>();
+ m_searching_timer->set_single_shot(true);
+ m_searching_timer->on_timeout = [this] {
+ cancel_searching();
+ };
+ }
+ m_searching_timer->set_interval(5 * 1000);
+ m_searching_timer->restart();
+}
+
+void AbstractView::do_search(String&& searching)
+{
+ if (searching.is_empty() || !model()) {
+ cancel_searching();
+ return;
+ }
+
+ auto found_indexes = model()->matches(searching, Model::MatchesFlag::FirstMatchOnly | Model::MatchesFlag::MatchAtStart | Model::MatchesFlag::CaseInsensitive, model()->parent_index(cursor_index()));
+ if (!found_indexes.is_empty() && found_indexes[0].is_valid()) {
+ auto& index = found_indexes[0];
+ m_highlighted_search_index = index;
+ m_searching = move(searching);
+ set_selection(index);
+ scroll_into_view(index);
+ update();
+ }
+}
+
+bool AbstractView::is_searchable() const
+{
+ if (!m_searchable || !model())
+ return false;
+ return model()->is_searchable();
+}
+
+void AbstractView::set_searchable(bool searchable)
+{
+ if (m_searchable == searchable)
+ return;
+ m_searchable = searchable;
+ if (!m_searchable)
+ cancel_searching();
+}
+
+bool AbstractView::is_highlighting_searching(const ModelIndex& index) const
+{
+ return index == m_highlighted_search_index;
+}
+
+void AbstractView::draw_item_text(Gfx::Painter& painter, const ModelIndex& index, bool is_selected, const Gfx::IntRect& text_rect, const StringView& item_text, const Gfx::Font& font, Gfx::TextAlignment alignment, Gfx::TextElision elision)
+{
+ Color text_color;
+ if (is_selected)
+ text_color = is_focused() ? palette().selection_text() : palette().inactive_selection_text();
+ else
+ text_color = index.data(ModelRole::ForegroundColor).to_color(palette().color(foreground_role()));
+ if (is_highlighting_searching(index)) {
+ Utf8View searching_text(searching());
+ auto searching_length = searching_text.length();
+
+ // Highlight the text background first
+ painter.draw_text([&](const Gfx::IntRect& rect, u32) {
+ if (searching_length > 0) {
+ searching_length--;
+ painter.fill_rect(rect.inflated(0, 2), palette().highlight_searching());
+ }
+ },
+ text_rect, item_text, font, alignment, elision);
+
+ // Then draw the text
+ auto highlight_text_color = palette().highlight_searching_text();
+ searching_length = searching_text.length();
+ painter.draw_text([&](const Gfx::IntRect& rect, u32 code_point) {
+ if (searching_length > 0) {
+ searching_length--;
+ painter.draw_glyph_or_emoji(rect.location(), code_point, font, highlight_text_color);
+ } else {
+ painter.draw_glyph_or_emoji(rect.location(), code_point, font, text_color);
+ }
+ },
+ text_rect, item_text, font, alignment, elision);
+ } else {
+ if (m_draw_item_text_with_shadow) {
+ painter.draw_text(text_rect.translated(1, 1), item_text, font, alignment, Color::Black, elision);
+ painter.draw_text(text_rect, item_text, font, alignment, Color::White, elision);
+ } else {
+ painter.draw_text(text_rect, item_text, font, alignment, text_color, elision);
+ }
+ }
+}
+
+void AbstractView::focusin_event(FocusEvent& event)
+{
+ ScrollableWidget::focusin_event(event);
+
+ if (model() && !cursor_index().is_valid()) {
+ move_cursor(CursorMovement::Home, SelectionUpdate::None);
+ clear_selection();
+ }
+}
+
+void AbstractView::drag_enter_event(DragEvent& event)
+{
+ if (!model())
+ return;
+ // NOTE: Right now, AbstractView always accepts drags since we won't get "drag move" events
+ // unless we accept the "drag enter" event.
+ // We might be able to reduce event traffic by communicating the set of drag-accepting
+ // rects in this widget to the windowing system somehow.
+ event.accept();
+ dbgln("accepting drag of {}", event.mime_types().first());
+}
+
+void AbstractView::drag_move_event(DragEvent& event)
+{
+ if (!model())
+ return;
+ auto index = index_at_event_position(event.position());
+ ModelIndex new_drop_candidate_index;
+ if (index.is_valid()) {
+ bool acceptable = model()->accepts_drag(index, event.mime_types());
+ if (acceptable)
+ new_drop_candidate_index = index;
+ }
+ if (m_drop_candidate_index != new_drop_candidate_index) {
+ m_drop_candidate_index = new_drop_candidate_index;
+ update();
+ }
+ if (m_drop_candidate_index.is_valid())
+ event.accept();
+}
+
+void AbstractView::drag_leave_event(Event&)
+{
+ if (m_drop_candidate_index.is_valid()) {
+ m_drop_candidate_index = {};
+ update();
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/AbstractView.h b/Userland/Libraries/LibGUI/AbstractView.h
new file mode 100644
index 0000000000..e8ce1f0114
--- /dev/null
+++ b/Userland/Libraries/LibGUI/AbstractView.h
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/ModelSelection.h>
+#include <LibGUI/ScrollableWidget.h>
+#include <LibGfx/TextElision.h>
+
+namespace GUI {
+
+class AbstractView
+ : public ScrollableWidget
+ , public ModelClient {
+
+ C_OBJECT_ABSTRACT(AbstractView);
+
+public:
+ enum class CursorMovement {
+ Up,
+ Down,
+ Left,
+ Right,
+ Home,
+ End,
+ PageUp,
+ PageDown,
+ };
+
+ enum class SelectionUpdate {
+ None,
+ Set,
+ Shift,
+ Ctrl,
+ ClearIfNotSelected
+ };
+
+ enum class SelectionBehavior {
+ SelectItems,
+ SelectRows,
+ };
+
+ enum class SelectionMode {
+ SingleSelection,
+ MultiSelection,
+ NoSelection,
+ };
+
+ virtual void move_cursor(CursorMovement, SelectionUpdate) { }
+
+ void set_model(RefPtr<Model>);
+ Model* model() { return m_model.ptr(); }
+ const Model* model() const { return m_model.ptr(); }
+
+ ModelSelection& selection() { return m_selection; }
+ const ModelSelection& selection() const { return m_selection; }
+ virtual void select_all() { }
+
+ bool is_editable() const { return m_editable; }
+ void set_editable(bool editable) { m_editable = editable; }
+
+ bool is_searching() const { return !m_searching.is_null(); }
+
+ bool is_searchable() const;
+ void set_searchable(bool);
+
+ enum EditTrigger {
+ None = 0,
+ DoubleClicked = 1 << 0,
+ EditKeyPressed = 1 << 1,
+ AnyKeyPressed = 1 << 2,
+ };
+
+ unsigned edit_triggers() const { return m_edit_triggers; }
+ void set_edit_triggers(unsigned);
+
+ SelectionBehavior selection_behavior() const { return m_selection_behavior; }
+ void set_selection_behavior(SelectionBehavior behavior) { m_selection_behavior = behavior; }
+
+ SelectionMode selection_mode() const { return m_selection_mode; }
+ void set_selection_mode(SelectionMode);
+
+ virtual void model_did_update(unsigned flags) override;
+ virtual void did_update_selection();
+
+ virtual Gfx::IntRect content_rect(const ModelIndex&) const { return {}; }
+ virtual ModelIndex index_at_event_position(const Gfx::IntPoint&) const { return {}; }
+ void begin_editing(const ModelIndex&);
+ void stop_editing();
+
+ void set_activates_on_selection(bool b) { m_activates_on_selection = b; }
+ bool activates_on_selection() const { return m_activates_on_selection; }
+
+ Function<void()> on_selection_change;
+ Function<void(const ModelIndex&)> on_activation;
+ Function<void(const ModelIndex&)> on_selection;
+ Function<void(const ModelIndex&, const ContextMenuEvent&)> on_context_menu_request;
+ Function<void(const ModelIndex&, const DropEvent&)> on_drop;
+
+ Function<OwnPtr<ModelEditingDelegate>(const ModelIndex&)> aid_create_editing_delegate;
+
+ void notify_selection_changed(Badge<ModelSelection>);
+
+ NonnullRefPtr<Gfx::Font> font_for_index(const ModelIndex&) const;
+
+ void set_key_column_and_sort_order(int column, SortOrder);
+
+ int key_column() const { return m_key_column; }
+ SortOrder sort_order() const { return m_sort_order; }
+
+ virtual void scroll_into_view(const ModelIndex&, [[maybe_unused]] bool scroll_horizontally = true, [[maybe_unused]] bool scroll_vertically = true) { }
+
+ const ModelIndex& cursor_index() const { return m_cursor_index; }
+ void set_cursor(ModelIndex, SelectionUpdate, bool scroll_cursor_into_view = true);
+
+ bool is_tab_key_navigation_enabled() const { return m_tab_key_navigation_enabled; }
+ void set_tab_key_navigation_enabled(bool enabled) { m_tab_key_navigation_enabled = enabled; }
+
+ void set_draw_item_text_with_shadow(bool b) { m_draw_item_text_with_shadow = b; }
+
+protected:
+ AbstractView();
+ virtual ~AbstractView() override;
+
+ virtual void keydown_event(KeyEvent&) override;
+ virtual void mousedown_event(MouseEvent&) override;
+ virtual void mousemove_event(MouseEvent&) override;
+ virtual void mouseup_event(MouseEvent&) override;
+ virtual void doubleclick_event(MouseEvent&) override;
+ virtual void context_menu_event(ContextMenuEvent&) override;
+ virtual void drag_enter_event(DragEvent&) override;
+ virtual void drag_move_event(DragEvent&) override;
+ virtual void drag_leave_event(Event&) override;
+ virtual void drop_event(DropEvent&) override;
+ virtual void leave_event(Core::Event&) override;
+ virtual void hide_event(HideEvent&) override;
+ virtual void focusin_event(FocusEvent&) override;
+
+ virtual void clear_selection();
+ virtual void set_selection(const ModelIndex&);
+ virtual void add_selection(const ModelIndex&);
+ virtual void remove_selection(const ModelIndex&);
+ virtual void toggle_selection(const ModelIndex&);
+ virtual void did_change_hovered_index([[maybe_unused]] const ModelIndex& old_index, [[maybe_unused]] const ModelIndex& new_index) { }
+ virtual void did_change_cursor_index([[maybe_unused]] const ModelIndex& old_index, [[maybe_unused]] const ModelIndex& new_index) { }
+
+ void draw_item_text(Gfx::Painter&, const ModelIndex&, bool, const Gfx::IntRect&, const StringView&, const Gfx::Font&, Gfx::TextAlignment, Gfx::TextElision);
+
+ virtual void did_scroll() override;
+ void set_hovered_index(const ModelIndex&);
+ void activate(const ModelIndex&);
+ void activate_selected();
+ void update_edit_widget_position();
+
+ StringView searching() const { return m_searching; }
+ void cancel_searching();
+ void start_searching_timer();
+ void do_search(String&&);
+ bool is_highlighting_searching(const ModelIndex&) const;
+
+ ModelIndex drop_candidate_index() const { return m_drop_candidate_index; }
+
+ bool m_editable { false };
+ bool m_searchable { true };
+ ModelIndex m_edit_index;
+ RefPtr<Widget> m_edit_widget;
+ Gfx::IntRect m_edit_widget_content_rect;
+ OwnPtr<ModelEditingDelegate> m_editing_delegate;
+
+ Gfx::IntPoint m_left_mousedown_position;
+ bool m_might_drag { false };
+
+ ModelIndex m_hovered_index;
+ ModelIndex m_highlighted_search_index;
+
+ int m_key_column { -1 };
+ SortOrder m_sort_order;
+
+private:
+ RefPtr<Model> m_model;
+ ModelSelection m_selection;
+ String m_searching;
+ RefPtr<Core::Timer> m_searching_timer;
+ ModelIndex m_cursor_index;
+ ModelIndex m_drop_candidate_index;
+ SelectionBehavior m_selection_behavior { SelectionBehavior::SelectItems };
+ SelectionMode m_selection_mode { SelectionMode::SingleSelection };
+ unsigned m_edit_triggers { EditTrigger::DoubleClicked | EditTrigger::EditKeyPressed };
+ bool m_activates_on_selection { false };
+ bool m_tab_key_navigation_enabled { false };
+ bool m_is_dragging { false };
+ bool m_draw_item_text_with_shadow { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Action.cpp b/Userland/Libraries/LibGUI/Action.cpp
new file mode 100644
index 0000000000..208b7b5c90
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Action.cpp
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/WeakPtr.h>
+#include <LibGUI/AboutDialog.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/ActionGroup.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/Icon.h>
+#include <LibGUI/MenuItem.h>
+#include <LibGUI/Window.h>
+
+namespace GUI {
+
+namespace CommonActions {
+
+NonnullRefPtr<Action> make_about_action(const String& app_name, const Icon& app_icon, Window* parent)
+{
+ auto weak_parent = AK::try_make_weak_ptr<Window>(parent);
+ return Action::create(String::formatted("About {}", app_name), app_icon.bitmap_for_size(16), [=](auto&) {
+ AboutDialog::show(app_name, app_icon.bitmap_for_size(32), weak_parent.ptr());
+ });
+}
+
+NonnullRefPtr<Action> make_open_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Open...", { Mod_Ctrl, Key_O }, Gfx::Bitmap::load_from_file("/res/icons/16x16/open.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_save_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Save", { Mod_Ctrl, Key_S }, Gfx::Bitmap::load_from_file("/res/icons/16x16/save.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_save_as_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Save As...", { Mod_Ctrl | Mod_Shift, Key_S }, Gfx::Bitmap::load_from_file("/res/icons/16x16/save.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_move_to_front_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Move to front", { Mod_Ctrl | Mod_Shift, Key_Up }, Gfx::Bitmap::load_from_file("/res/icons/16x16/move-to-front.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_move_to_back_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Move to back", { Mod_Ctrl | Mod_Shift, Key_Down }, Gfx::Bitmap::load_from_file("/res/icons/16x16/move-to-back.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_undo_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Undo", { Mod_Ctrl, Key_Z }, Gfx::Bitmap::load_from_file("/res/icons/16x16/undo.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_redo_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Redo", { Mod_Ctrl, Key_Y }, Gfx::Bitmap::load_from_file("/res/icons/16x16/redo.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_delete_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Delete", { Mod_None, Key_Delete }, Gfx::Bitmap::load_from_file("/res/icons/16x16/delete.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_cut_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Cut", { Mod_Ctrl, Key_X }, Gfx::Bitmap::load_from_file("/res/icons/16x16/edit-cut.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_copy_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Copy", { Mod_Ctrl, Key_C }, Gfx::Bitmap::load_from_file("/res/icons/16x16/edit-copy.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_paste_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Paste", { Mod_Ctrl, Key_V }, Gfx::Bitmap::load_from_file("/res/icons/16x16/paste.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_fullscreen_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Fullscreen", { Mod_None, Key_F11 }, move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_quit_action(Function<void(Action&)> callback)
+{
+ return Action::create("Quit", { Mod_Alt, Key_F4 }, move(callback));
+}
+
+NonnullRefPtr<Action> make_help_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Contents", { Mod_None, Key_F1 }, Gfx::Bitmap::load_from_file("/res/icons/16x16/app-help.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_go_back_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Go back", { Mod_Alt, Key_Left }, Gfx::Bitmap::load_from_file("/res/icons/16x16/go-back.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_go_forward_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Go forward", { Mod_Alt, Key_Right }, Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_go_home_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Go home", { Mod_Alt, Key_Home }, Gfx::Bitmap::load_from_file("/res/icons/16x16/go-home.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_reload_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Reload", { Mod_Ctrl, Key_R }, Gfx::Bitmap::load_from_file("/res/icons/16x16/reload.png"), move(callback), parent);
+}
+
+NonnullRefPtr<Action> make_select_all_action(Function<void(Action&)> callback, Core::Object* parent)
+{
+ return Action::create("Select all", { Mod_Ctrl, Key_A }, Gfx::Bitmap::load_from_file("/res/icons/16x16/select-all.png"), move(callback), parent);
+}
+
+}
+
+Action::Action(const StringView& text, Function<void(Action&)> on_activation_callback, Core::Object* parent, bool checkable)
+ : Core::Object(parent)
+ , on_activation(move(on_activation_callback))
+ , m_text(text)
+ , m_checkable(checkable)
+{
+}
+
+Action::Action(const StringView& text, RefPtr<Gfx::Bitmap>&& icon, Function<void(Action&)> on_activation_callback, Core::Object* parent, bool checkable)
+ : Core::Object(parent)
+ , on_activation(move(on_activation_callback))
+ , m_text(text)
+ , m_icon(move(icon))
+ , m_checkable(checkable)
+{
+}
+
+Action::Action(const StringView& text, const Shortcut& shortcut, Function<void(Action&)> on_activation_callback, Core::Object* parent, bool checkable)
+ : Action(text, shortcut, nullptr, move(on_activation_callback), parent, checkable)
+{
+}
+
+Action::Action(const StringView& text, const Shortcut& shortcut, RefPtr<Gfx::Bitmap>&& icon, Function<void(Action&)> on_activation_callback, Core::Object* parent, bool checkable)
+ : Core::Object(parent)
+ , on_activation(move(on_activation_callback))
+ , m_text(text)
+ , m_icon(move(icon))
+ , m_shortcut(shortcut)
+ , m_checkable(checkable)
+{
+ if (parent && is<Widget>(*parent)) {
+ m_scope = ShortcutScope::WidgetLocal;
+ } else if (parent && is<Window>(*parent)) {
+ m_scope = ShortcutScope::WindowLocal;
+ } else {
+ m_scope = ShortcutScope::ApplicationGlobal;
+ if (auto* app = Application::the()) {
+ app->register_global_shortcut_action({}, *this);
+ }
+ }
+}
+
+Action::~Action()
+{
+ if (m_shortcut.is_valid() && m_scope == ShortcutScope::ApplicationGlobal) {
+ if (auto* app = Application::the())
+ app->unregister_global_shortcut_action({}, *this);
+ }
+}
+
+void Action::activate(Core::Object* activator)
+{
+ if (!on_activation)
+ return;
+
+ if (activator)
+ m_activator = activator->make_weak_ptr();
+
+ if (is_checkable()) {
+ if (m_action_group) {
+ if (m_action_group->is_unchecking_allowed())
+ set_checked(!is_checked());
+ else
+ set_checked(true);
+ } else {
+ set_checked(!is_checked());
+ }
+ }
+
+ on_activation(*this);
+ m_activator = nullptr;
+}
+
+void Action::register_button(Badge<Button>, Button& button)
+{
+ m_buttons.set(&button);
+}
+
+void Action::unregister_button(Badge<Button>, Button& button)
+{
+ m_buttons.remove(&button);
+}
+
+void Action::register_menu_item(Badge<MenuItem>, MenuItem& menu_item)
+{
+ m_menu_items.set(&menu_item);
+}
+
+void Action::unregister_menu_item(Badge<MenuItem>, MenuItem& menu_item)
+{
+ m_menu_items.remove(&menu_item);
+}
+
+template<typename Callback>
+void Action::for_each_toolbar_button(Callback callback)
+{
+ for (auto& it : m_buttons)
+ callback(*it);
+}
+
+template<typename Callback>
+void Action::for_each_menu_item(Callback callback)
+{
+ for (auto& it : m_menu_items)
+ callback(*it);
+}
+
+void Action::set_enabled(bool enabled)
+{
+ if (m_enabled == enabled)
+ return;
+ m_enabled = enabled;
+ for_each_toolbar_button([enabled](auto& button) {
+ button.set_enabled(enabled);
+ });
+ for_each_menu_item([enabled](auto& item) {
+ item.set_enabled(enabled);
+ });
+}
+
+void Action::set_checked(bool checked)
+{
+ if (m_checked == checked)
+ return;
+ m_checked = checked;
+
+ if (m_checked && m_action_group) {
+ m_action_group->for_each_action([this](auto& other_action) {
+ if (this == &other_action)
+ return IterationDecision::Continue;
+ if (other_action.is_checkable())
+ other_action.set_checked(false);
+ return IterationDecision::Continue;
+ });
+ }
+
+ for_each_toolbar_button([checked](auto& button) {
+ button.set_checked(checked);
+ });
+ for_each_menu_item([checked](MenuItem& item) {
+ item.set_checked(checked);
+ });
+}
+
+void Action::set_group(Badge<ActionGroup>, ActionGroup* group)
+{
+ m_action_group = AK::try_make_weak_ptr(group);
+}
+
+void Action::set_icon(const Gfx::Bitmap* icon)
+{
+ m_icon = icon;
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Action.h b/Userland/Libraries/LibGUI/Action.h
new file mode 100644
index 0000000000..9b006031bb
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Action.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Badge.h>
+#include <AK/Function.h>
+#include <AK/HashTable.h>
+#include <AK/NonnullRefPtr.h>
+#include <AK/RefCounted.h>
+#include <AK/String.h>
+#include <AK/WeakPtr.h>
+#include <AK/Weakable.h>
+#include <LibCore/Object.h>
+#include <LibGUI/Forward.h>
+#include <LibGUI/Shortcut.h>
+#include <LibGfx/Forward.h>
+
+namespace GUI {
+
+namespace CommonActions {
+NonnullRefPtr<Action> make_about_action(const String& app_name, const Icon& app_icon, Window* parent = nullptr);
+NonnullRefPtr<Action> make_open_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_save_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_save_as_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_undo_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_redo_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_cut_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_copy_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_paste_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_delete_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_move_to_front_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_move_to_back_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_fullscreen_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_quit_action(Function<void(Action&)>);
+NonnullRefPtr<Action> make_help_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_go_back_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_go_forward_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_go_home_action(Function<void(Action&)> callback, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_reload_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+NonnullRefPtr<Action> make_select_all_action(Function<void(Action&)>, Core::Object* parent = nullptr);
+};
+
+class Action final : public Core::Object {
+ C_OBJECT(Action)
+public:
+ enum class ShortcutScope {
+ None,
+ WidgetLocal,
+ WindowLocal,
+ ApplicationGlobal,
+ };
+ static NonnullRefPtr<Action> create(const StringView& text, Function<void(Action&)> callback, Core::Object* parent = nullptr)
+ {
+ return adopt(*new Action(text, move(callback), parent));
+ }
+ static NonnullRefPtr<Action> create(const StringView& text, RefPtr<Gfx::Bitmap>&& icon, Function<void(Action&)> callback, Core::Object* parent = nullptr)
+ {
+ return adopt(*new Action(text, move(icon), move(callback), parent));
+ }
+ static NonnullRefPtr<Action> create(const StringView& text, const Shortcut& shortcut, Function<void(Action&)> callback, Core::Object* parent = nullptr)
+ {
+ return adopt(*new Action(text, shortcut, move(callback), parent));
+ }
+ static NonnullRefPtr<Action> create(const StringView& text, const Shortcut& shortcut, RefPtr<Gfx::Bitmap>&& icon, Function<void(Action&)> callback, Core::Object* parent = nullptr)
+ {
+ return adopt(*new Action(text, shortcut, move(icon), move(callback), parent));
+ }
+ static NonnullRefPtr<Action> create_checkable(const StringView& text, Function<void(Action&)> callback, Core::Object* parent = nullptr)
+ {
+ return adopt(*new Action(text, move(callback), parent, true));
+ }
+ static NonnullRefPtr<Action> create_checkable(const StringView& text, RefPtr<Gfx::Bitmap>&& icon, Function<void(Action&)> callback, Core::Object* parent = nullptr)
+ {
+ return adopt(*new Action(text, move(icon), move(callback), parent, true));
+ }
+ static NonnullRefPtr<Action> create_checkable(const StringView& text, const Shortcut& shortcut, Function<void(Action&)> callback, Core::Object* parent = nullptr)
+ {
+ return adopt(*new Action(text, shortcut, move(callback), parent, true));
+ }
+ static NonnullRefPtr<Action> create_checkable(const StringView& text, const Shortcut& shortcut, RefPtr<Gfx::Bitmap>&& icon, Function<void(Action&)> callback, Core::Object* parent = nullptr)
+ {
+ return adopt(*new Action(text, shortcut, move(icon), move(callback), parent, true));
+ }
+
+ virtual ~Action() override;
+
+ String text() const { return m_text; }
+ void set_text(String text) { m_text = move(text); }
+ Shortcut shortcut() const { return m_shortcut; }
+ const Gfx::Bitmap* icon() const { return m_icon.ptr(); }
+ void set_icon(const Gfx::Bitmap*);
+
+ const Core::Object* activator() const { return m_activator.ptr(); }
+ Core::Object* activator() { return m_activator.ptr(); }
+
+ Function<void(Action&)> on_activation;
+
+ void activate(Core::Object* activator = nullptr);
+
+ bool is_enabled() const { return m_enabled; }
+ void set_enabled(bool);
+
+ bool is_checkable() const { return m_checkable; }
+ void set_checkable(bool checkable) { m_checkable = checkable; }
+
+ bool is_checked() const
+ {
+ ASSERT(is_checkable());
+ return m_checked;
+ }
+ void set_checked(bool);
+
+ bool swallow_key_event_when_disabled() const { return m_swallow_key_event_when_disabled; }
+ void set_swallow_key_event_when_disabled(bool swallow) { m_swallow_key_event_when_disabled = swallow; }
+
+ void register_button(Badge<Button>, Button&);
+ void unregister_button(Badge<Button>, Button&);
+ void register_menu_item(Badge<MenuItem>, MenuItem&);
+ void unregister_menu_item(Badge<MenuItem>, MenuItem&);
+
+ const ActionGroup* group() const { return m_action_group.ptr(); }
+ void set_group(Badge<ActionGroup>, ActionGroup*);
+
+private:
+ Action(const StringView& text, Function<void(Action&)> = nullptr, Core::Object* = nullptr, bool checkable = false);
+ Action(const StringView& text, const Shortcut&, Function<void(Action&)> = nullptr, Core::Object* = nullptr, bool checkable = false);
+ Action(const StringView& text, const Shortcut&, RefPtr<Gfx::Bitmap>&& icon, Function<void(Action&)> = nullptr, Core::Object* = nullptr, bool checkable = false);
+ Action(const StringView& text, RefPtr<Gfx::Bitmap>&& icon, Function<void(Action&)> = nullptr, Core::Object* = nullptr, bool checkable = false);
+
+ template<typename Callback>
+ void for_each_toolbar_button(Callback);
+ template<typename Callback>
+ void for_each_menu_item(Callback);
+
+ String m_text;
+ RefPtr<Gfx::Bitmap> m_icon;
+ Shortcut m_shortcut;
+ bool m_enabled { true };
+ bool m_checkable { false };
+ bool m_checked { false };
+ bool m_swallow_key_event_when_disabled { false };
+ ShortcutScope m_scope { ShortcutScope::None };
+
+ HashTable<Button*> m_buttons;
+ HashTable<MenuItem*> m_menu_items;
+ WeakPtr<ActionGroup> m_action_group;
+ WeakPtr<Core::Object> m_activator;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ActionGroup.cpp b/Userland/Libraries/LibGUI/ActionGroup.cpp
new file mode 100644
index 0000000000..5f8d5d4514
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ActionGroup.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Action.h>
+#include <LibGUI/ActionGroup.h>
+
+namespace GUI {
+
+void ActionGroup::add_action(Action& action)
+{
+ action.set_group({}, this);
+ m_actions.set(&action);
+}
+
+void ActionGroup::remove_action(Action& action)
+{
+ action.set_group({}, nullptr);
+ m_actions.remove(&action);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ActionGroup.h b/Userland/Libraries/LibGUI/ActionGroup.h
new file mode 100644
index 0000000000..fe087714d5
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ActionGroup.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashTable.h>
+#include <AK/Weakable.h>
+#include <LibGUI/Forward.h>
+
+namespace GUI {
+
+class ActionGroup : public Weakable<ActionGroup> {
+public:
+ ActionGroup() { }
+ ~ActionGroup() { }
+
+ void add_action(Action&);
+ void remove_action(Action&);
+
+ bool is_exclusive() const { return m_exclusive; }
+ void set_exclusive(bool exclusive) { m_exclusive = exclusive; }
+
+ bool is_unchecking_allowed() const { return m_unchecking_allowed; }
+ void set_unchecking_allowed(bool unchecking_allowed) { m_unchecking_allowed = unchecking_allowed; }
+
+ template<typename C>
+ void for_each_action(C callback)
+ {
+ for (auto& it : m_actions) {
+ if (callback(*it) == IterationDecision::Break)
+ break;
+ }
+ }
+
+private:
+ HashTable<Action*> m_actions;
+ bool m_exclusive { false };
+ bool m_unchecking_allowed { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Application.cpp b/Userland/Libraries/LibGUI/Application.cpp
new file mode 100644
index 0000000000..fbd0c7260d
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Application.cpp
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/NeverDestroyed.h>
+#include <LibCore/EventLoop.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/Clipboard.h>
+#include <LibGUI/Desktop.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/MenuBar.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/Window.h>
+#include <LibGUI/WindowServerConnection.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+class Application::TooltipWindow final : public Window {
+ C_OBJECT(TooltipWindow);
+
+public:
+ void set_tooltip(String tooltip)
+ {
+ // FIXME: Add some kind of GUI::Label auto-sizing feature.
+ int text_width = m_label->font().width(tooltip);
+ set_rect(rect().x(), rect().y(), text_width + 10, m_label->font().glyph_height() + 8);
+ m_label->set_text(move(tooltip));
+ }
+
+private:
+ TooltipWindow()
+ {
+ set_window_type(WindowType::Tooltip);
+ m_label = set_main_widget<Label>();
+ m_label->set_background_role(Gfx::ColorRole::Tooltip);
+ m_label->set_foreground_role(Gfx::ColorRole::TooltipText);
+ m_label->set_fill_with_background_color(true);
+ m_label->set_frame_thickness(1);
+ m_label->set_frame_shape(Gfx::FrameShape::Container);
+ m_label->set_frame_shadow(Gfx::FrameShadow::Plain);
+ }
+
+ RefPtr<Label> m_label;
+};
+
+static NeverDestroyed<WeakPtr<Application>> s_the;
+
+Application* Application::the()
+{
+ // NOTE: If we don't explicitly call revoke_weak_ptrs() in the
+ // ~Application destructor, we would have to change this to
+ // return s_the->strong_ref().ptr();
+ // This is because this is using the unsafe operator*/operator->
+ // that do not have the ability to check the ref count!
+ return *s_the;
+}
+
+Application::Application(int argc, char** argv)
+{
+ ASSERT(!*s_the);
+ *s_the = *this;
+ m_event_loop = make<Core::EventLoop>();
+ WindowServerConnection::the();
+ Clipboard::initialize({});
+ if (argc > 0)
+ m_invoked_as = argv[0];
+
+ if (getenv("GUI_FOCUS_DEBUG"))
+ m_focus_debugging_enabled = true;
+
+ if (getenv("GUI_DND_DEBUG"))
+ m_dnd_debugging_enabled = true;
+
+ for (int i = 1; i < argc; i++) {
+ String arg(argv[i]);
+ m_args.append(move(arg));
+ }
+
+ m_tooltip_show_timer = Core::Timer::create_single_shot(700, [this] {
+ tooltip_show_timer_did_fire();
+ });
+
+ m_tooltip_hide_timer = Core::Timer::create_single_shot(50, [this] {
+ tooltip_hide_timer_did_fire();
+ });
+}
+
+Application::~Application()
+{
+ revoke_weak_ptrs();
+}
+
+int Application::exec()
+{
+ return m_event_loop->exec();
+}
+
+void Application::quit(int exit_code)
+{
+ m_event_loop->quit(exit_code);
+}
+
+void Application::set_menubar(RefPtr<MenuBar> menubar)
+{
+ if (m_menubar)
+ m_menubar->notify_removed_from_application({});
+ m_menubar = move(menubar);
+ if (m_menubar)
+ m_menubar->notify_added_to_application({});
+}
+
+void Application::register_global_shortcut_action(Badge<Action>, Action& action)
+{
+ m_global_shortcut_actions.set(action.shortcut(), &action);
+}
+
+void Application::unregister_global_shortcut_action(Badge<Action>, Action& action)
+{
+ m_global_shortcut_actions.remove(action.shortcut());
+}
+
+Action* Application::action_for_key_event(const KeyEvent& event)
+{
+ auto it = m_global_shortcut_actions.find(Shortcut(event.modifiers(), (KeyCode)event.key()));
+ if (it == m_global_shortcut_actions.end())
+ return nullptr;
+ return (*it).value;
+}
+
+void Application::show_tooltip(String tooltip, const Widget* tooltip_source_widget)
+{
+ m_tooltip_source_widget = tooltip_source_widget;
+ if (!m_tooltip_window) {
+ m_tooltip_window = TooltipWindow::construct();
+ m_tooltip_window->set_double_buffering_enabled(false);
+ }
+ m_tooltip_window->set_tooltip(move(tooltip));
+
+ if (m_tooltip_window->is_visible()) {
+ tooltip_show_timer_did_fire();
+ m_tooltip_show_timer->stop();
+ m_tooltip_hide_timer->stop();
+ } else {
+ m_tooltip_show_timer->restart();
+ m_tooltip_hide_timer->stop();
+ }
+}
+
+void Application::hide_tooltip()
+{
+ m_tooltip_show_timer->stop();
+ m_tooltip_hide_timer->start();
+}
+
+void Application::did_create_window(Badge<Window>)
+{
+ if (m_event_loop->was_exit_requested())
+ m_event_loop->unquit();
+}
+
+void Application::did_delete_last_window(Badge<Window>)
+{
+ if (m_quit_when_last_window_deleted)
+ m_event_loop->quit(0);
+}
+
+void Application::set_system_palette(SharedBuffer& buffer)
+{
+ if (!m_system_palette)
+ m_system_palette = Gfx::PaletteImpl::create_with_shared_buffer(buffer);
+ else
+ m_system_palette->replace_internal_buffer({}, buffer);
+
+ if (!m_palette)
+ m_palette = m_system_palette;
+}
+
+void Application::set_palette(const Palette& palette)
+{
+ m_palette = palette.impl();
+}
+
+Gfx::Palette Application::palette() const
+{
+ return Palette(*m_palette);
+}
+
+void Application::tooltip_show_timer_did_fire()
+{
+ ASSERT(m_tooltip_window);
+ Gfx::IntRect desktop_rect = Desktop::the().rect();
+
+ const int margin = 30;
+ Gfx::IntPoint adjusted_pos = WindowServerConnection::the().send_sync<Messages::WindowServer::GetGlobalCursorPosition>()->position();
+
+ adjusted_pos.move_by(0, 18);
+
+ if (adjusted_pos.x() + m_tooltip_window->width() >= desktop_rect.width() - margin) {
+ adjusted_pos = adjusted_pos.translated(-m_tooltip_window->width(), 0);
+ }
+ if (adjusted_pos.y() + m_tooltip_window->height() >= desktop_rect.height() - margin) {
+ adjusted_pos = adjusted_pos.translated(0, -(m_tooltip_window->height() * 2));
+ }
+
+ m_tooltip_window->move_to(adjusted_pos);
+ m_tooltip_window->show();
+}
+
+void Application::tooltip_hide_timer_did_fire()
+{
+ m_tooltip_source_widget = nullptr;
+ if (m_tooltip_window)
+ m_tooltip_window->hide();
+}
+
+void Application::window_did_become_active(Badge<Window>, Window& window)
+{
+ m_active_window = window.make_weak_ptr<Window>();
+}
+
+void Application::window_did_become_inactive(Badge<Window>, Window& window)
+{
+ if (m_active_window.ptr() != &window)
+ return;
+ m_active_window = nullptr;
+}
+
+void Application::set_pending_drop_widget(Widget* widget)
+{
+ if (m_pending_drop_widget == widget)
+ return;
+ if (m_pending_drop_widget)
+ m_pending_drop_widget->update();
+ m_pending_drop_widget = widget;
+ if (m_pending_drop_widget)
+ m_pending_drop_widget->update();
+}
+
+void Application::set_drag_hovered_widget_impl(Widget* widget, const Gfx::IntPoint& position, Vector<String> mime_types)
+{
+ if (widget == m_drag_hovered_widget)
+ return;
+
+ if (m_drag_hovered_widget) {
+ Event leave_event(Event::DragLeave);
+ m_drag_hovered_widget->dispatch_event(leave_event, m_drag_hovered_widget->window());
+ }
+
+ set_pending_drop_widget(nullptr);
+ m_drag_hovered_widget = widget;
+
+ if (m_drag_hovered_widget) {
+ DragEvent enter_event(Event::DragEnter, position, move(mime_types));
+ enter_event.ignore();
+ m_drag_hovered_widget->dispatch_event(enter_event, m_drag_hovered_widget->window());
+ if (enter_event.is_accepted())
+ set_pending_drop_widget(m_drag_hovered_widget);
+ }
+}
+
+void Application::notify_drag_cancelled(Badge<WindowServerConnection>)
+{
+ set_drag_hovered_widget_impl(nullptr);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Application.h b/Userland/Libraries/LibGUI/Application.h
new file mode 100644
index 0000000000..4114ef631d
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Application.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/OwnPtr.h>
+#include <AK/String.h>
+#include <AK/WeakPtr.h>
+#include <LibCore/Object.h>
+#include <LibGUI/Forward.h>
+#include <LibGUI/Shortcut.h>
+#include <LibGUI/Widget.h>
+#include <LibGfx/Point.h>
+
+namespace GUI {
+
+class Application : public Core::Object {
+ C_OBJECT(Application);
+
+public:
+ static Application* the();
+
+ ~Application();
+
+ int exec();
+ void quit(int = 0);
+
+ void set_menubar(RefPtr<MenuBar>);
+ Action* action_for_key_event(const KeyEvent&);
+
+ void register_global_shortcut_action(Badge<Action>, Action&);
+ void unregister_global_shortcut_action(Badge<Action>, Action&);
+
+ void show_tooltip(String, const Widget* tooltip_source_widget);
+ void hide_tooltip();
+ Widget* tooltip_source_widget() { return m_tooltip_source_widget; };
+
+ bool quit_when_last_window_deleted() const { return m_quit_when_last_window_deleted; }
+ void set_quit_when_last_window_deleted(bool b) { m_quit_when_last_window_deleted = b; }
+
+ void did_create_window(Badge<Window>);
+ void did_delete_last_window(Badge<Window>);
+
+ const String& invoked_as() const { return m_invoked_as; }
+ const Vector<String>& args() const { return m_args; }
+
+ Gfx::Palette palette() const;
+ void set_palette(const Gfx::Palette&);
+
+ void set_system_palette(SharedBuffer&);
+
+ bool focus_debugging_enabled() const { return m_focus_debugging_enabled; }
+ bool dnd_debugging_enabled() const { return m_dnd_debugging_enabled; }
+
+ Core::EventLoop& event_loop() { return *m_event_loop; }
+
+ Window* active_window() { return m_active_window; }
+ const Window* active_window() const { return m_active_window; }
+
+ void window_did_become_active(Badge<Window>, Window&);
+ void window_did_become_inactive(Badge<Window>, Window&);
+
+ Widget* drag_hovered_widget() { return m_drag_hovered_widget.ptr(); }
+ const Widget* drag_hovered_widget() const { return m_drag_hovered_widget.ptr(); }
+
+ Widget* pending_drop_widget() { return m_pending_drop_widget.ptr(); }
+ const Widget* pending_drop_widget() const { return m_pending_drop_widget.ptr(); }
+
+ void set_drag_hovered_widget(Badge<Window>, Widget* widget, const Gfx::IntPoint& position = {}, Vector<String> mime_types = {})
+ {
+ set_drag_hovered_widget_impl(widget, position, move(mime_types));
+ }
+ void notify_drag_cancelled(Badge<WindowServerConnection>);
+
+private:
+ Application(int argc, char** argv);
+
+ void tooltip_show_timer_did_fire();
+ void tooltip_hide_timer_did_fire();
+
+ void set_drag_hovered_widget_impl(Widget*, const Gfx::IntPoint& = {}, Vector<String> = {});
+ void set_pending_drop_widget(Widget*);
+
+ OwnPtr<Core::EventLoop> m_event_loop;
+ RefPtr<MenuBar> m_menubar;
+ RefPtr<Gfx::PaletteImpl> m_palette;
+ RefPtr<Gfx::PaletteImpl> m_system_palette;
+ HashMap<Shortcut, Action*> m_global_shortcut_actions;
+ class TooltipWindow;
+ RefPtr<Core::Timer> m_tooltip_show_timer;
+ RefPtr<Core::Timer> m_tooltip_hide_timer;
+ RefPtr<TooltipWindow> m_tooltip_window;
+ RefPtr<Widget> m_tooltip_source_widget;
+ WeakPtr<Window> m_active_window;
+ bool m_quit_when_last_window_deleted { true };
+ bool m_focus_debugging_enabled { false };
+ bool m_dnd_debugging_enabled { false };
+ String m_invoked_as;
+ Vector<String> m_args;
+ WeakPtr<Widget> m_drag_hovered_widget;
+ WeakPtr<Widget> m_pending_drop_widget;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/AutocompleteProvider.cpp b/Userland/Libraries/LibGUI/AutocompleteProvider.cpp
new file mode 100644
index 0000000000..383bead624
--- /dev/null
+++ b/Userland/Libraries/LibGUI/AutocompleteProvider.cpp
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/AutocompleteProvider.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/TableView.h>
+#include <LibGUI/TextEditor.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/Bitmap.h>
+
+static RefPtr<Gfx::Bitmap> s_cpp_identifier_icon;
+static RefPtr<Gfx::Bitmap> s_unspecified_identifier_icon;
+
+namespace GUI {
+
+class AutocompleteSuggestionModel final : public GUI::Model {
+public:
+ explicit AutocompleteSuggestionModel(Vector<AutocompleteProvider::Entry>&& suggestions)
+ : m_suggestions(move(suggestions))
+ {
+ }
+
+ enum Column {
+ Icon,
+ Name,
+ __Column_Count,
+ };
+
+ enum InternalRole {
+ __ModelRoleCustom = (int)GUI::ModelRole::Custom,
+ PartialInputLength,
+ Kind,
+ };
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return m_suggestions.size(); }
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return Column::__Column_Count; }
+ virtual GUI::Variant data(const GUI::ModelIndex& index, GUI::ModelRole role) const override
+ {
+ auto& suggestion = m_suggestions.at(index.row());
+ if (role == GUI::ModelRole::Display) {
+ if (index.column() == Column::Name) {
+ return suggestion.completion;
+ }
+ if (index.column() == Column::Icon) {
+ // TODO
+ if (suggestion.language == GUI::AutocompleteProvider::Language::Cpp) {
+ if (!s_cpp_identifier_icon) {
+ s_cpp_identifier_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/completion/cpp-identifier.png");
+ }
+ return *s_cpp_identifier_icon;
+ }
+ if (suggestion.language == GUI::AutocompleteProvider::Language::Unspecified) {
+ if (!s_unspecified_identifier_icon) {
+ s_unspecified_identifier_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/completion/unspecified-identifier.png");
+ }
+ return *s_unspecified_identifier_icon;
+ }
+ return {};
+ }
+ }
+
+ if ((int)role == InternalRole::Kind)
+ return (u32)suggestion.kind;
+
+ if ((int)role == InternalRole::PartialInputLength)
+ return (i64)suggestion.partial_input_length;
+
+ return {};
+ }
+ virtual void update() override {};
+
+ void set_suggestions(Vector<AutocompleteProvider::Entry>&& suggestions) { m_suggestions = move(suggestions); }
+
+private:
+ Vector<AutocompleteProvider::Entry> m_suggestions;
+};
+
+AutocompleteBox::~AutocompleteBox() { }
+
+AutocompleteBox::AutocompleteBox(TextEditor& editor)
+ : m_editor(editor)
+{
+ m_popup_window = GUI::Window::construct(m_editor->window());
+ m_popup_window->set_window_type(GUI::WindowType::Tooltip);
+ m_popup_window->set_rect(0, 0, 200, 100);
+
+ m_suggestion_view = m_popup_window->set_main_widget<GUI::TableView>();
+ m_suggestion_view->set_column_headers_visible(false);
+}
+
+void AutocompleteBox::update_suggestions(Vector<AutocompleteProvider::Entry>&& suggestions)
+{
+ bool has_suggestions = !suggestions.is_empty();
+ if (m_suggestion_view->model()) {
+ auto& model = *static_cast<AutocompleteSuggestionModel*>(m_suggestion_view->model());
+ model.set_suggestions(move(suggestions));
+ } else {
+ m_suggestion_view->set_model(adopt(*new AutocompleteSuggestionModel(move(suggestions))));
+ m_suggestion_view->update();
+ if (has_suggestions)
+ m_suggestion_view->set_cursor(m_suggestion_view->model()->index(0), GUI::AbstractView::SelectionUpdate::Set);
+ }
+
+ m_suggestion_view->model()->update();
+ m_suggestion_view->update();
+ if (!has_suggestions)
+ close();
+}
+
+bool AutocompleteBox::is_visible() const
+{
+ return m_popup_window->is_visible();
+}
+
+void AutocompleteBox::show(Gfx::IntPoint suggstion_box_location)
+{
+ if (!m_suggestion_view->model() || m_suggestion_view->model()->row_count() == 0)
+ return;
+
+ m_popup_window->move_to(suggstion_box_location);
+ if (!is_visible())
+ m_suggestion_view->move_cursor(GUI::AbstractView::CursorMovement::Home, GUI::AbstractTableView::SelectionUpdate::Set);
+ m_popup_window->show();
+}
+
+void AutocompleteBox::close()
+{
+ m_popup_window->hide();
+}
+
+void AutocompleteBox::next_suggestion()
+{
+ GUI::ModelIndex new_index = m_suggestion_view->selection().first();
+ if (new_index.is_valid())
+ new_index = m_suggestion_view->model()->index(new_index.row() + 1);
+ else
+ new_index = m_suggestion_view->model()->index(0);
+
+ if (m_suggestion_view->model()->is_valid(new_index)) {
+ m_suggestion_view->selection().set(new_index);
+ m_suggestion_view->scroll_into_view(new_index, Orientation::Vertical);
+ }
+}
+
+void AutocompleteBox::previous_suggestion()
+{
+ GUI::ModelIndex new_index = m_suggestion_view->selection().first();
+ if (new_index.is_valid())
+ new_index = m_suggestion_view->model()->index(new_index.row() - 1);
+ else
+ new_index = m_suggestion_view->model()->index(0);
+
+ if (m_suggestion_view->model()->is_valid(new_index)) {
+ m_suggestion_view->selection().set(new_index);
+ m_suggestion_view->scroll_into_view(new_index, Orientation::Vertical);
+ }
+}
+
+void AutocompleteBox::apply_suggestion()
+{
+ if (m_editor.is_null())
+ return;
+
+ if (!m_editor->is_editable())
+ return;
+
+ auto selected_index = m_suggestion_view->selection().first();
+ if (!selected_index.is_valid())
+ return;
+
+ auto suggestion_index = m_suggestion_view->model()->index(selected_index.row(), AutocompleteSuggestionModel::Column::Name);
+ auto suggestion = suggestion_index.data().to_string();
+ size_t partial_length = suggestion_index.data((GUI::ModelRole)AutocompleteSuggestionModel::InternalRole::PartialInputLength).to_i64();
+
+ ASSERT(suggestion.length() >= partial_length);
+ auto completion = suggestion.substring_view(partial_length, suggestion.length() - partial_length);
+ m_editor->insert_at_cursor_or_replace_selection(completion);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/AutocompleteProvider.h b/Userland/Libraries/LibGUI/AutocompleteProvider.h
new file mode 100644
index 0000000000..6987f3b0ca
--- /dev/null
+++ b/Userland/Libraries/LibGUI/AutocompleteProvider.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Forward.h>
+#include <LibGUI/TextEditor.h>
+#include <LibGUI/Window.h>
+
+namespace GUI {
+
+class AutocompleteProvider {
+ AK_MAKE_NONCOPYABLE(AutocompleteProvider);
+ AK_MAKE_NONMOVABLE(AutocompleteProvider);
+
+public:
+ virtual ~AutocompleteProvider() { }
+
+ enum class CompletionKind {
+ Identifier,
+ };
+
+ enum class Language {
+ Unspecified,
+ Cpp,
+ };
+
+ struct Entry {
+ String completion;
+ size_t partial_input_length { 0 };
+ CompletionKind kind { CompletionKind::Identifier };
+ Language language { Language::Unspecified };
+ };
+
+ virtual void provide_completions(Function<void(Vector<Entry>)>) = 0;
+
+ void attach(TextEditor& editor)
+ {
+ ASSERT(!m_editor);
+ m_editor = editor;
+ }
+ void detach() { m_editor.clear(); }
+
+protected:
+ AutocompleteProvider() { }
+
+ WeakPtr<TextEditor> m_editor;
+};
+
+class AutocompleteBox final {
+public:
+ explicit AutocompleteBox(TextEditor&);
+ ~AutocompleteBox();
+
+ void update_suggestions(Vector<AutocompleteProvider::Entry>&& suggestions);
+ bool is_visible() const;
+ void show(Gfx::IntPoint suggstion_box_location);
+ void close();
+
+ void next_suggestion();
+ void previous_suggestion();
+ void apply_suggestion();
+
+private:
+ WeakPtr<TextEditor> m_editor;
+ RefPtr<GUI::Window> m_popup_window;
+ RefPtr<GUI::TableView> m_suggestion_view;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/BoxLayout.cpp b/Userland/Libraries/LibGUI/BoxLayout.cpp
new file mode 100644
index 0000000000..fa532db19a
--- /dev/null
+++ b/Userland/Libraries/LibGUI/BoxLayout.cpp
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/JsonObject.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Widget.h>
+#include <LibGfx/Orientation.h>
+#include <stdio.h>
+
+//#define GBOXLAYOUT_DEBUG
+
+REGISTER_WIDGET(GUI, HorizontalBoxLayout)
+REGISTER_WIDGET(GUI, VerticalBoxLayout)
+
+namespace GUI {
+
+BoxLayout::BoxLayout(Orientation orientation)
+ : m_orientation(orientation)
+{
+ register_property(
+ "orientation", [this] { return m_orientation == Gfx::Orientation::Vertical ? "Vertical" : "Horizontal"; }, nullptr);
+}
+
+Gfx::IntSize BoxLayout::preferred_size() const
+{
+ Gfx::IntSize size;
+ size.set_primary_size_for_orientation(orientation(), preferred_primary_size());
+ size.set_secondary_size_for_orientation(orientation(), preferred_secondary_size());
+ return size;
+}
+
+int BoxLayout::preferred_primary_size() const
+{
+ int size = 0;
+
+ for (auto& entry : m_entries) {
+ if (!entry.widget || !entry.widget->is_visible())
+ continue;
+ int min_size = entry.widget->min_size().primary_size_for_orientation(orientation());
+ int max_size = entry.widget->max_size().primary_size_for_orientation(orientation());
+ int preferred_primary_size = -1;
+ if (entry.widget->is_shrink_to_fit() && entry.widget->layout()) {
+ preferred_primary_size = entry.widget->layout()->preferred_size().primary_size_for_orientation(orientation());
+ }
+ int item_size = max(0, preferred_primary_size);
+ item_size = max(min_size, item_size);
+ item_size = min(max_size, item_size);
+ size += item_size + spacing();
+ }
+ if (size > 0)
+ size -= spacing();
+
+ if (orientation() == Gfx::Orientation::Horizontal)
+ size += margins().left() + margins().right();
+ else
+ size += margins().top() + margins().bottom();
+
+ if (!size)
+ return -1;
+ return size;
+}
+
+int BoxLayout::preferred_secondary_size() const
+{
+ int size = 0;
+ for (auto& entry : m_entries) {
+ if (!entry.widget || !entry.widget->is_visible())
+ continue;
+ int min_size = entry.widget->min_size().secondary_size_for_orientation(orientation());
+ int preferred_secondary_size = -1;
+ if (entry.widget->is_shrink_to_fit() && entry.widget->layout()) {
+ preferred_secondary_size = entry.widget->layout()->preferred_size().secondary_size_for_orientation(orientation());
+ size = max(size, preferred_secondary_size);
+ }
+ size = max(min_size, size);
+ }
+
+ if (orientation() == Gfx::Orientation::Horizontal)
+ size += margins().top() + margins().bottom();
+ else
+ size += margins().left() + margins().right();
+
+ if (!size)
+ return -1;
+ return size;
+}
+
+void BoxLayout::run(Widget& widget)
+{
+ if (m_entries.is_empty())
+ return;
+
+ struct Item {
+ Widget* widget { nullptr };
+ int min_size { -1 };
+ int max_size { -1 };
+ int size { 0 };
+ bool final { false };
+ };
+
+ Vector<Item, 32> items;
+
+ for (size_t i = 0; i < m_entries.size(); ++i) {
+ auto& entry = m_entries[i];
+ if (entry.type == Entry::Type::Spacer) {
+ items.append(Item { nullptr, -1, -1 });
+ continue;
+ }
+ if (!entry.widget)
+ continue;
+ if (!entry.widget->is_visible())
+ continue;
+ auto min_size = entry.widget->min_size();
+ auto max_size = entry.widget->max_size();
+
+ if (entry.widget->is_shrink_to_fit() && entry.widget->layout()) {
+ auto preferred_size = entry.widget->layout()->preferred_size();
+ min_size = max_size = preferred_size;
+ }
+
+ items.append(Item { entry.widget.ptr(), min_size.primary_size_for_orientation(orientation()), max_size.primary_size_for_orientation(orientation()) });
+ }
+
+ if (items.is_empty())
+ return;
+
+ int available_size = widget.size().primary_size_for_orientation(orientation()) - spacing() * (items.size() - 1);
+ int unfinished_items = items.size();
+
+ if (orientation() == Gfx::Orientation::Horizontal)
+ available_size -= margins().left() + margins().right();
+ else
+ available_size -= margins().top() + margins().bottom();
+
+ // Pass 1: Set all items to their minimum size.
+ for (auto& item : items) {
+ item.size = 0;
+ if (item.min_size >= 0)
+ item.size = item.min_size;
+ available_size -= item.size;
+
+ if (item.min_size >= 0 && item.max_size >= 0 && item.min_size == item.max_size) {
+ // Fixed-size items finish immediately in the first pass.
+ item.final = true;
+ --unfinished_items;
+ }
+ }
+
+ // Pass 2: Distribute remaining available size evenly, respecting each item's maximum size.
+ while (unfinished_items && available_size > 0) {
+ int slice = available_size / unfinished_items;
+ available_size = 0;
+
+ for (auto& item : items) {
+ if (item.final)
+ continue;
+
+ int item_size_with_full_slice = item.size + slice;
+ item.size = item_size_with_full_slice;
+
+ if (item.max_size >= 0)
+ item.size = min(item.max_size, item_size_with_full_slice);
+
+ // If the slice was more than we needed, return remained to available_size.
+ int remainder_to_give_back = item_size_with_full_slice - item.size;
+ available_size += remainder_to_give_back;
+
+ if (item.max_size >= 0 && item.size == item.max_size) {
+ // We've hit the item's max size. Don't give it any more space.
+ item.final = true;
+ --unfinished_items;
+ }
+ }
+ }
+
+ // Pass 3: Place the widgets.
+ int current_x = margins().left();
+ int current_y = margins().top();
+
+ for (auto& item : items) {
+ Gfx::IntRect rect { current_x, current_y, 0, 0 };
+
+ rect.set_primary_size_for_orientation(orientation(), item.size);
+
+ if (item.widget) {
+ int secondary = widget.size().secondary_size_for_orientation(orientation());
+ if (orientation() == Gfx::Orientation::Horizontal)
+ secondary -= margins().top() + margins().bottom();
+ else
+ secondary -= margins().left() + margins().right();
+
+ int min_secondary = item.widget->min_size().secondary_size_for_orientation(orientation());
+ int max_secondary = item.widget->max_size().secondary_size_for_orientation(orientation());
+ if (min_secondary >= 0)
+ secondary = max(secondary, min_secondary);
+ if (max_secondary >= 0)
+ secondary = min(secondary, max_secondary);
+
+ rect.set_secondary_size_for_orientation(orientation(), secondary);
+
+ if (orientation() == Gfx::Orientation::Horizontal)
+ rect.center_vertically_within(widget.rect());
+ else
+ rect.center_horizontally_within(widget.rect());
+
+ item.widget->set_relative_rect(rect);
+ }
+
+ if (orientation() == Gfx::Orientation::Horizontal)
+ current_x += rect.width() + spacing();
+ else
+ current_y += rect.height() + spacing();
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/BoxLayout.h b/Userland/Libraries/LibGUI/BoxLayout.h
new file mode 100644
index 0000000000..6f1ac615b5
--- /dev/null
+++ b/Userland/Libraries/LibGUI/BoxLayout.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Forward.h>
+#include <LibGUI/Layout.h>
+#include <LibGfx/Orientation.h>
+
+namespace GUI {
+
+class BoxLayout : public Layout {
+ C_OBJECT(BoxLayout);
+
+public:
+ virtual ~BoxLayout() override { }
+
+ Gfx::Orientation orientation() const { return m_orientation; }
+
+ virtual void run(Widget&) override;
+ virtual Gfx::IntSize preferred_size() const override;
+
+protected:
+ explicit BoxLayout(Gfx::Orientation);
+
+private:
+ int preferred_primary_size() const;
+ int preferred_secondary_size() const;
+
+ Gfx::Orientation m_orientation;
+};
+
+class VerticalBoxLayout final : public BoxLayout {
+ C_OBJECT(VerticalBoxLayout);
+
+public:
+ explicit VerticalBoxLayout()
+ : BoxLayout(Gfx::Orientation::Vertical)
+ {
+ }
+ virtual ~VerticalBoxLayout() override { }
+};
+
+class HorizontalBoxLayout final : public BoxLayout {
+ C_OBJECT(HorizontalBoxLayout);
+
+public:
+ explicit HorizontalBoxLayout()
+ : BoxLayout(Gfx::Orientation::Horizontal)
+ {
+ }
+ virtual ~HorizontalBoxLayout() override { }
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/BreadcrumbBar.cpp b/Userland/Libraries/LibGUI/BreadcrumbBar.cpp
new file mode 100644
index 0000000000..9df961dc7d
--- /dev/null
+++ b/Userland/Libraries/LibGUI/BreadcrumbBar.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/BreadcrumbBar.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Palette.h>
+
+REGISTER_WIDGET(GUI, BreadcrumbBar)
+
+namespace GUI {
+
+class BreadcrumbButton : public Button {
+ C_OBJECT(BreadcrumbButton);
+
+public:
+ virtual ~BreadcrumbButton() override { }
+
+ virtual bool is_uncheckable() const override { return false; }
+ virtual void drop_event(DropEvent& event) override
+ {
+ if (on_drop)
+ on_drop(event);
+ }
+
+ virtual void drag_enter_event(DragEvent& event) override
+ {
+ update();
+ if (on_drag_enter)
+ on_drag_enter(event);
+ }
+
+ virtual void drag_leave_event(Event&) override
+ {
+ update();
+ }
+
+ virtual void paint_event(PaintEvent& event) override
+ {
+ Button::paint_event(event);
+ if (has_pending_drop()) {
+ Painter painter(*this);
+ painter.draw_rect(rect(), palette().selection(), true);
+ }
+ }
+
+ Function<void(DropEvent&)> on_drop;
+ Function<void(DragEvent&)> on_drag_enter;
+
+private:
+ BreadcrumbButton() { }
+};
+
+BreadcrumbBar::BreadcrumbBar()
+{
+ auto& layout = set_layout<HorizontalBoxLayout>();
+ layout.set_spacing(0);
+}
+
+BreadcrumbBar::~BreadcrumbBar()
+{
+}
+
+void BreadcrumbBar::clear_segments()
+{
+ m_segments.clear();
+ remove_all_children();
+}
+
+void BreadcrumbBar::append_segment(const String& text, const Gfx::Bitmap* icon, const String& data)
+{
+ auto& button = add<BreadcrumbButton>();
+ button.set_button_style(Gfx::ButtonStyle::CoolBar);
+ button.set_text(text);
+ button.set_icon(icon);
+ button.set_focus_policy(FocusPolicy::TabFocus);
+ button.set_checkable(true);
+ button.set_exclusive(true);
+ button.on_click = [this, index = m_segments.size()](auto) {
+ if (on_segment_click)
+ on_segment_click(index);
+ };
+ button.on_drop = [this, index = m_segments.size()](auto& drop_event) {
+ if (on_segment_drop)
+ on_segment_drop(index, drop_event);
+ };
+ button.on_drag_enter = [this, index = m_segments.size()](auto& event) {
+ if (on_segment_drag_enter)
+ on_segment_drag_enter(index, event);
+ };
+
+ auto button_text_width = button.font().width(text);
+ auto icon_width = icon ? icon->width() : 0;
+ auto icon_padding = icon ? 4 : 0;
+ button.set_fixed_size(button_text_width + icon_width + icon_padding + 16, 16 + 8);
+
+ Segment segment { icon, text, data, button.make_weak_ptr<GUI::Button>() };
+
+ m_segments.append(move(segment));
+}
+
+void BreadcrumbBar::set_selected_segment(Optional<size_t> index)
+{
+ if (!index.has_value()) {
+ for_each_child_of_type<GUI::AbstractButton>([&](auto& button) {
+ button.set_checked(false);
+ return IterationDecision::Continue;
+ });
+ return;
+ }
+
+ auto& segment = m_segments[index.value()];
+ ASSERT(segment.button);
+ segment.button->set_checked(true);
+}
+
+void BreadcrumbBar::doubleclick_event(MouseEvent& event)
+{
+ if (on_doubleclick)
+ on_doubleclick(event);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/BreadcrumbBar.h b/Userland/Libraries/LibGUI/BreadcrumbBar.h
new file mode 100644
index 0000000000..2cba06ce66
--- /dev/null
+++ b/Userland/Libraries/LibGUI/BreadcrumbBar.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class BreadcrumbBar : public GUI::Widget {
+ C_OBJECT(BreadcrumbBar);
+
+public:
+ virtual ~BreadcrumbBar() override;
+
+ void clear_segments();
+ void append_segment(const String& text, const Gfx::Bitmap* icon = nullptr, const String& data = {});
+
+ size_t segment_count() const { return m_segments.size(); }
+ String segment_data(size_t index) const { return m_segments[index].data; }
+
+ void set_selected_segment(Optional<size_t> index);
+ Optional<size_t> selected_segment() const { return m_selected_segment; }
+
+ Function<void(size_t index)> on_segment_click;
+ Function<void(size_t index, DropEvent&)> on_segment_drop;
+ Function<void(size_t index, DragEvent&)> on_segment_drag_enter;
+ Function<void(MouseEvent& event)> on_doubleclick;
+
+private:
+ BreadcrumbBar();
+
+ struct Segment {
+ RefPtr<const Gfx::Bitmap> icon;
+ String text;
+ String data;
+ WeakPtr<GUI::Button> button;
+ };
+
+ Vector<Segment> m_segments;
+ Optional<size_t> m_selected_segment;
+
+ virtual void doubleclick_event(GUI::MouseEvent&) override;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Button.cpp b/Userland/Libraries/LibGUI/Button.cpp
new file mode 100644
index 0000000000..94f7db79ae
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Button.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/ActionGroup.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+REGISTER_WIDGET(GUI, Button)
+
+namespace GUI {
+
+Button::Button(String text)
+ : AbstractButton(move(text))
+{
+ set_min_width(32);
+ set_fixed_height(22);
+ set_focus_policy(GUI::FocusPolicy::StrongFocus);
+
+ REGISTER_ENUM_PROPERTY(
+ "button_style", button_style, set_button_style, Gfx::ButtonStyle,
+ { Gfx::ButtonStyle::Normal, "Normal" },
+ { Gfx::ButtonStyle::CoolBar, "CoolBar" });
+}
+
+Button::~Button()
+{
+ if (m_action)
+ m_action->unregister_button({}, *this);
+}
+
+void Button::paint_event(PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ Gfx::StylePainter::paint_button(painter, rect(), palette(), m_button_style, is_being_pressed(), is_hovered(), is_checked(), is_enabled(), is_focused());
+
+ if (text().is_empty() && !m_icon)
+ return;
+
+ auto content_rect = rect().shrunken(8, 2);
+ auto icon_location = m_icon ? content_rect.center().translated(-(m_icon->width() / 2), -(m_icon->height() / 2)) : Gfx::IntPoint();
+ if (m_icon && !text().is_empty())
+ icon_location.set_x(content_rect.x());
+
+ if (is_being_pressed() || is_checked())
+ painter.translate(1, 1);
+ else if (m_icon && is_enabled() && is_hovered() && button_style() == Gfx::ButtonStyle::CoolBar) {
+ auto shadow_color = palette().button().darkened(0.7f);
+ painter.blit_filtered(icon_location.translated(1, 1), *m_icon, m_icon->rect(), [&shadow_color](auto) {
+ return shadow_color;
+ });
+ icon_location.move_by(-1, -1);
+ }
+
+ if (m_icon) {
+ if (is_enabled()) {
+ if (is_hovered())
+ painter.blit_brightened(icon_location, *m_icon, m_icon->rect());
+ else
+ painter.blit(icon_location, *m_icon, m_icon->rect());
+ } else {
+ painter.blit_disabled(icon_location, *m_icon, m_icon->rect(), palette());
+ }
+ }
+ auto& font = is_checked() ? Gfx::FontDatabase::default_bold_font() : this->font();
+ if (m_icon && !text().is_empty()) {
+ content_rect.move_by(m_icon->width() + 4, 0);
+ content_rect.set_width(content_rect.width() - m_icon->width() - 4);
+ }
+
+ Gfx::IntRect text_rect { 0, 0, font.width(text()), font.glyph_height() };
+ if (text_rect.width() > content_rect.width())
+ text_rect.set_width(content_rect.width());
+ text_rect.align_within(content_rect, text_alignment());
+ paint_text(painter, text_rect, font, text_alignment());
+
+ if (is_focused()) {
+ Gfx::IntRect focus_rect;
+ if (m_icon && !text().is_empty())
+ focus_rect = text_rect.inflated(6, 6);
+ else
+ focus_rect = rect().shrunken(8, 8);
+ painter.draw_focus_rect(focus_rect, palette().focus_outline());
+ }
+}
+
+void Button::click(unsigned modifiers)
+{
+ if (!is_enabled())
+ return;
+
+ NonnullRefPtr protector = *this;
+
+ if (is_checkable()) {
+ if (is_checked() && !is_uncheckable())
+ return;
+ set_checked(!is_checked());
+ }
+ if (on_click)
+ on_click(modifiers);
+ if (m_action)
+ m_action->activate(this);
+}
+
+void Button::context_menu_event(ContextMenuEvent& context_menu_event)
+{
+ if (!is_enabled())
+ return;
+ if (on_context_menu_request)
+ on_context_menu_request(context_menu_event);
+}
+
+void Button::set_action(Action& action)
+{
+ m_action = action;
+ action.register_button({}, *this);
+ set_enabled(action.is_enabled());
+ set_checkable(action.is_checkable());
+ if (action.is_checkable())
+ set_checked(action.is_checked());
+}
+
+void Button::set_icon(RefPtr<Gfx::Bitmap>&& icon)
+{
+ if (m_icon == icon)
+ return;
+ m_icon = move(icon);
+ update();
+}
+
+bool Button::is_uncheckable() const
+{
+ if (!m_action)
+ return true;
+ if (!m_action->group())
+ return true;
+ return m_action->group()->is_unchecking_allowed();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Button.h b/Userland/Libraries/LibGUI/Button.h
new file mode 100644
index 0000000000..ae3c416be3
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Button.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <LibGUI/AbstractButton.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/StylePainter.h>
+#include <LibGfx/TextAlignment.h>
+
+namespace GUI {
+
+class Button : public AbstractButton {
+ C_OBJECT(Button);
+
+public:
+ virtual ~Button() override;
+
+ void set_icon(RefPtr<Gfx::Bitmap>&&);
+ const Gfx::Bitmap* icon() const { return m_icon.ptr(); }
+ Gfx::Bitmap* icon() { return m_icon.ptr(); }
+
+ void set_text_alignment(Gfx::TextAlignment text_alignment) { m_text_alignment = text_alignment; }
+ Gfx::TextAlignment text_alignment() const { return m_text_alignment; }
+
+ Function<void(unsigned modifiers)> on_click;
+ Function<void(const ContextMenuEvent&)> on_context_menu_request;
+
+ void set_button_style(Gfx::ButtonStyle style) { m_button_style = style; }
+ Gfx::ButtonStyle button_style() const { return m_button_style; }
+
+ virtual void click(unsigned modifiers = 0) override;
+ virtual void context_menu_event(ContextMenuEvent&) override;
+
+ void set_action(Action&);
+
+ virtual bool is_uncheckable() const override;
+
+protected:
+ explicit Button(String text = {});
+ virtual void paint_event(PaintEvent&) override;
+
+private:
+ RefPtr<Gfx::Bitmap> m_icon;
+ Gfx::ButtonStyle m_button_style { Gfx::ButtonStyle::Normal };
+ Gfx::TextAlignment m_text_alignment { Gfx::TextAlignment::Center };
+ WeakPtr<Action> m_action;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/CMakeLists.txt b/Userland/Libraries/LibGUI/CMakeLists.txt
new file mode 100644
index 0000000000..f5b8bc2f66
--- /dev/null
+++ b/Userland/Libraries/LibGUI/CMakeLists.txt
@@ -0,0 +1,114 @@
+compile_gml(FontPickerDialog.gml FontPickerDialogGML.h font_picker_dialog_gml)
+
+set(SOURCES
+ AboutDialog.cpp
+ AbstractButton.cpp
+ AbstractSlider.cpp
+ AbstractTableView.cpp
+ AbstractView.cpp
+ Action.cpp
+ ActionGroup.cpp
+ Application.cpp
+ AutocompleteProvider.cpp
+ BoxLayout.cpp
+ BreadcrumbBar.cpp
+ Button.cpp
+ Calendar.cpp
+ CheckBox.cpp
+ Clipboard.cpp
+ ColorInput.cpp
+ ColorPicker.cpp
+ ColumnsView.cpp
+ ComboBox.cpp
+ Command.cpp
+ ControlBoxButton.cpp
+ CppSyntaxHighlighter.cpp
+ Desktop.cpp
+ Dialog.cpp
+ DisplayLink.cpp
+ DragOperation.cpp
+ EditingEngine.cpp
+ EmojiInputDialog.cpp
+ Event.cpp
+ FileIconProvider.cpp
+ FilePicker.cpp
+ FileSystemModel.cpp
+ FilteringProxyModel.cpp
+ FontPicker.cpp
+ FontPickerDialogGML.h
+ Frame.cpp
+ GMLFormatter.cpp
+ GMLLexer.cpp
+ GMLParser.cpp
+ GMLSyntaxHighlighter.cpp
+ GroupBox.cpp
+ HeaderView.cpp
+ INILexer.cpp
+ INISyntaxHighlighter.cpp
+ Icon.cpp
+ IconView.cpp
+ ImageWidget.cpp
+ InputBox.cpp
+ JSSyntaxHighlighter.cpp
+ JsonArrayModel.cpp
+ Label.cpp
+ Layout.cpp
+ LazyWidget.cpp
+ LinkLabel.cpp
+ ListView.cpp
+ Menu.cpp
+ MenuBar.cpp
+ MenuItem.cpp
+ MessageBox.cpp
+ Model.cpp
+ ModelIndex.cpp
+ ModelSelection.cpp
+ MultiView.cpp
+ Notification.cpp
+ OpacitySlider.cpp
+ Painter.cpp
+ ProcessChooser.cpp
+ ProgressBar.cpp
+ RadioButton.cpp
+ RegularEditingEngine.cpp
+ ResizeCorner.cpp
+ RunningProcessesModel.cpp
+ ScrollBar.cpp
+ ScrollableWidget.cpp
+ SeparatorWidget.cpp
+ ShellSyntaxHighlighter.cpp
+ Shortcut.cpp
+ Slider.cpp
+ SortingProxyModel.cpp
+ SpinBox.cpp
+ Splitter.cpp
+ StackWidget.cpp
+ StatusBar.cpp
+ SyntaxHighlighter.cpp
+ TabWidget.cpp
+ TableView.cpp
+ TextBox.cpp
+ TextDocument.cpp
+ TextEditor.cpp
+ ToolBar.cpp
+ ToolBarContainer.cpp
+ TreeView.cpp
+ UndoStack.cpp
+ Variant.cpp
+ VimEditingEngine.cpp
+ Widget.cpp
+ Window.cpp
+ WindowServerConnection.cpp
+)
+
+set(GENERATED_SOURCES
+ ../../Services/WindowServer/WindowClientEndpoint.h
+ ../../Services/WindowServer/WindowServerEndpoint.h
+ ../../Services/NotificationServer/NotificationClientEndpoint.h
+ ../../Services/NotificationServer/NotificationServerEndpoint.h
+ ../../Services/Clipboard/ClipboardClientEndpoint.h
+ ../../Services/Clipboard/ClipboardServerEndpoint.h
+)
+
+serenity_lib(LibGUI gui)
+target_link_libraries(LibGUI LibCore LibGfx LibIPC LibThread LibCpp LibShell LibRegex LibJS)
diff --git a/Userland/Libraries/LibGUI/Calendar.cpp b/Userland/Libraries/LibGUI/Calendar.cpp
new file mode 100644
index 0000000000..6ca093e397
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Calendar.cpp
@@ -0,0 +1,371 @@
+/*
+ * Copyright (c) 2019-2020, Ryan Grieb <ryan.m.grieb@gmail.com>
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/DateTime.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/Calendar.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+static const char* long_day_names[] = {
+ "Sunday", "Monday", "Tuesday", "Wednesday",
+ "Thursday", "Friday", "Saturday"
+};
+
+static const char* short_day_names[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+static const char* mini_day_names[] = { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" };
+static const char* micro_day_names[] = { "S", "M", "T", "W", "T", "F", "S" };
+
+static const char* long_month_names[] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+};
+
+static const char* short_month_names[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+Calendar::Calendar(Core::DateTime date_time)
+ : m_selected_date(date_time)
+ , m_selected_year(date_time.year())
+ , m_selected_month(date_time.month())
+{
+ set_fill_with_background_color(true);
+ set_layout<GUI::VerticalBoxLayout>();
+ layout()->set_spacing(0);
+
+ m_day_name_container = add<GUI::Widget>();
+ m_day_name_container->set_layout<GUI::HorizontalBoxLayout>();
+ m_day_name_container->set_fixed_height(16);
+ m_day_name_container->layout()->set_spacing(0);
+ m_day_name_container->set_fill_with_background_color(true);
+ m_day_name_container->set_background_role(Gfx::ColorRole::HoverHighlight);
+ for (auto& day : m_day_names) {
+ day = m_day_name_container->add<GUI::Label>();
+ day->set_font(Gfx::FontDatabase::default_bold_font());
+ }
+
+ m_calendar_tile_container = add<GUI::Widget>();
+ m_calendar_tile_container->set_layout<GUI::VerticalBoxLayout>();
+ m_calendar_tile_container->layout()->set_spacing(0);
+
+ for (auto& row : m_week_rows) {
+ row = m_calendar_tile_container->add<GUI::Widget>();
+ row->set_layout<GUI::HorizontalBoxLayout>();
+ row->layout()->set_spacing(0);
+ }
+
+ int i = 0;
+ for (int j = 0; j < 6; j++)
+ for (int k = 0; k < 7; k++) {
+ m_calendar_tiles[i] = m_week_rows[j]->add<CalendarTile>(i, date_time);
+ m_calendar_tiles[i]->on_click = [this](int index) {
+ m_previous_selected_date = m_selected_date;
+ m_selected_date = m_calendar_tiles[index]->get_date_time();
+ update_tiles(m_selected_date.year(), m_selected_date.month());
+ if (on_calendar_tile_click)
+ on_calendar_tile_click();
+ };
+ m_calendar_tiles[i]->on_doubleclick = [this](int index) {
+ if (m_calendar_tiles[index]->get_date_time().day() != m_previous_selected_date.day())
+ return;
+ if (on_calendar_tile_doubleclick)
+ on_calendar_tile_doubleclick();
+ };
+ i++;
+ }
+
+ m_month_tile_container = add<GUI::Widget>();
+ m_month_tile_container->set_visible(false);
+ m_month_tile_container->set_layout<GUI::VerticalBoxLayout>();
+ m_month_tile_container->set_fill_with_background_color(true);
+ m_month_tile_container->set_background_role(Gfx::ColorRole::HoverHighlight);
+ m_month_tile_container->layout()->set_spacing(0);
+
+ for (auto& row : m_month_rows) {
+ row = m_month_tile_container->add<GUI::Widget>();
+ row->set_layout<GUI::HorizontalBoxLayout>();
+ row->layout()->set_spacing(0);
+ }
+
+ i = 0;
+ for (int j = 0; j < 3; j++)
+ for (int k = 0; k < 4; k++) {
+ m_month_tiles[i] = m_month_rows[j]->add<MonthTile>(i, date_time);
+ m_month_tiles[i]->set_button_style(Gfx::ButtonStyle::CoolBar);
+ m_month_tiles[i]->on_indexed_click = [this](int index) {
+ toggle_mode();
+ update_tiles(m_month_tiles[index]->get_date_time().year(), m_month_tiles[index]->get_date_time().month());
+ if (on_month_tile_click)
+ on_month_tile_click();
+ };
+ i++;
+ }
+
+ update_tiles(selected_year(), selected_month());
+}
+
+Calendar::~Calendar()
+{
+}
+
+void Calendar::toggle_mode()
+{
+ m_mode == Month ? m_mode = Year : m_mode = Month;
+
+ if (mode() == Month) {
+ m_day_name_container->set_visible(true);
+ m_calendar_tile_container->set_visible(true);
+ m_month_tile_container->set_visible(false);
+ } else {
+ m_day_name_container->set_visible(false);
+ m_calendar_tile_container->set_visible(false);
+ m_month_tile_container->set_visible(true);
+ }
+
+ this->resize(this->height(), this->width());
+ update_tiles(selected_year(), selected_month());
+}
+
+void Calendar::set_grid(bool grid)
+{
+ if (m_grid == grid)
+ return;
+
+ m_grid = grid;
+
+ for (int i = 0; i < 42; i++) {
+ m_calendar_tiles[i]->set_grid(grid);
+ m_calendar_tiles[i]->update();
+ }
+}
+
+void Calendar::resize_event(GUI::ResizeEvent& event)
+{
+ if (m_day_name_container->is_visible()) {
+ for (int i = 0; i < 7; i++) {
+ if (event.size().width() < 120)
+ m_day_names[i]->set_text(micro_day_names[i]);
+ else if (event.size().width() < 200)
+ m_day_names[i]->set_text(mini_day_names[i]);
+ else if (event.size().width() < 480)
+ m_day_names[i]->set_text(short_day_names[i]);
+ else
+ m_day_names[i]->set_text(long_day_names[i]);
+ }
+ }
+
+ if (m_month_tile_container->is_visible()) {
+ for (int i = 0; i < 12; i++) {
+ if (event.size().width() < 250)
+ m_month_tiles[i]->set_text(short_month_names[i]);
+ else
+ m_month_tiles[i]->set_text(long_month_names[i]);
+ }
+ }
+
+ (event.size().width() < 200) ? set_grid(false) : set_grid(true);
+}
+
+void Calendar::update_tiles(unsigned int target_year, unsigned int target_month)
+{
+ set_selected_calendar(target_year, target_month);
+ if (mode() == Month) {
+ unsigned int i = 0;
+ for (int y = 0; y < 6; y++)
+ for (int x = 0; x < 7; x++) {
+ auto date_time = Core::DateTime::create(target_year, target_month, 1);
+ unsigned int start_of_month = date_time.weekday();
+ unsigned int year;
+ unsigned int month;
+ unsigned int day;
+
+ if (start_of_month > i) {
+ month = (target_month - 1 == 0) ? 12 : target_month - 1;
+ year = (month == 12) ? target_year - 1 : target_year;
+ date_time.set_time(year, month, 1);
+ day = (date_time.days_in_month() - (start_of_month) + i) + 1;
+ date_time.set_time(year, month, day);
+ } else if ((i - start_of_month) + 1 > date_time.days_in_month()) {
+ month = (target_month + 1) > 12 ? 1 : target_month + 1;
+ year = (month == 1) ? target_year + 1 : target_year;
+ day = ((i - start_of_month) + 1) - date_time.days_in_month();
+ date_time.set_time(year, month, day);
+ } else {
+ month = target_month;
+ year = target_year;
+ day = (i - start_of_month) + 1;
+ date_time.set_time(year, month, day);
+ }
+
+ m_calendar_tiles[i]->update_values(i, date_time);
+ m_calendar_tiles[i]->set_selected(date_time.year() == m_selected_date.year() && date_time.month() == m_selected_date.month() && date_time.day() == m_selected_date.day());
+ m_calendar_tiles[i]->set_outside_selection(date_time.month() != selected_month() || date_time.year() != selected_year());
+ m_calendar_tiles[i]->update();
+ i++;
+ }
+ } else {
+ for (int i = 0; i < 12; i++) {
+ auto date_time = Core::DateTime::create(target_year, i + 1, 1);
+ m_month_tiles[i]->update_values(date_time);
+ }
+ }
+}
+
+const String Calendar::selected_calendar_text(bool long_names)
+{
+ if (mode() == Month)
+ return String::formatted("{} {}", long_names ? long_month_names[m_selected_month - 1] : short_month_names[m_selected_month - 1], m_selected_year);
+ else
+ return String::number(m_selected_year);
+}
+
+void Calendar::set_selected_calendar(unsigned int year, unsigned int month)
+{
+ m_selected_year = year;
+ m_selected_month = month;
+}
+
+Calendar::MonthTile::MonthTile(int index, Core::DateTime date_time)
+ : m_index(index)
+ , m_date_time(date_time)
+{
+}
+
+Calendar::MonthTile::~MonthTile()
+{
+}
+
+void Calendar::MonthTile::mouseup_event(GUI::MouseEvent& event)
+{
+ if (on_indexed_click)
+ on_indexed_click(m_index);
+
+ GUI::Button::mouseup_event(event);
+}
+
+Calendar::CalendarTile::CalendarTile(int index, Core::DateTime date_time)
+{
+ set_frame_thickness(0);
+ update_values(index, date_time);
+}
+
+void Calendar::CalendarTile::update_values(int index, Core::DateTime date_time)
+{
+ m_index = index;
+ m_date_time = date_time;
+ m_display_date = (m_date_time.day() == 1) ? String::formatted("{} {}", short_month_names[m_date_time.month() - 1], m_date_time.day()) : String::number(m_date_time.day());
+}
+
+Calendar::CalendarTile::~CalendarTile()
+{
+}
+
+void Calendar::CalendarTile::doubleclick_event(GUI::MouseEvent&)
+{
+ if (on_doubleclick)
+ on_doubleclick(m_index);
+}
+
+void Calendar::CalendarTile::mousedown_event(GUI::MouseEvent&)
+{
+ if (on_click)
+ on_click(m_index);
+}
+void Calendar::CalendarTile::enter_event(Core::Event&)
+{
+ m_hovered = true;
+ update();
+}
+
+void Calendar::CalendarTile::leave_event(Core::Event&)
+{
+ m_hovered = false;
+ update();
+}
+
+bool Calendar::CalendarTile::is_today() const
+{
+ auto current_date_time = Core::DateTime::now();
+ return m_date_time.day() == current_date_time.day() && m_date_time.month() == current_date_time.month() && m_date_time.year() == current_date_time.year();
+}
+
+void Calendar::CalendarTile::paint_event(GUI::PaintEvent& event)
+{
+ GUI::Frame::paint_event(event);
+
+ GUI::Painter painter(*this);
+ painter.add_clip_rect(frame_inner_rect());
+
+ if (is_hovered() || is_selected())
+ painter.fill_rect(frame_inner_rect(), palette().hover_highlight());
+ else
+ painter.fill_rect(frame_inner_rect(), palette().base());
+
+ if (m_index < 7)
+ painter.draw_line(frame_inner_rect().top_left(), frame_inner_rect().top_right(), Color::NamedColor::Black);
+ if (!((m_index + 1) % 7 == 0) && has_grid())
+ painter.draw_line(frame_inner_rect().top_right(), frame_inner_rect().bottom_right(), Color::NamedColor::Black);
+ if (m_index < 35 && has_grid())
+ painter.draw_line(frame_inner_rect().bottom_left(), frame_inner_rect().bottom_right(), Color::NamedColor::Black);
+
+ Gfx::IntRect day_rect;
+ if (has_grid()) {
+ day_rect = Gfx::IntRect(frame_inner_rect().x(), frame_inner_rect().y(), frame_inner_rect().width(), font().glyph_height() + 4);
+ day_rect.set_y(frame_inner_rect().y() + 4);
+ } else {
+ day_rect = Gfx::IntRect(frame_inner_rect());
+ }
+
+ int highlight_rect_width = (font().glyph_width('0') * (m_display_date.length() + 1)) + 2;
+ auto display_date = (m_date_time.day() == 1 && frame_inner_rect().width() > highlight_rect_width) ? m_display_date : String::number(m_date_time.day());
+
+ if (is_today()) {
+ if (has_grid()) {
+ auto highlight_rect = Gfx::IntRect(day_rect.width() / 2 - (highlight_rect_width / 2), day_rect.y(), highlight_rect_width, font().glyph_height() + 4);
+ painter.draw_rect(highlight_rect, palette().base_text());
+ } else if (is_selected()) {
+ painter.draw_rect(frame_inner_rect(), palette().base_text());
+ }
+ painter.draw_text(day_rect, display_date, Gfx::FontDatabase::default_bold_font(), Gfx::TextAlignment::Center, palette().base_text());
+ } else if (is_outside_selection()) {
+ painter.draw_text(day_rect, display_date, Gfx::FontDatabase::default_font(), Gfx::TextAlignment::Center, Color::LightGray);
+ } else {
+ if (!has_grid() && is_selected())
+ painter.draw_rect(frame_inner_rect(), palette().base_text());
+ painter.draw_text(day_rect, display_date, Gfx::FontDatabase::default_font(), Gfx::TextAlignment::Center, palette().base_text());
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Calendar.h b/Userland/Libraries/LibGUI/Calendar.h
new file mode 100644
index 0000000000..a160665591
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Calendar.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2019-2020, Ryan Grieb <ryan.m.grieb@gmail.com>
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <LibCore/DateTime.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/Frame.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class Calendar final : public GUI::Widget {
+ C_OBJECT(Calendar)
+
+public:
+ enum Mode {
+ Month,
+ Year
+ };
+
+ enum {
+ ShortNames,
+ LongNames
+ };
+
+ Calendar(Core::DateTime);
+ virtual ~Calendar() override;
+
+ unsigned int selected_year() const { return m_selected_year; }
+ unsigned int selected_month() const { return m_selected_month; }
+ const String selected_calendar_text(bool long_names = ShortNames);
+ void update_tiles(unsigned int target_year, unsigned int target_month);
+ void set_selected_calendar(unsigned int year, unsigned int month);
+ void set_selected_date(Core::DateTime date_time) { m_selected_date = date_time; }
+ Core::DateTime selected_date() const { return m_selected_date; }
+ void toggle_mode();
+ void set_grid(bool grid);
+ bool has_grid() { return m_grid; }
+ Mode mode() const { return m_mode; }
+
+ Function<void()> on_calendar_tile_click;
+ Function<void()> on_calendar_tile_doubleclick;
+ Function<void()> on_month_tile_click;
+
+private:
+ virtual void resize_event(GUI::ResizeEvent&) override;
+
+ class CalendarTile final : public GUI::Frame {
+ C_OBJECT(CalendarTile)
+ public:
+ CalendarTile(int index, Core::DateTime m_date_time);
+ void update_values(int index, Core::DateTime date_time);
+ virtual ~CalendarTile() override;
+ bool is_today() const;
+ bool is_hovered() const { return m_hovered; }
+ bool is_selected() const { return m_selected; }
+ void set_selected(bool b) { m_selected = b; }
+ bool is_outside_selection() const { return m_outside_selection; }
+ void set_outside_selection(bool b) { m_outside_selection = b; }
+ bool has_grid() const { return m_grid; }
+ void set_grid(bool b) { m_grid = b; }
+ Core::DateTime get_date_time() { return m_date_time; }
+ Function<void(int index)> on_doubleclick;
+ Function<void(int index)> on_click;
+
+ private:
+ virtual void doubleclick_event(GUI::MouseEvent&) override;
+ virtual void mousedown_event(GUI::MouseEvent&) override;
+ virtual void enter_event(Core::Event&) override;
+ virtual void leave_event(Core::Event&) override;
+ virtual void paint_event(GUI::PaintEvent&) override;
+
+ int m_index { 0 };
+ bool m_outside_selection { false };
+ bool m_hovered { false };
+ bool m_selected { false };
+ bool m_grid { true };
+ String m_display_date;
+ Core::DateTime m_date_time;
+ };
+
+ class MonthTile final : public GUI::Button {
+ C_OBJECT(MonthTile)
+ public:
+ MonthTile(int index, Core::DateTime m_date_time);
+ virtual ~MonthTile() override;
+ void update_values(Core::DateTime date_time) { m_date_time = date_time; }
+ Core::DateTime get_date_time() { return m_date_time; }
+ Function<void(int index)> on_indexed_click;
+
+ private:
+ virtual void mouseup_event(GUI::MouseEvent&) override;
+
+ int m_index { 0 };
+ Core::DateTime m_date_time;
+ };
+
+ RefPtr<MonthTile> m_month_tiles[12];
+ RefPtr<CalendarTile> m_calendar_tiles[42];
+ RefPtr<GUI::Label> m_day_names[7];
+ RefPtr<GUI::Widget> m_week_rows[6];
+ RefPtr<GUI::Widget> m_month_rows[3];
+ RefPtr<GUI::Widget> m_month_tile_container;
+ RefPtr<GUI::Widget> m_calendar_tile_container;
+ RefPtr<GUI::Widget> m_day_name_container;
+
+ Core::DateTime m_selected_date;
+ Core::DateTime m_previous_selected_date;
+ unsigned int m_selected_year { 0 };
+ unsigned int m_selected_month { 0 };
+ bool m_grid { true };
+ Mode m_mode { Month };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/CheckBox.cpp b/Userland/Libraries/LibGUI/CheckBox.cpp
new file mode 100644
index 0000000000..b2f4a10456
--- /dev/null
+++ b/Userland/Libraries/LibGUI/CheckBox.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/CheckBox.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/CharacterBitmap.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+REGISTER_WIDGET(GUI, CheckBox)
+
+namespace GUI {
+
+static const int s_box_width = 13;
+static const int s_box_height = 13;
+
+CheckBox::CheckBox(String text)
+ : AbstractButton(move(text))
+{
+ set_min_width(32);
+ set_fixed_height(22);
+}
+
+CheckBox::~CheckBox()
+{
+}
+
+void CheckBox::paint_event(PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ auto text_rect = rect();
+ text_rect.set_left(s_box_width + 4);
+ text_rect.set_width(font().width(text()));
+ text_rect.set_top(height() / 2 - font().glyph_height() / 2);
+ text_rect.set_height(font().glyph_height());
+
+ if (fill_with_background_color())
+ painter.fill_rect(rect(), palette().window());
+
+ if (is_enabled() && is_hovered())
+ painter.fill_rect(rect(), palette().hover_highlight());
+
+ Gfx::IntRect box_rect {
+ 0, height() / 2 - s_box_height / 2 - 1,
+ s_box_width, s_box_height
+ };
+
+ Gfx::StylePainter::paint_check_box(painter, box_rect, palette(), is_enabled(), is_checked(), is_being_pressed());
+
+ paint_text(painter, text_rect, font(), Gfx::TextAlignment::TopLeft);
+
+ if (is_focused())
+ painter.draw_focus_rect(text_rect.inflated(6, 6), palette().focus_outline());
+}
+
+void CheckBox::click(unsigned)
+{
+ if (!is_enabled())
+ return;
+ set_checked(!is_checked());
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/CheckBox.h b/Userland/Libraries/LibGUI/CheckBox.h
new file mode 100644
index 0000000000..733a264321
--- /dev/null
+++ b/Userland/Libraries/LibGUI/CheckBox.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/AbstractButton.h>
+
+namespace GUI {
+
+class CheckBox : public AbstractButton {
+ C_OBJECT(CheckBox);
+
+public:
+ virtual ~CheckBox() override;
+
+ virtual void click(unsigned modifiers = 0) override;
+
+private:
+ explicit CheckBox(String = {});
+
+ // These don't make sense for a check box, so hide them.
+ using AbstractButton::auto_repeat_interval;
+ using AbstractButton::set_auto_repeat_interval;
+
+ virtual void paint_event(PaintEvent&) override;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Clipboard.cpp b/Userland/Libraries/LibGUI/Clipboard.cpp
new file mode 100644
index 0000000000..ff2873d543
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Clipboard.cpp
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Badge.h>
+#include <AK/SharedBuffer.h>
+#include <Clipboard/ClipboardClientEndpoint.h>
+#include <Clipboard/ClipboardServerEndpoint.h>
+#include <LibGUI/Clipboard.h>
+#include <LibIPC/ServerConnection.h>
+
+namespace GUI {
+
+class ClipboardServerConnection : public IPC::ServerConnection<ClipboardClientEndpoint, ClipboardServerEndpoint>
+ , public ClipboardClientEndpoint {
+ C_OBJECT(ClipboardServerConnection);
+
+public:
+ virtual void handshake() override
+ {
+ auto response = send_sync<Messages::ClipboardServer::Greet>();
+ set_my_client_id(response->client_id());
+ }
+
+private:
+ ClipboardServerConnection()
+ : IPC::ServerConnection<ClipboardClientEndpoint, ClipboardServerEndpoint>(*this, "/tmp/portal/clipboard")
+ {
+ }
+ virtual void handle(const Messages::ClipboardClient::ClipboardDataChanged&) override;
+};
+
+Clipboard& Clipboard::the()
+{
+ static Clipboard* s_the;
+ if (!s_the)
+ s_the = new Clipboard;
+ return *s_the;
+}
+
+ClipboardServerConnection* s_connection;
+
+static ClipboardServerConnection& connection()
+{
+ return *s_connection;
+}
+
+void Clipboard::initialize(Badge<Application>)
+{
+ s_connection = &ClipboardServerConnection::construct().leak_ref();
+}
+
+Clipboard::Clipboard()
+{
+}
+
+Clipboard::DataAndType Clipboard::data_and_type() const
+{
+ auto response = connection().send_sync<Messages::ClipboardServer::GetClipboardData>();
+ if (response->shbuf_id() < 0)
+ return {};
+ auto shared_buffer = SharedBuffer::create_from_shbuf_id(response->shbuf_id());
+ if (!shared_buffer) {
+ dbgln("GUI::Clipboard::data() failed to attach to the shared buffer");
+ return {};
+ }
+ if (response->data_size() > shared_buffer->size()) {
+ dbgln("GUI::Clipboard::data() clipping contents size is greater than shared buffer size");
+ return {};
+ }
+ auto data = ByteBuffer::copy(shared_buffer->data<void>(), response->data_size());
+ auto type = response->mime_type();
+ auto metadata = response->metadata().entries();
+ return { data, type, metadata };
+}
+
+void Clipboard::set_data(ReadonlyBytes data, const String& type, const HashMap<String, String>& metadata)
+{
+ auto shared_buffer = SharedBuffer::create_with_size(data.size());
+ if (!shared_buffer) {
+ dbgln("GUI::Clipboard::set_data() failed to create a shared buffer");
+ return;
+ }
+ if (!data.is_empty())
+ memcpy(shared_buffer->data<void>(), data.data(), data.size());
+ shared_buffer->seal();
+ shared_buffer->share_with(connection().server_pid());
+
+ connection().send_sync<Messages::ClipboardServer::SetClipboardData>(shared_buffer->shbuf_id(), data.size(), type, metadata);
+}
+
+void ClipboardServerConnection::handle(const Messages::ClipboardClient::ClipboardDataChanged& message)
+{
+ auto& clipboard = Clipboard::the();
+ if (clipboard.on_change)
+ clipboard.on_change(message.mime_type());
+}
+
+RefPtr<Gfx::Bitmap> Clipboard::bitmap() const
+{
+ auto clipping = data_and_type();
+
+ if (clipping.mime_type != "image/x-serenityos")
+ return nullptr;
+
+ auto width = clipping.metadata.get("width").value_or("0").to_uint();
+ if (!width.has_value() || width.value() == 0)
+ return nullptr;
+
+ auto height = clipping.metadata.get("height").value_or("0").to_uint();
+ if (!height.has_value() || height.value() == 0)
+ return nullptr;
+
+ auto pitch = clipping.metadata.get("pitch").value_or("0").to_uint();
+ if (!pitch.has_value() || pitch.value() == 0)
+ return nullptr;
+
+ auto format = clipping.metadata.get("format").value_or("0").to_uint();
+ if (!format.has_value() || format.value() == 0)
+ return nullptr;
+
+ auto clipping_bitmap = Gfx::Bitmap::create_wrapper((Gfx::BitmapFormat)format.value(), { (int)width.value(), (int)height.value() }, pitch.value(), clipping.data.data());
+ auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, { (int)width.value(), (int)height.value() });
+
+ for (int y = 0; y < clipping_bitmap->height(); ++y) {
+ for (int x = 0; x < clipping_bitmap->width(); ++x) {
+ auto pixel = clipping_bitmap->get_pixel(x, y);
+ bitmap->set_pixel(x, y, pixel);
+ }
+ }
+
+ return bitmap;
+}
+
+void Clipboard::set_bitmap(const Gfx::Bitmap& bitmap)
+{
+ HashMap<String, String> metadata;
+ metadata.set("width", String::number(bitmap.width()));
+ metadata.set("height", String::number(bitmap.height()));
+ metadata.set("format", String::number((int)bitmap.format()));
+ metadata.set("pitch", String::number(bitmap.pitch()));
+ set_data({ bitmap.scanline(0), bitmap.size_in_bytes() }, "image/x-serenityos", metadata);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Clipboard.h b/Userland/Libraries/LibGUI/Clipboard.h
new file mode 100644
index 0000000000..a01f96e509
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Clipboard.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/Function.h>
+#include <AK/HashMap.h>
+#include <AK/String.h>
+#include <LibGUI/Forward.h>
+#include <LibGfx/Forward.h>
+
+namespace GUI {
+
+class Clipboard {
+public:
+ static Clipboard& the();
+
+ ByteBuffer data() const { return data_and_type().data; }
+ String mime_type() const { return data_and_type().mime_type; }
+ void set_data(ReadonlyBytes, const String& mime_type = "text/plain", const HashMap<String, String>& metadata = {});
+
+ void set_plain_text(const String& text)
+ {
+ set_data(text.bytes());
+ }
+
+ void set_bitmap(const Gfx::Bitmap&);
+ RefPtr<Gfx::Bitmap> bitmap() const;
+
+ struct DataAndType {
+ ByteBuffer data;
+ String mime_type;
+ HashMap<String, String> metadata;
+ };
+
+ DataAndType data_and_type() const;
+
+ Function<void(const String& mime_type)> on_change;
+
+ static void initialize(Badge<Application>);
+
+private:
+ Clipboard();
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ColorInput.cpp b/Userland/Libraries/LibGUI/ColorInput.cpp
new file mode 100644
index 0000000000..f37522a1d9
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ColorInput.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/Timer.h>
+#include <LibGUI/ColorInput.h>
+#include <LibGUI/ColorPicker.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/Palette.h>
+
+REGISTER_WIDGET(GUI, ColorInput)
+
+namespace GUI {
+
+ColorInput::ColorInput()
+ : TextEditor(TextEditor::SingleLine)
+{
+ set_min_width(32);
+ set_fixed_height(22);
+ TextEditor::on_change = [this] {
+ auto parsed_color = Color::from_string(text());
+ if (parsed_color.has_value())
+ set_color_without_changing_text(parsed_color.value());
+ };
+
+ REGISTER_STRING_PROPERTY("color_picker_title", color_picker_title, set_color_picker_title);
+ REGISTER_BOOL_PROPERTY("has_alpha_channel", has_alpha_channel, set_color_has_alpha_channel);
+}
+
+ColorInput::~ColorInput()
+{
+}
+
+Gfx::IntRect ColorInput::color_rect() const
+{
+ auto color_box_padding = 3;
+ auto color_box_size = height() - color_box_padding - color_box_padding;
+ return { width() - color_box_size - color_box_padding, color_box_padding, color_box_size, color_box_size };
+}
+
+void ColorInput::set_color_without_changing_text(Color color)
+{
+ if (m_color == color)
+ return;
+ m_color = color;
+ update();
+ if (on_change)
+ on_change();
+}
+
+void ColorInput::set_color(Color color)
+{
+ if (m_color == color)
+ return;
+ set_text(m_color_has_alpha_channel ? color.to_string() : color.to_string_without_alpha());
+};
+
+void ColorInput::mousedown_event(MouseEvent& event)
+{
+ if (event.button() == MouseButton::Left && color_rect().contains(event.position())) {
+ m_may_be_color_rect_click = true;
+ return;
+ }
+
+ TextEditor::mousedown_event(event);
+}
+
+void ColorInput::mouseup_event(MouseEvent& event)
+{
+ if (event.button() == MouseButton::Left) {
+ bool is_color_rect_click = m_may_be_color_rect_click && color_rect().contains(event.position());
+ m_may_be_color_rect_click = false;
+ if (is_color_rect_click) {
+ auto dialog = GUI::ColorPicker::construct(m_color, window(), m_color_picker_title);
+ dialog->set_color_has_alpha_channel(m_color_has_alpha_channel);
+ if (dialog->exec() == GUI::Dialog::ExecOK)
+ set_color(dialog->color());
+ event.accept();
+ return;
+ }
+ }
+ TextEditor::mouseup_event(event);
+}
+
+void ColorInput::mousemove_event(MouseEvent& event)
+{
+ if (color_rect().contains(event.position())) {
+ set_override_cursor(Gfx::StandardCursor::Hand);
+ event.accept();
+ return;
+ } else {
+ set_override_cursor(Gfx::StandardCursor::IBeam);
+ }
+
+ TextEditor::mousemove_event(event);
+}
+
+void ColorInput::paint_event(PaintEvent& event)
+{
+ TextEditor::paint_event(event);
+
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ painter.fill_rect(color_rect(), m_color);
+ painter.draw_rect(color_rect(), Color::Black);
+}
+}
diff --git a/Userland/Libraries/LibGUI/ColorInput.h b/Userland/Libraries/LibGUI/ColorInput.h
new file mode 100644
index 0000000000..1ab774efcf
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ColorInput.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <LibGUI/TextEditor.h>
+
+namespace GUI {
+
+class ColorInput final : public TextEditor {
+ C_OBJECT(ColorInput);
+
+public:
+ virtual ~ColorInput() override;
+
+ bool has_alpha_channel() const { return m_color_has_alpha_channel; }
+ void set_color_has_alpha_channel(bool has_alpha) { m_color_has_alpha_channel = has_alpha; }
+
+ void set_color(Color);
+ Color color() { return m_color; }
+
+ void set_color_picker_title(String title) { m_color_picker_title = move(title); }
+ String color_picker_title() { return m_color_picker_title; }
+
+ Function<void()> on_change;
+
+protected:
+ virtual void mousedown_event(MouseEvent&) override;
+ virtual void mouseup_event(MouseEvent&) override;
+ virtual void mousemove_event(MouseEvent&) override;
+ virtual void paint_event(PaintEvent&) override;
+
+private:
+ ColorInput();
+
+ Gfx::IntRect color_rect() const;
+ void set_color_without_changing_text(Color);
+
+ Color m_color;
+ String m_color_picker_title { "Select color" };
+ bool m_color_has_alpha_channel { true };
+ bool m_may_be_color_rect_click { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ColorPicker.cpp b/Userland/Libraries/LibGUI/ColorPicker.cpp
new file mode 100644
index 0000000000..cfa7287afb
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ColorPicker.cpp
@@ -0,0 +1,724 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/ColorPicker.h>
+#include <LibGUI/Frame.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/SpinBox.h>
+#include <LibGUI/TabWidget.h>
+#include <LibGUI/TextBox.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+class ColorButton : public AbstractButton {
+ C_OBJECT(ColorButton);
+
+public:
+ virtual ~ColorButton() override;
+
+ void set_selected(bool selected);
+ Color color() const { return m_color; }
+
+ Function<void(const Color)> on_click;
+
+protected:
+ virtual void click(unsigned modifiers = 0) override;
+ virtual void doubleclick_event(GUI::MouseEvent&) override;
+ virtual void paint_event(PaintEvent&) override;
+
+private:
+ explicit ColorButton(ColorPicker& picker, Color color = {});
+
+ ColorPicker& m_picker;
+ Color m_color;
+ bool m_selected { false };
+};
+
+class ColorField final : public GUI::Frame {
+ C_OBJECT(ColorField);
+
+public:
+ Function<void(Color)> on_pick;
+ void set_color(Color);
+ void set_hue(double);
+ void set_hue_from_pick(double);
+
+private:
+ ColorField(Color color);
+
+ Color m_color;
+ // save hue separately so full white color doesn't reset it to 0
+ double m_hue;
+
+ RefPtr<Gfx::Bitmap> m_color_bitmap;
+ bool m_being_pressed { false };
+ Gfx::IntPoint m_last_position;
+
+ void create_color_bitmap();
+ void pick_color_at_position(GUI::MouseEvent& event);
+ void recalculate_position();
+
+ virtual void mousedown_event(GUI::MouseEvent&) override;
+ virtual void mouseup_event(GUI::MouseEvent&) override;
+ virtual void mousemove_event(GUI::MouseEvent&) override;
+ virtual void paint_event(GUI::PaintEvent&) override;
+ virtual void resize_event(ResizeEvent&) override;
+};
+
+class ColorSlider final : public GUI::Frame {
+ C_OBJECT(ColorSlider);
+
+public:
+ Function<void(double)> on_pick;
+ void set_value(double);
+
+private:
+ ColorSlider(double value);
+
+ double m_value;
+
+ RefPtr<Gfx::Bitmap> m_color_bitmap;
+ bool m_being_pressed { false };
+ int m_last_position;
+
+ void pick_value_at_position(GUI::MouseEvent& event);
+ void recalculate_position();
+
+ virtual void mousedown_event(GUI::MouseEvent&) override;
+ virtual void mouseup_event(GUI::MouseEvent&) override;
+ virtual void mousemove_event(GUI::MouseEvent&) override;
+ virtual void paint_event(GUI::PaintEvent&) override;
+ virtual void resize_event(ResizeEvent&) override;
+};
+
+class ColorPreview final : public GUI::Widget {
+ C_OBJECT(ColorPreview);
+
+public:
+ void set_color(Color);
+
+private:
+ ColorPreview(Color);
+
+ Color m_color;
+ virtual void paint_event(GUI::PaintEvent&) override;
+};
+
+class CustomColorWidget final : public GUI::Widget {
+ C_OBJECT(CustomColorWidget);
+
+public:
+ Function<void(Color)> on_pick;
+ void set_color(Color);
+
+private:
+ CustomColorWidget(Color);
+
+ RefPtr<ColorField> m_color_field;
+ RefPtr<ColorSlider> m_color_slider;
+};
+
+ColorPicker::ColorPicker(Color color, Window* parent_window, String title)
+ : Dialog(parent_window)
+ , m_color(color)
+{
+ set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/color-chooser.png"));
+ set_title(title);
+ set_resizable(false);
+ resize(458, 326);
+
+ build_ui();
+}
+
+ColorPicker::~ColorPicker()
+{
+}
+
+void ColorPicker::set_color_has_alpha_channel(bool has_alpha)
+{
+ if (m_color_has_alpha_channel == has_alpha)
+ return;
+
+ m_color_has_alpha_channel = has_alpha;
+ update_color_widgets();
+}
+
+void ColorPicker::build_ui()
+{
+ auto& root_container = set_main_widget<Widget>();
+ root_container.set_layout<VerticalBoxLayout>();
+ root_container.layout()->set_margins({ 4, 4, 4, 4 });
+ root_container.set_fill_with_background_color(true);
+
+ auto& tab_widget = root_container.add<GUI::TabWidget>();
+
+ auto& tab_palette = tab_widget.add_tab<Widget>("Palette");
+ tab_palette.set_layout<VerticalBoxLayout>();
+ tab_palette.layout()->set_margins({ 4, 4, 4, 4 });
+ tab_palette.layout()->set_spacing(4);
+
+ build_ui_palette(tab_palette);
+
+ auto& tab_custom_color = tab_widget.add_tab<Widget>("Custom Color");
+ tab_custom_color.set_layout<VerticalBoxLayout>();
+ tab_custom_color.layout()->set_margins({ 4, 4, 4, 4 });
+ tab_custom_color.layout()->set_spacing(4);
+
+ build_ui_custom(tab_custom_color);
+
+ auto& button_container = root_container.add<Widget>();
+ button_container.set_fixed_height(22);
+ button_container.set_layout<HorizontalBoxLayout>();
+ button_container.layout()->set_spacing(4);
+ button_container.layout()->add_spacer();
+
+ auto& ok_button = button_container.add<Button>();
+ ok_button.set_fixed_width(80);
+ ok_button.set_text("OK");
+ ok_button.on_click = [this](auto) {
+ done(ExecOK);
+ };
+
+ auto& cancel_button = button_container.add<Button>();
+ cancel_button.set_fixed_width(80);
+ cancel_button.set_text("Cancel");
+ cancel_button.on_click = [this](auto) {
+ done(ExecCancel);
+ };
+}
+
+void ColorPicker::build_ui_palette(Widget& root_container)
+{
+ unsigned colors[4][9] = {
+ { 0xef2929, 0xf0b143, 0xfce94f, 0x9fe13a, 0x7c9ece, 0xa680a8, 0xe1ba70, 0x888a85, 0xeeeeec },
+ { 0xba1e09, 0xf57900, 0xe9d51a, 0x8bd121, 0x4164a3, 0x6f517b, 0xb77f19, 0x555753, 0xd4d7cf },
+ { 0x961605, 0xbf600c, 0xe9d51a, 0x619910, 0x2b4986, 0x573666, 0x875b09, 0x2f3436, 0xbbbdb6 },
+ { 0x000000, 0x2f3436, 0x555753, 0x808080, 0xbabdb6, 0xd3d7cf, 0xeeeeec, 0xf3f3f3, 0xffffff }
+ };
+
+ for (int r = 0; r < 4; r++) {
+ auto& colors_row = root_container.add<Widget>();
+ colors_row.set_layout<HorizontalBoxLayout>();
+
+ for (int i = 0; i < 8; i++) {
+ create_color_button(colors_row, colors[r][i]);
+ }
+ }
+}
+
+void ColorPicker::build_ui_custom(Widget& root_container)
+{
+ enum RGBComponent {
+ Red,
+ Green,
+ Blue,
+ Alpha
+ };
+
+ auto& horizontal_container = root_container.add<Widget>();
+ horizontal_container.set_fill_with_background_color(true);
+ horizontal_container.set_layout<HorizontalBoxLayout>();
+
+ // Left Side
+ m_custom_color = horizontal_container.add<CustomColorWidget>(m_color);
+ m_custom_color->set_fixed_size(299, 260);
+ m_custom_color->on_pick = [this](Color color) {
+ if (m_color == color)
+ return;
+
+ m_color = color;
+ update_color_widgets();
+ };
+
+ // Right Side
+ auto& vertical_container = horizontal_container.add<Widget>();
+ vertical_container.set_layout<VerticalBoxLayout>();
+ vertical_container.layout()->set_margins({ 8, 0, 0, 0 });
+ vertical_container.set_fixed_width(128);
+
+ auto& preview_container = vertical_container.add<Frame>();
+ preview_container.set_layout<VerticalBoxLayout>();
+ preview_container.layout()->set_margins({ 2, 2, 2, 2 });
+ preview_container.layout()->set_spacing(0);
+ preview_container.set_fixed_height(128);
+
+ // Current color
+ preview_container.add<ColorPreview>(m_color);
+
+ // Preview selected color
+ m_preview_widget = preview_container.add<ColorPreview>(m_color);
+
+ vertical_container.layout()->add_spacer();
+
+ // HTML
+ auto& html_container = vertical_container.add<GUI::Widget>();
+ html_container.set_layout<GUI::HorizontalBoxLayout>();
+ html_container.set_fixed_height(22);
+
+ auto& html_label = html_container.add<GUI::Label>();
+ html_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
+ html_label.set_fixed_width(48);
+ html_label.set_text("HTML:");
+
+ m_html_text = html_container.add<GUI::TextBox>();
+ m_html_text->set_text(m_color_has_alpha_channel ? m_color.to_string() : m_color.to_string_without_alpha());
+ m_html_text->on_change = [this]() {
+ auto color_name = m_html_text->text();
+ auto optional_color = Color::from_string(color_name);
+ if (optional_color.has_value() && (!color_name.starts_with("#") || color_name.length() == ((m_color_has_alpha_channel) ? 9 : 7))) {
+ // The color length must be 9/7 (unless it is a name like red), because:
+ // - If we allowed 5/4 character rgb color, the field would reset to 9/7 characters after you deleted 4/3 characters.
+ auto color = optional_color.value();
+ if (m_color == color)
+ return;
+
+ m_color = optional_color.value();
+ m_custom_color->set_color(color);
+ update_color_widgets();
+ }
+ };
+
+ // RGB Lines
+ auto make_spinbox = [&](RGBComponent component, int initial_value) {
+ auto& rgb_container = vertical_container.add<GUI::Widget>();
+ rgb_container.set_layout<GUI::HorizontalBoxLayout>();
+ rgb_container.set_fixed_height(22);
+
+ auto& rgb_label = rgb_container.add<GUI::Label>();
+ rgb_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
+ rgb_label.set_fixed_width(48);
+
+ auto& spinbox = rgb_container.add<SpinBox>();
+ spinbox.set_fixed_height(20);
+ spinbox.set_min(0);
+ spinbox.set_max(255);
+ spinbox.set_value(initial_value);
+ spinbox.set_enabled(m_color_has_alpha_channel);
+ spinbox.on_change = [this, component](auto value) {
+ auto color = m_color;
+
+ if (component == Red)
+ color.set_red(value);
+ if (component == Green)
+ color.set_green(value);
+ if (component == Blue)
+ color.set_blue(value);
+ if (component == Alpha)
+ color.set_alpha(value);
+
+ if (m_color == color)
+ return;
+
+ m_color = color;
+ m_custom_color->set_color(color);
+ update_color_widgets();
+ };
+
+ if (component == Red) {
+ rgb_label.set_text("Red:");
+ m_red_spinbox = spinbox;
+ } else if (component == Green) {
+ rgb_label.set_text("Green:");
+ m_green_spinbox = spinbox;
+ } else if (component == Blue) {
+ rgb_label.set_text("Blue:");
+ m_blue_spinbox = spinbox;
+ } else if (component == Alpha) {
+ rgb_label.set_text("Alpha:");
+ m_alpha_spinbox = spinbox;
+ }
+ };
+
+ make_spinbox(Red, m_color.red());
+ make_spinbox(Green, m_color.green());
+ make_spinbox(Blue, m_color.blue());
+ make_spinbox(Alpha, m_color.alpha());
+}
+
+void ColorPicker::update_color_widgets()
+{
+ m_preview_widget->set_color(m_color);
+
+ m_html_text->set_text(m_color_has_alpha_channel ? m_color.to_string() : m_color.to_string_without_alpha());
+
+ m_red_spinbox->set_value(m_color.red());
+ m_green_spinbox->set_value(m_color.green());
+ m_blue_spinbox->set_value(m_color.blue());
+ m_alpha_spinbox->set_value(m_color.alpha());
+ m_alpha_spinbox->set_enabled(m_color_has_alpha_channel);
+}
+
+void ColorPicker::create_color_button(Widget& container, unsigned rgb)
+{
+ Color color = Color::from_rgb(rgb);
+
+ auto& widget = container.add<ColorButton>(*this, color);
+ widget.on_click = [this](Color color) {
+ for (auto& value : m_color_widgets) {
+ value->set_selected(false);
+ value->update();
+ }
+
+ m_color = color;
+ m_custom_color->set_color(color);
+ update_color_widgets();
+ };
+
+ if (color == m_color) {
+ widget.set_selected(true);
+ }
+
+ m_color_widgets.append(&widget);
+}
+
+ColorButton::ColorButton(ColorPicker& picker, Color color)
+ : m_picker(picker)
+{
+ m_color = color;
+}
+
+ColorButton::~ColorButton()
+{
+}
+
+void ColorButton::set_selected(bool selected)
+{
+ m_selected = selected;
+}
+
+void ColorButton::doubleclick_event(GUI::MouseEvent&)
+{
+ click();
+ m_selected = true;
+ m_picker.done(Dialog::ExecOK);
+}
+
+void ColorButton::paint_event(PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ Gfx::StylePainter::paint_button(painter, rect(), palette(), Gfx::ButtonStyle::Normal, is_being_pressed(), is_hovered(), is_checked(), is_enabled(), is_focused());
+
+ painter.fill_rect(rect().shrunken(2, 2), m_color);
+
+ if (m_selected) {
+ painter.fill_rect(rect().shrunken(6, 6), Color::Black);
+ painter.fill_rect(rect().shrunken(10, 10), Color::White);
+ painter.fill_rect(rect().shrunken(14, 14), m_color);
+ }
+}
+
+void ColorButton::click(unsigned)
+{
+ if (on_click)
+ on_click(m_color);
+
+ m_selected = true;
+}
+
+CustomColorWidget::CustomColorWidget(Color color)
+{
+ set_layout<HorizontalBoxLayout>();
+
+ m_color_field = add<ColorField>(color);
+ auto size = 256 + (m_color_field->frame_thickness() * 2);
+ m_color_field->set_fixed_size(size, size);
+ m_color_field->on_pick = [this](Color color) {
+ if (on_pick)
+ on_pick(color);
+ };
+
+ m_color_slider = add<ColorSlider>(color.to_hsv().hue);
+ auto slider_width = 24 + (m_color_slider->frame_thickness() * 2);
+ m_color_slider->set_fixed_size(slider_width, size);
+ m_color_slider->on_pick = [this](double value) {
+ m_color_field->set_hue_from_pick(value);
+ };
+}
+
+void CustomColorWidget::set_color(Color color)
+{
+ m_color_field->set_color(color);
+ m_color_field->set_hue(color.to_hsv().hue);
+}
+
+ColorField::ColorField(Color color)
+ : m_color(color)
+ , m_hue(color.to_hsv().hue)
+{
+ create_color_bitmap();
+}
+
+void ColorField::create_color_bitmap()
+{
+ m_color_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, { 256, 256 });
+ auto painter = Gfx::Painter(*m_color_bitmap);
+
+ Gfx::HSV hsv;
+ hsv.hue = m_hue;
+ for (int x = 0; x < 256; x++) {
+ hsv.saturation = x / 255.0f;
+ for (int y = 0; y < 256; y++) {
+ hsv.value = (255 - y) / 255.0f;
+ Color color = Color::from_hsv(hsv);
+ painter.set_pixel({ x, y }, color);
+ }
+ }
+}
+
+void ColorField::set_color(Color color)
+{
+ if (m_color == color)
+ return;
+
+ m_color = color;
+ // don't save m_hue here by default, we don't want to set it to 0 in case color is full white
+ // m_hue = color.to_hsv().hue;
+
+ recalculate_position();
+}
+
+void ColorField::recalculate_position()
+{
+ Gfx::HSV hsv = m_color.to_hsv();
+ auto x = hsv.saturation * width();
+ auto y = (1 - hsv.value) * height();
+ m_last_position = Gfx::IntPoint(x, y);
+ update();
+}
+
+void ColorField::set_hue(double hue)
+{
+ if (m_hue == hue)
+ return;
+
+ auto hsv = m_color.to_hsv();
+ hsv.hue = hue;
+
+ m_hue = hue;
+ create_color_bitmap();
+
+ auto color = Color::from_hsv(hsv);
+ color.set_alpha(m_color.alpha());
+ set_color(color);
+}
+
+void ColorField::set_hue_from_pick(double hue)
+{
+ set_hue(hue);
+ if (on_pick)
+ on_pick(m_color);
+}
+
+void ColorField::pick_color_at_position(GUI::MouseEvent& event)
+{
+ if (!m_being_pressed)
+ return;
+
+ auto inner_rect = frame_inner_rect();
+ auto position = event.position().constrained(inner_rect).translated(-frame_thickness(), -frame_thickness());
+ auto color = Color::from_hsv(m_hue, (double)position.x() / inner_rect.width(), (double)(inner_rect.height() - position.y()) / inner_rect.height());
+ color.set_alpha(m_color.alpha());
+ m_last_position = position;
+ m_color = color;
+
+ if (on_pick)
+ on_pick(color);
+
+ update();
+}
+
+void ColorField::mousedown_event(GUI::MouseEvent& event)
+{
+ if (event.button() == GUI::MouseButton::Left) {
+ m_being_pressed = true;
+ pick_color_at_position(event);
+ }
+}
+
+void ColorField::mouseup_event(GUI::MouseEvent& event)
+{
+ if (event.button() == GUI::MouseButton::Left) {
+ m_being_pressed = false;
+ pick_color_at_position(event);
+ }
+}
+
+void ColorField::mousemove_event(GUI::MouseEvent& event)
+{
+ if (event.buttons() & GUI::MouseButton::Left)
+ pick_color_at_position(event);
+}
+
+void ColorField::paint_event(GUI::PaintEvent& event)
+{
+ Frame::paint_event(event);
+
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+ painter.add_clip_rect(frame_inner_rect());
+
+ painter.draw_scaled_bitmap(frame_inner_rect(), *m_color_bitmap, m_color_bitmap->rect());
+
+ painter.translate(frame_thickness(), frame_thickness());
+ painter.draw_line({ m_last_position.x() - 1, 0 }, { m_last_position.x() - 1, height() }, Color::White);
+ painter.draw_line({ m_last_position.x() + 1, 0 }, { m_last_position.x() + 1, height() }, Color::White);
+ painter.draw_line({ 0, m_last_position.y() - 1 }, { width(), m_last_position.y() - 1 }, Color::White);
+ painter.draw_line({ 0, m_last_position.y() + 1 }, { width(), m_last_position.y() + 1 }, Color::White);
+ painter.draw_line({ m_last_position.x(), 0 }, { m_last_position.x(), height() }, Color::Black);
+ painter.draw_line({ 0, m_last_position.y() }, { width(), m_last_position.y() }, Color::Black);
+}
+
+void ColorField::resize_event(ResizeEvent&)
+{
+ recalculate_position();
+}
+
+ColorSlider::ColorSlider(double value)
+ : m_value(value)
+{
+ m_color_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, { 32, 360 });
+ auto painter = Gfx::Painter(*m_color_bitmap);
+
+ for (int h = 0; h < 360; h++) {
+ Gfx::HSV hsv;
+ hsv.hue = h;
+ hsv.saturation = 1.0;
+ hsv.value = 1.0;
+ Color color = Color::from_hsv(hsv);
+ painter.draw_line({ 0, h }, { 32, h }, color);
+ }
+}
+
+void ColorSlider::set_value(double value)
+{
+ if (m_value == value)
+ return;
+
+ m_value = value;
+ recalculate_position();
+}
+
+void ColorSlider::recalculate_position()
+{
+ m_last_position = (m_value / 360.0) * height();
+ update();
+}
+
+void ColorSlider::pick_value_at_position(GUI::MouseEvent& event)
+{
+ if (!m_being_pressed)
+ return;
+
+ auto inner_rect = frame_inner_rect();
+ auto position = event.position().constrained(inner_rect).translated(-frame_thickness(), -frame_thickness());
+ auto hue = (double)position.y() / inner_rect.height() * 360;
+ m_last_position = position.y();
+ m_value = hue;
+
+ if (on_pick)
+ on_pick(m_value);
+
+ update();
+}
+
+void ColorSlider::mousedown_event(GUI::MouseEvent& event)
+{
+ if (event.button() == GUI::MouseButton::Left) {
+ m_being_pressed = true;
+ pick_value_at_position(event);
+ }
+}
+
+void ColorSlider::mouseup_event(GUI::MouseEvent& event)
+{
+ if (event.button() == GUI::MouseButton::Left) {
+ m_being_pressed = false;
+ pick_value_at_position(event);
+ }
+}
+
+void ColorSlider::mousemove_event(GUI::MouseEvent& event)
+{
+ if (event.buttons() & GUI::MouseButton::Left)
+ pick_value_at_position(event);
+}
+
+void ColorSlider::paint_event(GUI::PaintEvent& event)
+{
+ Frame::paint_event(event);
+
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+ painter.add_clip_rect(frame_inner_rect());
+
+ painter.draw_scaled_bitmap(frame_inner_rect(), *m_color_bitmap, m_color_bitmap->rect());
+
+ painter.translate(frame_thickness(), frame_thickness());
+ painter.draw_line({ 0, m_last_position - 1 }, { width(), m_last_position - 1 }, Color::White);
+ painter.draw_line({ 0, m_last_position + 1 }, { width(), m_last_position + 1 }, Color::White);
+ painter.draw_line({ 0, m_last_position }, { width(), m_last_position }, Color::Black);
+}
+
+void ColorSlider::resize_event(ResizeEvent&)
+{
+ recalculate_position();
+}
+
+ColorPreview::ColorPreview(Color color)
+ : m_color(color)
+{
+}
+
+void ColorPreview::set_color(Color color)
+{
+ if (m_color == color)
+ return;
+
+ m_color = color;
+ update();
+}
+
+void ColorPreview::paint_event(PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ if (m_color.alpha() < 255) {
+ Gfx::StylePainter::paint_transparency_grid(painter, rect(), palette());
+ painter.fill_rect(rect(), m_color);
+ painter.fill_rect({ 0, 0, rect().width() / 4, rect().height() }, m_color.with_alpha(255));
+ } else {
+ painter.fill_rect(rect(), m_color);
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ColorPicker.h b/Userland/Libraries/LibGUI/ColorPicker.h
new file mode 100644
index 0000000000..320614805f
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ColorPicker.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/AbstractButton.h>
+#include <LibGUI/Dialog.h>
+
+namespace GUI {
+
+class ColorButton;
+class ColorPreview;
+class CustomColorWidget;
+
+class ColorPicker final : public Dialog {
+ C_OBJECT(ColorPicker)
+
+public:
+ virtual ~ColorPicker() override;
+
+ bool color_has_alpha_channel() const { return m_color_has_alpha_channel; }
+ void set_color_has_alpha_channel(bool);
+ Color color() const { return m_color; }
+
+private:
+ explicit ColorPicker(Color, Window* parent_window = nullptr, String title = "Edit Color");
+
+ void build_ui();
+ void build_ui_custom(Widget& root_container);
+ void build_ui_palette(Widget& root_container);
+ void update_color_widgets();
+ void create_color_button(Widget& container, unsigned rgb);
+
+ Color m_color;
+ bool m_color_has_alpha_channel { true };
+
+ Vector<ColorButton*> m_color_widgets;
+ RefPtr<CustomColorWidget> m_custom_color;
+ RefPtr<ColorPreview> m_preview_widget;
+ RefPtr<TextBox> m_html_text;
+ RefPtr<SpinBox> m_red_spinbox;
+ RefPtr<SpinBox> m_green_spinbox;
+ RefPtr<SpinBox> m_blue_spinbox;
+ RefPtr<SpinBox> m_alpha_spinbox;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ColumnsView.cpp b/Userland/Libraries/LibGUI/ColumnsView.cpp
new file mode 100644
index 0000000000..9a18c4a5f3
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ColumnsView.cpp
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/ColumnsView.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGfx/CharacterBitmap.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+static const char* s_arrow_bitmap_data = {
+ " "
+ " # "
+ " ## "
+ " ### "
+ " #### "
+ " ### "
+ " ## "
+ " # "
+ " "
+};
+static const int s_arrow_bitmap_width = 9;
+static const int s_arrow_bitmap_height = 9;
+
+ColumnsView::ColumnsView()
+{
+ set_fill_with_background_color(true);
+ set_background_role(ColorRole::Base);
+ set_foreground_role(ColorRole::BaseText);
+ m_columns.append({ {}, 0 });
+}
+
+ColumnsView::~ColumnsView()
+{
+}
+
+void ColumnsView::select_all()
+{
+ Vector<Column> columns_for_selection;
+ selection().for_each_index([&](auto& index) {
+ for (auto& column : m_columns) {
+ if (column.parent_index == index.parent()) {
+ columns_for_selection.append(column);
+ return;
+ }
+ }
+ ASSERT_NOT_REACHED();
+ });
+
+ for (Column& column : columns_for_selection) {
+ int row_count = model()->row_count(column.parent_index);
+ for (int row = 0; row < row_count; row++) {
+ ModelIndex index = model()->index(row, m_model_column, column.parent_index);
+ selection().add(index);
+ }
+ }
+}
+
+void ColumnsView::paint_event(PaintEvent& event)
+{
+ AbstractView::paint_event(event);
+
+ if (!model())
+ return;
+
+ Painter painter(*this);
+ painter.add_clip_rect(frame_inner_rect());
+ painter.add_clip_rect(event.rect());
+ painter.translate(frame_thickness(), frame_thickness());
+ painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
+
+ int column_x = 0;
+
+ auto selection_color = is_focused() ? palette().selection() : palette().inactive_selection();
+
+ for (size_t i = 0; i < m_columns.size(); i++) {
+ auto& column = m_columns[i];
+ auto* next_column = i + 1 == m_columns.size() ? nullptr : &m_columns[i + 1];
+
+ ASSERT(column.width > 0);
+
+ int row_count = model()->row_count(column.parent_index);
+ for (int row = 0; row < row_count; row++) {
+ ModelIndex index = model()->index(row, m_model_column, column.parent_index);
+ ASSERT(index.is_valid());
+
+ bool is_selected_row = selection().contains(index);
+
+ Color background_color = palette().color(background_role());
+ Color text_color = palette().color(foreground_role());
+
+ if (next_column != nullptr && next_column->parent_index == index) {
+ background_color = palette().inactive_selection();
+ text_color = palette().inactive_selection_text();
+ }
+
+ if (is_selected_row) {
+ background_color = selection_color;
+ text_color = is_focused() ? palette().selection_text() : palette().inactive_selection_text();
+ }
+
+ Gfx::IntRect row_rect { column_x, row * item_height(), column.width, item_height() };
+ painter.fill_rect(row_rect, background_color);
+
+ auto icon = index.data(ModelRole::Icon);
+ Gfx::IntRect icon_rect = { column_x + icon_spacing(), 0, icon_size(), icon_size() };
+ icon_rect.center_vertically_within(row_rect);
+ if (icon.is_icon()) {
+ if (auto* bitmap = icon.as_icon().bitmap_for_size(icon_size())) {
+ if (is_selected_row) {
+ auto tint = selection_color.with_alpha(100);
+ painter.blit_filtered(icon_rect.location(), *bitmap, bitmap->rect(), [&](auto src) { return src.blend(tint); });
+ } else if (m_hovered_index.is_valid() && m_hovered_index.parent() == index.parent() && m_hovered_index.row() == index.row()) {
+ painter.blit_brightened(icon_rect.location(), *bitmap, bitmap->rect());
+ } else {
+ painter.blit(icon_rect.location(), *bitmap, bitmap->rect());
+ }
+ }
+ }
+
+ Gfx::IntRect text_rect = {
+ icon_rect.right() + 1 + icon_spacing(), row * item_height(),
+ column.width - icon_spacing() - icon_size() - icon_spacing() - icon_spacing() - s_arrow_bitmap_width - icon_spacing(), item_height()
+ };
+ draw_item_text(painter, index, is_selected_row, text_rect, index.data().to_string(), font_for_index(index), Gfx::TextAlignment::CenterLeft, Gfx::TextElision::None);
+
+ if (is_focused() && index == cursor_index()) {
+ painter.draw_rect(row_rect, palette().color(background_role()));
+ painter.draw_focus_rect(row_rect, palette().focus_outline());
+ }
+
+ if (has_pending_drop() && index == drop_candidate_index()) {
+ painter.draw_rect(row_rect, palette().selection(), true);
+ }
+
+ bool expandable = model()->row_count(index) > 0;
+ if (expandable) {
+ Gfx::IntRect arrow_rect = {
+ text_rect.right() + 1 + icon_spacing(), 0,
+ s_arrow_bitmap_width, s_arrow_bitmap_height
+ };
+ arrow_rect.center_vertically_within(row_rect);
+ static auto& arrow_bitmap = Gfx::CharacterBitmap::create_from_ascii(s_arrow_bitmap_data, s_arrow_bitmap_width, s_arrow_bitmap_height).leak_ref();
+ painter.draw_bitmap(arrow_rect.location(), arrow_bitmap, text_color);
+ }
+ }
+
+ int separator_height = content_size().height();
+ if (height() > separator_height)
+ separator_height = height();
+ painter.draw_line({ column_x + column.width, 0 }, { column_x + column.width, separator_height }, palette().button());
+ column_x += column.width + 1;
+ }
+}
+
+void ColumnsView::push_column(const ModelIndex& parent_index)
+{
+ ASSERT(model());
+
+ // Drop columns at the end.
+ ModelIndex grandparent = model()->parent_index(parent_index);
+ for (int i = m_columns.size() - 1; i > 0; i--) {
+ if (m_columns[i].parent_index == grandparent)
+ break;
+ m_columns.shrink(i);
+ dbgln("Dropping column {}", i);
+ }
+
+ // Add the new column.
+ dbgln("Adding a new column");
+ m_columns.append({ parent_index, 0 });
+ update_column_sizes();
+ update();
+}
+
+void ColumnsView::update_column_sizes()
+{
+ if (!model())
+ return;
+
+ int total_width = 0;
+ int total_height = 0;
+
+ for (auto& column : m_columns) {
+ int row_count = model()->row_count(column.parent_index);
+
+ int column_height = row_count * item_height();
+ if (column_height > total_height)
+ total_height = column_height;
+
+ column.width = 10;
+ for (int row = 0; row < row_count; row++) {
+ ModelIndex index = model()->index(row, m_model_column, column.parent_index);
+ ASSERT(index.is_valid());
+ auto text = index.data().to_string();
+ int row_width = icon_spacing() + icon_size() + icon_spacing() + font().width(text) + icon_spacing() + s_arrow_bitmap_width + icon_spacing();
+ if (row_width > column.width)
+ column.width = row_width;
+ }
+ total_width += column.width + 1;
+ }
+
+ set_content_size({ total_width, total_height });
+}
+
+ModelIndex ColumnsView::index_at_event_position(const Gfx::IntPoint& a_position) const
+{
+ if (!model())
+ return {};
+
+ auto position = a_position.translated(horizontal_scrollbar().value() - frame_thickness(), vertical_scrollbar().value() - frame_thickness());
+
+ int column_x = 0;
+
+ for (auto& column : m_columns) {
+ if (position.x() < column_x)
+ break;
+ if (position.x() > column_x + column.width) {
+ column_x += column.width;
+ continue;
+ }
+
+ int row = position.y() / item_height();
+ int row_count = model()->row_count(column.parent_index);
+ if (row >= row_count)
+ return {};
+
+ return model()->index(row, m_model_column, column.parent_index);
+ }
+
+ return {};
+}
+
+void ColumnsView::mousedown_event(MouseEvent& event)
+{
+ AbstractView::mousedown_event(event);
+
+ if (!model())
+ return;
+
+ if (event.button() != MouseButton::Left)
+ return;
+
+ auto index = index_at_event_position(event.position());
+ if (index.is_valid() && !(event.modifiers() & Mod_Ctrl)) {
+ if (model()->row_count(index))
+ push_column(index);
+ }
+}
+
+void ColumnsView::model_did_update(unsigned flags)
+{
+ AbstractView::model_did_update(flags);
+
+ // FIXME: Don't drop the columns on minor updates.
+ dbgln("Model was updated; dropping columns :(");
+ m_columns.clear();
+ m_columns.append({ {}, 0 });
+
+ update_column_sizes();
+ update();
+}
+
+void ColumnsView::move_cursor(CursorMovement movement, SelectionUpdate selection_update)
+{
+ if (!model())
+ return;
+ auto& model = *this->model();
+ if (!cursor_index().is_valid()) {
+ set_cursor(model.index(0, m_model_column, {}), SelectionUpdate::Set);
+ return;
+ }
+
+ ModelIndex new_index;
+ auto cursor_parent = model.parent_index(cursor_index());
+
+ switch (movement) {
+ case CursorMovement::Up: {
+ int row = cursor_index().row() > 0 ? cursor_index().row() - 1 : 0;
+ new_index = model.index(row, cursor_index().column(), cursor_parent);
+ break;
+ }
+ case CursorMovement::Down: {
+ int row = cursor_index().row() + 1;
+ new_index = model.index(row, cursor_index().column(), cursor_parent);
+ break;
+ }
+ case CursorMovement::Left:
+ new_index = cursor_parent;
+ break;
+ case CursorMovement::Right:
+ new_index = model.index(0, m_model_column, cursor_index());
+ if (model.is_valid(new_index)) {
+ if (model.is_valid(cursor_index()))
+ push_column(cursor_index());
+ update();
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (new_index.is_valid())
+ set_cursor(new_index, selection_update);
+}
+
+Gfx::IntRect ColumnsView::content_rect(const ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return {};
+
+ int column_x = 0;
+ for (auto& column : m_columns) {
+ if (column.parent_index == index.parent())
+ return { column_x + icon_size(), index.row() * item_height(), column.width - icon_size(), item_height() };
+ column_x += column.width + 1;
+ }
+
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ColumnsView.h b/Userland/Libraries/LibGUI/ColumnsView.h
new file mode 100644
index 0000000000..843b3d2e6c
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ColumnsView.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Vector.h>
+#include <LibGUI/AbstractView.h>
+
+namespace GUI {
+
+class ColumnsView : public AbstractView {
+ C_OBJECT(ColumnsView)
+public:
+ int model_column() const { return m_model_column; }
+ void set_model_column(int column) { m_model_column = column; }
+
+ virtual ModelIndex index_at_event_position(const Gfx::IntPoint&) const override;
+ virtual Gfx::IntRect content_rect(const ModelIndex&) const override;
+
+private:
+ ColumnsView();
+ virtual ~ColumnsView() override;
+ void push_column(const ModelIndex& parent_index);
+ void update_column_sizes();
+
+ int item_height() const { return 16; }
+ int icon_size() const { return 16; }
+ int icon_spacing() const { return 2; }
+ int text_padding() const { return 2; }
+
+ virtual void model_did_update(unsigned flags) override;
+ virtual void paint_event(PaintEvent&) override;
+ virtual void mousedown_event(MouseEvent& event) override;
+
+ void move_cursor(CursorMovement, SelectionUpdate) override;
+
+ virtual void select_all() override;
+ struct Column {
+ ModelIndex parent_index;
+ int width;
+ // TODO: per-column vertical scroll?
+ };
+
+ Vector<Column> m_columns;
+ int m_model_column { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ComboBox.cpp b/Userland/Libraries/LibGUI/ComboBox.cpp
new file mode 100644
index 0000000000..73721a4357
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ComboBox.cpp
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/ComboBox.h>
+#include <LibGUI/ControlBoxButton.h>
+#include <LibGUI/Desktop.h>
+#include <LibGUI/ListView.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGUI/TextBox.h>
+#include <LibGUI/Window.h>
+
+REGISTER_WIDGET(GUI, ComboBox)
+
+namespace GUI {
+
+class ComboBoxEditor final : public TextEditor {
+ C_OBJECT(ComboBoxEditor);
+
+public:
+ Function<void(int delta)> on_mousewheel;
+
+private:
+ ComboBoxEditor()
+ : TextEditor(TextEditor::SingleLine)
+ {
+ }
+
+ virtual void mousewheel_event(MouseEvent& event) override
+ {
+ if (!is_focused())
+ set_focus(true);
+ if (on_mousewheel)
+ on_mousewheel(event.wheel_delta());
+ }
+};
+
+ComboBox::ComboBox()
+{
+ set_min_width(32);
+ set_fixed_height(22);
+
+ m_editor = add<ComboBoxEditor>();
+ m_editor->set_frame_thickness(0);
+ m_editor->on_return_pressed = [this] {
+ if (on_return_pressed)
+ on_return_pressed();
+ };
+ m_editor->on_up_pressed = [this] {
+ navigate(AbstractView::CursorMovement::Up);
+ };
+ m_editor->on_down_pressed = [this] {
+ navigate(AbstractView::CursorMovement::Down);
+ };
+ m_editor->on_pageup_pressed = [this] {
+ navigate(AbstractView::CursorMovement::PageUp);
+ };
+ m_editor->on_pagedown_pressed = [this] {
+ navigate(AbstractView::CursorMovement::PageDown);
+ };
+ m_editor->on_mousewheel = [this](int delta) {
+ // Since we can only show one item at a time we don't want to
+ // skip any. So just move one item at a time.
+ navigate_relative(delta > 0 ? 1 : -1);
+ };
+ m_editor->on_mousedown = [this] {
+ if (only_allow_values_from_model())
+ m_open_button->click();
+ };
+
+ m_open_button = add<ControlBoxButton>(ControlBoxButton::DownArrow);
+ m_open_button->set_focus_policy(GUI::FocusPolicy::NoFocus);
+ m_open_button->on_click = [this](auto) {
+ if (m_list_window->is_visible())
+ close();
+ else
+ open();
+ };
+
+ m_list_window = add<Window>(window());
+ m_list_window->set_frameless(true);
+ m_list_window->set_accessory(true);
+ m_list_window->on_active_input_change = [this](bool is_active_input) {
+ if (!is_active_input) {
+ m_open_button->set_enabled(false);
+ close();
+ }
+ m_open_button->set_enabled(true);
+ };
+
+ m_list_view = m_list_window->set_main_widget<ListView>();
+ m_list_view->horizontal_scrollbar().set_visible(false);
+ m_list_view->set_alternating_row_colors(false);
+ m_list_view->set_hover_highlighting(true);
+ m_list_view->set_frame_thickness(1);
+ m_list_view->set_frame_shadow(Gfx::FrameShadow::Plain);
+ m_list_view->on_selection = [this](auto& index) {
+ ASSERT(model());
+ m_list_view->set_activates_on_selection(true);
+ if (m_updating_model)
+ selection_updated(index);
+ };
+
+ m_list_view->on_activation = [this](auto& index) {
+ deferred_invoke([this, index](auto&) {
+ selection_updated(index);
+ if (on_change)
+ on_change(m_editor->text(), index);
+ });
+ m_list_view->set_activates_on_selection(false);
+ close();
+ };
+
+ m_list_view->on_escape_pressed = [this] {
+ close();
+ };
+}
+
+ComboBox::~ComboBox()
+{
+}
+
+void ComboBox::navigate(AbstractView::CursorMovement cursor_movement)
+{
+ auto previous_selected = m_list_view->cursor_index();
+ m_list_view->move_cursor(cursor_movement, AbstractView::SelectionUpdate::Set);
+ auto current_selected = m_list_view->cursor_index();
+ selection_updated(current_selected);
+ if (previous_selected.row() != current_selected.row() && on_change)
+ on_change(m_editor->text(), current_selected);
+}
+
+void ComboBox::navigate_relative(int delta)
+{
+ auto previous_selected = m_list_view->cursor_index();
+ m_list_view->move_cursor_relative(delta, AbstractView::SelectionUpdate::Set);
+ auto current_selected = m_list_view->cursor_index();
+ selection_updated(current_selected);
+ if (previous_selected.row() != current_selected.row() && on_change)
+ on_change(m_editor->text(), current_selected);
+}
+
+void ComboBox::selection_updated(const ModelIndex& index)
+{
+ if (index.is_valid())
+ m_selected_index = index;
+ else
+ m_selected_index.clear();
+ auto new_value = index.data().to_string();
+ m_editor->set_text(new_value);
+ if (!m_only_allow_values_from_model)
+ m_editor->select_all();
+}
+
+void ComboBox::resize_event(ResizeEvent& event)
+{
+ Widget::resize_event(event);
+ int button_height = event.size().height() - frame_thickness() * 2;
+ int button_width = 15;
+ m_open_button->set_relative_rect(width() - button_width - frame_thickness(), frame_thickness(), button_width, button_height);
+ auto editor_rect = frame_inner_rect();
+ editor_rect.set_width(editor_rect.width() - button_width);
+ m_editor->set_relative_rect(editor_rect);
+}
+
+void ComboBox::set_model(NonnullRefPtr<Model> model)
+{
+ TemporaryChange change(m_updating_model, true);
+ m_selected_index.clear();
+ m_list_view->set_model(move(model));
+}
+
+void ComboBox::set_selected_index(size_t index)
+{
+ if (!m_list_view->model())
+ return;
+ TemporaryChange change(m_updating_model, true);
+ m_list_view->set_cursor(m_list_view->model()->index(index, 0), AbstractView::SelectionUpdate::Set);
+}
+
+size_t ComboBox::selected_index() const
+{
+ return m_selected_index.has_value() ? m_selected_index.value().row() : 0;
+}
+
+void ComboBox::select_all()
+{
+ m_editor->select_all();
+}
+
+void ComboBox::open()
+{
+ if (!model())
+ return;
+
+ auto my_screen_rect = screen_relative_rect();
+
+ int longest_item_width = 0;
+ for (int i = 0; i < model()->row_count(); ++i) {
+ auto index = model()->index(i);
+ auto item_text = index.data().to_string();
+ longest_item_width = max(longest_item_width, m_list_view->font().width(item_text));
+ }
+ Gfx::IntSize size {
+ max(width(), longest_item_width + m_list_view->width_occupied_by_vertical_scrollbar() + m_list_view->frame_thickness() * 2 + m_list_view->horizontal_padding()),
+ model()->row_count() * m_list_view->item_height() + m_list_view->frame_thickness() * 2
+ };
+
+ auto taskbar_height = GUI::Desktop::the().taskbar_height();
+ auto menubar_height = GUI::Desktop::the().menubar_height();
+ // NOTE: This is so the combobox bottom edge exactly fits the taskbar's
+ // top edge - the value was found through trial and error though.
+ auto offset = 8;
+ Gfx::IntRect list_window_rect { my_screen_rect.bottom_left(), size };
+ list_window_rect.intersect(Desktop::the().rect().shrunken(0, taskbar_height + menubar_height + offset));
+
+ m_editor->set_has_visible_list(true);
+ m_editor->set_focus(true);
+ if (m_selected_index.has_value()) {
+ // Don't set m_updating_model to true here because we only want to
+ // change the list view's selected item without triggering a change to it.
+ m_list_view->set_cursor(m_selected_index.value(), AbstractView::SelectionUpdate::Set);
+ }
+ m_list_window->set_rect(list_window_rect);
+ m_list_window->show();
+}
+
+void ComboBox::close()
+{
+ m_list_window->hide();
+ m_editor->set_has_visible_list(false);
+ m_editor->set_focus(true);
+}
+
+String ComboBox::text() const
+{
+ return m_editor->text();
+}
+
+void ComboBox::set_text(const String& text)
+{
+ m_editor->set_text(text);
+}
+
+void ComboBox::set_only_allow_values_from_model(bool b)
+{
+ if (m_only_allow_values_from_model == b)
+ return;
+ m_only_allow_values_from_model = b;
+ m_editor->set_mode(m_only_allow_values_from_model ? TextEditor::DisplayOnly : TextEditor::Editable);
+}
+
+Model* ComboBox::model()
+{
+ return m_list_view->model();
+}
+
+const Model* ComboBox::model() const
+{
+ return m_list_view->model();
+}
+
+int ComboBox::model_column() const
+{
+ return m_list_view->model_column();
+}
+
+void ComboBox::set_model_column(int column)
+{
+ m_list_view->set_model_column(column);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ComboBox.h b/Userland/Libraries/LibGUI/ComboBox.h
new file mode 100644
index 0000000000..79ab26b7e3
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ComboBox.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/AbstractView.h>
+#include <LibGUI/Frame.h>
+#include <LibGUI/Model.h>
+
+namespace GUI {
+
+class ComboBoxEditor;
+class ControlBoxButton;
+
+class ComboBox : public Frame {
+ C_OBJECT(ComboBox);
+
+public:
+ virtual ~ComboBox() override;
+
+ String text() const;
+ void set_text(const String&);
+
+ void open();
+ void close();
+ void select_all();
+
+ Model* model();
+ const Model* model() const;
+ void set_model(NonnullRefPtr<Model>);
+
+ size_t selected_index() const;
+ void set_selected_index(size_t index);
+
+ bool only_allow_values_from_model() const { return m_only_allow_values_from_model; }
+ void set_only_allow_values_from_model(bool);
+
+ int model_column() const;
+ void set_model_column(int);
+
+ Function<void(const String&, const ModelIndex&)> on_change;
+ Function<void()> on_return_pressed;
+
+protected:
+ ComboBox();
+ virtual void resize_event(ResizeEvent&) override;
+
+private:
+ void selection_updated(const ModelIndex&);
+ void navigate(AbstractView::CursorMovement);
+ void navigate_relative(int);
+
+ RefPtr<ComboBoxEditor> m_editor;
+ RefPtr<ControlBoxButton> m_open_button;
+ RefPtr<Window> m_list_window;
+ RefPtr<ListView> m_list_view;
+ Optional<ModelIndex> m_selected_index;
+ bool m_only_allow_values_from_model { false };
+ bool m_updating_model { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Command.cpp b/Userland/Libraries/LibGUI/Command.cpp
new file mode 100644
index 0000000000..7799a1e944
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Command.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Command.h>
+
+namespace GUI {
+
+Command::~Command()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Command.h b/Userland/Libraries/LibGUI/Command.h
new file mode 100644
index 0000000000..3b2f081a6a
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Command.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+
+namespace GUI {
+
+class Command {
+public:
+ virtual ~Command();
+
+ virtual void undo() { }
+ virtual void redo() { }
+
+ String action_text() const { return m_action_text; }
+
+ virtual bool is_insert_text() const { return false; }
+ virtual bool is_remove_text() const { return false; }
+
+protected:
+ Command() { }
+ void set_action_text(const String& text) { m_action_text = text; }
+
+private:
+ String m_action_text;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ControlBoxButton.cpp b/Userland/Libraries/LibGUI/ControlBoxButton.cpp
new file mode 100644
index 0000000000..f2cfb274c3
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ControlBoxButton.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2020, Charles Chaucer <thankyouverycool@github>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/ControlBoxButton.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/CharacterBitmap.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+static const char* s_up_arrow_bitmap_data = {
+ " "
+ " # "
+ " ### "
+ " ##### "
+ " ####### "
+ " "
+};
+
+static const char* s_down_arrow_bitmap_data = {
+ " "
+ " ####### "
+ " ##### "
+ " ### "
+ " # "
+ " "
+};
+
+static Gfx::CharacterBitmap* s_up_arrow_bitmap;
+static Gfx::CharacterBitmap* s_down_arrow_bitmap;
+static const int s_bitmap_width = 9;
+static const int s_bitmap_height = 6;
+
+ControlBoxButton::ControlBoxButton(Type type)
+ : m_type(type)
+{
+}
+
+ControlBoxButton::~ControlBoxButton()
+{
+}
+
+void ControlBoxButton::paint_event(PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ Gfx::StylePainter::paint_button(painter, rect(), palette(), Gfx::ButtonStyle::Normal, is_being_pressed(), is_hovered(), is_checked(), is_enabled(), is_focused());
+
+ auto button_location = rect().location().translated((width() - s_bitmap_width) / 2, (height() - s_bitmap_height) / 2);
+
+ if (is_being_pressed())
+ button_location.move_by(1, 1);
+
+ if (type() == UpArrow) {
+ if (!s_up_arrow_bitmap)
+ s_up_arrow_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_up_arrow_bitmap_data, s_bitmap_width, s_bitmap_height).leak_ref();
+ if (!is_enabled())
+ painter.draw_bitmap(button_location.translated(1, 1), *s_up_arrow_bitmap, palette().threed_highlight());
+ painter.draw_bitmap(button_location, *s_up_arrow_bitmap, is_enabled() ? palette().button_text() : palette().threed_shadow1());
+ } else {
+ if (!s_down_arrow_bitmap)
+ s_down_arrow_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_down_arrow_bitmap_data, s_bitmap_width, s_bitmap_height).leak_ref();
+ if (!is_enabled())
+ painter.draw_bitmap(button_location.translated(1, 1), *s_down_arrow_bitmap, palette().threed_highlight());
+ painter.draw_bitmap(button_location, *s_down_arrow_bitmap, is_enabled() ? palette().button_text() : palette().threed_shadow1());
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ControlBoxButton.h b/Userland/Libraries/LibGUI/ControlBoxButton.h
new file mode 100644
index 0000000000..7399010ee8
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ControlBoxButton.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, Charles Chaucer <thankyouverycool@github>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Button.h>
+
+namespace GUI {
+
+class ControlBoxButton final : public Button {
+ C_OBJECT(ControlBoxButton);
+
+public:
+ enum Type {
+ UpArrow,
+ DownArrow
+ };
+
+ virtual ~ControlBoxButton() override;
+
+private:
+ explicit ControlBoxButton(const Type type);
+ virtual void paint_event(PaintEvent& event) override;
+
+ Type m_type { DownArrow };
+ Type type() const { return m_type; }
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/CppSyntaxHighlighter.cpp b/Userland/Libraries/LibGUI/CppSyntaxHighlighter.cpp
new file mode 100644
index 0000000000..2a2572ef05
--- /dev/null
+++ b/Userland/Libraries/LibGUI/CppSyntaxHighlighter.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCpp/Lexer.h>
+#include <LibGUI/CppSyntaxHighlighter.h>
+#include <LibGUI/TextEditor.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+static TextStyle style_for_token_type(Gfx::Palette palette, Cpp::Token::Type type)
+{
+ switch (type) {
+ case Cpp::Token::Type::Keyword:
+ return { palette.syntax_keyword(), true };
+ case Cpp::Token::Type::KnownType:
+ return { palette.syntax_type(), true };
+ case Cpp::Token::Type::Identifier:
+ return { palette.syntax_identifier(), false };
+ case Cpp::Token::Type::DoubleQuotedString:
+ case Cpp::Token::Type::SingleQuotedString:
+ case Cpp::Token::Type::RawString:
+ return { palette.syntax_string(), false };
+ case Cpp::Token::Type::Integer:
+ case Cpp::Token::Type::Float:
+ return { palette.syntax_number(), false };
+ case Cpp::Token::Type::IncludePath:
+ return { palette.syntax_preprocessor_value(), false };
+ case Cpp::Token::Type::EscapeSequence:
+ return { palette.syntax_keyword(), true };
+ case Cpp::Token::Type::PreprocessorStatement:
+ case Cpp::Token::Type::IncludeStatement:
+ return { palette.syntax_preprocessor_statement(), false };
+ case Cpp::Token::Type::Comment:
+ return { palette.syntax_comment(), false };
+ default:
+ return { palette.base_text(), false };
+ }
+}
+
+bool CppSyntaxHighlighter::is_identifier(void* token) const
+{
+ auto cpp_token = static_cast<Cpp::Token::Type>(reinterpret_cast<size_t>(token));
+ return cpp_token == Cpp::Token::Type::Identifier;
+}
+
+bool CppSyntaxHighlighter::is_navigatable(void* token) const
+{
+ auto cpp_token = static_cast<Cpp::Token::Type>(reinterpret_cast<size_t>(token));
+ return cpp_token == Cpp::Token::Type::IncludePath;
+}
+
+void CppSyntaxHighlighter::rehighlight(Gfx::Palette palette)
+{
+ ASSERT(m_editor);
+ auto text = m_editor->text();
+ Cpp::Lexer lexer(text);
+ auto tokens = lexer.lex();
+
+ Vector<GUI::TextDocumentSpan> spans;
+ for (auto& token : tokens) {
+#ifdef DEBUG_SYNTAX_HIGHLIGHTING
+ dbg() << token.to_string() << " @ " << token.m_start.line << ":" << token.m_start.column << " - " << token.m_end.line << ":" << token.m_end.column;
+#endif
+ GUI::TextDocumentSpan span;
+ span.range.set_start({ token.m_start.line, token.m_start.column });
+ span.range.set_end({ token.m_end.line, token.m_end.column });
+ auto style = style_for_token_type(palette, token.m_type);
+ span.attributes.color = style.color;
+ span.attributes.bold = style.bold;
+ span.is_skippable = token.m_type == Cpp::Token::Type::Whitespace;
+ span.data = reinterpret_cast<void*>(token.m_type);
+ spans.append(span);
+ }
+ m_editor->document().set_spans(spans);
+
+ m_has_brace_buddies = false;
+ highlight_matching_token_pair();
+
+ m_editor->update();
+}
+
+Vector<SyntaxHighlighter::MatchingTokenPair> CppSyntaxHighlighter::matching_token_pairs() const
+{
+ static Vector<SyntaxHighlighter::MatchingTokenPair> pairs;
+ if (pairs.is_empty()) {
+ pairs.append({ reinterpret_cast<void*>(Cpp::Token::Type::LeftCurly), reinterpret_cast<void*>(Cpp::Token::Type::RightCurly) });
+ pairs.append({ reinterpret_cast<void*>(Cpp::Token::Type::LeftParen), reinterpret_cast<void*>(Cpp::Token::Type::RightParen) });
+ pairs.append({ reinterpret_cast<void*>(Cpp::Token::Type::LeftBracket), reinterpret_cast<void*>(Cpp::Token::Type::RightBracket) });
+ }
+ return pairs;
+}
+
+bool CppSyntaxHighlighter::token_types_equal(void* token1, void* token2) const
+{
+ return static_cast<Cpp::Token::Type>(reinterpret_cast<size_t>(token1)) == static_cast<Cpp::Token::Type>(reinterpret_cast<size_t>(token2));
+}
+
+CppSyntaxHighlighter::~CppSyntaxHighlighter()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/CppSyntaxHighlighter.h b/Userland/Libraries/LibGUI/CppSyntaxHighlighter.h
new file mode 100644
index 0000000000..a67ca78cf2
--- /dev/null
+++ b/Userland/Libraries/LibGUI/CppSyntaxHighlighter.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/SyntaxHighlighter.h>
+
+namespace GUI {
+
+class CppSyntaxHighlighter final : public SyntaxHighlighter {
+public:
+ CppSyntaxHighlighter() { }
+ virtual ~CppSyntaxHighlighter() override;
+
+ virtual bool is_identifier(void*) const override;
+ virtual bool is_navigatable(void*) const override;
+
+ virtual SyntaxLanguage language() const override { return SyntaxLanguage::Cpp; }
+ virtual void rehighlight(Gfx::Palette) override;
+
+protected:
+ virtual Vector<MatchingTokenPair> matching_token_pairs() const override;
+ virtual bool token_types_equal(void*, void*) const override;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Desktop.cpp b/Userland/Libraries/LibGUI/Desktop.cpp
new file mode 100644
index 0000000000..f0bb7b4759
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Desktop.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Badge.h>
+#include <LibCore/ConfigFile.h>
+#include <LibGUI/Desktop.h>
+#include <LibGUI/WindowServerConnection.h>
+#include <string.h>
+#include <unistd.h>
+
+namespace GUI {
+
+Desktop& Desktop::the()
+{
+ static Desktop* the;
+ if (!the)
+ the = new Desktop;
+ return *the;
+}
+
+Desktop::Desktop()
+{
+}
+
+void Desktop::did_receive_screen_rect(Badge<WindowServerConnection>, const Gfx::IntRect& rect)
+{
+ if (m_rect == rect)
+ return;
+ m_rect = rect;
+ if (on_rect_change)
+ on_rect_change(rect);
+}
+
+void Desktop::set_background_color(const StringView& background_color)
+{
+ WindowServerConnection::the().post_message(Messages::WindowServer::SetBackgroundColor(background_color));
+}
+
+void Desktop::set_wallpaper_mode(const StringView& mode)
+{
+ WindowServerConnection::the().post_message(Messages::WindowServer::SetWallpaperMode(mode));
+}
+
+bool Desktop::set_wallpaper(const StringView& path, bool save_config)
+{
+ WindowServerConnection::the().post_message(Messages::WindowServer::AsyncSetWallpaper(path));
+ auto ret_val = WindowServerConnection::the().wait_for_specific_message<Messages::WindowClient::AsyncSetWallpaperFinished>()->success();
+
+ if (ret_val && save_config) {
+ RefPtr<Core::ConfigFile> config = Core::ConfigFile::get_for_app("WindowManager");
+ dbgln("Saving wallpaper path '{}' to config file at {}", path, config->file_name());
+ config->write_entry("Background", "Wallpaper", path);
+ config->sync();
+ }
+
+ return ret_val;
+}
+
+String Desktop::wallpaper() const
+{
+ return WindowServerConnection::the().send_sync<Messages::WindowServer::GetWallpaper>()->path();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Desktop.h b/Userland/Libraries/LibGUI/Desktop.h
new file mode 100644
index 0000000000..5b531ad3d1
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Desktop.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/String.h>
+#include <LibGUI/Forward.h>
+#include <LibGfx/Rect.h>
+
+namespace GUI {
+
+class Desktop {
+public:
+ static Desktop& the();
+ Desktop();
+
+ void set_background_color(const StringView& background_color);
+
+ void set_wallpaper_mode(const StringView& mode);
+
+ String wallpaper() const;
+ bool set_wallpaper(const StringView& path, bool save_config = true);
+
+ Gfx::IntRect rect() const { return m_rect; }
+
+ int taskbar_height() const { return 28; }
+ int menubar_height() const { return 19; }
+
+ void did_receive_screen_rect(Badge<WindowServerConnection>, const Gfx::IntRect&);
+
+ Function<void(const Gfx::IntRect&)> on_rect_change;
+
+private:
+ Gfx::IntRect m_rect;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Dialog.cpp b/Userland/Libraries/LibGUI/Dialog.cpp
new file mode 100644
index 0000000000..e1988da503
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Dialog.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/EventLoop.h>
+#include <LibGUI/Dialog.h>
+#include <LibGUI/Event.h>
+
+namespace GUI {
+
+Dialog::Dialog(Window* parent_window)
+ : Window(parent_window)
+{
+ set_modal(true);
+ set_minimizable(false);
+}
+
+Dialog::~Dialog()
+{
+}
+
+int Dialog::exec()
+{
+ ASSERT(!m_event_loop);
+ m_event_loop = make<Core::EventLoop>();
+ if (parent() && is<Window>(parent())) {
+ auto& parent_window = *static_cast<Window*>(parent());
+ if (parent_window.is_visible()) {
+ center_within(parent_window);
+ } else {
+ center_on_screen();
+ }
+ } else {
+ center_on_screen();
+ }
+ show();
+ auto result = m_event_loop->exec();
+ m_event_loop = nullptr;
+ dbgln("{}: Event loop returned with result {}", *this, result);
+ remove_from_parent();
+ return result;
+}
+
+void Dialog::done(int result)
+{
+ if (!m_event_loop)
+ return;
+ m_result = result;
+ dbgln("{}: Quit event loop with result {}", *this, result);
+ m_event_loop->quit(result);
+}
+
+void Dialog::event(Core::Event& event)
+{
+ if (event.type() == Event::KeyUp) {
+ auto& key_event = static_cast<KeyEvent&>(event);
+ if (key_event.key() == KeyCode::Key_Escape) {
+ done(ExecCancel);
+ event.accept();
+ return;
+ }
+ }
+
+ Window::event(event);
+}
+
+void Dialog::close()
+{
+ Window::close();
+ m_event_loop->quit(ExecCancel);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Dialog.h b/Userland/Libraries/LibGUI/Dialog.h
new file mode 100644
index 0000000000..76ee87919a
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Dialog.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Window.h>
+
+namespace GUI {
+
+class Dialog : public Window {
+ C_OBJECT(Dialog)
+public:
+ enum ExecResult {
+ ExecOK = 0,
+ ExecCancel = 1,
+ ExecAborted = 2,
+ ExecYes = 3,
+ ExecNo = 4,
+ };
+
+ virtual ~Dialog() override;
+
+ int exec();
+
+ int result() const { return m_result; }
+ void done(int result);
+
+ virtual void event(Core::Event&) override;
+
+ virtual void close() override;
+
+protected:
+ explicit Dialog(Window* parent_window);
+
+private:
+ OwnPtr<Core::EventLoop> m_event_loop;
+ int m_result { ExecAborted };
+};
+
+}
+
+template<>
+struct AK::Formatter<GUI::Dialog> : Formatter<Core::Object> {
+};
diff --git a/Userland/Libraries/LibGUI/DisplayLink.cpp b/Userland/Libraries/LibGUI/DisplayLink.cpp
new file mode 100644
index 0000000000..a2bf46e921
--- /dev/null
+++ b/Userland/Libraries/LibGUI/DisplayLink.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Badge.h>
+#include <AK/Function.h>
+#include <AK/HashMap.h>
+#include <LibGUI/DisplayLink.h>
+#include <LibGUI/WindowServerConnection.h>
+
+namespace GUI {
+
+class DisplayLinkCallback : public RefCounted<DisplayLinkCallback> {
+public:
+ DisplayLinkCallback(i32 link_id, Function<void(i32)> callback)
+ : m_link_id(link_id)
+ , m_callback(move(callback))
+ {
+ }
+
+ void invoke()
+ {
+ m_callback(m_link_id);
+ }
+
+private:
+ i32 m_link_id { 0 };
+ Function<void(i32)> m_callback;
+};
+
+static HashMap<i32, RefPtr<DisplayLinkCallback>>& callbacks()
+{
+ static HashMap<i32, RefPtr<DisplayLinkCallback>>* map;
+ if (!map)
+ map = new HashMap<i32, RefPtr<DisplayLinkCallback>>;
+ return *map;
+}
+
+static i32 s_next_callback_id = 1;
+
+i32 DisplayLink::register_callback(Function<void(i32)> callback)
+{
+ if (callbacks().is_empty())
+ WindowServerConnection::the().post_message(Messages::WindowServer::EnableDisplayLink());
+
+ i32 callback_id = s_next_callback_id++;
+ callbacks().set(callback_id, adopt(*new DisplayLinkCallback(callback_id, move(callback))));
+
+ return callback_id;
+}
+
+bool DisplayLink::unregister_callback(i32 callback_id)
+{
+ ASSERT(callbacks().contains(callback_id));
+ callbacks().remove(callback_id);
+
+ if (callbacks().is_empty())
+ WindowServerConnection::the().post_message(Messages::WindowServer::DisableDisplayLink());
+
+ return true;
+}
+
+void DisplayLink::notify(Badge<WindowServerConnection>)
+{
+ auto copy_of_callbacks = callbacks();
+ for (auto& it : copy_of_callbacks)
+ it.value->invoke();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/DisplayLink.h b/Userland/Libraries/LibGUI/DisplayLink.h
new file mode 100644
index 0000000000..2d1803b675
--- /dev/null
+++ b/Userland/Libraries/LibGUI/DisplayLink.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibGUI/Forward.h>
+
+namespace GUI {
+
+class DisplayLink {
+public:
+ static i32 register_callback(Function<void(i32)>);
+ static bool unregister_callback(i32 callback_id);
+
+ static void notify(Badge<WindowServerConnection>);
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/DragOperation.cpp b/Userland/Libraries/LibGUI/DragOperation.cpp
new file mode 100644
index 0000000000..060f25fc62
--- /dev/null
+++ b/Userland/Libraries/LibGUI/DragOperation.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Badge.h>
+#include <AK/SharedBuffer.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/MimeData.h>
+#include <LibGUI/DragOperation.h>
+#include <LibGUI/WindowServerConnection.h>
+#include <LibGfx/Bitmap.h>
+
+namespace GUI {
+
+static DragOperation* s_current_drag_operation;
+
+DragOperation::DragOperation(Core::Object* parent)
+ : Core::Object(parent)
+{
+}
+
+DragOperation::~DragOperation()
+{
+}
+
+DragOperation::Outcome DragOperation::exec()
+{
+ ASSERT(!s_current_drag_operation);
+ ASSERT(!m_event_loop);
+ ASSERT(m_mime_data);
+
+ int bitmap_id = -1;
+ Gfx::IntSize bitmap_size;
+ RefPtr<Gfx::Bitmap> shared_bitmap;
+ if (m_mime_data->has_format("image/x-raw-bitmap")) {
+ auto data = m_mime_data->data("image/x-raw-bitmap");
+ auto bitmap = Gfx::Bitmap::create_from_serialized_byte_buffer(move(data));
+ shared_bitmap = bitmap->to_bitmap_backed_by_shared_buffer();
+ shared_bitmap->shared_buffer()->share_with(WindowServerConnection::the().server_pid());
+ bitmap_id = shared_bitmap->shbuf_id();
+ bitmap_size = shared_bitmap->size();
+ }
+
+ auto response = WindowServerConnection::the().send_sync<Messages::WindowServer::StartDrag>(
+ m_mime_data->text(),
+ m_mime_data->all_data(),
+ bitmap_id, bitmap_size);
+
+ if (!response->started()) {
+ m_outcome = Outcome::Cancelled;
+ return m_outcome;
+ }
+
+ s_current_drag_operation = this;
+ m_event_loop = make<Core::EventLoop>();
+ auto result = m_event_loop->exec();
+ m_event_loop = nullptr;
+ dbgln("{}: event loop returned with result {}", class_name(), result);
+ remove_from_parent();
+ s_current_drag_operation = nullptr;
+ return m_outcome;
+}
+
+void DragOperation::done(Outcome outcome)
+{
+ ASSERT(m_outcome == Outcome::None);
+ m_outcome = outcome;
+ m_event_loop->quit(0);
+}
+
+void DragOperation::notify_accepted(Badge<WindowServerConnection>)
+{
+ ASSERT(s_current_drag_operation);
+ s_current_drag_operation->done(Outcome::Accepted);
+}
+
+void DragOperation::notify_cancelled(Badge<WindowServerConnection>)
+{
+ if (s_current_drag_operation)
+ s_current_drag_operation->done(Outcome::Cancelled);
+}
+
+void DragOperation::set_text(const String& text)
+{
+ if (!m_mime_data)
+ m_mime_data = Core::MimeData::construct();
+ m_mime_data->set_text(text);
+}
+void DragOperation::set_bitmap(const Gfx::Bitmap* bitmap)
+{
+ if (!m_mime_data)
+ m_mime_data = Core::MimeData::construct();
+ if (bitmap)
+ m_mime_data->set_data("image/x-raw-bitmap", bitmap->serialize_to_byte_buffer());
+}
+void DragOperation::set_data(const String& data_type, const String& data)
+{
+ if (!m_mime_data)
+ m_mime_data = Core::MimeData::construct();
+ m_mime_data->set_data(data_type, data.to_byte_buffer());
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/DragOperation.h b/Userland/Libraries/LibGUI/DragOperation.h
new file mode 100644
index 0000000000..8595c0a848
--- /dev/null
+++ b/Userland/Libraries/LibGUI/DragOperation.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/OwnPtr.h>
+#include <LibCore/Object.h>
+#include <LibGUI/Forward.h>
+#include <LibGfx/Forward.h>
+
+namespace GUI {
+
+class DragOperation : public Core::Object {
+ C_OBJECT(DragOperation)
+public:
+ enum class Outcome {
+ None,
+ Accepted,
+ Cancelled,
+ };
+
+ virtual ~DragOperation() override;
+
+ void set_mime_data(RefPtr<Core::MimeData> mime_data) { m_mime_data = move(mime_data); }
+ void set_text(const String& text);
+ void set_bitmap(const Gfx::Bitmap* bitmap);
+ void set_data(const String& data_type, const String& data);
+
+ Outcome exec();
+ Outcome outcome() const { return m_outcome; }
+
+ static void notify_accepted(Badge<WindowServerConnection>);
+ static void notify_cancelled(Badge<WindowServerConnection>);
+
+protected:
+ explicit DragOperation(Core::Object* parent = nullptr);
+
+private:
+ void done(Outcome);
+
+ OwnPtr<Core::EventLoop> m_event_loop;
+ Outcome m_outcome { Outcome::None };
+ RefPtr<Core::MimeData> m_mime_data;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/EditingEngine.cpp b/Userland/Libraries/LibGUI/EditingEngine.cpp
new file mode 100644
index 0000000000..4cd0251a13
--- /dev/null
+++ b/Userland/Libraries/LibGUI/EditingEngine.cpp
@@ -0,0 +1,453 @@
+/*
+ * Copyright (c) 2021, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/EditingEngine.h>
+#include <LibGUI/Event.h>
+#include <LibGUI/TextEditor.h>
+
+namespace GUI {
+
+EditingEngine::~EditingEngine()
+{
+}
+
+void EditingEngine::attach(TextEditor& editor)
+{
+ ASSERT(!m_editor);
+ m_editor = editor;
+}
+
+void EditingEngine::detach()
+{
+ ASSERT(m_editor);
+ m_editor = nullptr;
+}
+
+bool EditingEngine::on_key(const KeyEvent& event)
+{
+ if (event.key() == KeyCode::Key_Left) {
+ if (!event.shift() && m_editor->selection()->is_valid()) {
+ m_editor->set_cursor(m_editor->selection()->normalized().start());
+ m_editor->selection()->clear();
+ m_editor->did_update_selection();
+ if (!event.ctrl()) {
+ m_editor->update();
+ return true;
+ }
+ }
+ if (event.ctrl()) {
+ move_to_previous_span(event);
+ if (event.shift() && m_editor->selection()->start().is_valid()) {
+ m_editor->selection()->set_end(m_editor->cursor());
+ m_editor->did_update_selection();
+ }
+ return true;
+ }
+ move_one_left(event);
+ if (event.shift() && m_editor->selection()->start().is_valid()) {
+ m_editor->selection()->set_end(m_editor->cursor());
+ m_editor->did_update_selection();
+ }
+ return true;
+ }
+
+ if (event.key() == KeyCode::Key_Right) {
+ if (!event.shift() && m_editor->selection()->is_valid()) {
+ m_editor->set_cursor(m_editor->selection()->normalized().end());
+ m_editor->selection()->clear();
+ m_editor->did_update_selection();
+ if (!event.ctrl()) {
+ m_editor->update();
+ return true;
+ }
+ }
+ if (event.ctrl()) {
+ move_to_next_span(event);
+ return true;
+ }
+ move_one_right(event);
+ if (event.shift() && m_editor->selection()->start().is_valid()) {
+ m_editor->selection()->set_end(m_editor->cursor());
+ m_editor->did_update_selection();
+ }
+ return true;
+ }
+
+ if (event.key() == KeyCode::Key_Up) {
+ move_one_up(event);
+ if (event.shift() && m_editor->selection()->start().is_valid()) {
+ m_editor->selection()->set_end(m_editor->cursor());
+ m_editor->did_update_selection();
+ }
+ return true;
+ }
+
+ if (event.key() == KeyCode::Key_Down) {
+ move_one_down(event);
+ if (event.shift() && m_editor->selection()->start().is_valid()) {
+ m_editor->selection()->set_end(m_editor->cursor());
+ m_editor->did_update_selection();
+ }
+ return true;
+ }
+
+ if (event.key() == KeyCode::Key_Home) {
+ if (event.ctrl()) {
+ m_editor->toggle_selection_if_needed_for_event(event.shift());
+ move_to_first_line();
+ if (event.shift() && m_editor->selection()->start().is_valid()) {
+ m_editor->selection()->set_end(m_editor->cursor());
+ m_editor->did_update_selection();
+ }
+ } else {
+ move_to_line_beginning(event);
+ if (event.shift() && m_editor->selection()->start().is_valid()) {
+ m_editor->selection()->set_end(m_editor->cursor());
+ m_editor->did_update_selection();
+ }
+ }
+ return true;
+ }
+
+ if (event.key() == KeyCode::Key_End) {
+ if (event.ctrl()) {
+ m_editor->toggle_selection_if_needed_for_event(event.shift());
+ move_to_last_line();
+ if (event.shift() && m_editor->selection()->start().is_valid()) {
+ m_editor->selection()->set_end(m_editor->cursor());
+ m_editor->did_update_selection();
+ }
+ } else {
+ move_to_line_end(event);
+ if (event.shift() && m_editor->selection()->start().is_valid()) {
+ m_editor->selection()->set_end(m_editor->cursor());
+ m_editor->did_update_selection();
+ }
+ }
+ return true;
+ }
+
+ if (event.key() == KeyCode::Key_PageUp) {
+ move_page_up(event);
+ if (event.shift() && m_editor->selection()->start().is_valid()) {
+ m_editor->selection()->set_end(m_editor->cursor());
+ m_editor->did_update_selection();
+ }
+ return true;
+ }
+
+ if (event.key() == KeyCode::Key_PageDown) {
+ move_page_down(event);
+ if (event.shift() && m_editor->selection()->start().is_valid()) {
+ m_editor->selection()->set_end(m_editor->cursor());
+ m_editor->did_update_selection();
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void EditingEngine::move_one_left(const KeyEvent& event)
+{
+ if (m_editor->cursor().column() > 0) {
+ int new_column = m_editor->cursor().column() - 1;
+ m_editor->toggle_selection_if_needed_for_event(event.shift());
+ m_editor->set_cursor(m_editor->cursor().line(), new_column);
+ } else if (m_editor->cursor().line() > 0) {
+ int new_line = m_editor->cursor().line() - 1;
+ int new_column = m_editor->lines()[new_line].length();
+ m_editor->toggle_selection_if_needed_for_event(event.shift());
+ m_editor->set_cursor(new_line, new_column);
+ }
+}
+
+void EditingEngine::move_one_right(const KeyEvent& event)
+{
+ int new_line = m_editor->cursor().line();
+ int new_column = m_editor->cursor().column();
+ if (m_editor->cursor().column() < m_editor->current_line().length()) {
+ new_line = m_editor->cursor().line();
+ new_column = m_editor->cursor().column() + 1;
+ } else if (m_editor->cursor().line() != m_editor->line_count() - 1) {
+ new_line = m_editor->cursor().line() + 1;
+ new_column = 0;
+ }
+ m_editor->toggle_selection_if_needed_for_event(event.shift());
+ m_editor->set_cursor(new_line, new_column);
+}
+
+void EditingEngine::move_to_previous_span(const KeyEvent& event)
+{
+ TextPosition new_cursor;
+ if (m_editor->document().has_spans()) {
+ auto span = m_editor->document().first_non_skippable_span_before(m_editor->cursor());
+ if (span.has_value()) {
+ new_cursor = span.value().range.start();
+ } else {
+ // No remaining spans, just use word break calculation
+ new_cursor = m_editor->document().first_word_break_before(m_editor->cursor(), true);
+ }
+ } else {
+ new_cursor = m_editor->document().first_word_break_before(m_editor->cursor(), true);
+ }
+ m_editor->toggle_selection_if_needed_for_event(event.shift());
+ m_editor->set_cursor(new_cursor);
+}
+
+void EditingEngine::move_to_next_span(const KeyEvent& event)
+{
+ TextPosition new_cursor;
+ if (m_editor->document().has_spans()) {
+ auto span = m_editor->document().first_non_skippable_span_after(m_editor->cursor());
+ if (span.has_value()) {
+ new_cursor = span.value().range.start();
+ } else {
+ // No remaining spans, just use word break calculation
+ new_cursor = m_editor->document().first_word_break_after(m_editor->cursor());
+ }
+ } else {
+ new_cursor = m_editor->document().first_word_break_after(m_editor->cursor());
+ }
+ m_editor->toggle_selection_if_needed_for_event(event.shift());
+ m_editor->set_cursor(new_cursor);
+ if (event.shift() && m_editor->selection()->start().is_valid()) {
+ m_editor->selection()->set_end(m_editor->cursor());
+ m_editor->did_update_selection();
+ }
+}
+
+void EditingEngine::move_to_line_beginning(const KeyEvent& event)
+{
+ TextPosition new_cursor;
+ m_editor->toggle_selection_if_needed_for_event(event.shift());
+ if (m_editor->is_line_wrapping_enabled()) {
+ // FIXME: Replicate the first_nonspace_column behavior in wrapping mode.
+ auto home_position = m_editor->cursor_content_rect().location().translated(-m_editor->width(), 0);
+ new_cursor = m_editor->text_position_at_content_position(home_position);
+ } else {
+ size_t first_nonspace_column = m_editor->current_line().first_non_whitespace_column();
+ if (m_editor->cursor().column() == first_nonspace_column) {
+ new_cursor = { m_editor->cursor().line(), 0 };
+ } else {
+ new_cursor = { m_editor->cursor().line(), first_nonspace_column };
+ }
+ }
+ m_editor->set_cursor(new_cursor);
+}
+
+void EditingEngine::move_to_line_end(const KeyEvent& event)
+{
+ TextPosition new_cursor;
+ if (m_editor->is_line_wrapping_enabled()) {
+ auto end_position = m_editor->cursor_content_rect().location().translated(m_editor->width(), 0);
+ new_cursor = m_editor->text_position_at_content_position(end_position);
+ } else {
+ new_cursor = { m_editor->cursor().line(), m_editor->current_line().length() };
+ }
+ m_editor->toggle_selection_if_needed_for_event(event.shift());
+ m_editor->set_cursor(new_cursor);
+}
+
+void EditingEngine::move_one_up(const KeyEvent& event)
+{
+ if (m_editor->cursor().line() > 0 || m_editor->is_line_wrapping_enabled()) {
+ if (event.ctrl() && event.shift()) {
+ move_selected_lines_up();
+ return;
+ }
+ TextPosition new_cursor;
+ if (m_editor->is_line_wrapping_enabled()) {
+ auto position_above = m_editor->cursor_content_rect().location().translated(0, -m_editor->line_height());
+ new_cursor = m_editor->text_position_at_content_position(position_above);
+ } else {
+ size_t new_line = m_editor->cursor().line() - 1;
+ size_t new_column = min(m_editor->cursor().column(), m_editor->line(new_line).length());
+ new_cursor = { new_line, new_column };
+ }
+ m_editor->toggle_selection_if_needed_for_event(event.shift());
+ m_editor->set_cursor(new_cursor);
+ }
+};
+
+void EditingEngine::move_one_down(const KeyEvent& event)
+{
+ if (m_editor->cursor().line() < (m_editor->line_count() - 1) || m_editor->is_line_wrapping_enabled()) {
+ if (event.ctrl() && event.shift()) {
+ move_selected_lines_down();
+ return;
+ }
+ TextPosition new_cursor;
+ if (m_editor->is_line_wrapping_enabled()) {
+ new_cursor = m_editor->text_position_at_content_position(m_editor->cursor_content_rect().location().translated(0, m_editor->line_height()));
+ auto position_below = m_editor->cursor_content_rect().location().translated(0, m_editor->line_height());
+ new_cursor = m_editor->text_position_at_content_position(position_below);
+ } else {
+ size_t new_line = m_editor->cursor().line() + 1;
+ size_t new_column = min(m_editor->cursor().column(), m_editor->line(new_line).length());
+ new_cursor = { new_line, new_column };
+ }
+ m_editor->toggle_selection_if_needed_for_event(event.shift());
+ m_editor->set_cursor(new_cursor);
+ }
+};
+
+void EditingEngine::move_up(const KeyEvent& event, double page_height_factor)
+{
+ if (m_editor->cursor().line() > 0 || m_editor->is_line_wrapping_enabled()) {
+ int pixels = (int)(m_editor->visible_content_rect().height() * page_height_factor);
+
+ TextPosition new_cursor;
+ if (m_editor->is_line_wrapping_enabled()) {
+ auto position_above = m_editor->cursor_content_rect().location().translated(0, -pixels);
+ new_cursor = m_editor->text_position_at_content_position(position_above);
+ } else {
+ size_t page_step = (size_t)pixels / (size_t)m_editor->line_height();
+ size_t new_line = m_editor->cursor().line() < page_step ? 0 : m_editor->cursor().line() - page_step;
+ size_t new_column = min(m_editor->cursor().column(), m_editor->line(new_line).length());
+ new_cursor = { new_line, new_column };
+ }
+ m_editor->toggle_selection_if_needed_for_event(event.shift());
+ m_editor->set_cursor(new_cursor);
+ }
+};
+
+void EditingEngine::move_down(const KeyEvent& event, double page_height_factor)
+{
+ if (m_editor->cursor().line() < (m_editor->line_count() - 1) || m_editor->is_line_wrapping_enabled()) {
+ int pixels = (int)(m_editor->visible_content_rect().height() * page_height_factor);
+ TextPosition new_cursor;
+ if (m_editor->is_line_wrapping_enabled()) {
+ auto position_below = m_editor->cursor_content_rect().location().translated(0, pixels);
+ new_cursor = m_editor->text_position_at_content_position(position_below);
+ } else {
+ size_t new_line = min(m_editor->line_count() - 1, m_editor->cursor().line() + pixels / m_editor->line_height());
+ size_t new_column = min(m_editor->cursor().column(), m_editor->lines()[new_line].length());
+ new_cursor = { new_line, new_column };
+ }
+ m_editor->toggle_selection_if_needed_for_event(event.shift());
+ m_editor->set_cursor(new_cursor);
+ };
+}
+
+void EditingEngine::move_page_up(const KeyEvent& event)
+{
+ move_up(event, 1);
+};
+
+void EditingEngine::move_page_down(const KeyEvent& event)
+{
+ move_down(event, 1);
+};
+
+void EditingEngine::move_to_first_line()
+{
+ m_editor->set_cursor(0, 0);
+};
+
+void EditingEngine::move_to_last_line()
+{
+ m_editor->set_cursor(m_editor->line_count() - 1, m_editor->lines()[m_editor->line_count() - 1].length());
+};
+
+void EditingEngine::get_selection_line_boundaries(size_t& first_line, size_t& last_line)
+{
+ auto selection = m_editor->normalized_selection();
+ if (!selection.is_valid()) {
+ first_line = m_editor->cursor().line();
+ last_line = m_editor->cursor().line();
+ return;
+ }
+ first_line = selection.start().line();
+ last_line = selection.end().line();
+ if (first_line != last_line && selection.end().column() == 0)
+ last_line -= 1;
+}
+
+void EditingEngine::move_selected_lines_up()
+{
+ if (!m_editor->is_editable())
+ return;
+ size_t first_line;
+ size_t last_line;
+ get_selection_line_boundaries(first_line, last_line);
+
+ if (first_line == 0)
+ return;
+
+ auto& lines = m_editor->document().lines();
+ lines.insert((int)last_line, lines.take((int)first_line - 1));
+ m_editor->set_cursor({ first_line - 1, 0 });
+
+ if (m_editor->has_selection()) {
+ m_editor->selection()->set_start({ first_line - 1, 0 });
+ m_editor->selection()->set_end({ last_line - 1, m_editor->line(last_line - 1).length() });
+ }
+
+ m_editor->did_change();
+ m_editor->update();
+}
+
+void EditingEngine::move_selected_lines_down()
+{
+ if (!m_editor->is_editable())
+ return;
+ size_t first_line;
+ size_t last_line;
+ get_selection_line_boundaries(first_line, last_line);
+
+ auto& lines = m_editor->document().lines();
+ ASSERT(lines.size() != 0);
+ if (last_line >= lines.size() - 1)
+ return;
+
+ lines.insert((int)first_line, lines.take((int)last_line + 1));
+ m_editor->set_cursor({ first_line + 1, 0 });
+
+ if (m_editor->has_selection()) {
+ m_editor->selection()->set_start({ first_line + 1, 0 });
+ m_editor->selection()->set_end({ last_line + 1, m_editor->line(last_line + 1).length() });
+ }
+
+ m_editor->did_change();
+ m_editor->update();
+}
+
+void EditingEngine::delete_char()
+{
+ if (!m_editor->is_editable())
+ return;
+ m_editor->do_delete();
+};
+
+void EditingEngine::delete_line()
+{
+ if (!m_editor->is_editable())
+ return;
+ m_editor->delete_current_line();
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/EditingEngine.h b/Userland/Libraries/LibGUI/EditingEngine.h
new file mode 100644
index 0000000000..37c25410ce
--- /dev/null
+++ b/Userland/Libraries/LibGUI/EditingEngine.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2021, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Noncopyable.h>
+#include <LibGUI/Event.h>
+#include <LibGUI/TextDocument.h>
+
+namespace GUI {
+
+enum CursorWidth {
+ NARROW,
+ WIDE
+};
+
+class EditingEngine {
+ AK_MAKE_NONCOPYABLE(EditingEngine);
+ AK_MAKE_NONMOVABLE(EditingEngine);
+
+public:
+ virtual ~EditingEngine();
+
+ virtual CursorWidth cursor_width() const { return NARROW; }
+
+ void attach(TextEditor& editor);
+ void detach();
+
+ virtual bool on_key(const KeyEvent& event);
+
+protected:
+ EditingEngine() { }
+
+ WeakPtr<TextEditor> m_editor;
+
+ void move_one_left(const KeyEvent& event);
+ void move_one_right(const KeyEvent& event);
+ void move_one_up(const KeyEvent& event);
+ void move_one_down(const KeyEvent& event);
+ void move_to_previous_span(const KeyEvent& event);
+ void move_to_next_span(const KeyEvent& event);
+ void move_to_line_beginning(const KeyEvent& event);
+ void move_to_line_end(const KeyEvent& event);
+ void move_page_up(const KeyEvent& event);
+ void move_page_down(const KeyEvent& event);
+ void move_to_first_line();
+ void move_to_last_line();
+
+ void move_up(const KeyEvent& event, double page_height_factor);
+ void move_down(const KeyEvent& event, double page_height_factor);
+
+ void get_selection_line_boundaries(size_t& first_line, size_t& last_line);
+
+ void delete_line();
+ void delete_char();
+
+private:
+ void move_selected_lines_up();
+ void move_selected_lines_down();
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/EmojiInputDialog.cpp b/Userland/Libraries/LibGUI/EmojiInputDialog.cpp
new file mode 100644
index 0000000000..06072d6815
--- /dev/null
+++ b/Userland/Libraries/LibGUI/EmojiInputDialog.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LexicalPath.h>
+#include <AK/StringBuilder.h>
+#include <AK/Utf32View.h>
+#include <LibCore/DirIterator.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/EmojiInputDialog.h>
+#include <LibGUI/Event.h>
+#include <LibGUI/Frame.h>
+#include <stdlib.h>
+
+namespace GUI {
+
+static Vector<u32> supported_emoji_code_points()
+{
+ Vector<u32> code_points;
+ Core::DirIterator dt("/res/emoji", Core::DirIterator::SkipDots);
+ while (dt.has_next()) {
+ auto filename = dt.next_path();
+ auto lexical_path = LexicalPath(filename);
+ if (lexical_path.extension() != "png")
+ continue;
+ auto basename = lexical_path.basename();
+ if (!basename.starts_with("U+"))
+ continue;
+ u32 code_point = strtoul(basename.characters() + 2, nullptr, 16);
+ code_points.append(code_point);
+ }
+ return code_points;
+}
+
+EmojiInputDialog::EmojiInputDialog(Window* parent_window)
+ : Dialog(parent_window)
+{
+ set_frameless(true);
+
+ auto& main_widget = set_main_widget<Frame>();
+ main_widget.set_frame_shape(Gfx::FrameShape::Container);
+ main_widget.set_frame_shadow(Gfx::FrameShadow::Raised);
+ main_widget.set_fill_with_background_color(true);
+ auto& main_layout = main_widget.set_layout<VerticalBoxLayout>();
+ main_layout.set_margins({ 1, 1, 1, 1 });
+ main_layout.set_spacing(0);
+
+ auto code_points = supported_emoji_code_points();
+
+ size_t index = 0;
+ size_t columns = 6;
+ size_t rows = ceil_div(code_points.size(), columns);
+
+ for (size_t row = 0; row < rows && index < code_points.size(); ++row) {
+ auto& horizontal_container = main_widget.add<Widget>();
+ auto& horizontal_layout = horizontal_container.set_layout<HorizontalBoxLayout>();
+ horizontal_layout.set_spacing(0);
+ for (size_t column = 0; column < columns; ++column) {
+ if (index < code_points.size()) {
+ StringBuilder builder;
+ builder.append(Utf32View(&code_points[index++], 1));
+ auto emoji_text = builder.to_string();
+ auto& button = horizontal_container.add<Button>(emoji_text);
+ button.set_min_size(16, 16);
+ button.set_button_style(Gfx::ButtonStyle::CoolBar);
+ button.on_click = [this, button = &button](auto) {
+ m_selected_emoji_text = button->text();
+ done(ExecOK);
+ };
+ } else {
+ horizontal_container.add<Widget>();
+ }
+ }
+ }
+}
+
+void EmojiInputDialog::event(Core::Event& event)
+{
+ if (event.type() == Event::KeyDown) {
+ auto& key_event = static_cast<KeyEvent&>(event);
+ if (key_event.key() == Key_Escape) {
+ done(ExecCancel);
+ return;
+ }
+ }
+ Dialog::event(event);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/EmojiInputDialog.h b/Userland/Libraries/LibGUI/EmojiInputDialog.h
new file mode 100644
index 0000000000..16db130f03
--- /dev/null
+++ b/Userland/Libraries/LibGUI/EmojiInputDialog.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Dialog.h>
+
+namespace GUI {
+
+class EmojiInputDialog final : public Dialog {
+ C_OBJECT(EmojiInputDialog);
+
+public:
+ const String& selected_emoji_text() const { return m_selected_emoji_text; }
+
+private:
+ virtual void event(Core::Event&) override;
+ explicit EmojiInputDialog(Window* parent_window);
+
+ String m_selected_emoji_text;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Event.cpp b/Userland/Libraries/LibGUI/Event.cpp
new file mode 100644
index 0000000000..4b5ed888a5
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Event.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibCore/MimeData.h>
+#include <LibGUI/Event.h>
+
+namespace GUI {
+
+DropEvent::DropEvent(const Gfx::IntPoint& position, const String& text, NonnullRefPtr<Core::MimeData> mime_data)
+ : Event(Event::Drop)
+ , m_position(position)
+ , m_text(text)
+ , m_mime_data(move(mime_data))
+{
+}
+
+DropEvent::~DropEvent()
+{
+}
+
+String KeyEvent::to_string() const
+{
+ Vector<String, 8> parts;
+
+ if (m_modifiers & Mod_Ctrl)
+ parts.append("Ctrl");
+ if (m_modifiers & Mod_Shift)
+ parts.append("Shift");
+ if (m_modifiers & Mod_Alt)
+ parts.append("Alt");
+ if (m_modifiers & Mod_Logo)
+ parts.append("Logo");
+
+ if (auto* key_name = key_code_to_string(static_cast<KeyCode>(m_key)))
+ parts.append(key_name);
+ else
+ parts.append("(Invalid)");
+
+ StringBuilder builder;
+ for (size_t i = 0; i < parts.size(); ++i) {
+ builder.append(parts[i]);
+ if (i != parts.size() - 1)
+ builder.append('+');
+ }
+ return builder.to_string();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Event.h b/Userland/Libraries/LibGUI/Event.h
new file mode 100644
index 0000000000..ce60f00e12
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Event.h
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/StringBuilder.h>
+#include <AK/Vector.h>
+#include <Kernel/API/KeyCode.h>
+#include <LibCore/Event.h>
+#include <LibGUI/FocusSource.h>
+#include <LibGUI/WindowType.h>
+#include <LibGfx/Point.h>
+#include <LibGfx/Rect.h>
+
+namespace GUI {
+
+class Event : public Core::Event {
+public:
+ enum Type {
+ Show = 1000,
+ Hide,
+ Paint,
+ MultiPaint,
+ Resize,
+ MouseMove,
+ MouseDown,
+ MouseDoubleClick,
+ MouseUp,
+ MouseWheel,
+ Enter,
+ Leave,
+ KeyDown,
+ KeyUp,
+ WindowEntered,
+ WindowLeft,
+ WindowBecameInactive,
+ WindowBecameActive,
+ WindowInputEntered,
+ WindowInputLeft,
+ FocusIn,
+ FocusOut,
+ WindowCloseRequest,
+ ContextMenu,
+ EnabledChange,
+ DragEnter,
+ DragLeave,
+ DragMove,
+ Drop,
+ ThemeChange,
+
+ __Begin_WM_Events,
+ WM_WindowRemoved,
+ WM_WindowStateChanged,
+ WM_WindowRectChanged,
+ WM_WindowIconBitmapChanged,
+ __End_WM_Events,
+ };
+
+ Event() { }
+ explicit Event(Type type)
+ : Core::Event(type)
+ {
+ }
+ virtual ~Event() { }
+
+ bool is_key_event() const { return type() == KeyUp || type() == KeyDown; }
+ bool is_paint_event() const { return type() == Paint; }
+};
+
+class WMEvent : public Event {
+public:
+ WMEvent(Type type, int client_id, int window_id)
+ : Event(type)
+ , m_client_id(client_id)
+ , m_window_id(window_id)
+ {
+ }
+
+ int client_id() const { return m_client_id; }
+ int window_id() const { return m_window_id; }
+
+private:
+ int m_client_id { -1 };
+ int m_window_id { -1 };
+};
+
+class WMWindowRemovedEvent : public WMEvent {
+public:
+ WMWindowRemovedEvent(int client_id, int window_id)
+ : WMEvent(Event::Type::WM_WindowRemoved, client_id, window_id)
+ {
+ }
+};
+
+class WMWindowStateChangedEvent : public WMEvent {
+public:
+ WMWindowStateChangedEvent(int client_id, int window_id, int parent_client_id, int parent_window_id, const StringView& title, const Gfx::IntRect& rect, bool is_active, bool is_modal, WindowType window_type, bool is_minimized, bool is_frameless, int progress)
+ : WMEvent(Event::Type::WM_WindowStateChanged, client_id, window_id)
+ , m_parent_client_id(parent_client_id)
+ , m_parent_window_id(parent_window_id)
+ , m_title(title)
+ , m_rect(rect)
+ , m_window_type(window_type)
+ , m_active(is_active)
+ , m_modal(is_modal)
+ , m_minimized(is_minimized)
+ , m_frameless(is_frameless)
+ , m_progress(progress)
+ {
+ }
+
+ int parent_client_id() const { return m_parent_client_id; }
+ int parent_window_id() const { return m_parent_window_id; }
+ const String& title() const { return m_title; }
+ const Gfx::IntRect& rect() const { return m_rect; }
+ bool is_active() const { return m_active; }
+ bool is_modal() const { return m_modal; }
+ WindowType window_type() const { return m_window_type; }
+ bool is_minimized() const { return m_minimized; }
+ bool is_frameless() const { return m_frameless; }
+ int progress() const { return m_progress; }
+
+private:
+ int m_parent_client_id;
+ int m_parent_window_id;
+ String m_title;
+ Gfx::IntRect m_rect;
+ WindowType m_window_type;
+ bool m_active;
+ bool m_modal;
+ bool m_minimized;
+ bool m_frameless;
+ int m_progress;
+};
+
+class WMWindowRectChangedEvent : public WMEvent {
+public:
+ WMWindowRectChangedEvent(int client_id, int window_id, const Gfx::IntRect& rect)
+ : WMEvent(Event::Type::WM_WindowRectChanged, client_id, window_id)
+ , m_rect(rect)
+ {
+ }
+
+ const Gfx::IntRect& rect() const { return m_rect; }
+
+private:
+ Gfx::IntRect m_rect;
+};
+
+class WMWindowIconBitmapChangedEvent : public WMEvent {
+public:
+ WMWindowIconBitmapChangedEvent(int client_id, int window_id, int icon_buffer_id, const Gfx::IntSize& icon_size)
+ : WMEvent(Event::Type::WM_WindowIconBitmapChanged, client_id, window_id)
+ , m_icon_buffer_id(icon_buffer_id)
+ , m_icon_size(icon_size)
+ {
+ }
+
+ int icon_buffer_id() const { return m_icon_buffer_id; }
+ const Gfx::IntSize& icon_size() const { return m_icon_size; }
+
+private:
+ int m_icon_buffer_id;
+ Gfx::IntSize m_icon_size;
+};
+
+class MultiPaintEvent final : public Event {
+public:
+ explicit MultiPaintEvent(const Vector<Gfx::IntRect, 32>& rects, const Gfx::IntSize& window_size)
+ : Event(Event::MultiPaint)
+ , m_rects(rects)
+ , m_window_size(window_size)
+ {
+ }
+
+ const Vector<Gfx::IntRect, 32>& rects() const { return m_rects; }
+ const Gfx::IntSize& window_size() const { return m_window_size; }
+
+private:
+ Vector<Gfx::IntRect, 32> m_rects;
+ Gfx::IntSize m_window_size;
+};
+
+class PaintEvent final : public Event {
+public:
+ explicit PaintEvent(const Gfx::IntRect& rect, const Gfx::IntSize& window_size = {})
+ : Event(Event::Paint)
+ , m_rect(rect)
+ , m_window_size(window_size)
+ {
+ }
+
+ const Gfx::IntRect& rect() const { return m_rect; }
+ const Gfx::IntSize& window_size() const { return m_window_size; }
+
+private:
+ Gfx::IntRect m_rect;
+ Gfx::IntSize m_window_size;
+};
+
+class ResizeEvent final : public Event {
+public:
+ explicit ResizeEvent(const Gfx::IntSize& size)
+ : Event(Event::Resize)
+ , m_size(size)
+ {
+ }
+
+ const Gfx::IntSize& size() const { return m_size; }
+
+private:
+ Gfx::IntSize m_size;
+};
+
+class ContextMenuEvent final : public Event {
+public:
+ explicit ContextMenuEvent(const Gfx::IntPoint& position, const Gfx::IntPoint& screen_position)
+ : Event(Event::ContextMenu)
+ , m_position(position)
+ , m_screen_position(screen_position)
+ {
+ }
+
+ const Gfx::IntPoint& position() const { return m_position; }
+ const Gfx::IntPoint& screen_position() const { return m_screen_position; }
+
+private:
+ Gfx::IntPoint m_position;
+ Gfx::IntPoint m_screen_position;
+};
+
+class ShowEvent final : public Event {
+public:
+ ShowEvent()
+ : Event(Event::Show)
+ {
+ }
+};
+
+class HideEvent final : public Event {
+public:
+ HideEvent()
+ : Event(Event::Hide)
+ {
+ }
+};
+
+enum MouseButton : u8 {
+ None = 0,
+ Left = 1,
+ Right = 2,
+ Middle = 4,
+ Back = 8,
+ Forward = 16,
+};
+
+class KeyEvent final : public Event {
+public:
+ KeyEvent(Type type, KeyCode key, u8 modifiers, u32 code_point, u32 scancode)
+ : Event(type)
+ , m_key(key)
+ , m_modifiers(modifiers)
+ , m_code_point(code_point)
+ , m_scancode(scancode)
+ {
+ }
+
+ KeyCode key() const { return m_key; }
+ bool ctrl() const { return m_modifiers & Mod_Ctrl; }
+ bool alt() const { return m_modifiers & Mod_Alt; }
+ bool shift() const { return m_modifiers & Mod_Shift; }
+ bool logo() const { return m_modifiers & Mod_Logo; }
+ u8 modifiers() const { return m_modifiers; }
+ u32 code_point() const { return m_code_point; }
+ String text() const
+ {
+ StringBuilder sb;
+ sb.append_code_point(m_code_point);
+ return sb.to_string();
+ }
+ u32 scancode() const { return m_scancode; }
+
+ String to_string() const;
+
+private:
+ friend class WindowServerConnection;
+ KeyCode m_key { KeyCode::Key_Invalid };
+ u8 m_modifiers { 0 };
+ u32 m_code_point { 0 };
+ u32 m_scancode { 0 };
+};
+
+class MouseEvent final : public Event {
+public:
+ MouseEvent(Type type, const Gfx::IntPoint& position, unsigned buttons, MouseButton button, unsigned modifiers, int wheel_delta)
+ : Event(type)
+ , m_position(position)
+ , m_buttons(buttons)
+ , m_button(button)
+ , m_modifiers(modifiers)
+ , m_wheel_delta(wheel_delta)
+ {
+ }
+
+ const Gfx::IntPoint& position() const { return m_position; }
+ int x() const { return m_position.x(); }
+ int y() const { return m_position.y(); }
+ MouseButton button() const { return m_button; }
+ unsigned buttons() const { return m_buttons; }
+ bool ctrl() const { return m_modifiers & Mod_Ctrl; }
+ bool alt() const { return m_modifiers & Mod_Alt; }
+ bool shift() const { return m_modifiers & Mod_Shift; }
+ bool logo() const { return m_modifiers & Mod_Logo; }
+ unsigned modifiers() const { return m_modifiers; }
+ int wheel_delta() const { return m_wheel_delta; }
+
+private:
+ Gfx::IntPoint m_position;
+ unsigned m_buttons { 0 };
+ MouseButton m_button { MouseButton::None };
+ unsigned m_modifiers { 0 };
+ int m_wheel_delta { 0 };
+};
+
+class DragEvent final : public Event {
+public:
+ DragEvent(Type type, const Gfx::IntPoint& position, Vector<String> mime_types)
+ : Event(type)
+ , m_position(position)
+ , m_mime_types(move(mime_types))
+ {
+ }
+
+ const Gfx::IntPoint& position() const { return m_position; }
+ const Vector<String>& mime_types() const { return m_mime_types; }
+
+private:
+ Gfx::IntPoint m_position;
+ Vector<String> m_mime_types;
+};
+
+class DropEvent final : public Event {
+public:
+ DropEvent(const Gfx::IntPoint&, const String& text, NonnullRefPtr<Core::MimeData> mime_data);
+
+ ~DropEvent();
+
+ const Gfx::IntPoint& position() const { return m_position; }
+ const String& text() const { return m_text; }
+ const Core::MimeData& mime_data() const { return m_mime_data; }
+
+private:
+ Gfx::IntPoint m_position;
+ String m_text;
+ NonnullRefPtr<Core::MimeData> m_mime_data;
+};
+
+class ThemeChangeEvent final : public Event {
+public:
+ ThemeChangeEvent()
+ : Event(Type::ThemeChange)
+ {
+ }
+};
+
+class FocusEvent final : public Event {
+public:
+ explicit FocusEvent(Type type, FocusSource source)
+ : Event(type)
+ , m_source(source)
+ {
+ }
+
+ FocusSource source() const { return m_source; }
+
+private:
+ FocusSource m_source { FocusSource::Programmatic };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/FileIconProvider.cpp b/Userland/Libraries/LibGUI/FileIconProvider.cpp
new file mode 100644
index 0000000000..9d2ec26ba0
--- /dev/null
+++ b/Userland/Libraries/LibGUI/FileIconProvider.cpp
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/String.h>
+#include <LibCore/ConfigFile.h>
+#include <LibCore/DirIterator.h>
+#include <LibCore/File.h>
+#include <LibCore/StandardPaths.h>
+#include <LibELF/Image.h>
+#include <LibGUI/FileIconProvider.h>
+#include <LibGUI/Icon.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/PNGLoader.h>
+#include <sys/stat.h>
+
+namespace GUI {
+
+static Icon s_hard_disk_icon;
+static Icon s_directory_icon;
+static Icon s_directory_open_icon;
+static Icon s_inaccessible_directory_icon;
+static Icon s_home_directory_icon;
+static Icon s_home_directory_open_icon;
+static Icon s_file_icon;
+static Icon s_symlink_icon;
+static Icon s_socket_icon;
+static Icon s_executable_icon;
+static Icon s_filetype_image_icon;
+static RefPtr<Gfx::Bitmap> s_symlink_emblem;
+static RefPtr<Gfx::Bitmap> s_symlink_emblem_small;
+
+static HashMap<String, Icon> s_filetype_icons;
+static HashMap<String, Vector<String>> s_filetype_patterns;
+
+static void initialize_executable_icon_if_needed()
+{
+ static bool initialized = false;
+ if (initialized)
+ return;
+ initialized = true;
+ s_executable_icon = Icon::default_icon("filetype-executable");
+}
+
+static void initialize_if_needed()
+{
+ static bool s_initialized = false;
+ if (s_initialized)
+ return;
+
+ auto config = Core::ConfigFile::open("/etc/FileIconProvider.ini");
+
+ s_symlink_emblem = Gfx::Bitmap::load_from_file("/res/icons/symlink-emblem.png");
+ s_symlink_emblem_small = Gfx::Bitmap::load_from_file("/res/icons/symlink-emblem-small.png");
+
+ s_hard_disk_icon = Icon::default_icon("hard-disk");
+ s_directory_icon = Icon::default_icon("filetype-folder");
+ s_directory_open_icon = Icon::default_icon("filetype-folder-open");
+ s_inaccessible_directory_icon = Icon::default_icon("filetype-folder-inaccessible");
+ s_home_directory_icon = Icon::default_icon("home-directory");
+ s_home_directory_open_icon = Icon::default_icon("home-directory-open");
+ s_file_icon = Icon::default_icon("filetype-unknown");
+ s_symlink_icon = Icon::default_icon("filetype-symlink");
+ s_socket_icon = Icon::default_icon("filetype-socket");
+
+ s_filetype_image_icon = Icon::default_icon("filetype-image");
+
+ initialize_executable_icon_if_needed();
+
+ for (auto& filetype : config->keys("Icons")) {
+ s_filetype_icons.set(filetype, Icon::default_icon(String::formatted("filetype-{}", filetype)));
+ s_filetype_patterns.set(filetype, config->read_entry("Icons", filetype).split(','));
+ }
+
+ s_initialized = true;
+}
+
+Icon FileIconProvider::directory_icon()
+{
+ initialize_if_needed();
+ return s_directory_icon;
+}
+
+Icon FileIconProvider::directory_open_icon()
+{
+ initialize_if_needed();
+ return s_directory_open_icon;
+}
+
+Icon FileIconProvider::home_directory_icon()
+{
+ initialize_if_needed();
+ return s_home_directory_icon;
+}
+
+Icon FileIconProvider::home_directory_open_icon()
+{
+ initialize_if_needed();
+ return s_home_directory_open_icon;
+}
+
+Icon FileIconProvider::filetype_image_icon()
+{
+ initialize_if_needed();
+ return s_filetype_image_icon;
+}
+
+Icon FileIconProvider::icon_for_path(const String& path)
+{
+ struct stat stat;
+ if (::stat(path.characters(), &stat) < 0)
+ return {};
+ return icon_for_path(path, stat.st_mode);
+}
+
+Icon FileIconProvider::icon_for_executable(const String& path)
+{
+ static HashMap<String, Icon> app_icon_cache;
+
+ if (auto it = app_icon_cache.find(path); it != app_icon_cache.end())
+ return it->value;
+
+ initialize_executable_icon_if_needed();
+
+ // If the icon for an app isn't in the cache we attempt to load the file as an ELF image and extract
+ // the serenity_app_icon_* sections which should contain the icons as raw PNG data. In the future it would
+ // be better if the binary signalled the image format being used or we deduced it, e.g. using magic bytes.
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error()) {
+ app_icon_cache.set(path, s_executable_icon);
+ return s_executable_icon;
+ }
+
+ auto& mapped_file = file_or_error.value();
+
+ if (mapped_file->size() < SELFMAG) {
+ app_icon_cache.set(path, s_executable_icon);
+ return s_executable_icon;
+ }
+
+ if (memcmp(mapped_file->data(), ELFMAG, SELFMAG) != 0) {
+ app_icon_cache.set(path, s_executable_icon);
+ return s_executable_icon;
+ }
+
+ auto image = ELF::Image((const u8*)mapped_file->data(), mapped_file->size());
+ if (!image.is_valid()) {
+ app_icon_cache.set(path, s_executable_icon);
+ return s_executable_icon;
+ }
+
+ // If any of the required sections are missing then use the defaults
+ Icon icon;
+ struct IconSection {
+ const char* section_name;
+ int image_size;
+ };
+
+ static const IconSection icon_sections[] = { { .section_name = "serenity_icon_s", .image_size = 16 }, { .section_name = "serenity_icon_m", .image_size = 32 } };
+
+ bool had_error = false;
+ for (const auto& icon_section : icon_sections) {
+ auto section = image.lookup_section(icon_section.section_name);
+
+ RefPtr<Gfx::Bitmap> bitmap;
+ if (section.is_undefined()) {
+ bitmap = s_executable_icon.bitmap_for_size(icon_section.image_size)->clone();
+ } else {
+ bitmap = Gfx::load_png_from_memory(reinterpret_cast<const u8*>(section.raw_data()), section.size());
+ }
+
+ if (!bitmap) {
+ dbgln("Failed to find embedded icon and failed to clone default icon for application {} at icon size {}", path, icon_section.image_size);
+ had_error = true;
+ continue;
+ }
+
+ icon.set_bitmap_for_size(icon_section.image_size, std::move(bitmap));
+ }
+
+ if (had_error) {
+ app_icon_cache.set(path, s_executable_icon);
+ return s_executable_icon;
+ }
+ app_icon_cache.set(path, icon);
+ return icon;
+}
+
+Icon FileIconProvider::icon_for_path(const String& path, mode_t mode)
+{
+ initialize_if_needed();
+ if (path == "/")
+ return s_hard_disk_icon;
+ if (S_ISDIR(mode)) {
+ if (path == Core::StandardPaths::home_directory())
+ return s_home_directory_icon;
+ if (access(path.characters(), R_OK | X_OK) < 0)
+ return s_inaccessible_directory_icon;
+ return s_directory_icon;
+ }
+ if (S_ISLNK(mode)) {
+ auto raw_symlink_target = Core::File::read_link(path);
+ if (raw_symlink_target.is_null())
+ return s_symlink_icon;
+
+ String target_path;
+ if (raw_symlink_target.starts_with('/')) {
+ target_path = raw_symlink_target;
+ } else {
+ target_path = Core::File::real_path_for(String::formatted("{}/{}", LexicalPath(path).dirname(), raw_symlink_target));
+ }
+ auto target_icon = icon_for_path(target_path);
+
+ Icon generated_icon;
+ for (auto size : target_icon.sizes()) {
+ auto& emblem = size < 32 ? *s_symlink_emblem_small : *s_symlink_emblem;
+ auto original_bitmap = target_icon.bitmap_for_size(size);
+ ASSERT(original_bitmap);
+ auto generated_bitmap = original_bitmap->clone();
+ if (!generated_bitmap) {
+ dbgln("Failed to clone {}x{} icon for symlink variant", size, size);
+ return s_symlink_icon;
+ }
+ GUI::Painter painter(*generated_bitmap);
+ painter.blit({ size - emblem.width(), size - emblem.height() }, emblem, emblem.rect());
+
+ generated_icon.set_bitmap_for_size(size, move(generated_bitmap));
+ }
+ return generated_icon;
+ }
+ if (S_ISSOCK(mode))
+ return s_socket_icon;
+
+ if (mode & (S_IXUSR | S_IXGRP | S_IXOTH))
+ return icon_for_executable(path);
+
+ if (Gfx::Bitmap::is_path_a_supported_image_format(path.view()))
+ return s_filetype_image_icon;
+
+ for (auto& filetype : s_filetype_icons.keys()) {
+ auto patterns = s_filetype_patterns.get(filetype).value();
+ for (auto& pattern : patterns) {
+ if (path.matches(pattern, CaseSensitivity::CaseInsensitive))
+ return s_filetype_icons.get(filetype).value();
+ }
+ }
+
+ return s_file_icon;
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/FileIconProvider.h b/Userland/Libraries/LibGUI/FileIconProvider.h
new file mode 100644
index 0000000000..ca3938b537
--- /dev/null
+++ b/Userland/Libraries/LibGUI/FileIconProvider.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibGUI/Forward.h>
+#include <sys/types.h>
+
+namespace GUI {
+
+class FileIconProvider {
+public:
+ static Icon icon_for_path(const String&, mode_t);
+ static Icon icon_for_path(const String&);
+ static Icon icon_for_executable(const String&);
+
+ static Icon filetype_image_icon();
+ static Icon directory_icon();
+ static Icon directory_open_icon();
+ static Icon home_directory_icon();
+ static Icon home_directory_open_icon();
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/FilePicker.cpp b/Userland/Libraries/LibGUI/FilePicker.cpp
new file mode 100644
index 0000000000..fa51d0cc95
--- /dev/null
+++ b/Userland/Libraries/LibGUI/FilePicker.cpp
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <AK/LexicalPath.h>
+#include <LibCore/StandardPaths.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/FileIconProvider.h>
+#include <LibGUI/FilePicker.h>
+#include <LibGUI/FileSystemModel.h>
+#include <LibGUI/InputBox.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/MessageBox.h>
+#include <LibGUI/MultiView.h>
+#include <LibGUI/SortingProxyModel.h>
+#include <LibGUI/TextBox.h>
+#include <LibGUI/ToolBar.h>
+#include <LibGfx/FontDatabase.h>
+#include <string.h>
+
+namespace GUI {
+
+Optional<String> FilePicker::get_open_filepath(Window* parent_window, const String& window_title, Options options)
+{
+ auto picker = FilePicker::construct(parent_window, Mode::Open, options);
+
+ if (!window_title.is_null())
+ picker->set_title(window_title);
+
+ if (picker->exec() == Dialog::ExecOK) {
+ String file_path = picker->selected_file().string();
+
+ if (file_path.is_null())
+ return {};
+
+ return file_path;
+ }
+ return {};
+}
+
+Optional<String> FilePicker::get_save_filepath(Window* parent_window, const String& title, const String& extension, Options options)
+{
+ auto picker = FilePicker::construct(parent_window, Mode::Save, options, String::formatted("{}.{}", title, extension));
+
+ if (picker->exec() == Dialog::ExecOK) {
+ String file_path = picker->selected_file().string();
+
+ if (file_path.is_null())
+ return {};
+
+ return file_path;
+ }
+ return {};
+}
+
+FilePicker::FilePicker(Window* parent_window, Mode mode, Options options, const StringView& file_name, const StringView& path)
+ : Dialog(parent_window)
+ , m_model(FileSystemModel::create())
+ , m_mode(mode)
+{
+ switch (m_mode) {
+ case Mode::Open:
+ case Mode::OpenMultiple:
+ set_title("Open");
+ set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/open.png"));
+ break;
+ case Mode::Save:
+ set_title("Save as");
+ set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/save.png"));
+ break;
+ }
+ resize(560, 320);
+ auto& horizontal_container = set_main_widget<Widget>();
+ horizontal_container.set_layout<HorizontalBoxLayout>();
+ horizontal_container.layout()->set_margins({ 4, 4, 4, 4 });
+ horizontal_container.set_fill_with_background_color(true);
+
+ auto& vertical_container = horizontal_container.add<Widget>();
+ vertical_container.set_layout<VerticalBoxLayout>();
+ vertical_container.layout()->set_spacing(4);
+
+ auto& upper_container = vertical_container.add<Widget>();
+ upper_container.set_layout<HorizontalBoxLayout>();
+ upper_container.layout()->set_spacing(2);
+ upper_container.set_fixed_height(26);
+
+ auto& toolbar = upper_container.add<ToolBar>();
+ toolbar.set_fixed_width(165);
+ toolbar.set_has_frame(false);
+
+ m_location_textbox = upper_container.add<TextBox>();
+ m_location_textbox->set_text(path);
+
+ m_view = vertical_container.add<MultiView>();
+ m_view->set_selection_mode(m_mode == Mode::OpenMultiple ? GUI::AbstractView::SelectionMode::MultiSelection : GUI::AbstractView::SelectionMode::SingleSelection);
+ m_view->set_model(SortingProxyModel::create(*m_model));
+ m_view->set_model_column(FileSystemModel::Column::Name);
+ m_view->set_key_column_and_sort_order(GUI::FileSystemModel::Column::Name, GUI::SortOrder::Ascending);
+ m_view->set_column_hidden(FileSystemModel::Column::Owner, true);
+ m_view->set_column_hidden(FileSystemModel::Column::Group, true);
+ m_view->set_column_hidden(FileSystemModel::Column::Permissions, true);
+ m_view->set_column_hidden(FileSystemModel::Column::Inode, true);
+ m_view->set_column_hidden(FileSystemModel::Column::SymlinkTarget, true);
+
+ set_path(path);
+
+ m_model->register_client(*this);
+
+ m_location_textbox->on_return_pressed = [this] {
+ set_path(m_location_textbox->text());
+ };
+
+ auto open_parent_directory_action = Action::create("Open parent directory", { Mod_Alt, Key_Up }, Gfx::Bitmap::load_from_file("/res/icons/16x16/open-parent-directory.png"), [this](const Action&) {
+ set_path(String::formatted("{}/..", m_model->root_path()));
+ });
+ toolbar.add_action(*open_parent_directory_action);
+
+ auto go_home_action = CommonActions::make_go_home_action([this](auto&) {
+ set_path(Core::StandardPaths::home_directory());
+ });
+ toolbar.add_action(go_home_action);
+ toolbar.add_separator();
+
+ auto mkdir_action = Action::create("New directory...", Gfx::Bitmap::load_from_file("/res/icons/16x16/mkdir.png"), [this](const Action&) {
+ String value;
+ if (InputBox::show(value, this, "Enter name:", "New directory") == InputBox::ExecOK && !value.is_empty()) {
+ auto new_dir_path = LexicalPath::canonicalized_path(String::formatted("{}/{}", m_model->root_path(), value));
+ int rc = mkdir(new_dir_path.characters(), 0777);
+ if (rc < 0) {
+ MessageBox::show(this, String::formatted("mkdir(\"{}\") failed: {}", new_dir_path, strerror(errno)), "Error", MessageBox::Type::Error);
+ } else {
+ m_model->update();
+ }
+ }
+ });
+
+ toolbar.add_action(*mkdir_action);
+
+ toolbar.add_separator();
+
+ toolbar.add_action(m_view->view_as_icons_action());
+ toolbar.add_action(m_view->view_as_table_action());
+ toolbar.add_action(m_view->view_as_columns_action());
+
+ auto& lower_container = vertical_container.add<Widget>();
+ lower_container.set_layout<VerticalBoxLayout>();
+ lower_container.layout()->set_spacing(4);
+ lower_container.set_fixed_height(48);
+
+ auto& filename_container = lower_container.add<Widget>();
+ filename_container.set_fixed_height(22);
+ filename_container.set_layout<HorizontalBoxLayout>();
+ auto& filename_label = filename_container.add<Label>("File name:");
+ filename_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
+ filename_label.set_fixed_width(60);
+ m_filename_textbox = filename_container.add<TextBox>();
+ m_filename_textbox->set_focus(true);
+ if (m_mode == Mode::Save) {
+ m_filename_textbox->set_text(file_name);
+ m_filename_textbox->select_all();
+ }
+ m_filename_textbox->on_return_pressed = [&] {
+ on_file_return();
+ };
+
+ m_view->on_selection_change = [this] {
+ auto index = m_view->selection().first();
+ auto& filter_model = (SortingProxyModel&)*m_view->model();
+ auto local_index = filter_model.map_to_source(index);
+ const FileSystemModel::Node& node = m_model->node(local_index);
+ LexicalPath path { node.full_path() };
+
+ if (have_preview())
+ clear_preview();
+
+ if (!node.is_directory())
+ m_filename_textbox->set_text(node.name);
+ if (have_preview())
+ set_preview(path);
+ };
+
+ auto& button_container = lower_container.add<Widget>();
+ button_container.set_fixed_height(22);
+ button_container.set_layout<HorizontalBoxLayout>();
+ button_container.layout()->set_spacing(4);
+ button_container.layout()->add_spacer();
+
+ auto& cancel_button = button_container.add<Button>();
+ cancel_button.set_fixed_width(80);
+ cancel_button.set_text("Cancel");
+ cancel_button.on_click = [this](auto) {
+ done(ExecCancel);
+ };
+
+ auto& ok_button = button_container.add<Button>();
+ ok_button.set_fixed_width(80);
+ ok_button.set_text(ok_button_name(m_mode));
+ ok_button.on_click = [this](auto) {
+ on_file_return();
+ };
+
+ m_view->on_activation = [this](auto& index) {
+ auto& filter_model = (SortingProxyModel&)*m_view->model();
+ auto local_index = filter_model.map_to_source(index);
+ const FileSystemModel::Node& node = m_model->node(local_index);
+ auto path = node.full_path();
+
+ if (node.is_directory()) {
+ set_path(path);
+ // NOTE: 'node' is invalid from here on
+ } else {
+ on_file_return();
+ }
+ };
+
+ if (!((unsigned)options & (unsigned)Options::DisablePreview)) {
+ m_preview_container = horizontal_container.add<Frame>();
+ m_preview_container->set_visible(false);
+ m_preview_container->set_fixed_width(180);
+ m_preview_container->set_layout<VerticalBoxLayout>();
+ m_preview_container->layout()->set_margins({ 8, 8, 8, 8 });
+
+ m_preview_image = m_preview_container->add<ImageWidget>();
+ m_preview_image->set_should_stretch(true);
+ m_preview_image->set_auto_resize(false);
+ m_preview_image->set_fixed_size(160, 160);
+
+ m_preview_name_label = m_preview_container->add<Label>();
+ m_preview_name_label->set_font(Gfx::FontDatabase::default_bold_font());
+ m_preview_name_label->set_fixed_height(m_preview_name_label->font().glyph_height());
+
+ m_preview_geometry_label = m_preview_container->add<Label>();
+ m_preview_geometry_label->set_fixed_height(m_preview_name_label->font().glyph_height());
+ }
+}
+
+FilePicker::~FilePicker()
+{
+ m_model->unregister_client(*this);
+}
+
+void FilePicker::model_did_update(unsigned)
+{
+ m_location_textbox->set_text(m_model->root_path());
+ if (have_preview())
+ clear_preview();
+}
+
+void FilePicker::set_preview(const LexicalPath& path)
+{
+ if (Gfx::Bitmap::is_path_a_supported_image_format(path.string())) {
+ auto bitmap = Gfx::Bitmap::load_from_file(path.string());
+ if (!bitmap) {
+ clear_preview();
+ return;
+ }
+ bool should_stretch = bitmap->width() > m_preview_image->width() || bitmap->height() > m_preview_image->height();
+ m_preview_name_label->set_text(path.basename());
+ m_preview_geometry_label->set_text(bitmap->size().to_string());
+ m_preview_image->set_should_stretch(should_stretch);
+ m_preview_image->set_bitmap(move(bitmap));
+ m_preview_container->set_visible(true);
+ }
+}
+
+void FilePicker::clear_preview()
+{
+ m_preview_image->set_bitmap(nullptr);
+ m_preview_name_label->set_text(String::empty());
+ m_preview_geometry_label->set_text(String::empty());
+ m_preview_container->set_visible(false);
+}
+
+void FilePicker::on_file_return()
+{
+ LexicalPath path(String::formatted("{}/{}", m_model->root_path(), m_filename_textbox->text()));
+
+ if (FilePicker::file_exists(path.string()) && m_mode == Mode::Save) {
+ auto result = MessageBox::show(this, "File already exists, overwrite?", "Existing File", MessageBox::Type::Warning, MessageBox::InputType::OKCancel);
+ if (result == MessageBox::ExecCancel)
+ return;
+ }
+
+ m_selected_file = path;
+ done(ExecOK);
+}
+
+bool FilePicker::file_exists(const StringView& path)
+{
+ struct stat st;
+ int rc = stat(path.to_string().characters(), &st);
+ if (rc < 0) {
+ if (errno == ENOENT)
+ return false;
+ }
+ if (rc == 0) {
+ return true;
+ }
+ return false;
+}
+
+void FilePicker::set_path(const String& path)
+{
+ auto new_path = LexicalPath(path).string();
+ m_location_textbox->set_icon(FileIconProvider::icon_for_path(new_path).bitmap_for_size(16));
+ m_model->set_root_path(new_path);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/FilePicker.h b/Userland/Libraries/LibGUI/FilePicker.h
new file mode 100644
index 0000000000..526f527325
--- /dev/null
+++ b/Userland/Libraries/LibGUI/FilePicker.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/LexicalPath.h>
+#include <AK/Optional.h>
+#include <LibCore/StandardPaths.h>
+#include <LibGUI/Dialog.h>
+#include <LibGUI/ImageWidget.h>
+#include <LibGUI/Model.h>
+
+namespace GUI {
+
+class FilePicker final
+ : public Dialog
+ , private ModelClient {
+ C_OBJECT(FilePicker);
+
+public:
+ enum class Mode {
+ Open,
+ OpenMultiple,
+ Save
+ };
+
+ enum class Options : unsigned {
+ None = 0,
+ DisablePreview = (1 << 0)
+ };
+
+ static Optional<String> get_open_filepath(Window* parent_window, Options options)
+ {
+ return get_open_filepath(parent_window, {}, options);
+ }
+ static Optional<String> get_open_filepath(Window* parent_window, const String& window_title = {}, Options options = Options::None);
+ static Optional<String> get_save_filepath(Window* parent_window, const String& title, const String& extension, Options options = Options::None);
+ static bool file_exists(const StringView& path);
+
+ virtual ~FilePicker() override;
+
+ LexicalPath selected_file() const { return m_selected_file; }
+
+private:
+ bool have_preview() const { return m_preview_container; }
+ void set_preview(const LexicalPath&);
+ void clear_preview();
+ void on_file_return();
+
+ void set_path(const String&);
+
+ // ^GUI::ModelClient
+ virtual void model_did_update(unsigned) override;
+
+ FilePicker(Window* parent_window, Mode type = Mode::Open, Options = Options::None, const StringView& file_name = "Untitled", const StringView& path = Core::StandardPaths::home_directory());
+
+ static String ok_button_name(Mode mode)
+ {
+ switch (mode) {
+ case Mode::Open:
+ case Mode::OpenMultiple:
+ return "Open";
+ case Mode::Save:
+ return "Save";
+ default:
+ return "OK";
+ }
+ }
+
+ RefPtr<MultiView> m_view;
+ NonnullRefPtr<FileSystemModel> m_model;
+ LexicalPath m_selected_file;
+
+ RefPtr<TextBox> m_filename_textbox;
+ RefPtr<TextBox> m_location_textbox;
+ RefPtr<Frame> m_preview_container;
+ RefPtr<ImageWidget> m_preview_image;
+ RefPtr<Label> m_preview_name_label;
+ RefPtr<Label> m_preview_geometry_label;
+ Mode m_mode { Mode::Open };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/FileSystemModel.cpp b/Userland/Libraries/LibGUI/FileSystemModel.cpp
new file mode 100644
index 0000000000..1a4934da2d
--- /dev/null
+++ b/Userland/Libraries/LibGUI/FileSystemModel.cpp
@@ -0,0 +1,647 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LexicalPath.h>
+#include <AK/QuickSort.h>
+#include <AK/StringBuilder.h>
+#include <LibCore/DirIterator.h>
+#include <LibCore/File.h>
+#include <LibCore/StandardPaths.h>
+#include <LibGUI/FileIconProvider.h>
+#include <LibGUI/FileSystemModel.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/Bitmap.h>
+#include <LibThread/BackgroundAction.h>
+#include <dirent.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+namespace GUI {
+
+ModelIndex FileSystemModel::Node::index(int column) const
+{
+ if (!parent)
+ return {};
+ for (size_t row = 0; row < parent->children.size(); ++row) {
+ if (&parent->children[row] == this)
+ return m_model.create_index(row, column, const_cast<Node*>(this));
+ }
+ ASSERT_NOT_REACHED();
+}
+
+bool FileSystemModel::Node::fetch_data(const String& full_path, bool is_root)
+{
+ struct stat st;
+ int rc;
+ if (is_root)
+ rc = stat(full_path.characters(), &st);
+ else
+ rc = lstat(full_path.characters(), &st);
+ if (rc < 0) {
+ m_error = errno;
+ perror("stat/lstat");
+ return false;
+ }
+
+ size = st.st_size;
+ mode = st.st_mode;
+ uid = st.st_uid;
+ gid = st.st_gid;
+ inode = st.st_ino;
+ mtime = st.st_mtime;
+
+ if (S_ISLNK(mode)) {
+ symlink_target = Core::File::read_link(full_path);
+ if (symlink_target.is_null())
+ perror("readlink");
+ }
+
+ if (S_ISDIR(mode)) {
+ is_accessible_directory = access(full_path.characters(), R_OK | X_OK) == 0;
+ }
+
+ return true;
+}
+
+void FileSystemModel::Node::traverse_if_needed()
+{
+ if (!is_directory() || has_traversed)
+ return;
+
+ has_traversed = true;
+
+ if (m_parent_of_root) {
+ auto root = adopt_own(*new Node(m_model));
+ root->fetch_data("/", true);
+ root->name = "/";
+ root->parent = this;
+ children.append(move(root));
+ return;
+ }
+
+ total_size = 0;
+
+ auto full_path = this->full_path();
+ Core::DirIterator di(full_path, m_model.should_show_dotfiles() ? Core::DirIterator::SkipParentAndBaseDir : Core::DirIterator::SkipDots);
+ if (di.has_error()) {
+ m_error = di.error();
+ fprintf(stderr, "DirIterator: %s\n", di.error_string());
+ return;
+ }
+
+ Vector<String> child_names;
+ while (di.has_next()) {
+ child_names.append(di.next_path());
+ }
+ quick_sort(child_names);
+
+ for (auto& name : child_names) {
+ String child_path = String::formatted("{}/{}", full_path, name);
+ auto child = adopt_own(*new Node(m_model));
+ bool ok = child->fetch_data(child_path, false);
+ if (!ok)
+ continue;
+ if (m_model.m_mode == DirectoriesOnly && !S_ISDIR(child->mode))
+ continue;
+ child->name = name;
+ child->parent = this;
+ total_size += child->size;
+ children.append(move(child));
+ }
+
+ if (m_watch_fd >= 0)
+ return;
+
+ m_watch_fd = watch_file(full_path.characters(), full_path.length());
+ if (m_watch_fd < 0) {
+ perror("watch_file");
+ return;
+ }
+ fcntl(m_watch_fd, F_SETFD, FD_CLOEXEC);
+ dbgln("Watching {} for changes, m_watch_fd={}", full_path, m_watch_fd);
+ m_notifier = Core::Notifier::construct(m_watch_fd, Core::Notifier::Event::Read);
+ m_notifier->on_ready_to_read = [this] {
+ char buffer[32];
+ int rc = read(m_notifier->fd(), buffer, sizeof(buffer));
+ ASSERT(rc >= 0);
+
+ has_traversed = false;
+ mode = 0;
+ children.clear();
+ reify_if_needed();
+ m_model.did_update();
+ };
+}
+
+void FileSystemModel::Node::reify_if_needed()
+{
+ traverse_if_needed();
+ if (mode != 0)
+ return;
+ fetch_data(full_path(), parent == nullptr || parent->m_parent_of_root);
+}
+
+String FileSystemModel::Node::full_path() const
+{
+ Vector<String, 32> lineage;
+ for (auto* ancestor = parent; ancestor; ancestor = ancestor->parent) {
+ lineage.append(ancestor->name);
+ }
+ StringBuilder builder;
+ builder.append(m_model.root_path());
+ for (int i = lineage.size() - 1; i >= 0; --i) {
+ builder.append('/');
+ builder.append(lineage[i]);
+ }
+ builder.append('/');
+ builder.append(name);
+ return LexicalPath::canonicalized_path(builder.to_string());
+}
+
+ModelIndex FileSystemModel::index(const StringView& path, int column) const
+{
+ LexicalPath lexical_path(path);
+ const Node* node = m_root->m_parent_of_root ? &m_root->children.first() : m_root;
+ if (lexical_path.string() == "/")
+ return node->index(column);
+ for (size_t i = 0; i < lexical_path.parts().size(); ++i) {
+ auto& part = lexical_path.parts()[i];
+ bool found = false;
+ for (auto& child : node->children) {
+ if (child.name == part) {
+ const_cast<Node&>(child).reify_if_needed();
+ node = &child;
+ found = true;
+ if (i == lexical_path.parts().size() - 1)
+ return child.index(column);
+ break;
+ }
+ }
+ if (!found)
+ return {};
+ }
+ return {};
+}
+
+String FileSystemModel::full_path(const ModelIndex& index) const
+{
+ auto& node = this->node(index);
+ const_cast<Node&>(node).reify_if_needed();
+ return node.full_path();
+}
+
+FileSystemModel::FileSystemModel(const StringView& root_path, Mode mode)
+ : m_root_path(LexicalPath::canonicalized_path(root_path))
+ , m_mode(mode)
+{
+ setpwent();
+ while (auto* passwd = getpwent())
+ m_user_names.set(passwd->pw_uid, passwd->pw_name);
+ endpwent();
+
+ setgrent();
+ while (auto* group = getgrent())
+ m_group_names.set(group->gr_gid, group->gr_name);
+ endgrent();
+
+ update();
+}
+
+FileSystemModel::~FileSystemModel()
+{
+}
+
+String FileSystemModel::name_for_uid(uid_t uid) const
+{
+ auto it = m_user_names.find(uid);
+ if (it == m_user_names.end())
+ return String::number(uid);
+ return (*it).value;
+}
+
+String FileSystemModel::name_for_gid(gid_t gid) const
+{
+ auto it = m_group_names.find(gid);
+ if (it == m_group_names.end())
+ return String::number(gid);
+ return (*it).value;
+}
+
+static String permission_string(mode_t mode)
+{
+ StringBuilder builder;
+ if (S_ISDIR(mode))
+ builder.append("d");
+ else if (S_ISLNK(mode))
+ builder.append("l");
+ else if (S_ISBLK(mode))
+ builder.append("b");
+ else if (S_ISCHR(mode))
+ builder.append("c");
+ else if (S_ISFIFO(mode))
+ builder.append("f");
+ else if (S_ISSOCK(mode))
+ builder.append("s");
+ else if (S_ISREG(mode))
+ builder.append("-");
+ else
+ builder.append("?");
+
+ builder.appendf("%c%c%c%c%c%c%c%c",
+ mode & S_IRUSR ? 'r' : '-',
+ mode & S_IWUSR ? 'w' : '-',
+ mode & S_ISUID ? 's' : (mode & S_IXUSR ? 'x' : '-'),
+ mode & S_IRGRP ? 'r' : '-',
+ mode & S_IWGRP ? 'w' : '-',
+ mode & S_ISGID ? 's' : (mode & S_IXGRP ? 'x' : '-'),
+ mode & S_IROTH ? 'r' : '-',
+ mode & S_IWOTH ? 'w' : '-');
+
+ if (mode & S_ISVTX)
+ builder.append("t");
+ else
+ builder.appendf("%c", mode & S_IXOTH ? 'x' : '-');
+ return builder.to_string();
+}
+
+void FileSystemModel::Node::set_selected(bool selected)
+{
+ if (m_selected == selected)
+ return;
+ m_selected = selected;
+}
+
+void FileSystemModel::update_node_on_selection(const ModelIndex& index, const bool selected)
+{
+ Node& node = const_cast<Node&>(this->node(index));
+ node.set_selected(selected);
+}
+
+void FileSystemModel::set_root_path(const StringView& root_path)
+{
+ if (root_path.is_null())
+ m_root_path = {};
+ else
+ m_root_path = LexicalPath::canonicalized_path(root_path);
+ update();
+
+ if (m_root->has_error()) {
+ if (on_error)
+ on_error(m_root->error(), m_root->error_string());
+ } else if (on_complete) {
+ on_complete();
+ }
+}
+
+void FileSystemModel::update()
+{
+ m_root = adopt_own(*new Node(*this));
+
+ if (m_root_path.is_null())
+ m_root->m_parent_of_root = true;
+
+ m_root->reify_if_needed();
+
+ did_update();
+}
+
+int FileSystemModel::row_count(const ModelIndex& index) const
+{
+ Node& node = const_cast<Node&>(this->node(index));
+ node.reify_if_needed();
+ if (node.is_directory())
+ return node.children.size();
+ return 0;
+}
+
+const FileSystemModel::Node& FileSystemModel::node(const ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return *m_root;
+ ASSERT(index.internal_data());
+ return *(Node*)index.internal_data();
+}
+
+ModelIndex FileSystemModel::index(int row, int column, const ModelIndex& parent) const
+{
+ if (row < 0 || column < 0)
+ return {};
+ auto& node = this->node(parent);
+ const_cast<Node&>(node).reify_if_needed();
+ if (static_cast<size_t>(row) >= node.children.size())
+ return {};
+ return create_index(row, column, &node.children[row]);
+}
+
+ModelIndex FileSystemModel::parent_index(const ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return {};
+ auto& node = this->node(index);
+ if (!node.parent) {
+ ASSERT(&node == m_root);
+ return {};
+ }
+ return node.parent->index(index.column());
+}
+
+Variant FileSystemModel::data(const ModelIndex& index, ModelRole role) const
+{
+ ASSERT(index.is_valid());
+
+ if (role == ModelRole::TextAlignment) {
+ switch (index.column()) {
+ case Column::Icon:
+ return Gfx::TextAlignment::Center;
+ case Column::Size:
+ case Column::Inode:
+ return Gfx::TextAlignment::CenterRight;
+ case Column::Name:
+ case Column::Owner:
+ case Column::Group:
+ case Column::ModificationTime:
+ case Column::Permissions:
+ case Column::SymlinkTarget:
+ return Gfx::TextAlignment::CenterLeft;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ auto& node = this->node(index);
+
+ if (role == ModelRole::Custom) {
+ // For GUI::FileSystemModel, custom role means the full path.
+ ASSERT(index.column() == Column::Name);
+ return node.full_path();
+ }
+
+ if (role == ModelRole::MimeData) {
+ if (index.column() == Column::Name) {
+ StringBuilder builder;
+ builder.append("file://");
+ builder.append(node.full_path());
+ return builder.to_string();
+ }
+ return {};
+ }
+
+ if (role == ModelRole::Sort) {
+ switch (index.column()) {
+ case Column::Icon:
+ return node.is_directory() ? 0 : 1;
+ case Column::Name:
+ return node.name;
+ case Column::Size:
+ return (int)node.size;
+ case Column::Owner:
+ return name_for_uid(node.uid);
+ case Column::Group:
+ return name_for_gid(node.gid);
+ case Column::Permissions:
+ return permission_string(node.mode);
+ case Column::ModificationTime:
+ return node.mtime;
+ case Column::Inode:
+ return (int)node.inode;
+ case Column::SymlinkTarget:
+ return node.symlink_target;
+ }
+ ASSERT_NOT_REACHED();
+ }
+
+ if (role == ModelRole::Display) {
+ switch (index.column()) {
+ case Column::Icon:
+ return icon_for(node);
+ case Column::Name:
+ return node.name;
+ case Column::Size:
+ return (int)node.size;
+ case Column::Owner:
+ return name_for_uid(node.uid);
+ case Column::Group:
+ return name_for_gid(node.gid);
+ case Column::Permissions:
+ return permission_string(node.mode);
+ case Column::ModificationTime:
+ return timestamp_string(node.mtime);
+ case Column::Inode:
+ return (int)node.inode;
+ case Column::SymlinkTarget:
+ return node.symlink_target;
+ }
+ }
+
+ if (role == ModelRole::Icon) {
+ return icon_for(node);
+ }
+ return {};
+}
+
+Icon FileSystemModel::icon_for(const Node& node) const
+{
+ if (node.full_path() == "/")
+ return FileIconProvider::icon_for_path("/");
+
+ if (Gfx::Bitmap::is_path_a_supported_image_format(node.name)) {
+ if (!node.thumbnail) {
+ if (!const_cast<FileSystemModel*>(this)->fetch_thumbnail_for(node))
+ return FileIconProvider::filetype_image_icon();
+ }
+ return GUI::Icon(FileIconProvider::filetype_image_icon().bitmap_for_size(16), *node.thumbnail);
+ }
+
+ if (node.is_directory()) {
+ if (node.full_path() == Core::StandardPaths::home_directory()) {
+ if (node.is_selected())
+ return FileIconProvider::home_directory_open_icon();
+ return FileIconProvider::home_directory_icon();
+ }
+ if (node.is_selected() && node.is_accessible_directory)
+ return FileIconProvider::directory_open_icon();
+ }
+
+ return FileIconProvider::icon_for_path(node.full_path(), node.mode);
+}
+
+static HashMap<String, RefPtr<Gfx::Bitmap>> s_thumbnail_cache;
+
+static RefPtr<Gfx::Bitmap> render_thumbnail(const StringView& path)
+{
+ auto png_bitmap = Gfx::Bitmap::load_from_file(path);
+ if (!png_bitmap)
+ return nullptr;
+
+ double scale = min(32 / (double)png_bitmap->width(), 32 / (double)png_bitmap->height());
+
+ auto thumbnail = Gfx::Bitmap::create(png_bitmap->format(), { 32, 32 });
+ Gfx::IntRect destination = Gfx::IntRect(0, 0, (int)(png_bitmap->width() * scale), (int)(png_bitmap->height() * scale));
+ destination.center_within(thumbnail->rect());
+
+ Painter painter(*thumbnail);
+ painter.draw_scaled_bitmap(destination, *png_bitmap, png_bitmap->rect());
+ return thumbnail;
+}
+
+bool FileSystemModel::fetch_thumbnail_for(const Node& node)
+{
+ // See if we already have the thumbnail
+ // we're looking for in the cache.
+ auto path = node.full_path();
+ auto it = s_thumbnail_cache.find(path);
+ if (it != s_thumbnail_cache.end()) {
+ if (!(*it).value)
+ return false;
+ node.thumbnail = (*it).value;
+ return true;
+ }
+
+ // Otherwise, arrange to render the thumbnail
+ // in background and make it available later.
+
+ s_thumbnail_cache.set(path, nullptr);
+ m_thumbnail_progress_total++;
+
+ auto weak_this = make_weak_ptr();
+
+ LibThread::BackgroundAction<RefPtr<Gfx::Bitmap>>::create(
+ [path] {
+ return render_thumbnail(path);
+ },
+
+ [this, path, weak_this](auto thumbnail) {
+ s_thumbnail_cache.set(path, move(thumbnail));
+
+ // The model was destroyed, no need to update
+ // progress or call any event handlers.
+ if (weak_this.is_null())
+ return;
+
+ m_thumbnail_progress++;
+ if (on_thumbnail_progress)
+ on_thumbnail_progress(m_thumbnail_progress, m_thumbnail_progress_total);
+ if (m_thumbnail_progress == m_thumbnail_progress_total) {
+ m_thumbnail_progress = 0;
+ m_thumbnail_progress_total = 0;
+ }
+
+ did_update();
+ });
+
+ return false;
+}
+
+int FileSystemModel::column_count(const ModelIndex&) const
+{
+ return Column::__Count;
+}
+
+String FileSystemModel::column_name(int column) const
+{
+ switch (column) {
+ case Column::Icon:
+ return "";
+ case Column::Name:
+ return "Name";
+ case Column::Size:
+ return "Size";
+ case Column::Owner:
+ return "Owner";
+ case Column::Group:
+ return "Group";
+ case Column::Permissions:
+ return "Mode";
+ case Column::ModificationTime:
+ return "Modified";
+ case Column::Inode:
+ return "Inode";
+ case Column::SymlinkTarget:
+ return "Symlink target";
+ }
+ ASSERT_NOT_REACHED();
+}
+
+bool FileSystemModel::accepts_drag(const ModelIndex& index, const Vector<String>& mime_types) const
+{
+ if (!index.is_valid())
+ return false;
+ if (!mime_types.contains_slow("text/uri-list"))
+ return false;
+ auto& node = this->node(index);
+ return node.is_directory();
+}
+
+void FileSystemModel::set_should_show_dotfiles(bool show)
+{
+ if (m_should_show_dotfiles == show)
+ return;
+ m_should_show_dotfiles = show;
+ update();
+}
+
+bool FileSystemModel::is_editable(const ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return false;
+ return index.column() == Column::Name;
+}
+
+void FileSystemModel::set_data(const ModelIndex& index, const Variant& data)
+{
+ ASSERT(is_editable(index));
+ Node& node = const_cast<Node&>(this->node(index));
+ auto dirname = LexicalPath(node.full_path()).dirname();
+ auto new_full_path = String::formatted("{}/{}", dirname, data.to_string());
+ int rc = rename(node.full_path().characters(), new_full_path.characters());
+ if (rc < 0) {
+ if (on_error)
+ on_error(errno, strerror(errno));
+ }
+}
+
+Vector<ModelIndex, 1> FileSystemModel::matches(const StringView& searching, unsigned flags, const ModelIndex& index)
+{
+ Node& node = const_cast<Node&>(this->node(index));
+ node.reify_if_needed();
+ Vector<ModelIndex, 1> found_indexes;
+ for (auto& child : node.children) {
+ if (string_matches(child.name, searching, flags)) {
+ const_cast<Node&>(child).reify_if_needed();
+ found_indexes.append(child.index(Column::Name));
+ if (flags & FirstMatchOnly)
+ break;
+ }
+ }
+
+ return found_indexes;
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/FileSystemModel.h b/Userland/Libraries/LibGUI/FileSystemModel.h
new file mode 100644
index 0000000000..2ef60bd708
--- /dev/null
+++ b/Userland/Libraries/LibGUI/FileSystemModel.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <LibCore/DateTime.h>
+#include <LibCore/Notifier.h>
+#include <LibGUI/Model.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+
+namespace GUI {
+
+class FileSystemModel
+ : public Model
+ , public Weakable<FileSystemModel> {
+ friend struct Node;
+
+public:
+ enum Mode {
+ Invalid,
+ DirectoriesOnly,
+ FilesAndDirectories
+ };
+
+ enum Column {
+ Icon = 0,
+ Name,
+ Size,
+ Owner,
+ Group,
+ Permissions,
+ ModificationTime,
+ Inode,
+ SymlinkTarget,
+ __Count,
+ };
+
+ struct Node {
+ ~Node() { close(m_watch_fd); }
+
+ String name;
+ String symlink_target;
+ size_t size { 0 };
+ mode_t mode { 0 };
+ uid_t uid { 0 };
+ gid_t gid { 0 };
+ ino_t inode { 0 };
+ time_t mtime { 0 };
+ bool is_accessible_directory { false };
+
+ size_t total_size { 0 };
+
+ mutable RefPtr<Gfx::Bitmap> thumbnail;
+ bool is_directory() const { return S_ISDIR(mode); }
+ bool is_executable() const { return mode & (S_IXUSR | S_IXGRP | S_IXOTH); }
+
+ bool is_selected() const { return m_selected; }
+ void set_selected(bool selected);
+
+ bool has_error() const { return m_error != 0; }
+ int error() const { return m_error; }
+ const char* error_string() const { return strerror(m_error); }
+
+ String full_path() const;
+
+ private:
+ friend class FileSystemModel;
+
+ explicit Node(FileSystemModel& model)
+ : m_model(model)
+ {
+ }
+
+ FileSystemModel& m_model;
+
+ Node* parent { nullptr };
+ NonnullOwnPtrVector<Node> children;
+ bool has_traversed { false };
+
+ bool m_selected { false };
+
+ int m_watch_fd { -1 };
+ RefPtr<Core::Notifier> m_notifier;
+
+ int m_error { 0 };
+ bool m_parent_of_root { false };
+
+ ModelIndex index(int column) const;
+ void traverse_if_needed();
+ void reify_if_needed();
+ bool fetch_data(const String& full_path, bool is_root);
+ };
+
+ static NonnullRefPtr<FileSystemModel> create(const StringView& root_path = "/", Mode mode = Mode::FilesAndDirectories)
+ {
+ return adopt(*new FileSystemModel(root_path, mode));
+ }
+ virtual ~FileSystemModel() override;
+
+ String root_path() const { return m_root_path; }
+ void set_root_path(const StringView&);
+ String full_path(const ModelIndex&) const;
+ ModelIndex index(const StringView& path, int column) const;
+
+ void update_node_on_selection(const ModelIndex&, const bool);
+ ModelIndex m_previously_selected_index {};
+
+ const Node& node(const ModelIndex& index) const;
+
+ Function<void(int done, int total)> on_thumbnail_progress;
+ Function<void()> on_complete;
+ Function<void(int error, const char* error_string)> on_error;
+
+ virtual int tree_column() const override { return Column::Name; }
+ virtual int row_count(const ModelIndex& = ModelIndex()) const override;
+ virtual int column_count(const ModelIndex& = ModelIndex()) const override;
+ virtual String column_name(int column) const override;
+ virtual Variant data(const ModelIndex&, ModelRole = ModelRole::Display) const override;
+ virtual void update() override;
+ virtual ModelIndex parent_index(const ModelIndex&) const override;
+ virtual ModelIndex index(int row, int column = 0, const ModelIndex& parent = ModelIndex()) const override;
+ virtual StringView drag_data_type() const override { return "text/uri-list"; }
+ virtual bool accepts_drag(const ModelIndex&, const Vector<String>& mime_types) const override;
+ virtual bool is_column_sortable(int column_index) const override { return column_index != Column::Icon; }
+ virtual bool is_editable(const ModelIndex&) const override;
+ virtual bool is_searchable() const override { return true; }
+ virtual void set_data(const ModelIndex&, const Variant&) override;
+ virtual Vector<ModelIndex, 1> matches(const StringView&, unsigned = MatchesFlag::AllMatching, const ModelIndex& = ModelIndex()) override;
+
+ static String timestamp_string(time_t timestamp)
+ {
+ return Core::DateTime::from_timestamp(timestamp).to_string();
+ }
+
+ bool should_show_dotfiles() const { return m_should_show_dotfiles; }
+ void set_should_show_dotfiles(bool);
+
+private:
+ FileSystemModel(const StringView& root_path, Mode);
+
+ String name_for_uid(uid_t) const;
+ String name_for_gid(gid_t) const;
+
+ HashMap<uid_t, String> m_user_names;
+ HashMap<gid_t, String> m_group_names;
+
+ bool fetch_thumbnail_for(const Node& node);
+ GUI::Icon icon_for(const Node& node) const;
+
+ String m_root_path;
+ Mode m_mode { Invalid };
+ OwnPtr<Node> m_root { nullptr };
+
+ unsigned m_thumbnail_progress { 0 };
+ unsigned m_thumbnail_progress_total { 0 };
+
+ bool m_should_show_dotfiles { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/FilteringProxyModel.cpp b/Userland/Libraries/LibGUI/FilteringProxyModel.cpp
new file mode 100644
index 0000000000..622af645aa
--- /dev/null
+++ b/Userland/Libraries/LibGUI/FilteringProxyModel.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/FilteringProxyModel.h>
+
+namespace GUI {
+
+ModelIndex FilteringProxyModel::index(int row, int column, const ModelIndex& parent_index) const
+{
+ int parent_row = parent_index.row();
+ if (!parent_index.is_valid())
+ parent_row = 0;
+
+ return create_index(parent_row + row, column);
+}
+
+int FilteringProxyModel::row_count(const ModelIndex&) const
+{
+ return m_matching_indices.size();
+}
+
+int FilteringProxyModel::column_count(const ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return {};
+
+ if ((size_t)index.row() > m_matching_indices.size() || index.row() < 0)
+ return 0;
+
+ return m_model.column_count(m_matching_indices[index.row()]);
+}
+
+Variant FilteringProxyModel::data(const ModelIndex& index, ModelRole role) const
+{
+ if (!index.is_valid())
+ return {};
+
+ if ((size_t)index.row() > m_matching_indices.size() || index.row() < 0)
+ return 0;
+
+ return m_matching_indices[index.row()].data(role);
+}
+
+void FilteringProxyModel::update()
+{
+ m_model.update();
+ filter();
+ did_update();
+}
+
+void FilteringProxyModel::filter()
+{
+ m_matching_indices.clear();
+
+ Function<void(ModelIndex&)> add_matching = [&](ModelIndex& parent_index) {
+ for (auto i = 0; i < m_model.row_count(parent_index); ++i) {
+ auto index = m_model.index(i, 0, parent_index);
+ if (!index.is_valid())
+ continue;
+
+ auto filter_matches = m_model.data_matches(index, m_filter_term);
+ bool matches = filter_matches == TriState::True;
+ if (filter_matches == TriState::Unknown) {
+ auto data = index.data();
+ if (data.is_string() && data.as_string().contains(m_filter_term))
+ matches = true;
+ }
+ if (matches)
+ m_matching_indices.append(index);
+
+ add_matching(index);
+ }
+ };
+
+ ModelIndex parent_index;
+ add_matching(parent_index);
+}
+
+void FilteringProxyModel::set_filter_term(const StringView& term)
+{
+ if (m_filter_term == term)
+ return;
+ m_filter_term = term;
+ update();
+}
+
+ModelIndex FilteringProxyModel::map(const ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return {};
+
+ auto row = index.row();
+ if (m_matching_indices.size() > (size_t)row)
+ return m_matching_indices[row];
+
+ return {};
+}
+
+bool FilteringProxyModel::is_searchable() const
+{
+ return m_model.is_searchable();
+}
+
+Vector<ModelIndex, 1> FilteringProxyModel::matches(const StringView& searching, unsigned flags, const ModelIndex& index)
+{
+ auto found_indexes = m_model.matches(searching, flags, index);
+ for (size_t i = 0; i < found_indexes.size(); i++)
+ found_indexes[i] = map(found_indexes[i]);
+ return found_indexes;
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/FilteringProxyModel.h b/Userland/Libraries/LibGUI/FilteringProxyModel.h
new file mode 100644
index 0000000000..94f53e7d94
--- /dev/null
+++ b/Userland/Libraries/LibGUI/FilteringProxyModel.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtr.h>
+#include <AK/Optional.h>
+#include <AK/String.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/TextBox.h>
+
+namespace GUI {
+
+class FilteringProxyModel final : public Model {
+public:
+ static NonnullRefPtr<FilteringProxyModel> construct(Model& model)
+ {
+ return adopt(*new FilteringProxyModel(model));
+ }
+
+ virtual ~FilteringProxyModel() override {};
+
+ virtual int row_count(const ModelIndex& = ModelIndex()) const override;
+ virtual int column_count(const ModelIndex& = ModelIndex()) const override;
+ virtual Variant data(const ModelIndex&, ModelRole = ModelRole::Display) const override;
+ virtual void update() override;
+ virtual ModelIndex index(int row, int column = 0, const ModelIndex& parent = ModelIndex()) const override;
+ virtual bool is_searchable() const override;
+ virtual Vector<ModelIndex, 1> matches(const StringView&, unsigned = MatchesFlag::AllMatching, const ModelIndex& = ModelIndex()) override;
+
+ void set_filter_term(const StringView& term);
+
+ ModelIndex map(const ModelIndex&) const;
+
+private:
+ void filter();
+ explicit FilteringProxyModel(Model& model)
+ : m_model(model)
+ {
+ }
+
+ Model& m_model;
+
+ // Maps row to actual model index.
+ Vector<ModelIndex> m_matching_indices;
+
+ String m_filter_term;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/FocusSource.h b/Userland/Libraries/LibGUI/FocusSource.h
new file mode 100644
index 0000000000..d8e6e238df
--- /dev/null
+++ b/Userland/Libraries/LibGUI/FocusSource.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace GUI {
+
+enum class FocusSource {
+ Programmatic,
+ Keyboard,
+ Mouse,
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/FontPicker.cpp b/Userland/Libraries/LibGUI/FontPicker.cpp
new file mode 100644
index 0000000000..7b46b1e322
--- /dev/null
+++ b/Userland/Libraries/LibGUI/FontPicker.cpp
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/QuickSort.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/FontPicker.h>
+#include <LibGUI/FontPickerDialogGML.h>
+#include <LibGUI/ItemListModel.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/ListView.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGUI/Widget.h>
+#include <LibGfx/FontDatabase.h>
+
+namespace GUI {
+
+struct FontWeightNameMapping {
+ constexpr FontWeightNameMapping(int w, const char* n)
+ : weight(w)
+ , name(n)
+ {
+ }
+ int weight { 0 };
+ StringView name;
+};
+
+static constexpr FontWeightNameMapping font_weight_names[] = {
+ { 100, "Thin" },
+ { 200, "Extra Light" },
+ { 300, "Light" },
+ { 400, "Regular" },
+ { 500, "Medium" },
+ { 600, "Semi Bold" },
+ { 700, "Bold" },
+ { 800, "Extra Bold" },
+ { 900, "Black" },
+ { 950, "Extra Black" },
+};
+
+static constexpr StringView weight_to_name(int weight)
+{
+ for (auto& it : font_weight_names) {
+ if (it.weight == weight)
+ return it.name;
+ }
+ return {};
+}
+
+class FontWeightListModel : public ItemListModel<int> {
+public:
+ FontWeightListModel(const Vector<int>& weights)
+ : ItemListModel(weights)
+ {
+ }
+
+ virtual Variant data(const ModelIndex& index, ModelRole role) const override
+ {
+ if (role == ModelRole::Custom)
+ return m_data.at(index.row());
+ if (role == ModelRole::Display)
+ return String(weight_to_name(m_data.at(index.row())));
+ return ItemListModel::data(index, role);
+ }
+};
+
+FontPicker::FontPicker(Window* parent_window, const Gfx::Font* current_font, bool fixed_width_only)
+ : Dialog(parent_window)
+ , m_fixed_width_only(fixed_width_only)
+{
+ set_title("Font picker");
+ resize(430, 280);
+ set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-font-editor.png"));
+
+ auto& widget = set_main_widget<GUI::Widget>();
+ if (!widget.load_from_gml(font_picker_dialog_gml))
+ ASSERT_NOT_REACHED();
+
+ m_family_list_view = *widget.find_descendant_of_type_named<ListView>("family_list_view");
+ m_family_list_view->set_model(ItemListModel<String>::create(m_families));
+ m_family_list_view->horizontal_scrollbar().set_visible(false);
+
+ m_weight_list_view = *widget.find_descendant_of_type_named<ListView>("weight_list_view");
+ m_weight_list_view->set_model(adopt(*new FontWeightListModel(m_weights)));
+ m_weight_list_view->horizontal_scrollbar().set_visible(false);
+
+ m_size_list_view = *widget.find_descendant_of_type_named<ListView>("size_list_view");
+ m_size_list_view->set_model(ItemListModel<int>::create(m_sizes));
+ m_size_list_view->horizontal_scrollbar().set_visible(false);
+
+ m_sample_text_label = *widget.find_descendant_of_type_named<Label>("sample_text_label");
+
+ m_families.clear();
+ Gfx::FontDatabase::the().for_each_font([&](auto& font) {
+ if (m_fixed_width_only && !font.is_fixed_width())
+ return;
+ if (!m_families.contains_slow(font.family()))
+ m_families.append(font.family());
+ });
+ quick_sort(m_families);
+
+ m_family_list_view->on_selection = [this](auto& index) {
+ m_family = index.data().to_string();
+ m_weights.clear();
+ Gfx::FontDatabase::the().for_each_font([&](auto& font) {
+ if (m_fixed_width_only && !font.is_fixed_width())
+ return;
+ if (font.family() == m_family.value() && !m_weights.contains_slow(font.weight())) {
+ m_weights.append(font.weight());
+ }
+ });
+ quick_sort(m_weights);
+ Optional<size_t> index_of_old_weight_in_new_list;
+ if (m_weight.has_value())
+ index_of_old_weight_in_new_list = m_weights.find_first_index(m_weight.value());
+
+ m_weight_list_view->model()->update();
+ m_weight_list_view->set_cursor(m_weight_list_view->model()->index(index_of_old_weight_in_new_list.value_or(0)), GUI::AbstractView::SelectionUpdate::Set);
+ update_font();
+ };
+
+ m_weight_list_view->on_selection = [this](auto& index) {
+ m_weight = index.data(ModelRole::Custom).to_i32();
+ m_sizes.clear();
+ Gfx::FontDatabase::the().for_each_font([&](auto& font) {
+ if (m_fixed_width_only && !font.is_fixed_width())
+ return;
+ if (font.family() == m_family.value() && font.weight() == m_weight.value()) {
+ m_sizes.append(font.presentation_size());
+ }
+ });
+ quick_sort(m_sizes);
+ Optional<size_t> index_of_old_size_in_new_list;
+ if (m_size.has_value()) {
+ index_of_old_size_in_new_list = m_sizes.find_first_index(m_size.value());
+ }
+
+ m_size_list_view->model()->update();
+ m_size_list_view->set_cursor(m_size_list_view->model()->index(index_of_old_size_in_new_list.value_or(0)), GUI::AbstractView::SelectionUpdate::Set);
+ update_font();
+ };
+
+ m_size_list_view->on_selection = [this](auto& index) {
+ m_size = index.data().to_i32();
+ update_font();
+ };
+
+ auto& ok_button = *widget.find_descendant_of_type_named<GUI::Button>("ok_button");
+ ok_button.on_click = [this](auto) {
+ done(ExecOK);
+ };
+
+ auto& cancel_button = *widget.find_descendant_of_type_named<GUI::Button>("cancel_button");
+ cancel_button.on_click = [this](auto) {
+ done(ExecCancel);
+ };
+
+ set_font(current_font);
+}
+
+FontPicker::~FontPicker()
+{
+}
+
+void FontPicker::set_font(const Gfx::Font* font)
+{
+ if (m_font == font)
+ return;
+ m_font = font;
+ m_sample_text_label->set_font(m_font);
+
+ if (!m_font) {
+ m_family = {};
+ m_weight = {};
+ m_size = {};
+ m_weights.clear();
+ m_sizes.clear();
+ m_weight_list_view->model()->update();
+ m_size_list_view->model()->update();
+ return;
+ }
+
+ m_family = font->family();
+ m_weight = font->weight();
+ m_size = font->presentation_size();
+
+ size_t family_index = m_families.find_first_index(m_font->family()).value();
+ m_family_list_view->set_cursor(m_family_list_view->model()->index(family_index), GUI::AbstractView::SelectionUpdate::Set);
+
+ size_t weight_index = m_weights.find_first_index(m_font->weight()).value();
+ m_weight_list_view->set_cursor(m_weight_list_view->model()->index(weight_index), GUI::AbstractView::SelectionUpdate::Set);
+
+ size_t size_index = m_sizes.find_first_index(m_font->presentation_size()).value();
+ m_size_list_view->set_cursor(m_size_list_view->model()->index(size_index), GUI::AbstractView::SelectionUpdate::Set);
+}
+
+void FontPicker::update_font()
+{
+ if (m_family.has_value() && m_size.has_value() && m_weight.has_value()) {
+ m_font = Gfx::FontDatabase::the().get(m_family.value(), m_size.value(), m_weight.value());
+ m_sample_text_label->set_font(m_font);
+ }
+}
+}
diff --git a/Userland/Libraries/LibGUI/FontPicker.h b/Userland/Libraries/LibGUI/FontPicker.h
new file mode 100644
index 0000000000..89ba8143f6
--- /dev/null
+++ b/Userland/Libraries/LibGUI/FontPicker.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Dialog.h>
+#include <LibGfx/Forward.h>
+
+namespace GUI {
+
+class FontPicker final : public GUI::Dialog {
+ C_OBJECT(FontPicker);
+
+public:
+ virtual ~FontPicker() override;
+
+ RefPtr<Gfx::Font> font() const { return m_font; }
+ void set_font(const Gfx::Font*);
+
+private:
+ FontPicker(Window* parent_window = nullptr, const Gfx::Font* current_font = nullptr, bool fixed_width_only = false);
+
+ void update_font();
+
+ const bool m_fixed_width_only;
+
+ RefPtr<Gfx::Font> m_font;
+
+ RefPtr<ListView> m_family_list_view;
+ RefPtr<ListView> m_weight_list_view;
+ RefPtr<ListView> m_size_list_view;
+ RefPtr<Label> m_sample_text_label;
+
+ Vector<String> m_families;
+ Vector<int> m_weights;
+ Vector<int> m_sizes;
+
+ Optional<String> m_family;
+ Optional<int> m_weight;
+ Optional<int> m_size;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/FontPickerDialog.gml b/Userland/Libraries/LibGUI/FontPickerDialog.gml
new file mode 100644
index 0000000000..8f45fb0627
--- /dev/null
+++ b/Userland/Libraries/LibGUI/FontPickerDialog.gml
@@ -0,0 +1,95 @@
+@GUI::Widget {
+ fill_with_background_color: true
+
+ layout: @GUI::VerticalBoxLayout {
+ margins: [4, 4, 4, 4]
+ }
+
+ @GUI::Widget {
+ layout: @GUI::HorizontalBoxLayout {
+ }
+
+ @GUI::Widget {
+ layout: @GUI::VerticalBoxLayout {
+ }
+
+ @GUI::Label {
+ text: "Family:"
+ text_alignment: "CenterLeft"
+ fixed_height: 16
+ }
+
+ @GUI::ListView {
+ name: "family_list_view"
+ }
+ }
+
+ @GUI::Widget {
+ fixed_width: 100
+
+ layout: @GUI::VerticalBoxLayout {
+ }
+
+ @GUI::Label {
+ text: "Weight:"
+ text_alignment: "CenterLeft"
+ fixed_height: 16
+ }
+
+ @GUI::ListView {
+ name: "weight_list_view"
+ }
+ }
+
+ @GUI::Widget {
+ fixed_width: 80
+
+ layout: @GUI::VerticalBoxLayout {
+ }
+
+ @GUI::Label {
+ text: "Size:"
+ text_alignment: "CenterLeft"
+ fixed_height: 16
+ }
+
+ @GUI::ListView {
+ name: "size_list_view"
+ }
+ }
+ }
+
+ @GUI::GroupBox {
+ layout: @GUI::VerticalBoxLayout {
+ }
+
+ title: "Sample text"
+ fixed_height: 80
+
+ @GUI::Label {
+ name: "sample_text_label"
+ text: "The quick brown fox jumps over the lazy dog."
+ }
+ }
+
+ @GUI::Widget {
+ fixed_height: 22
+ layout: @GUI::HorizontalBoxLayout {
+ }
+
+ @GUI::Widget {
+ }
+
+ @GUI::Button {
+ name: "ok_button"
+ text: "OK"
+ fixed_width: 80
+ }
+
+ @GUI::Button {
+ name: "cancel_button"
+ text: "Cancel"
+ fixed_width: 80
+ }
+ }
+}
diff --git a/Userland/Libraries/LibGUI/Forward.h b/Userland/Libraries/LibGUI/Forward.h
new file mode 100644
index 0000000000..3f7d19c408
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Forward.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace GUI {
+
+class AbstractButton;
+class AbstractTableView;
+class AbstractView;
+class Action;
+class ActionGroup;
+class Application;
+class AutocompleteBox;
+class AutocompleteProvider;
+class BoxLayout;
+class Button;
+class CheckBox;
+class Command;
+class DragEvent;
+class DropEvent;
+class EditingEngine;
+class FileSystemModel;
+class Frame;
+class GroupBox;
+class HeaderView;
+class HorizontalBoxLayout;
+class HorizontalSlider;
+class Icon;
+class IconView;
+class JsonArrayModel;
+class KeyEvent;
+class Label;
+class Layout;
+class ListView;
+class Menu;
+class MenuBar;
+class MenuItem;
+class Model;
+class ModelEditingDelegate;
+class ModelIndex;
+class MouseEvent;
+class MultiPaintEvent;
+class MultiView;
+class OpacitySlider;
+class PaintEvent;
+class Painter;
+class ResizeCorner;
+class ResizeEvent;
+class ScrollBar;
+class Slider;
+class SortingProxyModel;
+class SpinBox;
+class Splitter;
+class StackWidget;
+class StatusBar;
+class SyntaxHighlighter;
+class TabWidget;
+class TableView;
+class TextBox;
+class TextDocument;
+class TextDocumentLine;
+class TextDocumentUndoCommand;
+class TextEditor;
+class ThemeChangeEvent;
+class ToolBar;
+class ToolBarContainer;
+class TreeView;
+class Variant;
+class VerticalBoxLayout;
+class VerticalSlider;
+class WMEvent;
+class Widget;
+class WidgetClassRegistration;
+class Window;
+class WindowServerConnection;
+
+enum class ModelRole;
+enum class SortOrder;
+
+}
diff --git a/Userland/Libraries/LibGUI/Frame.cpp b/Userland/Libraries/LibGUI/Frame.cpp
new file mode 100644
index 0000000000..47a82b6e14
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Frame.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Frame.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+REGISTER_WIDGET(GUI, Frame)
+
+namespace GUI {
+
+Frame::Frame()
+{
+ set_frame_thickness(2);
+ set_frame_shape(Gfx::FrameShape::Container);
+ set_frame_shadow(Gfx::FrameShadow::Sunken);
+
+ REGISTER_INT_PROPERTY("thickness", frame_thickness, set_frame_thickness);
+ REGISTER_ENUM_PROPERTY("shadow", frame_shadow, set_frame_shadow, Gfx::FrameShadow,
+ { Gfx::FrameShadow::Plain, "Plain" },
+ { Gfx::FrameShadow::Raised, "Raised" },
+ { Gfx::FrameShadow::Sunken, "Sunken" });
+ REGISTER_ENUM_PROPERTY("shape", frame_shape, set_frame_shape, Gfx::FrameShape,
+ { Gfx::FrameShape::NoFrame, "NoFrame" },
+ { Gfx::FrameShape::Box, "Box" },
+ { Gfx::FrameShape::Container, "Container" },
+ { Gfx::FrameShape::Panel, "Panel" },
+ { Gfx::FrameShape::VerticalLine, "VerticalLine" },
+ { Gfx::FrameShape::HorizontalLine, "HorizontalLine" });
+}
+
+Frame::~Frame()
+{
+}
+
+void Frame::set_frame_thickness(int thickness)
+{
+ if (m_thickness == thickness)
+ return;
+ m_thickness = thickness;
+ set_content_margins({ thickness, thickness, thickness, thickness });
+}
+
+void Frame::paint_event(PaintEvent& event)
+{
+ if (m_shape == Gfx::FrameShape::NoFrame)
+ return;
+
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+ Gfx::StylePainter::paint_frame(painter, rect(), palette(), m_shape, m_shadow, m_thickness, spans_entire_window_horizontally());
+}
+
+Gfx::IntRect Frame::children_clip_rect() const
+{
+ return frame_inner_rect();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Frame.h b/Userland/Libraries/LibGUI/Frame.h
new file mode 100644
index 0000000000..746cb16a04
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Frame.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+#include <LibGfx/StylePainter.h>
+
+namespace GUI {
+
+class Frame : public Widget {
+ C_OBJECT(Frame)
+public:
+ virtual ~Frame() override;
+
+ int frame_thickness() const { return m_thickness; }
+ void set_frame_thickness(int thickness);
+
+ Gfx::FrameShadow frame_shadow() const { return m_shadow; }
+ void set_frame_shadow(Gfx::FrameShadow shadow) { m_shadow = shadow; }
+
+ Gfx::FrameShape frame_shape() const { return m_shape; }
+ void set_frame_shape(Gfx::FrameShape shape) { m_shape = shape; }
+
+ Gfx::IntRect frame_inner_rect_for_size(const Gfx::IntSize& size) const { return { m_thickness, m_thickness, size.width() - m_thickness * 2, size.height() - m_thickness * 2 }; }
+ Gfx::IntRect frame_inner_rect() const { return frame_inner_rect_for_size(size()); }
+
+ virtual Gfx::IntRect children_clip_rect() const override;
+
+protected:
+ Frame();
+ void paint_event(PaintEvent&) override;
+
+private:
+ int m_thickness { 0 };
+ Gfx::FrameShadow m_shadow { Gfx::FrameShadow::Plain };
+ Gfx::FrameShape m_shape { Gfx::FrameShape::NoFrame };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/GMLFormatter.cpp b/Userland/Libraries/LibGUI/GMLFormatter.cpp
new file mode 100644
index 0000000000..09c263bcc9
--- /dev/null
+++ b/Userland/Libraries/LibGUI/GMLFormatter.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2021, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/JsonObject.h>
+#include <AK/JsonValue.h>
+#include <AK/StringBuilder.h>
+#include <LibGUI/GMLFormatter.h>
+#include <LibGUI/GMLParser.h>
+
+namespace GUI {
+
+static String format_gml_object(const JsonObject& node, size_t indentation = 0, bool is_inline = false)
+{
+ StringBuilder builder;
+
+ auto indent = [&builder](size_t indentation) {
+ for (size_t i = 0; i < indentation; ++i)
+ builder.append(" ");
+ };
+
+ struct Property {
+ String key;
+ JsonValue value;
+ };
+ Vector<Property> properties;
+ node.for_each_member([&](auto& key, auto& value) {
+ if (key != "class" && key != "layout" && key != "children")
+ properties.append({ key, value });
+ return IterationDecision::Continue;
+ });
+
+ if (!is_inline)
+ indent(indentation);
+ builder.append('@');
+ builder.append(node.get("class").as_string());
+ builder.append(" {\n");
+
+ for (auto& property : properties) {
+ indent(indentation + 1);
+ builder.append(property.key);
+ builder.append(": ");
+ if (property.value.is_array()) {
+ // custom array serialization as AK's doesn't pretty-print
+ // objects and arrays (we only care about arrays (for now))
+ builder.append("[");
+ auto first = true;
+ property.value.as_array().for_each([&](auto& value) {
+ if (!first)
+ builder.append(", ");
+ first = false;
+ value.serialize(builder);
+ });
+ builder.append("]");
+ } else {
+ property.value.serialize(builder);
+ }
+ builder.append("\n");
+ }
+
+ if (node.has("layout")) {
+ auto layout = node.get("layout").as_object();
+ if (!properties.is_empty())
+ builder.append("\n");
+ indent(indentation + 1);
+ builder.append("layout: ");
+ builder.append(format_gml_object(move(layout), indentation + 1, true));
+ }
+
+ if (node.has("children")) {
+ auto children = node.get("children").as_array();
+ auto first = properties.is_empty() && !node.has("layout");
+ children.for_each([&](auto& value) {
+ if (!first)
+ builder.append("\n");
+ first = false;
+ builder.append(format_gml_object(value.as_object(), indentation + 1));
+ });
+ }
+
+ indent(indentation);
+ builder.append("}\n");
+
+ return builder.to_string();
+}
+
+String format_gml(const StringView& string)
+{
+ // FIXME: Preserve comments somehow, they're not contained
+ // in the JSON object returned by parse_gml()
+ auto ast = parse_gml(string);
+ if (ast.is_null())
+ return {};
+ ASSERT(ast.is_object());
+ return format_gml_object(ast.as_object());
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/GMLFormatter.h b/Userland/Libraries/LibGUI/GMLFormatter.h
new file mode 100644
index 0000000000..6ae32ddbe8
--- /dev/null
+++ b/Userland/Libraries/LibGUI/GMLFormatter.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2021, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+
+namespace GUI {
+
+String format_gml(const StringView&);
+
+}
diff --git a/Userland/Libraries/LibGUI/GMLLexer.cpp b/Userland/Libraries/LibGUI/GMLLexer.cpp
new file mode 100644
index 0000000000..ede1272356
--- /dev/null
+++ b/Userland/Libraries/LibGUI/GMLLexer.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "GMLLexer.h"
+#include <AK/Vector.h>
+#include <ctype.h>
+
+namespace GUI {
+
+GMLLexer::GMLLexer(const StringView& input)
+ : m_input(input)
+{
+}
+
+char GMLLexer::peek(size_t offset) const
+{
+ if ((m_index + offset) >= m_input.length())
+ return 0;
+ return m_input[m_index + offset];
+}
+
+char GMLLexer::consume()
+{
+ ASSERT(m_index < m_input.length());
+ char ch = m_input[m_index++];
+ m_previous_position = m_position;
+ if (ch == '\n') {
+ m_position.line++;
+ m_position.column = 0;
+ } else {
+ m_position.column++;
+ }
+ return ch;
+}
+
+static bool is_valid_identifier_start(char ch)
+{
+ return isalpha(ch) || ch == '_';
+}
+
+static bool is_valid_identifier_character(char ch)
+{
+ return isalnum(ch) || ch == '_';
+}
+
+static bool is_valid_class_character(char ch)
+{
+ return isalnum(ch) || ch == '_' || ch == ':';
+}
+
+Vector<GMLToken> GMLLexer::lex()
+{
+ Vector<GMLToken> tokens;
+
+ size_t token_start_index = 0;
+ GMLPosition token_start_position;
+
+ auto begin_token = [&] {
+ token_start_index = m_index;
+ token_start_position = m_position;
+ };
+
+ auto commit_token = [&](auto type) {
+ GMLToken token;
+ token.m_view = m_input.substring_view(token_start_index, m_index - token_start_index);
+ token.m_type = type;
+ token.m_start = token_start_position;
+ token.m_end = m_previous_position;
+ tokens.append(token);
+ };
+
+ auto consume_class = [&] {
+ begin_token();
+ consume();
+ commit_token(GMLToken::Type::ClassMarker);
+ begin_token();
+ while (is_valid_class_character(peek()))
+ consume();
+ commit_token(GMLToken::Type::ClassName);
+ };
+
+ while (m_index < m_input.length()) {
+ if (isspace(peek(0))) {
+ begin_token();
+ while (isspace(peek()))
+ consume();
+ continue;
+ }
+
+ // C++ style comments
+ if (peek(0) && peek(0) == '/' && peek(1) == '/') {
+ begin_token();
+ while (peek() && peek() != '\n')
+ consume();
+ commit_token(GMLToken::Type::Comment);
+ continue;
+ }
+
+ if (peek(0) == '{') {
+ begin_token();
+ consume();
+ commit_token(GMLToken::Type::LeftCurly);
+ continue;
+ }
+
+ if (peek(0) == '}') {
+ begin_token();
+ consume();
+ commit_token(GMLToken::Type::RightCurly);
+ continue;
+ }
+
+ if (peek(0) == '@') {
+ consume_class();
+ continue;
+ }
+
+ if (is_valid_identifier_start(peek(0))) {
+ begin_token();
+ consume();
+ while (is_valid_identifier_character(peek(0)))
+ consume();
+ commit_token(GMLToken::Type::Identifier);
+ continue;
+ }
+
+ if (peek(0) == ':') {
+ begin_token();
+ consume();
+ commit_token(GMLToken::Type::Colon);
+
+ while (isspace(peek()))
+ consume();
+
+ if (peek(0) == '@') {
+ consume_class();
+ } else {
+ begin_token();
+ while (peek() && peek() != '\n')
+ consume();
+ commit_token(GMLToken::Type::JsonValue);
+ }
+ continue;
+ }
+
+ consume();
+ commit_token(GMLToken::Type::Unknown);
+ }
+ return tokens;
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/GMLLexer.h b/Userland/Libraries/LibGUI/GMLLexer.h
new file mode 100644
index 0000000000..0803747dd9
--- /dev/null
+++ b/Userland/Libraries/LibGUI/GMLLexer.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/StringView.h>
+
+namespace GUI {
+
+#define FOR_EACH_TOKEN_TYPE \
+ __TOKEN(Unknown) \
+ __TOKEN(Comment) \
+ __TOKEN(ClassMarker) \
+ __TOKEN(ClassName) \
+ __TOKEN(LeftCurly) \
+ __TOKEN(RightCurly) \
+ __TOKEN(Identifier) \
+ __TOKEN(Colon) \
+ __TOKEN(JsonValue)
+
+struct GMLPosition {
+ size_t line;
+ size_t column;
+};
+
+struct GMLToken {
+ enum class Type {
+#define __TOKEN(x) x,
+ FOR_EACH_TOKEN_TYPE
+#undef __TOKEN
+ };
+
+ const char* to_string() const
+ {
+ switch (m_type) {
+#define __TOKEN(x) \
+ case Type::x: \
+ return #x;
+ FOR_EACH_TOKEN_TYPE
+#undef __TOKEN
+ }
+ ASSERT_NOT_REACHED();
+ }
+
+ Type m_type { Type::Unknown };
+ StringView m_view;
+ GMLPosition m_start;
+ GMLPosition m_end;
+};
+
+class GMLLexer {
+public:
+ GMLLexer(const StringView&);
+
+ Vector<GMLToken> lex();
+
+private:
+ char peek(size_t offset = 0) const;
+ char consume();
+
+ StringView m_input;
+ size_t m_index { 0 };
+ GMLPosition m_previous_position { 0, 0 };
+ GMLPosition m_position { 0, 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/GMLParser.cpp b/Userland/Libraries/LibGUI/GMLParser.cpp
new file mode 100644
index 0000000000..67df75479c
--- /dev/null
+++ b/Userland/Libraries/LibGUI/GMLParser.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/GenericLexer.h>
+#include <AK/JsonObject.h>
+#include <AK/JsonValue.h>
+#include <AK/Queue.h>
+#include <LibGUI/GMLLexer.h>
+#include <LibGUI/GMLParser.h>
+#include <ctype.h>
+
+namespace GUI {
+
+static Optional<JsonValue> parse_core_object(Queue<GMLToken>& tokens)
+{
+ JsonObject object;
+ JsonArray children;
+
+ auto peek = [&] {
+ if (tokens.is_empty())
+ return GMLToken::Type::Unknown;
+ return tokens.head().m_type;
+ };
+
+ while (peek() == GMLToken::Type::Comment)
+ tokens.dequeue();
+
+ if (peek() != GMLToken::Type::ClassMarker) {
+ dbgln("Expected class marker");
+ return {};
+ }
+
+ tokens.dequeue();
+
+ if (peek() != GMLToken::Type::ClassName) {
+ dbgln("Expected class name");
+ return {};
+ }
+
+ auto class_name = tokens.dequeue();
+ object.set("class", JsonValue(class_name.m_view));
+
+ if (peek() != GMLToken::Type::LeftCurly) {
+ dbgln("Expected {{");
+ return {};
+ }
+ tokens.dequeue();
+
+ for (;;) {
+ if (peek() == GMLToken::Type::RightCurly) {
+ // End of object
+ break;
+ }
+
+ if (peek() == GMLToken::Type::ClassMarker) {
+ // It's a child object.
+ auto value = parse_core_object(tokens);
+ if (!value.has_value()) {
+ dbgln("Parsing child object failed");
+ return {};
+ }
+ if (!value.value().is_object()) {
+ dbgln("Expected child to be Core::Object");
+ return {};
+ }
+ children.append(value.release_value());
+ } else if (peek() == GMLToken::Type::Identifier) {
+ // It's a property.
+ auto property_name = tokens.dequeue();
+
+ if (property_name.m_view.is_empty()) {
+ dbgln("Expected non-empty property name");
+ return {};
+ }
+
+ if (peek() != GMLToken::Type::Colon) {
+ dbgln("Expected ':'");
+ return {};
+ }
+ tokens.dequeue();
+
+ JsonValue value;
+ if (peek() == GMLToken::Type::ClassMarker) {
+ auto parsed_value = parse_core_object(tokens);
+ if (!parsed_value.has_value())
+ return {};
+ if (!parsed_value.value().is_object()) {
+ dbgln("Expected property to be Core::Object");
+ return {};
+ }
+ value = parsed_value.release_value();
+ } else if (peek() == GMLToken::Type::JsonValue) {
+ auto value_string = tokens.dequeue();
+ auto parsed_value = JsonValue::from_string(value_string.m_view);
+ if (!parsed_value.has_value()) {
+ dbgln("Expected property to be JSON value");
+ return {};
+ }
+ value = parsed_value.release_value();
+ }
+ object.set(property_name.m_view, move(value));
+ } else if (peek() == GMLToken::Type::Comment) {
+ tokens.dequeue();
+ } else {
+ dbgln("Expected child, property, comment, or }}");
+ return {};
+ }
+ }
+
+ if (peek() != GMLToken::Type::RightCurly) {
+ dbgln("Expected }}");
+ return {};
+ }
+ tokens.dequeue();
+
+ if (!children.is_empty())
+ object.set("children", move(children));
+
+ return object;
+}
+
+JsonValue parse_gml(const StringView& string)
+{
+ auto lexer = GMLLexer(string);
+
+ Queue<GMLToken> tokens;
+ for (auto& token : lexer.lex())
+ tokens.enqueue(token);
+
+ auto root = parse_core_object(tokens);
+
+ if (!root.has_value())
+ return JsonValue();
+
+ return root.release_value();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/GMLParser.h b/Userland/Libraries/LibGUI/GMLParser.h
new file mode 100644
index 0000000000..e6a419551d
--- /dev/null
+++ b/Userland/Libraries/LibGUI/GMLParser.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+
+namespace GUI {
+
+JsonValue parse_gml(const StringView&);
+
+}
diff --git a/Userland/Libraries/LibGUI/GMLSyntaxHighlighter.cpp b/Userland/Libraries/LibGUI/GMLSyntaxHighlighter.cpp
new file mode 100644
index 0000000000..01c4f96c36
--- /dev/null
+++ b/Userland/Libraries/LibGUI/GMLSyntaxHighlighter.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/GMLLexer.h>
+#include <LibGUI/GMLSyntaxHighlighter.h>
+#include <LibGUI/TextEditor.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+static TextStyle style_for_token_type(Gfx::Palette palette, GMLToken::Type type)
+{
+ switch (type) {
+ case GMLToken::Type::LeftCurly:
+ case GMLToken::Type::RightCurly:
+ return { palette.syntax_punctuation() };
+ case GMLToken::Type::ClassMarker:
+ return { palette.syntax_keyword() };
+ case GMLToken::Type::ClassName:
+ return { palette.syntax_identifier(), true };
+ case GMLToken::Type::Identifier:
+ return { palette.syntax_identifier() };
+ case GMLToken::Type::JsonValue:
+ return { palette.syntax_string() };
+ case GMLToken::Type::Comment:
+ return { palette.syntax_comment() };
+ default:
+ return { palette.base_text() };
+ }
+}
+
+bool GMLSyntaxHighlighter::is_identifier(void* token) const
+{
+ auto ini_token = static_cast<GUI::GMLToken::Type>(reinterpret_cast<size_t>(token));
+ return ini_token == GUI::GMLToken::Type::Identifier;
+}
+
+void GMLSyntaxHighlighter::rehighlight(Gfx::Palette palette)
+{
+ ASSERT(m_editor);
+ auto text = m_editor->text();
+ GMLLexer lexer(text);
+ auto tokens = lexer.lex();
+
+ Vector<GUI::TextDocumentSpan> spans;
+ for (auto& token : tokens) {
+ GUI::TextDocumentSpan span;
+ span.range.set_start({ token.m_start.line, token.m_start.column });
+ span.range.set_end({ token.m_end.line, token.m_end.column });
+ auto style = style_for_token_type(palette, token.m_type);
+ span.attributes.color = style.color;
+ span.attributes.bold = style.bold;
+ span.is_skippable = false;
+ span.data = reinterpret_cast<void*>(token.m_type);
+ spans.append(span);
+ }
+ m_editor->document().set_spans(spans);
+
+ m_has_brace_buddies = false;
+ highlight_matching_token_pair();
+
+ m_editor->update();
+}
+
+Vector<GMLSyntaxHighlighter::MatchingTokenPair> GMLSyntaxHighlighter::matching_token_pairs() const
+{
+ static Vector<SyntaxHighlighter::MatchingTokenPair> pairs;
+ if (pairs.is_empty()) {
+ pairs.append({ reinterpret_cast<void*>(GMLToken::Type::LeftCurly), reinterpret_cast<void*>(GMLToken::Type::RightCurly) });
+ }
+ return pairs;
+}
+
+bool GMLSyntaxHighlighter::token_types_equal(void* token1, void* token2) const
+{
+ return static_cast<GUI::GMLToken::Type>(reinterpret_cast<size_t>(token1)) == static_cast<GUI::GMLToken::Type>(reinterpret_cast<size_t>(token2));
+}
+
+GMLSyntaxHighlighter::~GMLSyntaxHighlighter()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/GMLSyntaxHighlighter.h b/Userland/Libraries/LibGUI/GMLSyntaxHighlighter.h
new file mode 100644
index 0000000000..dc1d73498e
--- /dev/null
+++ b/Userland/Libraries/LibGUI/GMLSyntaxHighlighter.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/SyntaxHighlighter.h>
+
+namespace GUI {
+
+class GMLSyntaxHighlighter final : public SyntaxHighlighter {
+public:
+ GMLSyntaxHighlighter() { }
+ virtual ~GMLSyntaxHighlighter() override;
+
+ virtual bool is_identifier(void*) const override;
+
+ virtual SyntaxLanguage language() const override { return SyntaxLanguage::INI; }
+ virtual void rehighlight(Gfx::Palette) override;
+
+protected:
+ virtual Vector<MatchingTokenPair> matching_token_pairs() const override;
+ virtual bool token_types_equal(void*, void*) const override;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/GroupBox.cpp b/Userland/Libraries/LibGUI/GroupBox.cpp
new file mode 100644
index 0000000000..4a60905452
--- /dev/null
+++ b/Userland/Libraries/LibGUI/GroupBox.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/GroupBox.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+REGISTER_WIDGET(GUI, GroupBox)
+
+namespace GUI {
+
+GroupBox::GroupBox(const StringView& title)
+ : m_title(title)
+{
+ REGISTER_STRING_PROPERTY("title", title, set_title);
+}
+
+GroupBox::~GroupBox()
+{
+}
+
+void GroupBox::paint_event(PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ Gfx::IntRect frame_rect {
+ 0, font().glyph_height() / 2,
+ width(), height() - font().glyph_height() / 2
+ };
+ Gfx::StylePainter::paint_frame(painter, frame_rect, palette(), Gfx::FrameShape::Box, Gfx::FrameShadow::Sunken, 2);
+
+ if (!m_title.is_empty()) {
+ Gfx::IntRect text_rect { 4, 0, font().width(m_title) + 6, font().glyph_height() };
+ painter.fill_rect(text_rect, palette().button());
+ painter.draw_text(text_rect, m_title, Gfx::TextAlignment::Center, palette().button_text());
+ }
+}
+
+void GroupBox::set_title(const StringView& title)
+{
+ if (m_title == title)
+ return;
+ m_title = title;
+ update();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/GroupBox.h b/Userland/Libraries/LibGUI/GroupBox.h
new file mode 100644
index 0000000000..1e56fb91f6
--- /dev/null
+++ b/Userland/Libraries/LibGUI/GroupBox.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class GroupBox : public Widget {
+ C_OBJECT(GroupBox)
+public:
+ virtual ~GroupBox() override;
+
+ String title() const { return m_title; }
+ void set_title(const StringView&);
+
+protected:
+ explicit GroupBox(const StringView& title = {});
+
+ virtual void paint_event(PaintEvent&) override;
+
+private:
+ String m_title;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/HeaderView.cpp b/Userland/Libraries/LibGUI/HeaderView.cpp
new file mode 100644
index 0000000000..0b394b63d2
--- /dev/null
+++ b/Userland/Libraries/LibGUI/HeaderView.cpp
@@ -0,0 +1,387 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/AbstractTableView.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/HeaderView.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+namespace GUI {
+
+static constexpr int minimum_column_size = 2;
+
+HeaderView::HeaderView(AbstractTableView& table_view, Gfx::Orientation orientation)
+ : m_table_view(table_view)
+ , m_orientation(orientation)
+{
+ set_font(Gfx::FontDatabase::default_bold_font());
+
+ if (m_orientation == Gfx::Orientation::Horizontal) {
+ set_fixed_height(16);
+ } else {
+ set_fixed_width(40);
+ }
+}
+
+HeaderView::~HeaderView()
+{
+}
+
+void HeaderView::set_section_size(int section, int size)
+{
+ auto& data = section_data(section);
+ if (data.size == size)
+ return;
+ data.size = size;
+ data.has_initialized_size = true;
+ data.size = size;
+ m_table_view.header_did_change_section_size({}, m_orientation, section, size);
+}
+
+int HeaderView::section_size(int section) const
+{
+ return section_data(section).size;
+}
+
+HeaderView::SectionData& HeaderView::section_data(int section) const
+{
+ if (static_cast<size_t>(section) >= m_section_data.size())
+ m_section_data.resize(section + 1);
+ return m_section_data.at(section);
+}
+
+Gfx::IntRect HeaderView::section_rect(int section) const
+{
+ if (!model())
+ return {};
+ auto& data = section_data(section);
+ if (!data.visibility)
+ return {};
+ int offset = 0;
+ for (int i = 0; i < section; ++i) {
+ if (!is_section_visible(i))
+ continue;
+ offset += section_data(i).size;
+ if (orientation() == Gfx::Orientation::Horizontal)
+ offset += m_table_view.horizontal_padding() * 2;
+ }
+ if (orientation() == Gfx::Orientation::Horizontal)
+ return { offset, 0, section_size(section) + m_table_view.horizontal_padding() * 2, height() };
+ return { 0, offset, width(), section_size(section) };
+}
+
+Gfx::IntRect HeaderView::section_resize_grabbable_rect(int section) const
+{
+ if (!model())
+ return {};
+ // FIXME: Support resizable rows.
+ if (m_orientation == Gfx::Orientation::Vertical)
+ return {};
+ auto rect = section_rect(section);
+ return { rect.right() - 1, rect.top(), 4, rect.height() };
+}
+
+int HeaderView::section_count() const
+{
+ if (!model())
+ return 0;
+ return m_orientation == Gfx::Orientation::Horizontal ? model()->column_count() : model()->row_count();
+}
+
+void HeaderView::mousedown_event(MouseEvent& event)
+{
+ if (!model())
+ return;
+
+ auto& model = *this->model();
+ int section_count = this->section_count();
+
+ for (int i = 0; i < section_count; ++i) {
+ if (section_resize_grabbable_rect(i).contains(event.position())) {
+ m_resizing_section = i;
+ m_in_section_resize = true;
+ m_section_resize_original_width = section_size(i);
+ m_section_resize_origin = event.position();
+ return;
+ }
+ auto rect = this->section_rect(i);
+ if (rect.contains(event.position()) && model.is_column_sortable(i)) {
+ m_pressed_section = i;
+ m_pressed_section_is_pressed = true;
+ update();
+ return;
+ }
+ }
+}
+
+void HeaderView::mousemove_event(MouseEvent& event)
+{
+ if (!model())
+ return;
+
+ if (m_in_section_resize) {
+ auto delta = event.position() - m_section_resize_origin;
+ int new_size = m_section_resize_original_width + delta.primary_offset_for_orientation(m_orientation);
+ if (new_size <= minimum_column_size)
+ new_size = minimum_column_size;
+ ASSERT(m_resizing_section >= 0 && m_resizing_section < model()->column_count());
+ set_section_size(m_resizing_section, new_size);
+ return;
+ }
+
+ if (m_pressed_section != -1) {
+ auto header_rect = this->section_rect(m_pressed_section);
+ if (header_rect.contains(event.position())) {
+ set_hovered_section(m_pressed_section);
+ if (!m_pressed_section_is_pressed)
+ update();
+ m_pressed_section_is_pressed = true;
+ } else {
+ set_hovered_section(-1);
+ if (m_pressed_section_is_pressed)
+ update();
+ m_pressed_section_is_pressed = false;
+ }
+ return;
+ }
+
+ if (event.buttons() == 0) {
+ int section_count = this->section_count();
+ bool found_hovered_header = false;
+ for (int i = 0; i < section_count; ++i) {
+ if (section_resize_grabbable_rect(i).contains(event.position())) {
+ set_override_cursor(Gfx::StandardCursor::ResizeColumn);
+ set_hovered_section(-1);
+ return;
+ }
+ if (section_rect(i).contains(event.position())) {
+ set_hovered_section(i);
+ found_hovered_header = true;
+ }
+ }
+ if (!found_hovered_header)
+ set_hovered_section(-1);
+ }
+ set_override_cursor(Gfx::StandardCursor::None);
+}
+
+void HeaderView::mouseup_event(MouseEvent& event)
+{
+ if (event.button() == MouseButton::Left) {
+ if (m_in_section_resize) {
+ if (!section_resize_grabbable_rect(m_resizing_section).contains(event.position()))
+ set_override_cursor(Gfx::StandardCursor::None);
+ m_in_section_resize = false;
+ return;
+ }
+ if (m_pressed_section != -1) {
+ if (m_orientation == Gfx::Orientation::Horizontal && section_rect(m_pressed_section).contains(event.position())) {
+ auto new_sort_order = m_table_view.sort_order();
+ if (m_table_view.key_column() == m_pressed_section)
+ new_sort_order = m_table_view.sort_order() == SortOrder::Ascending
+ ? SortOrder::Descending
+ : SortOrder::Ascending;
+ m_table_view.set_key_column_and_sort_order(m_pressed_section, new_sort_order);
+ }
+ m_pressed_section = -1;
+ m_pressed_section_is_pressed = false;
+ update();
+ return;
+ }
+ }
+}
+
+void HeaderView::paint_horizontal(Painter& painter)
+{
+ painter.draw_line({ 0, 0 }, { rect().right(), 0 }, palette().threed_highlight());
+ painter.draw_line({ 0, rect().bottom() }, { rect().right(), rect().bottom() }, palette().threed_shadow1());
+ int x_offset = 0;
+ int section_count = this->section_count();
+ for (int section = 0; section < section_count; ++section) {
+ if (!is_section_visible(section))
+ continue;
+ int section_width = section_size(section);
+ bool is_key_column = m_table_view.key_column() == section;
+ Gfx::IntRect cell_rect(x_offset, 0, section_width + m_table_view.horizontal_padding() * 2, height());
+ bool pressed = section == m_pressed_section && m_pressed_section_is_pressed;
+ bool hovered = section == m_hovered_section && model()->is_column_sortable(section);
+ Gfx::StylePainter::paint_button(painter, cell_rect, palette(), Gfx::ButtonStyle::Normal, pressed, hovered);
+ String text;
+ if (is_key_column) {
+ StringBuilder builder;
+ builder.append(model()->column_name(section));
+ if (m_table_view.sort_order() == SortOrder::Ascending)
+ builder.append(" \xE2\xAC\x86"); // UPWARDS BLACK ARROW
+ else if (m_table_view.sort_order() == SortOrder::Descending)
+ builder.append(" \xE2\xAC\x87"); // DOWNWARDS BLACK ARROW
+ text = builder.to_string();
+ } else {
+ text = model()->column_name(section);
+ }
+ auto text_rect = cell_rect.shrunken(m_table_view.horizontal_padding() * 2, 0);
+ if (pressed)
+ text_rect.move_by(1, 1);
+ painter.draw_text(text_rect, text, font(), section_alignment(section), palette().button_text());
+ x_offset += section_width + m_table_view.horizontal_padding() * 2;
+ }
+
+ if (x_offset < rect().right()) {
+ Gfx::IntRect cell_rect(x_offset, 0, width() - x_offset, height());
+ Gfx::StylePainter::paint_button(painter, cell_rect, palette(), Gfx::ButtonStyle::Normal, false, false);
+ }
+}
+
+void HeaderView::paint_vertical(Painter& painter)
+{
+ painter.draw_line(rect().top_left(), rect().bottom_left(), palette().threed_highlight());
+ painter.draw_line(rect().top_right(), rect().bottom_right(), palette().threed_shadow1());
+ int y_offset = 0;
+ int section_count = this->section_count();
+ for (int section = 0; section < section_count; ++section) {
+ if (!is_section_visible(section))
+ continue;
+ int section_size = this->section_size(section);
+ Gfx::IntRect cell_rect(0, y_offset, width(), section_size);
+ bool pressed = section == m_pressed_section && m_pressed_section_is_pressed;
+ bool hovered = false;
+ Gfx::StylePainter::paint_button(painter, cell_rect, palette(), Gfx::ButtonStyle::Normal, pressed, hovered);
+ String text = String::number(section);
+ auto text_rect = cell_rect.shrunken(m_table_view.horizontal_padding() * 2, 0);
+ if (pressed)
+ text_rect.move_by(1, 1);
+ painter.draw_text(text_rect, text, font(), section_alignment(section), palette().button_text());
+ y_offset += section_size;
+ }
+
+ if (y_offset < rect().bottom()) {
+ Gfx::IntRect cell_rect(0, y_offset, width(), height() - y_offset);
+ Gfx::StylePainter::paint_button(painter, cell_rect, palette(), Gfx::ButtonStyle::Normal, false, false);
+ }
+}
+
+void HeaderView::paint_event(PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+ painter.fill_rect(rect(), palette().button());
+ if (orientation() == Gfx::Orientation::Horizontal)
+ paint_horizontal(painter);
+ else
+ paint_vertical(painter);
+}
+
+void HeaderView::set_section_visible(int section, bool visible)
+{
+ auto& data = section_data(section);
+ if (data.visibility == visible)
+ return;
+ data.visibility = visible;
+ if (data.visibility_action) {
+ data.visibility_action->set_checked(visible);
+ }
+ m_table_view.header_did_change_section_visibility({}, m_orientation, section, visible);
+ update();
+}
+
+Menu& HeaderView::ensure_context_menu()
+{
+ // FIXME: This menu needs to be rebuilt if the model is swapped out,
+ // or if the column count/names change.
+ if (!m_context_menu) {
+ ASSERT(model());
+ m_context_menu = Menu::construct();
+
+ if (m_orientation == Gfx::Orientation::Vertical) {
+ dbgln("FIXME: Support context menus for vertical GUI::HeaderView");
+ return *m_context_menu;
+ }
+
+ int section_count = this->section_count();
+ for (int section = 0; section < section_count; ++section) {
+ auto& column_data = this->section_data(section);
+ auto name = model()->column_name(section);
+ column_data.visibility_action = Action::create_checkable(name, [this, section](auto& action) {
+ set_section_visible(section, action.is_checked());
+ });
+ column_data.visibility_action->set_checked(column_data.visibility);
+
+ m_context_menu->add_action(*column_data.visibility_action);
+ }
+ }
+ return *m_context_menu;
+}
+
+void HeaderView::context_menu_event(ContextMenuEvent& event)
+{
+ ensure_context_menu().popup(event.screen_position());
+}
+
+void HeaderView::leave_event(Core::Event& event)
+{
+ Widget::leave_event(event);
+ set_hovered_section(-1);
+}
+
+Gfx::TextAlignment HeaderView::section_alignment(int section) const
+{
+ return section_data(section).alignment;
+}
+
+void HeaderView::set_section_alignment(int section, Gfx::TextAlignment alignment)
+{
+ section_data(section).alignment = alignment;
+}
+
+bool HeaderView::is_section_visible(int section) const
+{
+ return section_data(section).visibility;
+}
+
+void HeaderView::set_hovered_section(int section)
+{
+ if (m_hovered_section == section)
+ return;
+ m_hovered_section = section;
+ update();
+}
+
+Model* HeaderView::model()
+{
+ return m_table_view.model();
+}
+
+const Model* HeaderView::model() const
+{
+ return m_table_view.model();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/HeaderView.h b/Userland/Libraries/LibGUI/HeaderView.h
new file mode 100644
index 0000000000..5132be3fb6
--- /dev/null
+++ b/Userland/Libraries/LibGUI/HeaderView.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+#include <LibGfx/Orientation.h>
+
+namespace GUI {
+
+class HeaderView final : public Widget {
+ C_OBJECT(HeaderView);
+
+public:
+ virtual ~HeaderView() override;
+
+ Gfx::Orientation orientation() const { return m_orientation; }
+
+ Model* model();
+ const Model* model() const;
+
+ void set_section_size(int section, int size);
+ int section_size(int section) const;
+
+ Gfx::TextAlignment section_alignment(int section) const;
+ void set_section_alignment(int section, Gfx::TextAlignment);
+
+ bool is_section_visible(int section) const;
+ void set_section_visible(int section, bool);
+
+ int section_count() const;
+ Gfx::IntRect section_rect(int section) const;
+
+private:
+ HeaderView(AbstractTableView&, Gfx::Orientation);
+
+ virtual void paint_event(PaintEvent&) override;
+ virtual void mousedown_event(MouseEvent&) override;
+ virtual void mousemove_event(MouseEvent&) override;
+ virtual void mouseup_event(MouseEvent&) override;
+ virtual void context_menu_event(ContextMenuEvent&) override;
+ virtual void leave_event(Core::Event&) override;
+
+ Gfx::IntRect section_resize_grabbable_rect(int) const;
+
+ void paint_horizontal(Painter&);
+ void paint_vertical(Painter&);
+
+ Menu& ensure_context_menu();
+ RefPtr<Menu> m_context_menu;
+
+ AbstractTableView& m_table_view;
+
+ Gfx::Orientation m_orientation { Gfx::Orientation::Horizontal };
+
+ struct SectionData {
+ int size { 0 };
+ bool has_initialized_size { false };
+ bool visibility { true };
+ RefPtr<Action> visibility_action;
+ Gfx::TextAlignment alignment { Gfx::TextAlignment::CenterLeft };
+ };
+ SectionData& section_data(int section) const;
+
+ void set_hovered_section(int);
+
+ mutable Vector<SectionData> m_section_data;
+
+ bool m_in_section_resize { false };
+ Gfx::IntPoint m_section_resize_origin;
+ int m_section_resize_original_width { 0 };
+ int m_resizing_section { -1 };
+ int m_pressed_section { -1 };
+ bool m_pressed_section_is_pressed { false };
+ int m_hovered_section { -1 };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/INILexer.cpp b/Userland/Libraries/LibGUI/INILexer.cpp
new file mode 100644
index 0000000000..6410a58c03
--- /dev/null
+++ b/Userland/Libraries/LibGUI/INILexer.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "INILexer.h"
+#include <AK/Vector.h>
+#include <ctype.h>
+
+namespace GUI {
+
+IniLexer::IniLexer(const StringView& input)
+ : m_input(input)
+{
+}
+
+char IniLexer::peek(size_t offset) const
+{
+ if ((m_index + offset) >= m_input.length())
+ return 0;
+ return m_input[m_index + offset];
+}
+
+char IniLexer::consume()
+{
+ ASSERT(m_index < m_input.length());
+ char ch = m_input[m_index++];
+ m_previous_position = m_position;
+ if (ch == '\n') {
+ m_position.line++;
+ m_position.column = 0;
+ } else {
+ m_position.column++;
+ }
+ return ch;
+}
+
+Vector<IniToken> IniLexer::lex()
+{
+ Vector<IniToken> tokens;
+
+ size_t token_start_index = 0;
+ IniPosition token_start_position;
+
+ auto emit_token = [&](auto type) {
+ IniToken token;
+ token.m_type = type;
+ token.m_start = m_position;
+ token.m_end = m_position;
+ tokens.append(token);
+ consume();
+ };
+
+ auto begin_token = [&] {
+ token_start_index = m_index;
+ token_start_position = m_position;
+ };
+
+ auto commit_token = [&](auto type) {
+ IniToken token;
+ token.m_type = type;
+ token.m_start = token_start_position;
+ token.m_end = m_previous_position;
+ tokens.append(token);
+ };
+
+ while (m_index < m_input.length()) {
+ auto ch = peek();
+
+ if (isspace(ch)) {
+ begin_token();
+ while (isspace(peek()))
+ consume();
+ commit_token(IniToken::Type::Whitespace);
+ continue;
+ }
+
+ // ;Comment
+ if (ch == ';') {
+ begin_token();
+ while (peek() && peek() != '\n')
+ consume();
+ commit_token(IniToken::Type::Comment);
+ continue;
+ }
+
+ // [Section]
+ if (ch == '[') {
+ // [ Token
+ begin_token();
+ consume();
+ commit_token(IniToken::Type::LeftBracket);
+
+ // Section
+ begin_token();
+ while (peek() && !(peek() == ']' || peek() == '\n'))
+ consume();
+ commit_token(IniToken::Type::section);
+
+ // ] Token
+ if (peek() && peek() == ']') {
+ begin_token();
+ consume();
+ commit_token(IniToken::Type::RightBracket);
+ }
+
+ continue;
+ }
+
+ // Empty Line
+ if (ch == '\n') {
+ consume();
+ emit_token(IniToken::Type::Unknown);
+ continue;
+ }
+
+ // Name=Value
+ begin_token();
+ while (peek() && !(peek() == '=' || peek() == '\n'))
+ consume();
+ commit_token(IniToken::Type::Name);
+
+ if (peek() && peek() == '=') {
+ begin_token();
+ consume();
+ commit_token(IniToken::Type::Equal);
+ }
+
+ if (peek()) {
+ begin_token();
+ while (peek() && peek() != '\n')
+ consume();
+ commit_token(IniToken::Type::Value);
+ }
+ }
+ return tokens;
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/INILexer.h b/Userland/Libraries/LibGUI/INILexer.h
new file mode 100644
index 0000000000..c4f4ba46d8
--- /dev/null
+++ b/Userland/Libraries/LibGUI/INILexer.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/StringView.h>
+
+namespace GUI {
+
+#define FOR_EACH_TOKEN_TYPE \
+ __TOKEN(Unknown) \
+ __TOKEN(Comment) \
+ __TOKEN(Whitespace) \
+ __TOKEN(section) \
+ __TOKEN(LeftBracket) \
+ __TOKEN(RightBracket) \
+ __TOKEN(Name) \
+ __TOKEN(Value) \
+ __TOKEN(Equal)
+
+struct IniPosition {
+ size_t line;
+ size_t column;
+};
+
+struct IniToken {
+ enum class Type {
+#define __TOKEN(x) x,
+ FOR_EACH_TOKEN_TYPE
+#undef __TOKEN
+ };
+
+ const char* to_string() const
+ {
+ switch (m_type) {
+#define __TOKEN(x) \
+ case Type::x: \
+ return #x;
+ FOR_EACH_TOKEN_TYPE
+#undef __TOKEN
+ }
+ ASSERT_NOT_REACHED();
+ }
+
+ Type m_type { Type::Unknown };
+ IniPosition m_start;
+ IniPosition m_end;
+};
+
+class IniLexer {
+public:
+ IniLexer(const StringView&);
+
+ Vector<IniToken> lex();
+
+private:
+ char peek(size_t offset = 0) const;
+ char consume();
+
+ StringView m_input;
+ size_t m_index { 0 };
+ IniPosition m_previous_position { 0, 0 };
+ IniPosition m_position { 0, 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/INISyntaxHighlighter.cpp b/Userland/Libraries/LibGUI/INISyntaxHighlighter.cpp
new file mode 100644
index 0000000000..3b798ea1dd
--- /dev/null
+++ b/Userland/Libraries/LibGUI/INISyntaxHighlighter.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/INILexer.h>
+#include <LibGUI/INISyntaxHighlighter.h>
+#include <LibGUI/TextEditor.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+static TextStyle style_for_token_type(Gfx::Palette palette, IniToken::Type type)
+{
+ switch (type) {
+ case IniToken::Type::LeftBracket:
+ case IniToken::Type::RightBracket:
+ case IniToken::Type::section:
+ return { palette.syntax_keyword(), true };
+ case IniToken::Type::Name:
+ return { palette.syntax_identifier() };
+ case IniToken::Type::Value:
+ return { palette.syntax_string() };
+ case IniToken::Type::Comment:
+ return { palette.syntax_comment() };
+ case IniToken::Type::Equal:
+ return { palette.syntax_operator(), true };
+ default:
+ return { palette.base_text() };
+ }
+}
+
+bool IniSyntaxHighlighter::is_identifier(void* token) const
+{
+ auto ini_token = static_cast<GUI::IniToken::Type>(reinterpret_cast<size_t>(token));
+ return ini_token == GUI::IniToken::Type::Name;
+}
+
+void IniSyntaxHighlighter::rehighlight(Gfx::Palette palette)
+{
+ ASSERT(m_editor);
+ auto text = m_editor->text();
+ IniLexer lexer(text);
+ auto tokens = lexer.lex();
+
+ Vector<GUI::TextDocumentSpan> spans;
+ for (auto& token : tokens) {
+ GUI::TextDocumentSpan span;
+ span.range.set_start({ token.m_start.line, token.m_start.column });
+ span.range.set_end({ token.m_end.line, token.m_end.column });
+ auto style = style_for_token_type(palette, token.m_type);
+ span.attributes.color = style.color;
+ span.attributes.bold = style.bold;
+ span.is_skippable = token.m_type == IniToken::Type::Whitespace;
+ span.data = reinterpret_cast<void*>(token.m_type);
+ spans.append(span);
+ }
+ m_editor->document().set_spans(spans);
+
+ m_has_brace_buddies = false;
+ highlight_matching_token_pair();
+
+ m_editor->update();
+}
+
+Vector<IniSyntaxHighlighter::MatchingTokenPair> IniSyntaxHighlighter::matching_token_pairs() const
+{
+ static Vector<SyntaxHighlighter::MatchingTokenPair> pairs;
+ if (pairs.is_empty()) {
+ pairs.append({ reinterpret_cast<void*>(IniToken::Type::LeftBracket), reinterpret_cast<void*>(IniToken::Type::RightBracket) });
+ }
+ return pairs;
+}
+
+bool IniSyntaxHighlighter::token_types_equal(void* token1, void* token2) const
+{
+ return static_cast<GUI::IniToken::Type>(reinterpret_cast<size_t>(token1)) == static_cast<GUI::IniToken::Type>(reinterpret_cast<size_t>(token2));
+}
+
+IniSyntaxHighlighter::~IniSyntaxHighlighter()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/INISyntaxHighlighter.h b/Userland/Libraries/LibGUI/INISyntaxHighlighter.h
new file mode 100644
index 0000000000..14f80d89c9
--- /dev/null
+++ b/Userland/Libraries/LibGUI/INISyntaxHighlighter.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/SyntaxHighlighter.h>
+
+namespace GUI {
+
+class IniSyntaxHighlighter final : public SyntaxHighlighter {
+public:
+ IniSyntaxHighlighter() { }
+ virtual ~IniSyntaxHighlighter() override;
+
+ virtual bool is_identifier(void*) const override;
+
+ virtual SyntaxLanguage language() const override { return SyntaxLanguage::INI; }
+ virtual void rehighlight(Gfx::Palette) override;
+
+protected:
+ virtual Vector<MatchingTokenPair> matching_token_pairs() const override;
+ virtual bool token_types_equal(void*, void*) const override;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Icon.cpp b/Userland/Libraries/LibGUI/Icon.cpp
new file mode 100644
index 0000000000..650b440842
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Icon.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <LibGUI/Icon.h>
+#include <LibGfx/Bitmap.h>
+
+namespace GUI {
+
+Icon::Icon()
+ : m_impl(IconImpl::create())
+{
+}
+
+Icon::Icon(const IconImpl& impl)
+ : m_impl(const_cast<IconImpl&>(impl))
+{
+}
+
+Icon::Icon(const Icon& other)
+ : m_impl(other.m_impl)
+{
+}
+
+Icon::Icon(RefPtr<Gfx::Bitmap>&& bitmap)
+ : Icon()
+{
+ if (bitmap) {
+ ASSERT(bitmap->width() == bitmap->height());
+ int size = bitmap->width();
+ set_bitmap_for_size(size, move(bitmap));
+ }
+}
+
+Icon::Icon(RefPtr<Gfx::Bitmap>&& bitmap1, RefPtr<Gfx::Bitmap>&& bitmap2)
+ : Icon(move(bitmap1))
+{
+ if (bitmap2) {
+ ASSERT(bitmap2->width() == bitmap2->height());
+ int size = bitmap2->width();
+ set_bitmap_for_size(size, move(bitmap2));
+ }
+}
+
+const Gfx::Bitmap* IconImpl::bitmap_for_size(int size) const
+{
+ auto it = m_bitmaps.find(size);
+ if (it != m_bitmaps.end())
+ return it->value.ptr();
+
+ int best_diff_so_far = INT32_MAX;
+ const Gfx::Bitmap* best_fit = nullptr;
+ for (auto& it : m_bitmaps) {
+ int abs_diff = abs(it.key - size);
+ if (abs_diff < best_diff_so_far) {
+ best_diff_so_far = abs_diff;
+ best_fit = it.value.ptr();
+ }
+ }
+ return best_fit;
+}
+
+void IconImpl::set_bitmap_for_size(int size, RefPtr<Gfx::Bitmap>&& bitmap)
+{
+ if (!bitmap) {
+ m_bitmaps.remove(size);
+ return;
+ }
+ m_bitmaps.set(size, move(bitmap));
+}
+
+Icon Icon::default_icon(const StringView& name)
+{
+ auto bitmap16 = Gfx::Bitmap::load_from_file(String::formatted("/res/icons/16x16/{}.png", name));
+ auto bitmap32 = Gfx::Bitmap::load_from_file(String::formatted("/res/icons/32x32/{}.png", name));
+ return Icon(move(bitmap16), move(bitmap32));
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Icon.h b/Userland/Libraries/LibGUI/Icon.h
new file mode 100644
index 0000000000..534729af7f
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Icon.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/NonnullRefPtr.h>
+#include <AK/RefCounted.h>
+#include <LibGfx/Bitmap.h>
+
+namespace GUI {
+
+class IconImpl : public RefCounted<IconImpl> {
+public:
+ static NonnullRefPtr<IconImpl> create() { return adopt(*new IconImpl); }
+ ~IconImpl() { }
+
+ const Gfx::Bitmap* bitmap_for_size(int) const;
+ void set_bitmap_for_size(int, RefPtr<Gfx::Bitmap>&&);
+
+ Vector<int> sizes() const
+ {
+ Vector<int> sizes;
+ for (auto& it : m_bitmaps)
+ sizes.append(it.key);
+ return sizes;
+ }
+
+private:
+ IconImpl() { }
+ HashMap<int, RefPtr<Gfx::Bitmap>> m_bitmaps;
+};
+
+class Icon {
+public:
+ Icon();
+ explicit Icon(RefPtr<Gfx::Bitmap>&&);
+ explicit Icon(RefPtr<Gfx::Bitmap>&&, RefPtr<Gfx::Bitmap>&&);
+ explicit Icon(const IconImpl&);
+ Icon(const Icon&);
+ ~Icon() { }
+
+ static Icon default_icon(const StringView&);
+
+ Icon& operator=(const Icon& other)
+ {
+ if (this != &other)
+ m_impl = other.m_impl;
+ return *this;
+ }
+
+ const Gfx::Bitmap* bitmap_for_size(int size) const { return m_impl->bitmap_for_size(size); }
+ void set_bitmap_for_size(int size, RefPtr<Gfx::Bitmap>&& bitmap) { m_impl->set_bitmap_for_size(size, move(bitmap)); }
+
+ const IconImpl& impl() const { return *m_impl; }
+
+ Vector<int> sizes() const { return m_impl->sizes(); }
+
+private:
+ NonnullRefPtr<IconImpl> m_impl;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/IconView.cpp b/Userland/Libraries/LibGUI/IconView.cpp
new file mode 100644
index 0000000000..cfb0111329
--- /dev/null
+++ b/Userland/Libraries/LibGUI/IconView.cpp
@@ -0,0 +1,821 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <AK/Utf8View.h>
+#include <LibCore/Timer.h>
+#include <LibGUI/DragOperation.h>
+#include <LibGUI/IconView.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGfx/Palette.h>
+
+//#define DRAGDROP_DEBUG
+
+namespace GUI {
+
+IconView::IconView()
+{
+ set_fill_with_background_color(true);
+ set_background_role(ColorRole::Base);
+ set_foreground_role(ColorRole::BaseText);
+ horizontal_scrollbar().set_visible(false);
+}
+
+IconView::~IconView()
+{
+}
+
+void IconView::select_all()
+{
+ for (int item_index = 0; item_index < item_count(); ++item_index) {
+ auto& item_data = m_item_data_cache[item_index];
+ if (!item_data.selected) {
+ if (item_data.is_valid())
+ add_selection(item_data);
+ else
+ add_selection(model()->index(item_index, model_column()));
+ }
+ }
+}
+
+void IconView::scroll_into_view(const ModelIndex& index, bool scroll_horizontally, bool scroll_vertically)
+{
+ if (!index.is_valid())
+ return;
+ ScrollableWidget::scroll_into_view(item_rect(index.row()), scroll_horizontally, scroll_vertically);
+}
+
+void IconView::resize_event(ResizeEvent& event)
+{
+ AbstractView::resize_event(event);
+ update_content_size();
+}
+
+void IconView::reinit_item_cache() const
+{
+ auto prev_item_count = m_item_data_cache.size();
+ size_t new_item_count = item_count();
+ auto items_to_invalidate = min(prev_item_count, new_item_count);
+
+ // if the new number of items is less, check if any of the
+ // ones not in the list anymore was selected
+ for (size_t i = new_item_count; i < m_item_data_cache.size(); i++) {
+ auto& item_data = m_item_data_cache[i];
+ if (item_data.selected) {
+ ASSERT(m_selected_count_cache > 0);
+ m_selected_count_cache--;
+ }
+ }
+ if ((size_t)m_first_selected_hint >= new_item_count)
+ m_first_selected_hint = 0;
+ m_item_data_cache.resize(new_item_count);
+ for (size_t i = 0; i < items_to_invalidate; i++) {
+ auto& item_data = m_item_data_cache[i];
+ // TODO: It's unfortunate that we have no way to know whether any
+ // data actually changed, so we have to invalidate *everyone*
+ if (item_data.is_valid() /* && !model()->is_valid(item_data.index)*/)
+ item_data.invalidate();
+ if (item_data.selected && i < (size_t)m_first_selected_hint)
+ m_first_selected_hint = (int)i;
+ }
+
+ m_item_data_cache_valid = true;
+}
+
+auto IconView::get_item_data(int item_index) const -> ItemData&
+{
+ if (!m_item_data_cache_valid)
+ reinit_item_cache();
+
+ auto& item_data = m_item_data_cache[item_index];
+ if (item_data.is_valid())
+ return item_data;
+
+ item_data.index = model()->index(item_index, model_column());
+ item_data.text = item_data.index.data().to_string();
+ get_item_rects(item_index, item_data, font_for_index(item_data.index));
+ item_data.valid = true;
+ return item_data;
+}
+
+auto IconView::item_data_from_content_position(const Gfx::IntPoint& content_position) const -> ItemData*
+{
+ if (!m_visual_row_count || !m_visual_column_count)
+ return nullptr;
+ int row, column;
+ column_row_from_content_position(content_position, row, column);
+ int item_index = (m_flow_direction == FlowDirection::LeftToRight)
+ ? row * m_visual_column_count + column
+ : column * m_visual_row_count + row;
+ if (item_index < 0 || item_index >= item_count())
+ return nullptr;
+ return &get_item_data(item_index);
+}
+
+void IconView::model_did_update(unsigned flags)
+{
+ AbstractView::model_did_update(flags);
+ if (!model() || (flags & GUI::Model::InvalidateAllIndexes)) {
+ m_item_data_cache.clear();
+ AbstractView::clear_selection();
+ m_selected_count_cache = 0;
+ m_first_selected_hint = 0;
+ }
+ m_item_data_cache_valid = false;
+ update_content_size();
+ update();
+}
+
+void IconView::update_content_size()
+{
+ if (!model())
+ return set_content_size({});
+
+ int content_width;
+ int content_height;
+
+ if (m_flow_direction == FlowDirection::LeftToRight) {
+ m_visual_column_count = max(1, available_size().width() / effective_item_size().width());
+ if (m_visual_column_count)
+ m_visual_row_count = ceil_div(model()->row_count(), m_visual_column_count);
+ else
+ m_visual_row_count = 0;
+ content_width = available_size().width();
+ content_height = m_visual_row_count * effective_item_size().height();
+ } else {
+ m_visual_row_count = max(1, available_size().height() / effective_item_size().height());
+ if (m_visual_row_count)
+ m_visual_column_count = ceil_div(model()->row_count(), m_visual_row_count);
+ else
+ m_visual_column_count = 0;
+ content_width = m_visual_column_count * effective_item_size().width();
+ content_height = available_size().height();
+ }
+
+ set_content_size({ content_width, content_height });
+
+ if (!m_item_data_cache_valid)
+ reinit_item_cache();
+
+ for (int item_index = 0; item_index < item_count(); item_index++) {
+ auto& item_data = m_item_data_cache[item_index];
+ if (item_data.is_valid())
+ update_item_rects(item_index, item_data);
+ }
+}
+
+Gfx::IntRect IconView::item_rect(int item_index) const
+{
+ if (!m_visual_row_count || !m_visual_column_count)
+ return {};
+ int visual_row_index;
+ int visual_column_index;
+
+ if (m_flow_direction == FlowDirection::LeftToRight) {
+ visual_row_index = item_index / m_visual_column_count;
+ visual_column_index = item_index % m_visual_column_count;
+ } else {
+ visual_row_index = item_index % m_visual_row_count;
+ visual_column_index = item_index / m_visual_row_count;
+ }
+
+ return {
+ visual_column_index * effective_item_size().width(),
+ visual_row_index * effective_item_size().height(),
+ effective_item_size().width(),
+ effective_item_size().height()
+ };
+}
+
+ModelIndex IconView::index_at_event_position(const Gfx::IntPoint& position) const
+{
+ ASSERT(model());
+ auto adjusted_position = to_content_position(position);
+ if (auto item_data = item_data_from_content_position(adjusted_position)) {
+ if (item_data->is_containing(adjusted_position))
+ return item_data->index;
+ }
+ return {};
+}
+
+void IconView::mousedown_event(MouseEvent& event)
+{
+ if (!model())
+ return AbstractView::mousedown_event(event);
+
+ if (event.button() != MouseButton::Left)
+ return AbstractView::mousedown_event(event);
+
+ auto index = index_at_event_position(event.position());
+ if (index.is_valid()) {
+ // We might start dragging this item, but not rubber-banding.
+ return AbstractView::mousedown_event(event);
+ }
+
+ if (event.modifiers() & Mod_Ctrl) {
+ m_rubber_banding_store_selection = true;
+ } else {
+ clear_selection();
+ m_rubber_banding_store_selection = false;
+ }
+
+ auto adjusted_position = to_content_position(event.position());
+
+ m_might_drag = false;
+ if (selection_mode() == SelectionMode::MultiSelection) {
+ m_rubber_banding = true;
+ m_rubber_band_origin = adjusted_position;
+ m_rubber_band_current = adjusted_position;
+ }
+}
+
+void IconView::mouseup_event(MouseEvent& event)
+{
+ if (m_rubber_banding && event.button() == MouseButton::Left) {
+ m_rubber_banding = false;
+ if (m_out_of_view_timer)
+ m_out_of_view_timer->stop();
+ update();
+ }
+ AbstractView::mouseup_event(event);
+}
+
+bool IconView::update_rubber_banding(const Gfx::IntPoint& position)
+{
+ auto adjusted_position = to_content_position(position);
+ if (m_rubber_band_current != adjusted_position) {
+ auto prev_rect = Gfx::IntRect::from_two_points(m_rubber_band_origin, m_rubber_band_current);
+ m_rubber_band_current = adjusted_position;
+ auto rubber_band_rect = Gfx::IntRect::from_two_points(m_rubber_band_origin, m_rubber_band_current);
+
+ // If the rectangle width or height is 0, we still want to be able
+ // to match the items in the path. An easy work-around for this
+ // is to simply set the width or height to 1
+ auto ensure_rect = [](Gfx::IntRect& rect) {
+ if (rect.width() <= 0)
+ rect.set_width(1);
+ if (rect.height() <= 0)
+ rect.set_height(1);
+ };
+ ensure_rect(prev_rect);
+ ensure_rect(rubber_band_rect);
+
+ // Clearing the entire selection every time is very expensive,
+ // determine what items may need to be deselected and what new
+ // items may need to be selected. Avoid a ton of allocations.
+
+ auto deselect_area = prev_rect.shatter(rubber_band_rect);
+ auto select_area = rubber_band_rect.shatter(prev_rect);
+
+ // Initialize all candidate's toggle flag. We need to know which
+ // items we touched because the various rectangles likely will
+ // contain the same item more than once
+ for_each_item_intersecting_rects(deselect_area, [](ItemData& item_data) -> IterationDecision {
+ item_data.selection_toggled = false;
+ return IterationDecision::Continue;
+ });
+ for_each_item_intersecting_rects(select_area, [](ItemData& item_data) -> IterationDecision {
+ item_data.selection_toggled = false;
+ return IterationDecision::Continue;
+ });
+
+ // Now toggle all items that are no longer in the selected area, once only
+ for_each_item_intersecting_rects(deselect_area, [&](ItemData& item_data) -> IterationDecision {
+ if (!item_data.selection_toggled && item_data.is_intersecting(prev_rect) && !item_data.is_intersecting(rubber_band_rect)) {
+ item_data.selection_toggled = true;
+ toggle_selection(item_data);
+ }
+ return IterationDecision::Continue;
+ });
+ // Now toggle all items that are in the new selected area, once only
+ for_each_item_intersecting_rects(select_area, [&](ItemData& item_data) -> IterationDecision {
+ if (!item_data.selection_toggled && !item_data.is_intersecting(prev_rect) && item_data.is_intersecting(rubber_band_rect)) {
+ item_data.selection_toggled = true;
+ toggle_selection(item_data);
+ }
+ return IterationDecision::Continue;
+ });
+
+ update();
+ return true;
+ }
+ return false;
+}
+
+#define SCROLL_OUT_OF_VIEW_HOT_MARGIN 20
+
+void IconView::mousemove_event(MouseEvent& event)
+{
+ if (!model())
+ return AbstractView::mousemove_event(event);
+
+ if (m_rubber_banding) {
+ auto in_view_rect = widget_inner_rect();
+ in_view_rect.shrink(SCROLL_OUT_OF_VIEW_HOT_MARGIN, SCROLL_OUT_OF_VIEW_HOT_MARGIN);
+ if (!in_view_rect.contains(event.position())) {
+ if (!m_out_of_view_timer) {
+ m_out_of_view_timer = add<Core::Timer>();
+ m_out_of_view_timer->set_interval(100);
+ m_out_of_view_timer->on_timeout = [this] {
+ scroll_out_of_view_timer_fired();
+ };
+ }
+
+ m_out_of_view_position = event.position();
+ if (!m_out_of_view_timer->is_active())
+ m_out_of_view_timer->start();
+ } else {
+ if (m_out_of_view_timer)
+ m_out_of_view_timer->stop();
+ }
+ if (update_rubber_banding(event.position()))
+ return;
+ }
+
+ AbstractView::mousemove_event(event);
+}
+
+void IconView::scroll_out_of_view_timer_fired()
+{
+ auto scroll_to = to_content_position(m_out_of_view_position);
+ // Adjust the scroll-to position by SCROLL_OUT_OF_VIEW_HOT_MARGIN / 2
+ // depending on which direction we're scrolling. This allows us to
+ // start scrolling before we actually leave the visible area, which
+ // is important when there is no space to further move the mouse. The
+ // speed of scrolling is determined by the distance between the mouse
+ // pointer and the widget's inner rect shrunken by the hot margin
+ auto in_view_rect = widget_inner_rect().shrunken(SCROLL_OUT_OF_VIEW_HOT_MARGIN, SCROLL_OUT_OF_VIEW_HOT_MARGIN);
+ int adjust_x = 0, adjust_y = 0;
+ if (m_out_of_view_position.y() > in_view_rect.bottom())
+ adjust_y = (SCROLL_OUT_OF_VIEW_HOT_MARGIN / 2) + min(SCROLL_OUT_OF_VIEW_HOT_MARGIN, m_out_of_view_position.y() - in_view_rect.bottom());
+ else if (m_out_of_view_position.y() < in_view_rect.top())
+ adjust_y = -(SCROLL_OUT_OF_VIEW_HOT_MARGIN / 2) + max(-SCROLL_OUT_OF_VIEW_HOT_MARGIN, m_out_of_view_position.y() - in_view_rect.top());
+ if (m_out_of_view_position.x() > in_view_rect.right())
+ adjust_x = (SCROLL_OUT_OF_VIEW_HOT_MARGIN / 2) + min(SCROLL_OUT_OF_VIEW_HOT_MARGIN, m_out_of_view_position.x() - in_view_rect.right());
+ else if (m_out_of_view_position.x() < in_view_rect.left())
+ adjust_x = -(SCROLL_OUT_OF_VIEW_HOT_MARGIN / 2) + max(-SCROLL_OUT_OF_VIEW_HOT_MARGIN, m_out_of_view_position.x() - in_view_rect.left());
+
+ ScrollableWidget::scroll_into_view({ scroll_to.translated(adjust_x, adjust_y), { 1, 1 } }, true, true);
+ update_rubber_banding(m_out_of_view_position);
+}
+
+void IconView::update_item_rects(int item_index, ItemData& item_data) const
+{
+ auto item_rect = this->item_rect(item_index);
+ item_data.icon_rect.center_within(item_rect);
+ item_data.icon_rect.move_by(0, item_data.icon_offset_y);
+ item_data.text_rect.center_horizontally_within(item_rect);
+ item_data.text_rect.set_top(item_rect.y() + item_data.text_offset_y);
+}
+
+Gfx::IntRect IconView::content_rect(const ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return {};
+ auto& item_data = get_item_data(index.row());
+ return item_data.text_rect.inflated(4, 4);
+}
+
+void IconView::did_change_hovered_index(const ModelIndex& old_index, const ModelIndex& new_index)
+{
+ AbstractView::did_change_hovered_index(old_index, new_index);
+ if (old_index.is_valid())
+ get_item_rects(old_index.row(), get_item_data(old_index.row()), font_for_index(old_index));
+ if (new_index.is_valid())
+ get_item_rects(new_index.row(), get_item_data(new_index.row()), font_for_index(new_index));
+}
+
+void IconView::did_change_cursor_index(const ModelIndex& old_index, const ModelIndex& new_index)
+{
+ AbstractView::did_change_cursor_index(old_index, new_index);
+ if (old_index.is_valid())
+ get_item_rects(old_index.row(), get_item_data(old_index.row()), font_for_index(old_index));
+ if (new_index.is_valid())
+ get_item_rects(new_index.row(), get_item_data(new_index.row()), font_for_index(new_index));
+}
+
+void IconView::get_item_rects(int item_index, ItemData& item_data, const Gfx::Font& font) const
+{
+ auto item_rect = this->item_rect(item_index);
+ item_data.icon_rect = { 0, 0, 32, 32 };
+ item_data.icon_rect.center_within(item_rect);
+ item_data.icon_offset_y = -font.glyph_height() - 6;
+ item_data.icon_rect.move_by(0, item_data.icon_offset_y);
+
+ int unwrapped_text_width = font.width(item_data.text);
+ int available_width = item_rect.width() - 6;
+
+ item_data.text_rect = { 0, item_data.icon_rect.bottom() + 6 + 1, 0, font.glyph_height() };
+ item_data.wrapped_text_lines.clear();
+
+ if ((unwrapped_text_width > available_width) && (item_data.selected || m_hovered_index == item_data.index || cursor_index() == item_data.index)) {
+ int current_line_width = 0;
+ int current_line_start = 0;
+ int widest_line_width = 0;
+ Utf8View utf8_view(item_data.text);
+ auto it = utf8_view.begin();
+ for (; it != utf8_view.end(); ++it) {
+ auto codepoint = *it;
+ auto glyph_width = font.glyph_width(codepoint);
+ if ((current_line_width + glyph_width + font.glyph_spacing()) > available_width) {
+ item_data.wrapped_text_lines.append(item_data.text.substring_view(current_line_start, utf8_view.byte_offset_of(it) - current_line_start));
+ current_line_start = utf8_view.byte_offset_of(it);
+ current_line_width = glyph_width;
+ } else {
+ current_line_width += glyph_width + font.glyph_spacing();
+ }
+ widest_line_width = max(widest_line_width, current_line_width);
+ }
+ if (current_line_width > 0) {
+ item_data.wrapped_text_lines.append(item_data.text.substring_view(current_line_start, utf8_view.byte_offset_of(it) - current_line_start));
+ }
+ item_data.text_rect.set_width(widest_line_width);
+ item_data.text_rect.center_horizontally_within(item_rect);
+ item_data.text_rect.intersect(item_rect);
+ item_data.text_rect.set_height(font.glyph_height() * item_data.wrapped_text_lines.size());
+ item_data.text_rect.inflate(6, 4);
+ } else {
+ item_data.text_rect.set_width(unwrapped_text_width);
+ item_data.text_rect.inflate(6, 4);
+ item_data.text_rect.center_horizontally_within(item_rect);
+ }
+ item_data.text_rect.intersect(item_rect);
+ item_data.text_offset_y = item_data.text_rect.y() - item_rect.y();
+}
+
+void IconView::second_paint_event(PaintEvent& event)
+{
+ if (!m_rubber_banding)
+ return;
+
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+ painter.add_clip_rect(widget_inner_rect());
+ painter.translate(frame_thickness(), frame_thickness());
+ painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
+
+ auto rubber_band_rect = Gfx::IntRect::from_two_points(m_rubber_band_origin, m_rubber_band_current);
+ painter.fill_rect(rubber_band_rect, palette().rubber_band_fill());
+ painter.draw_rect(rubber_band_rect, palette().rubber_band_border());
+}
+
+void IconView::paint_event(PaintEvent& event)
+{
+ Color widget_background_color = palette().color(background_role());
+ Frame::paint_event(event);
+
+ Painter painter(*this);
+ painter.add_clip_rect(widget_inner_rect());
+ painter.add_clip_rect(event.rect());
+
+ if (fill_with_background_color())
+ painter.fill_rect(event.rect(), widget_background_color);
+ painter.translate(frame_thickness(), frame_thickness());
+ painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
+
+ auto selection_color = is_focused() ? palette().selection() : palette().inactive_selection();
+
+ for_each_item_intersecting_rect(to_content_rect(event.rect()), [&](auto& item_data) -> IterationDecision {
+ Color background_color;
+ if (item_data.selected) {
+ background_color = selection_color;
+ } else {
+ if (fill_with_background_color())
+ background_color = widget_background_color;
+ }
+
+ auto icon = item_data.index.data(ModelRole::Icon);
+
+ if (icon.is_icon()) {
+ if (auto bitmap = icon.as_icon().bitmap_for_size(item_data.icon_rect.width())) {
+ Gfx::IntRect destination = bitmap->rect();
+ destination.center_within(item_data.icon_rect);
+
+ if (item_data.selected) {
+ auto tint = selection_color.with_alpha(100);
+ painter.blit_filtered(destination.location(), *bitmap, bitmap->rect(), [&](auto src) { return src.blend(tint); });
+ } else if (m_hovered_index.is_valid() && m_hovered_index == item_data.index) {
+ painter.blit_brightened(destination.location(), *bitmap, bitmap->rect());
+ } else {
+ painter.blit(destination.location(), *bitmap, bitmap->rect());
+ }
+ }
+ }
+
+ auto font = font_for_index(item_data.index);
+
+ const auto& text_rect = item_data.text_rect;
+
+ painter.fill_rect(text_rect, background_color);
+
+ if (is_focused() && item_data.index == cursor_index()) {
+ painter.draw_rect(text_rect, widget_background_color);
+ painter.draw_focus_rect(text_rect, palette().focus_outline());
+ }
+
+ if (!item_data.wrapped_text_lines.is_empty()) {
+ // Item text would not fit in the item text rect, let's break it up into lines..
+
+ const auto& lines = item_data.wrapped_text_lines;
+ size_t number_of_text_lines = min((size_t)text_rect.height() / font->glyph_height(), lines.size());
+ for (size_t line_index = 0; line_index < number_of_text_lines; ++line_index) {
+ Gfx::IntRect line_rect;
+ line_rect.set_width(text_rect.width());
+ line_rect.set_height(font->glyph_height());
+ line_rect.center_horizontally_within(item_data.text_rect);
+ line_rect.set_y(2 + item_data.text_rect.y() + line_index * font->glyph_height());
+ line_rect.inflate(6, 0);
+
+ // Shrink the line_rect on the last line to apply elision if there are more lines.
+ if (number_of_text_lines - 1 == line_index && lines.size() > number_of_text_lines)
+ line_rect.inflate(-(6 + 2 * font->max_glyph_width()), 0);
+
+ draw_item_text(painter, item_data.index, item_data.selected, line_rect, lines[line_index], font, Gfx::TextAlignment::Center, Gfx::TextElision::Right);
+ }
+ } else {
+ draw_item_text(painter, item_data.index, item_data.selected, item_data.text_rect, item_data.text, font, Gfx::TextAlignment::Center, Gfx::TextElision::Right);
+ }
+
+ if (has_pending_drop() && item_data.index == drop_candidate_index()) {
+ // FIXME: This visualization is not great, as it's also possible to drop things on the text label..
+ painter.draw_rect(item_data.icon_rect.inflated(8, 8), palette().selection(), true);
+ }
+ return IterationDecision::Continue;
+ });
+}
+
+int IconView::item_count() const
+{
+ if (!model())
+ return 0;
+ return model()->row_count();
+}
+
+void IconView::did_update_selection()
+{
+ AbstractView::did_update_selection();
+ if (m_changing_selection)
+ return;
+
+ // Selection was modified externally, we need to synchronize our cache
+ do_clear_selection();
+ selection().for_each_index([&](const ModelIndex& index) {
+ if (index.is_valid()) {
+ auto item_index = model_index_to_item_index(index);
+ if ((size_t)item_index < m_item_data_cache.size())
+ do_add_selection(get_item_data(item_index));
+ }
+ });
+}
+
+void IconView::do_clear_selection()
+{
+ for (size_t item_index = m_first_selected_hint; item_index < m_item_data_cache.size(); item_index++) {
+ if (m_selected_count_cache == 0)
+ break;
+ auto& item_data = m_item_data_cache[item_index];
+ if (!item_data.selected)
+ continue;
+ item_data.selected = false;
+ m_selected_count_cache--;
+ }
+ m_first_selected_hint = 0;
+ ASSERT(m_selected_count_cache == 0);
+}
+
+void IconView::clear_selection()
+{
+ TemporaryChange change(m_changing_selection, true);
+ AbstractView::clear_selection();
+ do_clear_selection();
+}
+
+bool IconView::do_add_selection(ItemData& item_data)
+{
+ if (!item_data.selected) {
+ item_data.selected = true;
+ m_selected_count_cache++;
+ int item_index = &item_data - &m_item_data_cache[0];
+ if (m_first_selected_hint > item_index)
+ m_first_selected_hint = item_index;
+ return true;
+ }
+ return false;
+}
+
+void IconView::add_selection(ItemData& item_data)
+{
+ if (do_add_selection(item_data))
+ AbstractView::add_selection(item_data.index);
+}
+
+void IconView::add_selection(const ModelIndex& new_index)
+{
+ TemporaryChange change(m_changing_selection, true);
+ auto item_index = model_index_to_item_index(new_index);
+ add_selection(get_item_data(item_index));
+}
+
+void IconView::toggle_selection(ItemData& item_data)
+{
+ if (!item_data.selected)
+ add_selection(item_data);
+ else
+ remove_selection(item_data);
+}
+
+void IconView::toggle_selection(const ModelIndex& new_index)
+{
+ TemporaryChange change(m_changing_selection, true);
+ auto item_index = model_index_to_item_index(new_index);
+ toggle_selection(get_item_data(item_index));
+}
+
+void IconView::remove_selection(ItemData& item_data)
+{
+ if (!item_data.selected)
+ return;
+
+ TemporaryChange change(m_changing_selection, true);
+ item_data.selected = false;
+ ASSERT(m_selected_count_cache > 0);
+ m_selected_count_cache--;
+ int item_index = &item_data - &m_item_data_cache[0];
+ if (m_first_selected_hint == item_index) {
+ m_first_selected_hint = 0;
+ while ((size_t)item_index < m_item_data_cache.size()) {
+ if (m_item_data_cache[item_index].selected) {
+ m_first_selected_hint = item_index;
+ break;
+ }
+ item_index++;
+ }
+ }
+ AbstractView::remove_selection(item_data.index);
+}
+
+void IconView::set_selection(const ModelIndex& new_index)
+{
+ TemporaryChange change(m_changing_selection, true);
+ do_clear_selection();
+ auto item_index = model_index_to_item_index(new_index);
+ auto& item_data = get_item_data(item_index);
+ item_data.selected = true;
+ m_selected_count_cache = 1;
+ if (item_index < m_first_selected_hint)
+ m_first_selected_hint = item_index;
+ AbstractView::set_selection(new_index);
+}
+
+int IconView::items_per_page() const
+{
+ if (m_flow_direction == FlowDirection::LeftToRight)
+ return (visible_content_rect().height() / effective_item_size().height()) * m_visual_column_count;
+ return (visible_content_rect().width() / effective_item_size().width()) * m_visual_row_count;
+}
+
+void IconView::move_cursor(CursorMovement movement, SelectionUpdate selection_update)
+{
+ if (!model())
+ return;
+ auto& model = *this->model();
+
+ if (!cursor_index().is_valid()) {
+ set_cursor(model.index(0, model_column()), SelectionUpdate::Set);
+ return;
+ }
+
+ auto new_row = cursor_index().row();
+
+ switch (movement) {
+ case CursorMovement::Right:
+ if (m_flow_direction == FlowDirection::LeftToRight)
+ new_row += 1;
+ else
+ new_row += m_visual_row_count;
+ break;
+ case CursorMovement::Left:
+ if (m_flow_direction == FlowDirection::LeftToRight)
+ new_row -= 1;
+ else
+ new_row -= m_visual_row_count;
+ break;
+ case CursorMovement::Up:
+ if (m_flow_direction == FlowDirection::LeftToRight)
+ new_row -= m_visual_column_count;
+ else
+ new_row -= 1;
+ break;
+ case CursorMovement::Down:
+ if (m_flow_direction == FlowDirection::LeftToRight)
+ new_row += m_visual_column_count;
+ else
+ new_row += 1;
+ break;
+ case CursorMovement::PageUp:
+ new_row = max(0, cursor_index().row() - items_per_page());
+ break;
+
+ case CursorMovement::PageDown:
+ new_row = min(model.row_count() - 1, cursor_index().row() + items_per_page());
+ break;
+
+ case CursorMovement::Home:
+ new_row = 0;
+ break;
+ case CursorMovement::End:
+ new_row = model.row_count() - 1;
+ break;
+ default:
+ return;
+ }
+ auto new_index = model.index(new_row, cursor_index().column());
+ if (new_index.is_valid())
+ set_cursor(new_index, selection_update);
+}
+
+void IconView::set_flow_direction(FlowDirection flow_direction)
+{
+ if (m_flow_direction == flow_direction)
+ return;
+ m_flow_direction = flow_direction;
+ m_item_data_cache.clear();
+ m_item_data_cache_valid = false;
+ update();
+}
+
+template<typename Function>
+inline IterationDecision IconView::for_each_item_intersecting_rect(const Gfx::IntRect& rect, Function f) const
+{
+ ASSERT(model());
+ if (rect.is_empty())
+ return IterationDecision::Continue;
+ int begin_row, begin_column;
+ column_row_from_content_position(rect.top_left(), begin_row, begin_column);
+ int end_row, end_column;
+ column_row_from_content_position(rect.bottom_right(), end_row, end_column);
+
+ int items_per_flow_axis_step;
+ int item_index;
+ int last_index;
+ if (m_flow_direction == FlowDirection::LeftToRight) {
+ items_per_flow_axis_step = end_column - begin_column + 1;
+ item_index = max(0, begin_row * m_visual_column_count + begin_column);
+ last_index = min(item_count(), end_row * m_visual_column_count + end_column + 1);
+ } else {
+ items_per_flow_axis_step = end_row - begin_row + 1;
+ item_index = max(0, begin_column * m_visual_row_count + begin_row);
+ last_index = min(item_count(), end_column * m_visual_row_count + end_row + 1);
+ }
+
+ while (item_index < last_index) {
+ for (int i = item_index; i < min(item_index + items_per_flow_axis_step, last_index); i++) {
+ auto& item_data = get_item_data(i);
+ if (item_data.is_intersecting(rect)) {
+ auto decision = f(item_data);
+ if (decision != IterationDecision::Continue)
+ return decision;
+ }
+ }
+ item_index += m_visual_column_count;
+ };
+
+ return IterationDecision::Continue;
+}
+
+template<typename Function>
+inline IterationDecision IconView::for_each_item_intersecting_rects(const Vector<Gfx::IntRect>& rects, Function f) const
+{
+ for (auto& rect : rects) {
+ auto decision = for_each_item_intersecting_rect(rect, f);
+ if (decision != IterationDecision::Continue)
+ return decision;
+ }
+ return IterationDecision::Continue;
+}
+}
diff --git a/Userland/Libraries/LibGUI/IconView.h b/Userland/Libraries/LibGUI/IconView.h
new file mode 100644
index 0000000000..c36960a1e2
--- /dev/null
+++ b/Userland/Libraries/LibGUI/IconView.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/IterationDecision.h>
+#include <LibGUI/AbstractView.h>
+#include <LibGUI/Forward.h>
+#include <LibGUI/Variant.h>
+
+namespace GUI {
+
+class IconView : public AbstractView {
+ C_OBJECT(IconView)
+public:
+ virtual ~IconView() override;
+
+ enum class FlowDirection {
+ LeftToRight,
+ TopToBottom,
+ };
+
+ FlowDirection flow_direction() const { return m_flow_direction; }
+ void set_flow_direction(FlowDirection);
+
+ int content_width() const;
+ int horizontal_padding() const { return m_horizontal_padding; }
+
+ virtual void scroll_into_view(const ModelIndex&, bool scroll_horizontally = true, bool scroll_vertically = true) override;
+
+ Gfx::IntSize effective_item_size() const { return m_effective_item_size; }
+
+ int model_column() const { return m_model_column; }
+ void set_model_column(int column) { m_model_column = column; }
+
+ virtual ModelIndex index_at_event_position(const Gfx::IntPoint&) const override;
+ virtual Gfx::IntRect content_rect(const ModelIndex&) const override;
+
+ virtual void select_all() override;
+
+private:
+ IconView();
+
+ virtual void model_did_update(unsigned flags) override;
+ virtual void paint_event(PaintEvent&) override;
+ virtual void second_paint_event(PaintEvent&) override;
+ virtual void resize_event(ResizeEvent&) override;
+ virtual void mousedown_event(MouseEvent&) override;
+ virtual void mousemove_event(MouseEvent&) override;
+ virtual void mouseup_event(MouseEvent&) override;
+ virtual void did_change_hovered_index(const ModelIndex& old_index, const ModelIndex& new_index) override;
+ virtual void did_change_cursor_index(const ModelIndex& old_index, const ModelIndex& new_index) override;
+
+ virtual void move_cursor(CursorMovement, SelectionUpdate) override;
+
+ struct ItemData {
+ Gfx::IntRect text_rect;
+ Gfx::IntRect icon_rect;
+ int icon_offset_y;
+ int text_offset_y;
+ String text;
+ Vector<StringView> wrapped_text_lines;
+ ModelIndex index;
+ bool valid { false };
+ bool selected { false }; // always valid
+ bool selection_toggled; // only used as a temporary marker
+
+ bool is_valid() const { return valid; }
+ void invalidate()
+ {
+ valid = false;
+ text = {};
+ }
+
+ bool is_intersecting(const Gfx::IntRect& rect) const
+ {
+ ASSERT(valid);
+ return icon_rect.intersects(rect) || text_rect.intersects(rect);
+ }
+
+ bool is_containing(const Gfx::IntPoint& point) const
+ {
+ ASSERT(valid);
+ return icon_rect.contains(point) || text_rect.contains(point);
+ }
+ };
+
+ template<typename Function>
+ IterationDecision for_each_item_intersecting_rect(const Gfx::IntRect&, Function) const;
+
+ template<typename Function>
+ IterationDecision for_each_item_intersecting_rects(const Vector<Gfx::IntRect>&, Function) const;
+
+ void column_row_from_content_position(const Gfx::IntPoint& content_position, int& row, int& column) const
+ {
+ row = max(0, min(m_visual_row_count - 1, content_position.y() / effective_item_size().height()));
+ column = max(0, min(m_visual_column_count - 1, content_position.x() / effective_item_size().width()));
+ }
+
+ int item_count() const;
+ Gfx::IntRect item_rect(int item_index) const;
+ void update_content_size();
+ void update_item_rects(int item_index, ItemData& item_data) const;
+ void get_item_rects(int item_index, ItemData& item_data, const Gfx::Font&) const;
+ bool update_rubber_banding(const Gfx::IntPoint&);
+ void scroll_out_of_view_timer_fired();
+ int items_per_page() const;
+
+ void reinit_item_cache() const;
+ int model_index_to_item_index(const ModelIndex& model_index) const
+ {
+ ASSERT(model_index.row() < item_count());
+ return model_index.row();
+ }
+
+ virtual void did_update_selection() override;
+ virtual void clear_selection() override;
+ virtual void add_selection(const ModelIndex& new_index) override;
+ virtual void set_selection(const ModelIndex& new_index) override;
+ virtual void toggle_selection(const ModelIndex& new_index) override;
+
+ ItemData& get_item_data(int) const;
+ ItemData* item_data_from_content_position(const Gfx::IntPoint&) const;
+ void do_clear_selection();
+ bool do_add_selection(ItemData&);
+ void add_selection(ItemData&);
+ void remove_selection(ItemData&);
+ void toggle_selection(ItemData&);
+
+ int m_horizontal_padding { 5 };
+ int m_model_column { 0 };
+ int m_visual_column_count { 0 };
+ int m_visual_row_count { 0 };
+
+ Gfx::IntSize m_effective_item_size { 80, 80 };
+
+ bool m_rubber_banding { false };
+ bool m_rubber_banding_store_selection { false };
+ RefPtr<Core::Timer> m_out_of_view_timer;
+ Gfx::IntPoint m_out_of_view_position;
+ Gfx::IntPoint m_rubber_band_origin;
+ Gfx::IntPoint m_rubber_band_current;
+
+ FlowDirection m_flow_direction { FlowDirection::LeftToRight };
+
+ mutable Vector<ItemData> m_item_data_cache;
+ mutable int m_selected_count_cache { 0 };
+ mutable int m_first_selected_hint { 0 };
+ mutable bool m_item_data_cache_valid { false };
+
+ bool m_changing_selection { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ImageWidget.cpp b/Userland/Libraries/LibGUI/ImageWidget.cpp
new file mode 100644
index 0000000000..a77ca3d1b7
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ImageWidget.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/MappedFile.h>
+#include <LibGUI/ImageWidget.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+
+REGISTER_WIDGET(GUI, ImageWidget)
+
+namespace GUI {
+
+ImageWidget::ImageWidget(const StringView&)
+ : m_timer(Core::Timer::construct())
+
+{
+ set_frame_thickness(0);
+ set_frame_shadow(Gfx::FrameShadow::Plain);
+ set_frame_shape(Gfx::FrameShape::NoFrame);
+ set_auto_resize(true);
+
+ REGISTER_BOOL_PROPERTY("auto_resize", auto_resize, set_auto_resize);
+ REGISTER_BOOL_PROPERTY("should_stretch", should_stretch, set_should_stretch);
+}
+
+ImageWidget::~ImageWidget()
+{
+}
+
+void ImageWidget::set_bitmap(const Gfx::Bitmap* bitmap)
+{
+ if (m_bitmap == bitmap)
+ return;
+
+ m_bitmap = bitmap;
+ if (m_bitmap && m_auto_resize)
+ set_fixed_size(m_bitmap->size());
+
+ update();
+}
+
+void ImageWidget::set_auto_resize(bool value)
+{
+ m_auto_resize = value;
+
+ if (m_bitmap)
+ set_fixed_size(m_bitmap->size());
+}
+
+void ImageWidget::animate()
+{
+ m_current_frame_index = (m_current_frame_index + 1) % m_image_decoder->frame_count();
+
+ const auto& current_frame = m_image_decoder->frame(m_current_frame_index);
+ set_bitmap(current_frame.image);
+
+ if (current_frame.duration != m_timer->interval()) {
+ m_timer->restart(current_frame.duration);
+ }
+
+ if (m_current_frame_index == m_image_decoder->frame_count() - 1) {
+ ++m_loops_completed;
+ if (m_loops_completed > 0 && m_loops_completed == m_image_decoder->loop_count()) {
+ m_timer->stop();
+ }
+ }
+}
+
+void ImageWidget::load_from_file(const StringView& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return;
+
+ auto& mapped_file = *file_or_error.value();
+ m_image_decoder = Gfx::ImageDecoder::create((const u8*)mapped_file.data(), mapped_file.size());
+ auto bitmap = m_image_decoder->bitmap();
+ ASSERT(bitmap);
+
+ set_bitmap(bitmap);
+
+ if (path.ends_with(".gif")) {
+ if (m_image_decoder->is_animated() && m_image_decoder->frame_count() > 1) {
+ const auto& first_frame = m_image_decoder->frame(0);
+ m_timer->set_interval(first_frame.duration);
+ m_timer->on_timeout = [this] { animate(); };
+ m_timer->start();
+ }
+ }
+}
+
+void ImageWidget::mousedown_event(GUI::MouseEvent&)
+{
+ if (on_click)
+ on_click();
+}
+
+void ImageWidget::paint_event(PaintEvent& event)
+{
+ Frame::paint_event(event);
+
+ if (!m_bitmap) {
+ return;
+ }
+
+ Painter painter(*this);
+
+ if (m_should_stretch) {
+ painter.draw_scaled_bitmap(frame_inner_rect(), *m_bitmap, m_bitmap->rect());
+ } else {
+ auto location = frame_inner_rect().center().translated(-(m_bitmap->width() / 2), -(m_bitmap->height() / 2));
+ painter.blit(location, *m_bitmap, m_bitmap->rect());
+ }
+}
+}
diff --git a/Userland/Libraries/LibGUI/ImageWidget.h b/Userland/Libraries/LibGUI/ImageWidget.h
new file mode 100644
index 0000000000..6ee492ed50
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ImageWidget.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibCore/Timer.h>
+#include <LibGUI/Frame.h>
+
+namespace GUI {
+
+class ImageWidget : public Frame {
+ C_OBJECT(ImageWidget)
+public:
+ virtual ~ImageWidget() override;
+
+ void set_bitmap(const Gfx::Bitmap*);
+ Gfx::Bitmap* bitmap() { return m_bitmap.ptr(); }
+
+ void set_should_stretch(bool value) { m_should_stretch = value; }
+ bool should_stretch() const { return m_should_stretch; }
+
+ void set_auto_resize(bool value);
+ bool auto_resize() const { return m_auto_resize; }
+
+ void animate();
+ void load_from_file(const StringView&);
+
+ Function<void()> on_click;
+
+protected:
+ explicit ImageWidget(const StringView& text = {});
+
+ virtual void mousedown_event(GUI::MouseEvent&) override;
+ virtual void paint_event(PaintEvent&) override;
+
+private:
+ RefPtr<Gfx::Bitmap> m_bitmap;
+ bool m_should_stretch { false };
+ bool m_auto_resize { false };
+
+ RefPtr<Gfx::ImageDecoder> m_image_decoder;
+ size_t m_current_frame_index { 0 };
+ size_t m_loops_completed { 0 };
+ NonnullRefPtr<Core::Timer> m_timer;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/InputBox.cpp b/Userland/Libraries/LibGUI/InputBox.cpp
new file mode 100644
index 0000000000..8e24da6fa6
--- /dev/null
+++ b/Userland/Libraries/LibGUI/InputBox.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/InputBox.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/TextBox.h>
+#include <LibGfx/Font.h>
+#include <stdio.h>
+
+namespace GUI {
+
+InputBox::InputBox(Window* parent_window, const StringView& prompt, const StringView& title)
+ : Dialog(parent_window)
+ , m_prompt(prompt)
+{
+ set_title(title);
+ build();
+}
+
+InputBox::~InputBox()
+{
+}
+
+int InputBox::show(String& text_value, Window* parent_window, const StringView& prompt, const StringView& title)
+{
+ auto box = InputBox::construct(parent_window, prompt, title);
+ box->set_resizable(false);
+ if (parent_window)
+ box->set_icon(parent_window->icon());
+ auto result = box->exec();
+ text_value = box->text_value();
+ return result;
+}
+
+void InputBox::build()
+{
+ auto& widget = set_main_widget<Widget>();
+
+ int text_width = widget.font().width(m_prompt);
+ int title_width = widget.font().width(title()) + 24 /* icon, plus a little padding -- not perfect */;
+ int max_width = AK::max(text_width, title_width);
+
+ set_rect(x(), y(), max_width + 140, 62);
+
+ widget.set_layout<VerticalBoxLayout>();
+ widget.set_fill_with_background_color(true);
+
+ widget.layout()->set_margins({ 6, 6, 6, 6 });
+ widget.layout()->set_spacing(6);
+
+ auto& label_editor_container = widget.add<Widget>();
+ label_editor_container.set_layout<HorizontalBoxLayout>();
+
+ auto& label = label_editor_container.add<Label>(m_prompt);
+ label.set_fixed_size(text_width, 16);
+
+ m_text_editor = label_editor_container.add<TextBox>();
+ m_text_editor->set_fixed_height(19);
+
+ auto& button_container_outer = widget.add<Widget>();
+ button_container_outer.set_fixed_height(20);
+ button_container_outer.set_layout<VerticalBoxLayout>();
+
+ auto& button_container_inner = button_container_outer.add<Widget>();
+ button_container_inner.set_layout<HorizontalBoxLayout>();
+ button_container_inner.layout()->set_spacing(6);
+ button_container_inner.layout()->set_margins({ 4, 4, 0, 4 });
+ button_container_inner.layout()->add_spacer();
+
+ m_ok_button = button_container_inner.add<Button>();
+ m_ok_button->set_fixed_height(20);
+ m_ok_button->set_text("OK");
+ m_ok_button->on_click = [this](auto) {
+ dbgln("GUI::InputBox: OK button clicked");
+ m_text_value = m_text_editor->text();
+ done(ExecOK);
+ };
+
+ m_cancel_button = button_container_inner.add<Button>();
+ m_cancel_button->set_fixed_height(20);
+ m_cancel_button->set_text("Cancel");
+ m_cancel_button->on_click = [this](auto) {
+ dbgln("GUI::InputBox: Cancel button clicked");
+ done(ExecCancel);
+ };
+
+ m_text_editor->on_return_pressed = [this] {
+ m_ok_button->click();
+ };
+ m_text_editor->on_escape_pressed = [this] {
+ m_cancel_button->click();
+ };
+ m_text_editor->set_focus(true);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/InputBox.h b/Userland/Libraries/LibGUI/InputBox.h
new file mode 100644
index 0000000000..1eb7c490d6
--- /dev/null
+++ b/Userland/Libraries/LibGUI/InputBox.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Dialog.h>
+
+namespace GUI {
+
+class InputBox : public Dialog {
+ C_OBJECT(InputBox)
+public:
+ virtual ~InputBox() override;
+
+ static int show(String& text_value, Window* parent_window, const StringView& prompt, const StringView& title);
+
+private:
+ explicit InputBox(Window* parent_window, const StringView& prompt, const StringView& title);
+
+ String text_value() const { return m_text_value; }
+
+ void build();
+ String m_prompt;
+ String m_text_value;
+
+ RefPtr<Button> m_ok_button;
+ RefPtr<Button> m_cancel_button;
+ RefPtr<TextEditor> m_text_editor;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ItemListModel.h b/Userland/Libraries/LibGUI/ItemListModel.h
new file mode 100644
index 0000000000..2290bd82de
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ItemListModel.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2019-2020, Jesse Buhgaiar <jooster669@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtr.h>
+#include <AK/Vector.h>
+#include <LibGUI/Model.h>
+
+namespace GUI {
+
+template<typename T>
+class ItemListModel : public Model {
+public:
+ static NonnullRefPtr<ItemListModel> create(const Vector<T>& data) { return adopt(*new ItemListModel<T>(data)); }
+
+ virtual ~ItemListModel() override { }
+
+ virtual int row_count(const ModelIndex&) const override
+ {
+ return m_data.size();
+ }
+
+ virtual int column_count(const ModelIndex&) const override
+ {
+ return 1;
+ }
+
+ virtual String column_name(int) const override
+ {
+ return "Data";
+ }
+
+ virtual Variant data(const ModelIndex& index, ModelRole role) const override
+ {
+ if (role == ModelRole::TextAlignment)
+ return Gfx::TextAlignment::CenterLeft;
+ if (role == ModelRole::Display)
+ return m_data.at(index.row());
+
+ return {};
+ }
+
+ virtual void update() override
+ {
+ did_update();
+ }
+
+protected:
+ explicit ItemListModel(const Vector<T>& data)
+ : m_data(data)
+ {
+ }
+
+ const Vector<T>& m_data;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/JSSyntaxHighlighter.cpp b/Userland/Libraries/LibGUI/JSSyntaxHighlighter.cpp
new file mode 100644
index 0000000000..0ceb45535b
--- /dev/null
+++ b/Userland/Libraries/LibGUI/JSSyntaxHighlighter.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/JSSyntaxHighlighter.h>
+#include <LibGUI/TextEditor.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Palette.h>
+#include <LibJS/Lexer.h>
+#include <LibJS/Token.h>
+
+namespace GUI {
+
+static TextStyle style_for_token_type(Gfx::Palette palette, JS::TokenType type)
+{
+ switch (JS::Token::category(type)) {
+ case JS::TokenCategory::Invalid:
+ return { palette.syntax_comment() };
+ case JS::TokenCategory::Number:
+ return { palette.syntax_number() };
+ case JS::TokenCategory::String:
+ return { palette.syntax_string() };
+ case JS::TokenCategory::Punctuation:
+ return { palette.syntax_punctuation() };
+ case JS::TokenCategory::Operator:
+ return { palette.syntax_operator() };
+ case JS::TokenCategory::Keyword:
+ return { palette.syntax_keyword(), true };
+ case JS::TokenCategory::ControlKeyword:
+ return { palette.syntax_control_keyword(), true };
+ case JS::TokenCategory::Identifier:
+ return { palette.syntax_identifier() };
+ default:
+ return { palette.base_text() };
+ }
+}
+
+bool JSSyntaxHighlighter::is_identifier(void* token) const
+{
+ auto js_token = static_cast<JS::TokenType>(reinterpret_cast<size_t>(token));
+ return js_token == JS::TokenType::Identifier;
+}
+
+bool JSSyntaxHighlighter::is_navigatable([[maybe_unused]] void* token) const
+{
+ return false;
+}
+
+void JSSyntaxHighlighter::rehighlight(Gfx::Palette palette)
+{
+ ASSERT(m_editor);
+ auto text = m_editor->text();
+
+ JS::Lexer lexer(text);
+
+ Vector<GUI::TextDocumentSpan> spans;
+ GUI::TextPosition position { 0, 0 };
+ GUI::TextPosition start { 0, 0 };
+
+ auto advance_position = [&position](char ch) {
+ if (ch == '\n') {
+ position.set_line(position.line() + 1);
+ position.set_column(0);
+ } else
+ position.set_column(position.column() + 1);
+ };
+
+ auto append_token = [&](StringView str, const JS::Token& token, bool is_trivia) {
+ if (str.is_empty())
+ return;
+
+ start = position;
+ for (size_t i = 0; i < str.length() - 1; ++i)
+ advance_position(str[i]);
+
+ GUI::TextDocumentSpan span;
+ span.range.set_start(start);
+ span.range.set_end({ position.line(), position.column() });
+ auto type = is_trivia ? JS::TokenType::Invalid : token.type();
+ auto style = style_for_token_type(palette, type);
+ span.attributes.color = style.color;
+ span.attributes.bold = style.bold;
+ span.is_skippable = is_trivia;
+ span.data = reinterpret_cast<void*>(static_cast<size_t>(type));
+ spans.append(span);
+ advance_position(str[str.length() - 1]);
+
+#ifdef DEBUG_SYNTAX_HIGHLIGHTING
+ dbg() << token.name() << (is_trivia ? " (trivia) @ \"" : " @ \"") << token.value() << "\" "
+ << span.range.start().line() << ":" << span.range.start().column() << " - "
+ << span.range.end().line() << ":" << span.range.end().column();
+#endif
+ };
+
+ bool was_eof = false;
+ for (auto token = lexer.next(); !was_eof; token = lexer.next()) {
+ append_token(token.trivia(), token, true);
+ append_token(token.value(), token, false);
+
+ if (token.type() == JS::TokenType::Eof)
+ was_eof = true;
+ }
+
+ m_editor->document().set_spans(spans);
+
+ m_has_brace_buddies = false;
+ highlight_matching_token_pair();
+
+ m_editor->update();
+}
+
+Vector<SyntaxHighlighter::MatchingTokenPair> JSSyntaxHighlighter::matching_token_pairs() const
+{
+ static Vector<SyntaxHighlighter::MatchingTokenPair> pairs;
+ if (pairs.is_empty()) {
+ pairs.append({ reinterpret_cast<void*>(JS::TokenType::CurlyOpen), reinterpret_cast<void*>(JS::TokenType::CurlyClose) });
+ pairs.append({ reinterpret_cast<void*>(JS::TokenType::ParenOpen), reinterpret_cast<void*>(JS::TokenType::ParenClose) });
+ pairs.append({ reinterpret_cast<void*>(JS::TokenType::BracketOpen), reinterpret_cast<void*>(JS::TokenType::BracketClose) });
+ }
+ return pairs;
+}
+
+bool JSSyntaxHighlighter::token_types_equal(void* token1, void* token2) const
+{
+ return static_cast<JS::TokenType>(reinterpret_cast<size_t>(token1)) == static_cast<JS::TokenType>(reinterpret_cast<size_t>(token2));
+}
+
+JSSyntaxHighlighter::~JSSyntaxHighlighter()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/JSSyntaxHighlighter.h b/Userland/Libraries/LibGUI/JSSyntaxHighlighter.h
new file mode 100644
index 0000000000..96b200f697
--- /dev/null
+++ b/Userland/Libraries/LibGUI/JSSyntaxHighlighter.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/SyntaxHighlighter.h>
+
+namespace GUI {
+
+class JSSyntaxHighlighter : public SyntaxHighlighter {
+public:
+ JSSyntaxHighlighter() { }
+ virtual ~JSSyntaxHighlighter() override;
+
+ virtual bool is_identifier(void*) const override;
+ virtual bool is_navigatable(void*) const override;
+
+ virtual SyntaxLanguage language() const override { return SyntaxLanguage::JavaScript; }
+ virtual void rehighlight(Gfx::Palette) override;
+
+protected:
+ virtual Vector<MatchingTokenPair> matching_token_pairs() const override;
+ virtual bool token_types_equal(void*, void*) const override;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/JsonArrayModel.cpp b/Userland/Libraries/LibGUI/JsonArrayModel.cpp
new file mode 100644
index 0000000000..31e6430200
--- /dev/null
+++ b/Userland/Libraries/LibGUI/JsonArrayModel.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/JsonObject.h>
+#include <LibCore/File.h>
+#include <LibGUI/JsonArrayModel.h>
+
+namespace GUI {
+
+void JsonArrayModel::update()
+{
+ auto file = Core::File::construct(m_json_path);
+ if (!file->open(Core::IODevice::ReadOnly)) {
+ dbg() << "Unable to open " << file->filename();
+ m_array.clear();
+ did_update();
+ return;
+ }
+
+ auto json = JsonValue::from_string(file->read_all());
+
+ ASSERT(json.has_value());
+ ASSERT(json.value().is_array());
+ m_array = json.value().as_array();
+
+ did_update();
+}
+
+bool JsonArrayModel::store()
+{
+ auto file = Core::File::construct(m_json_path);
+ if (!file->open(Core::IODevice::WriteOnly)) {
+ dbg() << "Unable to open " << file->filename();
+ return false;
+ }
+
+ file->write(m_array.to_string());
+ file->close();
+ return true;
+}
+
+bool JsonArrayModel::add(const Vector<JsonValue>&& values)
+{
+ ASSERT(values.size() == m_fields.size());
+ JsonObject obj;
+ for (size_t i = 0; i < m_fields.size(); ++i) {
+ auto& field_spec = m_fields[i];
+ obj.set(field_spec.json_field_name, values.at(i));
+ }
+ m_array.append(move(obj));
+ did_update();
+ return true;
+}
+
+bool JsonArrayModel::remove(int row)
+{
+ if (row >= m_array.size())
+ return false;
+
+ JsonArray new_array;
+ for (int i = 0; i < m_array.size(); ++i)
+ if (i != row)
+ new_array.append(m_array.at(i));
+
+ m_array = new_array;
+
+ did_update();
+
+ return true;
+}
+
+Variant JsonArrayModel::data(const ModelIndex& index, ModelRole role) const
+{
+ auto& field_spec = m_fields[index.column()];
+ auto& object = m_array.at(index.row()).as_object();
+
+ if (role == ModelRole::TextAlignment) {
+ return field_spec.text_alignment;
+ }
+
+ if (role == ModelRole::Display) {
+ auto& json_field_name = field_spec.json_field_name;
+ auto data = object.get(json_field_name);
+ if (field_spec.massage_for_display)
+ return field_spec.massage_for_display(object);
+ if (data.is_number())
+ return data;
+ return object.get(json_field_name).to_string();
+ }
+
+ if (role == ModelRole::Sort) {
+ if (field_spec.massage_for_sort)
+ return field_spec.massage_for_sort(object);
+ return data(index, ModelRole::Display);
+ }
+
+ if (role == ModelRole::Custom) {
+ if (field_spec.massage_for_custom)
+ return field_spec.massage_for_custom(object);
+ return {};
+ }
+
+ return {};
+}
+
+void JsonArrayModel::set_json_path(const String& json_path)
+{
+ if (m_json_path == json_path)
+ return;
+
+ m_json_path = json_path;
+ update();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/JsonArrayModel.h b/Userland/Libraries/LibGUI/JsonArrayModel.h
new file mode 100644
index 0000000000..1c44917dcf
--- /dev/null
+++ b/Userland/Libraries/LibGUI/JsonArrayModel.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/JsonArray.h>
+#include <AK/JsonObject.h>
+#include <LibGUI/Model.h>
+
+namespace GUI {
+
+class JsonArrayModel final : public Model {
+public:
+ struct FieldSpec {
+ FieldSpec(const String& a_column_name, Gfx::TextAlignment a_text_alignment, Function<Variant(const JsonObject&)>&& a_massage_for_display, Function<Variant(const JsonObject&)>&& a_massage_for_sort = {}, Function<Variant(const JsonObject&)>&& a_massage_for_custom = {})
+ : column_name(a_column_name)
+ , text_alignment(a_text_alignment)
+ , massage_for_display(move(a_massage_for_display))
+ , massage_for_sort(move(a_massage_for_sort))
+ , massage_for_custom(move(a_massage_for_custom))
+ {
+ }
+
+ FieldSpec(const String& a_json_field_name, const String& a_column_name, Gfx::TextAlignment a_text_alignment)
+ : json_field_name(a_json_field_name)
+ , column_name(a_column_name)
+ , text_alignment(a_text_alignment)
+ {
+ }
+
+ String json_field_name;
+ String column_name;
+ Gfx::TextAlignment text_alignment;
+ Function<Variant(const JsonObject&)> massage_for_display;
+ Function<Variant(const JsonObject&)> massage_for_sort;
+ Function<Variant(const JsonObject&)> massage_for_custom;
+ };
+
+ static NonnullRefPtr<JsonArrayModel> create(const String& json_path, Vector<FieldSpec>&& fields)
+ {
+ return adopt(*new JsonArrayModel(json_path, move(fields)));
+ }
+
+ virtual ~JsonArrayModel() override { }
+
+ virtual int row_count(const ModelIndex& = ModelIndex()) const override { return m_array.size(); }
+ virtual int column_count(const ModelIndex& = ModelIndex()) const override { return m_fields.size(); }
+ virtual String column_name(int column) const override { return m_fields[column].column_name; }
+ virtual Variant data(const ModelIndex&, ModelRole = ModelRole::Display) const override;
+ virtual void update() override;
+
+ const String& json_path() const { return m_json_path; }
+ void set_json_path(const String& json_path);
+
+ bool add(const Vector<JsonValue>&& fields);
+ bool remove(int row);
+ bool store();
+
+private:
+ JsonArrayModel(const String& json_path, Vector<FieldSpec>&& fields)
+ : m_json_path(json_path)
+ , m_fields(move(fields))
+ {
+ }
+
+ String m_json_path;
+ Vector<FieldSpec> m_fields;
+ JsonArray m_array;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Label.cpp b/Userland/Libraries/LibGUI/Label.cpp
new file mode 100644
index 0000000000..de4508f421
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Label.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Label.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Palette.h>
+
+REGISTER_WIDGET(GUI, Label)
+
+namespace GUI {
+
+Label::Label(String text)
+ : m_text(move(text))
+{
+ REGISTER_TEXT_ALIGNMENT_PROPERTY("text_alignment", text_alignment, set_text_alignment);
+
+ set_frame_thickness(0);
+ set_frame_shadow(Gfx::FrameShadow::Plain);
+ set_frame_shape(Gfx::FrameShape::NoFrame);
+
+ set_foreground_role(Gfx::ColorRole::WindowText);
+
+ REGISTER_STRING_PROPERTY("text", text, set_text);
+ REGISTER_BOOL_PROPERTY("autosize", is_autosize, set_autosize);
+}
+
+Label::~Label()
+{
+}
+
+void Label::set_autosize(bool autosize)
+{
+ if (m_autosize == autosize)
+ return;
+ m_autosize = autosize;
+ if (m_autosize)
+ size_to_fit();
+}
+
+void Label::set_icon(const Gfx::Bitmap* icon)
+{
+ if (m_icon == icon)
+ return;
+ m_icon = icon;
+ update();
+}
+
+void Label::set_text(String text)
+{
+ if (text == m_text)
+ return;
+ m_text = move(text);
+ if (m_autosize)
+ size_to_fit();
+ update();
+ did_change_text();
+}
+
+Gfx::IntRect Label::text_rect() const
+{
+ int indent = 0;
+ if (frame_thickness() > 0)
+ indent = font().glyph_width('x') / 2;
+ auto rect = frame_inner_rect();
+ rect.move_by(indent, 0);
+ rect.set_width(rect.width() - indent * 2);
+ return rect;
+}
+
+void Label::paint_event(PaintEvent& event)
+{
+ Frame::paint_event(event);
+
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ if (m_icon) {
+ if (m_should_stretch_icon) {
+ painter.draw_scaled_bitmap(frame_inner_rect(), *m_icon, m_icon->rect());
+ } else {
+ auto icon_location = frame_inner_rect().center().translated(-(m_icon->width() / 2), -(m_icon->height() / 2));
+ painter.blit(icon_location, *m_icon, m_icon->rect());
+ }
+ }
+ if (text().is_empty())
+ return;
+ auto text_rect = this->text_rect();
+
+ if (is_enabled()) {
+ painter.draw_text(text_rect, text(), m_text_alignment, palette().color(foreground_role()), Gfx::TextElision::Right);
+ } else {
+ painter.draw_text(text_rect.translated(1, 1), text(), font(), text_alignment(), Color::White, Gfx::TextElision::Right);
+ painter.draw_text(text_rect, text(), font(), text_alignment(), Color::from_rgb(0x808080), Gfx::TextElision::Right);
+ }
+}
+
+void Label::size_to_fit()
+{
+ set_fixed_width(font().width(m_text));
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Label.h b/Userland/Libraries/LibGUI/Label.h
new file mode 100644
index 0000000000..df17f28545
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Label.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Frame.h>
+#include <LibGfx/TextAlignment.h>
+
+namespace GUI {
+
+class Label : public Frame {
+ C_OBJECT(Label);
+
+public:
+ virtual ~Label() override;
+
+ String text() const { return m_text; }
+ void set_text(String);
+
+ void set_icon(const Gfx::Bitmap*);
+ const Gfx::Bitmap* icon() const { return m_icon.ptr(); }
+ Gfx::Bitmap* icon() { return m_icon.ptr(); }
+
+ Gfx::TextAlignment text_alignment() const { return m_text_alignment; }
+ void set_text_alignment(Gfx::TextAlignment text_alignment) { m_text_alignment = text_alignment; }
+
+ bool should_stretch_icon() const { return m_should_stretch_icon; }
+ void set_should_stretch_icon(bool b) { m_should_stretch_icon = b; }
+
+ bool is_autosize() const { return m_autosize; }
+ void set_autosize(bool);
+
+ Gfx::IntRect text_rect() const;
+
+protected:
+ explicit Label(String text = {});
+
+ virtual void paint_event(PaintEvent&) override;
+ virtual void did_change_text() { }
+
+private:
+ void size_to_fit();
+
+ String m_text;
+ RefPtr<Gfx::Bitmap> m_icon;
+ Gfx::TextAlignment m_text_alignment { Gfx::TextAlignment::Center };
+ bool m_should_stretch_icon { false };
+ bool m_autosize { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Layout.cpp b/Userland/Libraries/LibGUI/Layout.cpp
new file mode 100644
index 0000000000..250b1c5ca4
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Layout.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Badge.h>
+#include <AK/JsonObject.h>
+#include <LibGUI/Layout.h>
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+Layout::Layout()
+{
+ REGISTER_INT_PROPERTY("spacing", spacing, set_spacing);
+
+ register_property(
+ "margins",
+ [this] {
+ JsonObject margins_object;
+ margins_object.set("left", m_margins.left());
+ margins_object.set("right", m_margins.right());
+ margins_object.set("top", m_margins.top());
+ margins_object.set("bottom", m_margins.bottom());
+ return margins_object;
+ },
+ [this](auto value) {
+ if (!value.is_array() || value.as_array().size() != 4)
+ return false;
+ int m[4];
+ for (size_t i = 0; i < 4; ++i)
+ m[i] = value.as_array().at(i).to_i32();
+ set_margins({ m[0], m[1], m[2], m[3] });
+ return true;
+ });
+
+ register_property("entries",
+ [this] {
+ JsonArray entries_array;
+ for (auto& entry : m_entries) {
+ JsonObject entry_object;
+ if (entry.type == Entry::Type::Widget) {
+ entry_object.set("type", "Widget");
+ entry_object.set("widget", (FlatPtr)entry.widget.ptr());
+ } else if (entry.type == Entry::Type::Spacer) {
+ entry_object.set("type", "Spacer");
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+ entries_array.append(move(entry_object));
+ }
+ return entries_array;
+ });
+}
+
+Layout::~Layout()
+{
+}
+
+void Layout::notify_adopted(Badge<Widget>, Widget& widget)
+{
+ if (m_owner == &widget)
+ return;
+ m_owner = widget;
+}
+
+void Layout::notify_disowned(Badge<Widget>, Widget& widget)
+{
+ ASSERT(m_owner == &widget);
+ m_owner.clear();
+}
+
+void Layout::add_entry(Entry&& entry)
+{
+ m_entries.append(move(entry));
+ if (m_owner)
+ m_owner->notify_layout_changed({});
+}
+
+void Layout::add_spacer()
+{
+ Entry entry;
+ entry.type = Entry::Type::Spacer;
+ add_entry(move(entry));
+}
+
+void Layout::add_layout(OwnPtr<Layout>&& layout)
+{
+ Entry entry;
+ entry.type = Entry::Type::Layout;
+ entry.layout = move(layout);
+ add_entry(move(entry));
+}
+
+void Layout::add_widget(Widget& widget)
+{
+ Entry entry;
+ entry.type = Entry::Type::Widget;
+ entry.widget = widget;
+ add_entry(move(entry));
+}
+
+void Layout::insert_widget_before(Widget& widget, Widget& before_widget)
+{
+ Entry entry;
+ entry.type = Entry::Type::Widget;
+ entry.widget = widget;
+ m_entries.insert_before_matching(move(entry), [&](auto& existing_entry) {
+ return existing_entry.type == Entry::Type::Widget && existing_entry.widget.ptr() == &before_widget;
+ });
+ if (m_owner)
+ m_owner->notify_layout_changed({});
+}
+
+void Layout::remove_widget(Widget& widget)
+{
+ m_entries.remove_first_matching([&](auto& entry) {
+ return entry.widget == &widget;
+ });
+ if (m_owner)
+ m_owner->notify_layout_changed({});
+}
+
+void Layout::set_spacing(int spacing)
+{
+ if (m_spacing == spacing)
+ return;
+ m_spacing = spacing;
+ if (m_owner)
+ m_owner->notify_layout_changed({});
+}
+
+void Layout::set_margins(const Margins& margins)
+{
+ if (m_margins == margins)
+ return;
+ m_margins = margins;
+ if (m_owner)
+ m_owner->notify_layout_changed({});
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Layout.h b/Userland/Libraries/LibGUI/Layout.h
new file mode 100644
index 0000000000..2be8e57798
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Layout.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/OwnPtr.h>
+#include <AK/Vector.h>
+#include <AK/WeakPtr.h>
+#include <LibCore/Object.h>
+#include <LibGUI/Forward.h>
+#include <LibGUI/Margins.h>
+#include <LibGfx/Forward.h>
+
+namespace GUI {
+
+class Layout : public Core::Object {
+ C_OBJECT_ABSTRACT(Layout);
+
+public:
+ virtual ~Layout();
+
+ void add_widget(Widget&);
+ void insert_widget_before(Widget& widget, Widget& before_widget);
+ void add_layout(OwnPtr<Layout>&&);
+ void add_spacer();
+
+ void remove_widget(Widget&);
+
+ virtual void run(Widget&) = 0;
+ virtual Gfx::IntSize preferred_size() const = 0;
+
+ void notify_adopted(Badge<Widget>, Widget&);
+ void notify_disowned(Badge<Widget>, Widget&);
+
+ const Margins& margins() const { return m_margins; }
+ void set_margins(const Margins&);
+
+ int spacing() const { return m_spacing; }
+ void set_spacing(int);
+
+protected:
+ Layout();
+
+ struct Entry {
+ enum class Type {
+ Invalid = 0,
+ Widget,
+ Layout,
+ Spacer,
+ };
+
+ Type type { Type::Invalid };
+ WeakPtr<Widget> widget;
+ OwnPtr<Layout> layout;
+ };
+ void add_entry(Entry&&);
+
+ WeakPtr<Widget> m_owner;
+ Vector<Entry> m_entries;
+
+ Margins m_margins;
+ int m_spacing { 3 };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/LazyWidget.cpp b/Userland/Libraries/LibGUI/LazyWidget.cpp
new file mode 100644
index 0000000000..3e1a9c4951
--- /dev/null
+++ b/Userland/Libraries/LibGUI/LazyWidget.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/LazyWidget.h>
+
+namespace GUI {
+
+LazyWidget::LazyWidget()
+{
+}
+
+LazyWidget::~LazyWidget()
+{
+}
+
+void LazyWidget::show_event(ShowEvent&)
+{
+ if (m_has_been_shown)
+ return;
+ m_has_been_shown = true;
+
+ ASSERT(on_first_show);
+ on_first_show(*this);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/LazyWidget.h b/Userland/Libraries/LibGUI/LazyWidget.h
new file mode 100644
index 0000000000..8b7717fb96
--- /dev/null
+++ b/Userland/Libraries/LibGUI/LazyWidget.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class LazyWidget : public Widget {
+ C_OBJECT(LazyWidget)
+public:
+ virtual ~LazyWidget() override;
+
+ Function<void(LazyWidget&)> on_first_show;
+
+protected:
+ LazyWidget();
+
+private:
+ virtual void show_event(ShowEvent&) override;
+
+ bool m_has_been_shown { false };
+};
+}
diff --git a/Userland/Libraries/LibGUI/LinkLabel.cpp b/Userland/Libraries/LibGUI/LinkLabel.cpp
new file mode 100644
index 0000000000..db6dd09188
--- /dev/null
+++ b/Userland/Libraries/LibGUI/LinkLabel.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2020, Alex McGrath <amk@amk.ie>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Event.h>
+#include <LibGUI/LinkLabel.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Palette.h>
+
+REGISTER_WIDGET(GUI, LinkLabel)
+
+namespace GUI {
+
+LinkLabel::LinkLabel(String text)
+ : Label(move(text))
+{
+ set_override_cursor(Gfx::StandardCursor::Hand);
+ set_foreground_role(Gfx::ColorRole::Link);
+ set_focus_policy(FocusPolicy::TabFocus);
+}
+
+void LinkLabel::mousedown_event(MouseEvent& event)
+{
+ Label::mousedown_event(event);
+ if (on_click) {
+ on_click();
+ }
+}
+
+void LinkLabel::keydown_event(KeyEvent& event)
+{
+ Label::keydown_event(event);
+ if (event.key() == KeyCode::Key_Return || event.key() == KeyCode::Key_Space) {
+ if (on_click)
+ on_click();
+ }
+}
+
+void LinkLabel::paint_event(PaintEvent& event)
+{
+ Label::paint_event(event);
+ GUI::Painter painter(*this);
+
+ if (m_hovered)
+ painter.draw_line({ 0, rect().bottom() }, { font().width(text()), rect().bottom() }, palette().link());
+
+ if (is_focused())
+ painter.draw_focus_rect(text_rect(), palette().focus_outline());
+}
+
+void LinkLabel::enter_event(Core::Event& event)
+{
+ Label::enter_event(event);
+ m_hovered = true;
+ update();
+}
+
+void LinkLabel::leave_event(Core::Event& event)
+{
+ Label::leave_event(event);
+ m_hovered = false;
+ update();
+}
+
+void LinkLabel::did_change_text()
+{
+ Label::did_change_text();
+ update_tooltip_if_needed();
+}
+
+void LinkLabel::update_tooltip_if_needed()
+{
+ if (width() < font().width(text())) {
+ set_tooltip(text());
+ } else {
+ set_tooltip({});
+ }
+}
+
+void LinkLabel::resize_event(ResizeEvent& event)
+{
+ Label::resize_event(event);
+ update_tooltip_if_needed();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/LinkLabel.h b/Userland/Libraries/LibGUI/LinkLabel.h
new file mode 100644
index 0000000000..b438bfe670
--- /dev/null
+++ b/Userland/Libraries/LibGUI/LinkLabel.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020, Alex McGrath <amk@amk.ie>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Label.h>
+
+namespace GUI {
+
+class LinkLabel : public Label {
+ C_OBJECT(LinkLabel);
+
+public:
+ Function<void()> on_click;
+
+private:
+ explicit LinkLabel(String text = {});
+
+ virtual void mousedown_event(MouseEvent&) override;
+ virtual void paint_event(PaintEvent&) override;
+ virtual void resize_event(ResizeEvent&) override;
+ virtual void enter_event(Core::Event&) override;
+ virtual void leave_event(Core::Event&) override;
+ virtual void keydown_event(KeyEvent&) override;
+
+ virtual void did_change_text() override;
+
+ void update_tooltip_if_needed();
+
+ bool m_hovered { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ListView.cpp b/Userland/Libraries/LibGUI/ListView.cpp
new file mode 100644
index 0000000000..4b304f6751
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ListView.cpp
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/ListView.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGfx/Palette.h>
+
+REGISTER_WIDGET(GUI, ListView)
+
+namespace GUI {
+
+ListView::ListView()
+{
+ set_fill_with_background_color(true);
+ set_background_role(ColorRole::Base);
+ set_foreground_role(ColorRole::BaseText);
+}
+
+ListView::~ListView()
+{
+}
+
+void ListView::select_all()
+{
+ selection().clear();
+ for (int item_index = 0; item_index < item_count(); ++item_index) {
+ auto index = model()->index(item_index, m_model_column);
+ selection().add(index);
+ }
+}
+
+void ListView::update_content_size()
+{
+ if (!model())
+ return set_content_size({});
+
+ int content_width = 0;
+ for (int row = 0, row_count = model()->row_count(); row < row_count; ++row) {
+ auto text = model()->index(row, m_model_column).data();
+ content_width = max(content_width, font().width(text.to_string()));
+ }
+
+ content_width = max(content_width, widget_inner_rect().width());
+
+ int content_height = item_count() * item_height();
+ set_content_size({ content_width, content_height });
+}
+
+void ListView::resize_event(ResizeEvent& event)
+{
+ update_content_size();
+ AbstractView::resize_event(event);
+}
+
+void ListView::model_did_update(unsigned flags)
+{
+ AbstractView::model_did_update(flags);
+ update_content_size();
+ update();
+}
+
+Gfx::IntRect ListView::content_rect(int row) const
+{
+ return { 0, row * item_height(), content_width(), item_height() };
+}
+
+Gfx::IntRect ListView::content_rect(const ModelIndex& index) const
+{
+ return content_rect(index.row());
+}
+
+ModelIndex ListView::index_at_event_position(const Gfx::IntPoint& point) const
+{
+ ASSERT(model());
+
+ auto adjusted_position = this->adjusted_position(point);
+ for (int row = 0, row_count = model()->row_count(); row < row_count; ++row) {
+ if (!content_rect(row).contains(adjusted_position))
+ continue;
+ return model()->index(row, m_model_column);
+ }
+ return {};
+}
+
+Gfx::IntPoint ListView::adjusted_position(const Gfx::IntPoint& position) const
+{
+ return position.translated(horizontal_scrollbar().value() - frame_thickness(), vertical_scrollbar().value() - frame_thickness());
+}
+
+void ListView::paint_list_item(Painter& painter, int row_index, int painted_item_index)
+{
+ bool is_selected_row = selection().contains_row(row_index);
+
+ int y = painted_item_index * item_height();
+
+ Color background_color;
+ if (is_selected_row) {
+ background_color = is_focused() ? palette().selection() : palette().inactive_selection();
+ } else {
+ Color row_fill_color = palette().color(background_role());
+ if (alternating_row_colors() && (painted_item_index % 2)) {
+ background_color = row_fill_color.darkened(0.8f);
+ } else {
+ background_color = row_fill_color;
+ }
+ }
+
+ Gfx::IntRect row_rect(0, y, content_width(), item_height());
+ painter.fill_rect(row_rect, background_color);
+ auto index = model()->index(row_index, m_model_column);
+ auto data = index.data();
+ auto font = font_for_index(index);
+ if (data.is_bitmap()) {
+ painter.blit(row_rect.location(), data.as_bitmap(), data.as_bitmap().rect());
+ } else if (data.is_icon()) {
+ if (auto bitmap = data.as_icon().bitmap_for_size(16))
+ painter.blit(row_rect.location(), *bitmap, bitmap->rect());
+ } else {
+ Color text_color;
+ if (is_selected_row)
+ text_color = is_focused() ? palette().selection_text() : palette().inactive_selection_text();
+ else
+ text_color = index.data(ModelRole::ForegroundColor).to_color(palette().color(foreground_role()));
+ auto text_rect = row_rect;
+ text_rect.move_by(horizontal_padding(), 0);
+ text_rect.set_width(text_rect.width() - horizontal_padding() * 2);
+ auto text_alignment = index.data(ModelRole::TextAlignment).to_text_alignment(Gfx::TextAlignment::CenterLeft);
+ painter.draw_text(text_rect, data.to_string(), font, text_alignment, text_color);
+ }
+}
+
+void ListView::paint_event(PaintEvent& event)
+{
+ Frame::paint_event(event);
+
+ if (!model())
+ return;
+
+ Painter painter(*this);
+ painter.add_clip_rect(frame_inner_rect());
+ painter.add_clip_rect(event.rect());
+ painter.translate(frame_thickness(), frame_thickness());
+ painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
+
+ int exposed_width = max(content_size().width(), width());
+ int painted_item_index = 0;
+
+ for (int row_index = 0; row_index < model()->row_count(); ++row_index) {
+ paint_list_item(painter, row_index, painted_item_index);
+ ++painted_item_index;
+ };
+
+ Gfx::IntRect unpainted_rect(0, painted_item_index * item_height(), exposed_width, height());
+ if (fill_with_background_color())
+ painter.fill_rect(unpainted_rect, palette().color(background_role()));
+}
+
+int ListView::item_count() const
+{
+ if (!model())
+ return 0;
+ return model()->row_count();
+}
+
+void ListView::mousemove_event(MouseEvent& event)
+{
+ auto previous_hovered_index = m_hovered_index;
+ AbstractView::mousemove_event(event);
+ if (hover_highlighting() && previous_hovered_index != m_hovered_index)
+ set_cursor(m_hovered_index, SelectionUpdate::Set);
+}
+
+void ListView::keydown_event(KeyEvent& event)
+{
+ if (!model())
+ return AbstractView::keydown_event(event);
+
+ if (event.key() == KeyCode::Key_Escape) {
+ if (on_escape_pressed)
+ on_escape_pressed();
+ return;
+ }
+ AbstractView::keydown_event(event);
+}
+
+void ListView::move_cursor_relative(int steps, SelectionUpdate selection_update)
+{
+ if (!model())
+ return;
+ auto& model = *this->model();
+ ModelIndex new_index;
+ if (cursor_index().is_valid()) {
+ auto row = cursor_index().row();
+ if (steps > 0) {
+ if (row + steps >= model.row_count())
+ row = model.row_count() - 1;
+ else
+ row += steps;
+ } else if (steps < 0) {
+ if (row < -steps)
+ row = 0;
+ else
+ row += steps;
+ }
+ new_index = model.index(row, cursor_index().column());
+ } else {
+ new_index = model.index(0, 0);
+ }
+ set_cursor(new_index, selection_update);
+}
+
+void ListView::move_cursor(CursorMovement movement, SelectionUpdate selection_update)
+{
+ if (!model())
+ return;
+ auto& model = *this->model();
+
+ if (!cursor_index().is_valid()) {
+ set_cursor(model.index(0, 0), SelectionUpdate::Set);
+ return;
+ }
+
+ ModelIndex new_index;
+
+ switch (movement) {
+ case CursorMovement::Up:
+ new_index = model.index(cursor_index().row() - 1, cursor_index().column());
+ break;
+ case CursorMovement::Down:
+ new_index = model.index(cursor_index().row() + 1, cursor_index().column());
+ break;
+ case CursorMovement::Home:
+ new_index = model.index(0, 0);
+ break;
+ case CursorMovement::End:
+ new_index = model.index(model.row_count() - 1, 0);
+ break;
+ case CursorMovement::PageUp: {
+ int items_per_page = visible_content_rect().height() / item_height();
+ new_index = model.index(max(0, cursor_index().row() - items_per_page), cursor_index().column());
+ break;
+ }
+ case CursorMovement::PageDown: {
+ int items_per_page = visible_content_rect().height() / item_height();
+ new_index = model.index(min(model.row_count() - 1, cursor_index().row() + items_per_page), cursor_index().column());
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (new_index.is_valid())
+ set_cursor(new_index, selection_update);
+}
+
+void ListView::scroll_into_view(const ModelIndex& index, bool scroll_horizontally, bool scroll_vertically)
+{
+ if (!model())
+ return;
+ ScrollableWidget::scroll_into_view(content_rect(index.row()), scroll_horizontally, scroll_vertically);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ListView.h b/Userland/Libraries/LibGUI/ListView.h
new file mode 100644
index 0000000000..0609b00944
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ListView.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/AbstractView.h>
+
+namespace GUI {
+
+class ListView : public AbstractView {
+ C_OBJECT(ListView)
+public:
+ virtual ~ListView() override;
+
+ int item_height() const { return 16; }
+
+ bool alternating_row_colors() const { return m_alternating_row_colors; }
+ void set_alternating_row_colors(bool b) { m_alternating_row_colors = b; }
+
+ bool hover_highlighting() const { return m_hover_highlighting; }
+ void set_hover_highlighting(bool b) { m_hover_highlighting = b; }
+
+ int horizontal_padding() const { return m_horizontal_padding; }
+
+ virtual void scroll_into_view(const ModelIndex& index, bool scroll_horizontally, bool scroll_vertically) override;
+
+ Gfx::IntPoint adjusted_position(const Gfx::IntPoint&) const;
+
+ virtual ModelIndex index_at_event_position(const Gfx::IntPoint&) const override;
+ virtual Gfx::IntRect content_rect(const ModelIndex&) const override;
+
+ int model_column() const { return m_model_column; }
+ void set_model_column(int column) { m_model_column = column; }
+
+ virtual void select_all() override;
+
+ Function<void()> on_escape_pressed;
+
+ virtual void move_cursor(CursorMovement, SelectionUpdate) override;
+ void move_cursor_relative(int steps, SelectionUpdate);
+
+protected:
+ ListView();
+ virtual void paint_list_item(Painter&, int row_index, int painted_item_index);
+
+private:
+ virtual void model_did_update(unsigned flags) override;
+ virtual void paint_event(PaintEvent&) override;
+ virtual void keydown_event(KeyEvent&) override;
+ virtual void resize_event(ResizeEvent&) override;
+ virtual void mousemove_event(MouseEvent&) override;
+
+ Gfx::IntRect content_rect(int row) const;
+ int item_count() const;
+ void update_content_size();
+
+ int m_horizontal_padding { 2 };
+ int m_model_column { 0 };
+ bool m_alternating_row_colors { true };
+ bool m_hover_highlighting { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Margins.h b/Userland/Libraries/LibGUI/Margins.h
new file mode 100644
index 0000000000..330f519e11
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Margins.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace GUI {
+
+class Margins {
+public:
+ Margins() { }
+ Margins(int left, int top, int right, int bottom)
+ : m_left(left)
+ , m_top(top)
+ , m_right(right)
+ , m_bottom(bottom)
+ {
+ }
+ ~Margins() { }
+
+ bool is_null() const { return !m_left && !m_top && !m_right && !m_bottom; }
+
+ int left() const { return m_left; }
+ int top() const { return m_top; }
+ int right() const { return m_right; }
+ int bottom() const { return m_bottom; }
+
+ void set_left(int value) { m_left = value; }
+ void set_top(int value) { m_top = value; }
+ void set_right(int value) { m_right = value; }
+ void set_bottom(int value) { m_bottom = value; }
+
+ bool operator==(const Margins& other) const
+ {
+ return m_left == other.m_left
+ && m_top == other.m_top
+ && m_right == other.m_right
+ && m_bottom == other.m_bottom;
+ }
+
+private:
+ int m_left { 0 };
+ int m_top { 0 };
+ int m_right { 0 };
+ int m_bottom { 0 };
+};
+}
diff --git a/Userland/Libraries/LibGUI/Menu.cpp b/Userland/Libraries/LibGUI/Menu.cpp
new file mode 100644
index 0000000000..47e284312a
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Menu.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/HashMap.h>
+#include <AK/SharedBuffer.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/ActionGroup.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/MenuItem.h>
+#include <LibGUI/WindowServerConnection.h>
+#include <LibGfx/Bitmap.h>
+
+//#define MENU_DEBUG
+
+namespace GUI {
+
+static HashMap<int, Menu*>& all_menus()
+{
+ static HashMap<int, Menu*>* map;
+ if (!map)
+ map = new HashMap<int, Menu*>();
+ return *map;
+}
+
+Menu* Menu::from_menu_id(int menu_id)
+{
+ auto it = all_menus().find(menu_id);
+ if (it == all_menus().end())
+ return nullptr;
+ return (*it).value;
+}
+
+Menu::Menu(const StringView& name)
+ : m_name(name)
+{
+}
+
+Menu::~Menu()
+{
+ unrealize_menu();
+}
+
+void Menu::set_icon(const Gfx::Bitmap* icon)
+{
+ m_icon = icon;
+}
+
+void Menu::add_action(NonnullRefPtr<Action> action)
+{
+ m_items.append(make<MenuItem>(m_menu_id, move(action)));
+#ifdef GMENU_DEBUG
+ dbgln("GUI::Menu::add_action(): MenuItem Menu ID: {}", m_menu_id);
+#endif
+}
+
+Menu& Menu::add_submenu(const String& name)
+{
+ auto submenu = Menu::construct(name);
+ m_items.append(make<MenuItem>(m_menu_id, submenu));
+ return submenu;
+}
+
+void Menu::add_separator()
+{
+ m_items.append(make<MenuItem>(m_menu_id, MenuItem::Type::Separator));
+}
+
+void Menu::realize_if_needed(const RefPtr<Action>& default_action)
+{
+ if (m_menu_id == -1 || m_last_default_action.ptr() != default_action)
+ realize_menu(default_action);
+}
+
+void Menu::popup(const Gfx::IntPoint& screen_position, const RefPtr<Action>& default_action)
+{
+ realize_if_needed(default_action);
+ WindowServerConnection::the().post_message(Messages::WindowServer::PopupMenu(m_menu_id, screen_position));
+}
+
+void Menu::dismiss()
+{
+ if (m_menu_id == -1)
+ return;
+ WindowServerConnection::the().post_message(Messages::WindowServer::DismissMenu(m_menu_id));
+}
+
+template<typename IconContainerType>
+static int ensure_realized_icon(IconContainerType& container)
+{
+ int icon_buffer_id = -1;
+ if (container.icon()) {
+ ASSERT(container.icon()->format() == Gfx::BitmapFormat::RGBA32);
+ ASSERT(container.icon()->size() == Gfx::IntSize(16, 16));
+ if (container.icon()->shbuf_id() == -1) {
+ auto shared_buffer = SharedBuffer::create_with_size(container.icon()->size_in_bytes());
+ ASSERT(shared_buffer);
+ auto shared_icon = Gfx::Bitmap::create_with_shared_buffer(Gfx::BitmapFormat::RGBA32, *shared_buffer, container.icon()->size());
+ memcpy(shared_buffer->template data<u8>(), container.icon()->scanline_u8(0), container.icon()->size_in_bytes());
+ shared_buffer->seal();
+ shared_buffer->share_with(WindowServerConnection::the().server_pid());
+ container.set_icon(shared_icon);
+ }
+ icon_buffer_id = container.icon()->shbuf_id();
+ }
+ return icon_buffer_id;
+}
+
+int Menu::realize_menu(RefPtr<Action> default_action)
+{
+ unrealize_menu();
+ m_menu_id = WindowServerConnection::the().send_sync<Messages::WindowServer::CreateMenu>(m_name)->menu_id();
+
+#ifdef MENU_DEBUG
+ dbgln("GUI::Menu::realize_menu(): New menu ID: {}", m_menu_id);
+#endif
+ ASSERT(m_menu_id > 0);
+ for (size_t i = 0; i < m_items.size(); ++i) {
+ auto& item = m_items[i];
+ item.set_menu_id({}, m_menu_id);
+ item.set_identifier({}, i);
+ if (item.type() == MenuItem::Type::Separator) {
+ WindowServerConnection::the().send_sync<Messages::WindowServer::AddMenuSeparator>(m_menu_id);
+ continue;
+ }
+ if (item.type() == MenuItem::Type::Submenu) {
+ auto& submenu = *item.submenu();
+ submenu.realize_if_needed(default_action);
+ int icon_buffer_id = ensure_realized_icon(submenu);
+ WindowServerConnection::the().send_sync<Messages::WindowServer::AddMenuItem>(m_menu_id, i, submenu.menu_id(), submenu.name(), true, false, false, false, "", icon_buffer_id, false);
+ continue;
+ }
+ if (item.type() == MenuItem::Type::Action) {
+ auto& action = *item.action();
+ int icon_buffer_id = ensure_realized_icon(action);
+ auto shortcut_text = action.shortcut().is_valid() ? action.shortcut().to_string() : String();
+ bool exclusive = action.group() && action.group()->is_exclusive() && action.is_checkable();
+ bool is_default = (default_action.ptr() == &action);
+ WindowServerConnection::the().send_sync<Messages::WindowServer::AddMenuItem>(m_menu_id, i, -1, action.text(), action.is_enabled(), action.is_checkable(), action.is_checkable() ? action.is_checked() : false, is_default, shortcut_text, icon_buffer_id, exclusive);
+ }
+ }
+ all_menus().set(m_menu_id, this);
+ m_last_default_action = default_action;
+ return m_menu_id;
+}
+
+void Menu::unrealize_menu()
+{
+ if (m_menu_id == -1)
+ return;
+ all_menus().remove(m_menu_id);
+ WindowServerConnection::the().send_sync<Messages::WindowServer::DestroyMenu>(m_menu_id);
+ m_menu_id = -1;
+}
+
+void Menu::realize_menu_if_needed()
+{
+ if (menu_id() == -1)
+ realize_menu();
+}
+
+Action* Menu::action_at(size_t index)
+{
+ if (index >= m_items.size())
+ return nullptr;
+ return m_items[index].action();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Menu.h b/Userland/Libraries/LibGUI/Menu.h
new file mode 100644
index 0000000000..1031112830
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Menu.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullOwnPtrVector.h>
+#include <AK/WeakPtr.h>
+#include <LibCore/Object.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/Forward.h>
+#include <LibGfx/Forward.h>
+
+namespace GUI {
+
+class Menu final : public Core::Object {
+ C_OBJECT(Menu)
+public:
+ explicit Menu(const StringView& name = "");
+ virtual ~Menu() override;
+
+ void realize_menu_if_needed();
+
+ static Menu* from_menu_id(int);
+ int menu_id() const { return m_menu_id; }
+
+ const String& name() const { return m_name; }
+ const Gfx::Bitmap* icon() const { return m_icon.ptr(); }
+ void set_icon(const Gfx::Bitmap*);
+
+ Action* action_at(size_t);
+
+ void add_action(NonnullRefPtr<Action>);
+ void add_separator();
+ Menu& add_submenu(const String& name);
+
+ void popup(const Gfx::IntPoint& screen_position, const RefPtr<Action>& default_action = nullptr);
+ void dismiss();
+
+private:
+ friend class MenuBar;
+
+ int realize_menu(RefPtr<Action> default_action = nullptr);
+ void unrealize_menu();
+ void realize_if_needed(const RefPtr<Action>& default_action);
+
+ int m_menu_id { -1 };
+ String m_name;
+ RefPtr<Gfx::Bitmap> m_icon;
+ NonnullOwnPtrVector<MenuItem> m_items;
+ WeakPtr<Action> m_last_default_action;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/MenuBar.cpp b/Userland/Libraries/LibGUI/MenuBar.cpp
new file mode 100644
index 0000000000..ea8c9649b5
--- /dev/null
+++ b/Userland/Libraries/LibGUI/MenuBar.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Badge.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/MenuBar.h>
+#include <LibGUI/MenuItem.h>
+#include <LibGUI/WindowServerConnection.h>
+
+namespace GUI {
+
+MenuBar::MenuBar()
+{
+}
+
+MenuBar::~MenuBar()
+{
+ unrealize_menubar();
+}
+
+Menu& MenuBar::add_menu(String name)
+{
+ auto& menu = add<Menu>(move(name));
+ m_menus.append(menu);
+ return menu;
+}
+
+int MenuBar::realize_menubar()
+{
+ return WindowServerConnection::the().send_sync<Messages::WindowServer::CreateMenubar>()->menubar_id();
+}
+
+void MenuBar::unrealize_menubar()
+{
+ if (m_menubar_id == -1)
+ return;
+ WindowServerConnection::the().send_sync<Messages::WindowServer::DestroyMenubar>(m_menubar_id);
+ m_menubar_id = -1;
+}
+
+void MenuBar::notify_added_to_application(Badge<Application>)
+{
+ ASSERT(m_menubar_id == -1);
+ m_menubar_id = realize_menubar();
+ ASSERT(m_menubar_id != -1);
+ for (auto& menu : m_menus) {
+ int menu_id = menu.realize_menu();
+ ASSERT(menu_id != -1);
+ WindowServerConnection::the().send_sync<Messages::WindowServer::AddMenuToMenubar>(m_menubar_id, menu_id);
+ }
+ WindowServerConnection::the().send_sync<Messages::WindowServer::SetApplicationMenubar>(m_menubar_id);
+}
+
+void MenuBar::notify_removed_from_application(Badge<Application>)
+{
+ unrealize_menubar();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/MenuBar.h b/Userland/Libraries/LibGUI/MenuBar.h
new file mode 100644
index 0000000000..20c1b5678b
--- /dev/null
+++ b/Userland/Libraries/LibGUI/MenuBar.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/NonnullRefPtrVector.h>
+#include <LibCore/Object.h>
+#include <LibGUI/Forward.h>
+
+namespace GUI {
+
+class MenuBar : public Core::Object {
+ C_OBJECT(MenuBar);
+
+public:
+ ~MenuBar();
+
+ Menu& add_menu(String name);
+
+ void notify_added_to_application(Badge<Application>);
+ void notify_removed_from_application(Badge<Application>);
+
+private:
+ MenuBar();
+
+ int realize_menubar();
+ void unrealize_menubar();
+
+ int m_menubar_id { -1 };
+ NonnullRefPtrVector<Menu> m_menus;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/MenuItem.cpp b/Userland/Libraries/LibGUI/MenuItem.cpp
new file mode 100644
index 0000000000..8c6e5a92ec
--- /dev/null
+++ b/Userland/Libraries/LibGUI/MenuItem.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Action.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/MenuItem.h>
+#include <LibGUI/WindowServerConnection.h>
+
+namespace GUI {
+
+MenuItem::MenuItem(unsigned menu_id, Type type)
+ : m_type(type)
+ , m_menu_id(menu_id)
+{
+}
+
+MenuItem::MenuItem(unsigned menu_id, NonnullRefPtr<Action> action)
+ : m_type(Type::Action)
+ , m_menu_id(menu_id)
+ , m_action(move(action))
+{
+ m_action->register_menu_item({}, *this);
+ m_enabled = m_action->is_enabled();
+ m_checkable = m_action->is_checkable();
+ if (m_checkable)
+ m_checked = m_action->is_checked();
+}
+
+MenuItem::MenuItem(unsigned menu_id, NonnullRefPtr<Menu> submenu)
+ : m_type(Type::Submenu)
+ , m_menu_id(menu_id)
+ , m_submenu(move(submenu))
+{
+}
+
+MenuItem::~MenuItem()
+{
+ if (m_action)
+ m_action->unregister_menu_item({}, *this);
+}
+
+void MenuItem::set_enabled(bool enabled)
+{
+ if (m_enabled == enabled)
+ return;
+ m_enabled = enabled;
+ update_window_server();
+}
+
+void MenuItem::set_checked(bool checked)
+{
+ ASSERT(is_checkable());
+ if (m_checked == checked)
+ return;
+ m_checked = checked;
+ update_window_server();
+}
+
+void MenuItem::set_default(bool is_default)
+{
+ ASSERT(is_checkable());
+ if (m_default == is_default)
+ return;
+ m_default = is_default;
+ update_window_server();
+}
+
+void MenuItem::update_window_server()
+{
+ if (m_menu_id < 0)
+ return;
+ auto& action = *m_action;
+ auto shortcut_text = action.shortcut().is_valid() ? action.shortcut().to_string() : String();
+ WindowServerConnection::the().send_sync<Messages::WindowServer::UpdateMenuItem>(m_menu_id, m_identifier, -1, action.text(), action.is_enabled(), action.is_checkable(), action.is_checkable() ? action.is_checked() : false, m_default, shortcut_text);
+}
+
+void MenuItem::set_menu_id(Badge<Menu>, unsigned int menu_id)
+{
+ m_menu_id = menu_id;
+}
+
+void MenuItem::set_identifier(Badge<Menu>, unsigned identifier)
+{
+ m_identifier = identifier;
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/MenuItem.h b/Userland/Libraries/LibGUI/MenuItem.h
new file mode 100644
index 0000000000..1441061661
--- /dev/null
+++ b/Userland/Libraries/LibGUI/MenuItem.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/RefPtr.h>
+#include <LibGUI/Forward.h>
+
+namespace GUI {
+
+class MenuItem {
+public:
+ enum class Type {
+ Invalid,
+ Action,
+ Separator,
+ Submenu,
+ };
+
+ MenuItem(unsigned menu_id, Type);
+ MenuItem(unsigned menu_id, NonnullRefPtr<Action>);
+ MenuItem(unsigned menu_id, NonnullRefPtr<Menu>);
+ ~MenuItem();
+
+ Type type() const { return m_type; }
+
+ const Action* action() const { return m_action.ptr(); }
+ Action* action() { return m_action.ptr(); }
+ unsigned identifier() const { return m_identifier; }
+
+ Menu* submenu() { return m_submenu.ptr(); }
+ const Menu* submenu() const { return m_submenu.ptr(); }
+
+ bool is_checkable() const { return m_checkable; }
+ void set_checkable(bool checkable) { m_checkable = checkable; }
+
+ bool is_checked() const { return m_checked; }
+ void set_checked(bool);
+
+ bool is_enabled() const { return m_enabled; }
+ void set_enabled(bool);
+
+ bool is_default() const { return m_default; }
+ void set_default(bool);
+
+ void set_menu_id(Badge<Menu>, unsigned menu_id);
+ void set_identifier(Badge<Menu>, unsigned identifier);
+
+private:
+ void update_window_server();
+
+ Type m_type { Type::Invalid };
+ int m_menu_id { -1 };
+ unsigned m_identifier { 0 };
+ bool m_enabled { true };
+ bool m_checkable { false };
+ bool m_checked { false };
+ bool m_default { false };
+ RefPtr<Action> m_action;
+ RefPtr<Menu> m_submenu;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/MessageBox.cpp b/Userland/Libraries/LibGUI/MessageBox.cpp
new file mode 100644
index 0000000000..c0948495e9
--- /dev/null
+++ b/Userland/Libraries/LibGUI/MessageBox.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/ImageWidget.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/MessageBox.h>
+#include <LibGfx/Font.h>
+#include <stdio.h>
+
+namespace GUI {
+
+int MessageBox::show(Window* parent_window, const StringView& text, const StringView& title, Type type, InputType input_type)
+{
+ auto box = MessageBox::construct(parent_window, text, title, type, input_type);
+ if (parent_window)
+ box->set_icon(parent_window->icon());
+ return box->exec();
+}
+
+int MessageBox::show_error(Window* parent_window, const StringView& text)
+{
+ return show(parent_window, text, "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK);
+}
+
+MessageBox::MessageBox(Window* parent_window, const StringView& text, const StringView& title, Type type, InputType input_type)
+ : Dialog(parent_window)
+ , m_text(text)
+ , m_type(type)
+ , m_input_type(input_type)
+{
+ set_title(title);
+ build();
+}
+
+MessageBox::~MessageBox()
+{
+}
+
+RefPtr<Gfx::Bitmap> MessageBox::icon() const
+{
+ switch (m_type) {
+ case Type::Information:
+ return Gfx::Bitmap::load_from_file("/res/icons/32x32/msgbox-information.png");
+ case Type::Warning:
+ return Gfx::Bitmap::load_from_file("/res/icons/32x32/msgbox-warning.png");
+ case Type::Error:
+ return Gfx::Bitmap::load_from_file("/res/icons/32x32/msgbox-error.png");
+ case Type::Question:
+ return Gfx::Bitmap::load_from_file("/res/icons/32x32/msgbox-question.png");
+ default:
+ return nullptr;
+ }
+}
+
+bool MessageBox::should_include_ok_button() const
+{
+ return m_input_type == InputType::OK || m_input_type == InputType::OKCancel;
+}
+
+bool MessageBox::should_include_cancel_button() const
+{
+ return m_input_type == InputType::OKCancel || m_input_type == InputType::YesNoCancel;
+}
+
+bool MessageBox::should_include_yes_button() const
+{
+ return m_input_type == InputType::YesNo || m_input_type == InputType::YesNoCancel;
+}
+
+bool MessageBox::should_include_no_button() const
+{
+ return should_include_yes_button();
+}
+
+void MessageBox::build()
+{
+ auto& widget = set_main_widget<Widget>();
+
+ int text_width = widget.font().width(m_text);
+ int icon_width = 0;
+
+ widget.set_layout<VerticalBoxLayout>();
+ widget.set_fill_with_background_color(true);
+
+ widget.layout()->set_margins({ 8, 8, 8, 8 });
+ widget.layout()->set_spacing(6);
+
+ auto& message_container = widget.add<Widget>();
+ message_container.set_layout<HorizontalBoxLayout>();
+ message_container.layout()->set_margins({ 8, 0, 0, 0 });
+ message_container.layout()->set_spacing(8);
+
+ if (m_type != Type::None) {
+ auto& icon_image = message_container.add<ImageWidget>();
+ icon_image.set_bitmap(icon());
+ if (icon())
+ icon_width = icon()->width();
+ }
+
+ auto& label = message_container.add<Label>(m_text);
+ label.set_fixed_height(16);
+ if (m_type != Type::None)
+ label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
+
+ auto& button_container = widget.add<Widget>();
+ button_container.set_layout<HorizontalBoxLayout>();
+ button_container.set_fixed_height(24);
+ button_container.layout()->set_spacing(8);
+
+ constexpr int button_width = 80;
+ int button_count = 0;
+
+ auto add_button = [&](String label, Dialog::ExecResult result) {
+ auto& button = button_container.add<Button>();
+ button.set_fixed_width(button_width);
+ button.set_text(label);
+ button.on_click = [this, label, result](auto) {
+ done(result);
+ };
+ ++button_count;
+ };
+
+ button_container.layout()->add_spacer();
+ if (should_include_ok_button())
+ add_button("OK", Dialog::ExecOK);
+ if (should_include_yes_button())
+ add_button("Yes", Dialog::ExecYes);
+ if (should_include_no_button())
+ add_button("No", Dialog::ExecNo);
+ if (should_include_cancel_button())
+ add_button("Cancel", Dialog::ExecCancel);
+ button_container.layout()->add_spacer();
+
+ int width = (button_count * button_width) + ((button_count - 1) * button_container.layout()->spacing()) + 32;
+ width = max(width, text_width + icon_width + 56);
+
+ set_rect(x(), y(), width, 96);
+ set_resizable(false);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/MessageBox.h b/Userland/Libraries/LibGUI/MessageBox.h
new file mode 100644
index 0000000000..dcd4c64d3c
--- /dev/null
+++ b/Userland/Libraries/LibGUI/MessageBox.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Dialog.h>
+
+namespace GUI {
+
+class MessageBox : public Dialog {
+ C_OBJECT(MessageBox)
+public:
+ enum class Type {
+ None,
+ Information,
+ Warning,
+ Error,
+ Question
+ };
+
+ enum class InputType {
+ OK,
+ OKCancel,
+ YesNo,
+ YesNoCancel,
+ };
+
+ virtual ~MessageBox() override;
+
+ static int show(Window* parent_window, const StringView& text, const StringView& title, Type type = Type::None, InputType input_type = InputType::OK);
+ static int show_error(Window* parent_window, const StringView& text);
+
+private:
+ explicit MessageBox(Window* parent_window, const StringView& text, const StringView& title, Type type = Type::None, InputType input_type = InputType::OK);
+
+ bool should_include_ok_button() const;
+ bool should_include_cancel_button() const;
+ bool should_include_yes_button() const;
+ bool should_include_no_button() const;
+ void build();
+ RefPtr<Gfx::Bitmap> icon() const;
+
+ String m_text;
+ Type m_type { Type::None };
+ InputType m_input_type { InputType::OK };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Model.cpp b/Userland/Libraries/LibGUI/Model.cpp
new file mode 100644
index 0000000000..59685bf743
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Model.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/AbstractView.h>
+#include <LibGUI/Model.h>
+
+namespace GUI {
+
+Model::Model()
+{
+}
+
+Model::~Model()
+{
+}
+
+void Model::register_view(Badge<AbstractView>, AbstractView& view)
+{
+ m_views.set(&view);
+ m_clients.set(&view);
+}
+
+void Model::unregister_view(Badge<AbstractView>, AbstractView& view)
+{
+ m_views.remove(&view);
+ m_clients.remove(&view);
+}
+
+void Model::for_each_view(Function<void(AbstractView&)> callback)
+{
+ for (auto* view : m_views)
+ callback(*view);
+}
+
+void Model::did_update(unsigned flags)
+{
+ for (auto* client : m_clients)
+ client->model_did_update(flags);
+}
+
+ModelIndex Model::create_index(int row, int column, const void* data) const
+{
+ return ModelIndex(*this, row, column, const_cast<void*>(data));
+}
+
+ModelIndex Model::index(int row, int column, const ModelIndex&) const
+{
+ return create_index(row, column);
+}
+
+bool Model::accepts_drag(const ModelIndex&, const Vector<String>&) const
+{
+ return false;
+}
+
+void Model::register_client(ModelClient& client)
+{
+ m_clients.set(&client);
+}
+
+void Model::unregister_client(ModelClient& client)
+{
+ m_clients.remove(&client);
+}
+
+RefPtr<Core::MimeData> Model::mime_data(const ModelSelection& selection) const
+{
+ auto mime_data = Core::MimeData::construct();
+ RefPtr<Gfx::Bitmap> bitmap;
+
+ StringBuilder text_builder;
+ StringBuilder data_builder;
+ bool first = true;
+ selection.for_each_index([&](auto& index) {
+ auto text_data = index.data();
+ if (!first)
+ text_builder.append(", ");
+ text_builder.append(text_data.to_string());
+
+ if (!first)
+ data_builder.append('\n');
+ auto data = index.data(ModelRole::MimeData);
+ data_builder.append(data.to_string());
+
+ first = false;
+
+ if (!bitmap) {
+ Variant icon_data = index.data(ModelRole::Icon);
+ if (icon_data.is_icon())
+ bitmap = icon_data.as_icon().bitmap_for_size(32);
+ }
+ });
+
+ mime_data->set_data(drag_data_type(), data_builder.to_byte_buffer());
+ mime_data->set_text(text_builder.to_string());
+ if (bitmap)
+ mime_data->set_data("image/x-raw-bitmap", bitmap->serialize_to_byte_buffer());
+
+ return mime_data;
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Model.h b/Userland/Libraries/LibGUI/Model.h
new file mode 100644
index 0000000000..c3dad9572f
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Model.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Badge.h>
+#include <AK/Function.h>
+#include <AK/HashTable.h>
+#include <AK/RefCounted.h>
+#include <AK/String.h>
+#include <LibCore/MimeData.h>
+#include <LibGUI/ModelIndex.h>
+#include <LibGUI/ModelRole.h>
+#include <LibGUI/ModelSelection.h>
+#include <LibGUI/Variant.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/TextAlignment.h>
+
+namespace GUI {
+
+enum class SortOrder {
+ None,
+ Ascending,
+ Descending
+};
+
+class ModelClient {
+public:
+ virtual ~ModelClient() { }
+
+ virtual void model_did_update(unsigned flags) = 0;
+};
+
+class Model : public RefCounted<Model> {
+public:
+ enum UpdateFlag {
+ DontInvalidateIndexes = 0,
+ InvalidateAllIndexes = 1 << 0,
+ };
+
+ enum MatchesFlag {
+ AllMatching = 0,
+ FirstMatchOnly = 1 << 0,
+ CaseInsensitive = 1 << 1,
+ MatchAtStart = 1 << 2,
+ };
+
+ virtual ~Model();
+
+ virtual int row_count(const ModelIndex& = ModelIndex()) const = 0;
+ virtual int column_count(const ModelIndex& = ModelIndex()) const = 0;
+ virtual String column_name(int) const { return {}; }
+ virtual Variant data(const ModelIndex&, ModelRole = ModelRole::Display) const = 0;
+ virtual TriState data_matches(const ModelIndex&, Variant) const { return TriState::Unknown; }
+ virtual void update() = 0;
+ virtual ModelIndex parent_index(const ModelIndex&) const { return {}; }
+ virtual ModelIndex index(int row, int column = 0, const ModelIndex& parent = ModelIndex()) const;
+ virtual bool is_editable(const ModelIndex&) const { return false; }
+ virtual bool is_searchable() const { return false; }
+ virtual void set_data(const ModelIndex&, const Variant&) { }
+ virtual int tree_column() const { return 0; }
+ virtual bool accepts_drag(const ModelIndex&, const Vector<String>& mime_types) const;
+ virtual Vector<ModelIndex, 1> matches(const StringView&, unsigned = MatchesFlag::AllMatching, const ModelIndex& = ModelIndex()) { return {}; }
+
+ virtual bool is_column_sortable([[maybe_unused]] int column_index) const { return true; }
+ virtual void sort([[maybe_unused]] int column, SortOrder) { }
+
+ bool is_valid(const ModelIndex& index) const
+ {
+ auto parent_index = this->parent_index(index);
+ return index.row() >= 0 && index.row() < row_count(parent_index) && index.column() >= 0 && index.column() < column_count(parent_index);
+ }
+
+ virtual StringView drag_data_type() const { return {}; }
+ virtual RefPtr<Core::MimeData> mime_data(const ModelSelection&) const;
+
+ void register_view(Badge<AbstractView>, AbstractView&);
+ void unregister_view(Badge<AbstractView>, AbstractView&);
+
+ void register_client(ModelClient&);
+ void unregister_client(ModelClient&);
+
+protected:
+ Model();
+
+ void for_each_view(Function<void(AbstractView&)>);
+ void did_update(unsigned flags = UpdateFlag::InvalidateAllIndexes);
+
+ static bool string_matches(const StringView& str, const StringView& needle, unsigned flags)
+ {
+ auto case_sensitivity = (flags & CaseInsensitive) ? CaseSensitivity::CaseInsensitive : CaseSensitivity::CaseSensitive;
+ if (flags & MatchAtStart)
+ return str.starts_with(needle, case_sensitivity);
+ return str.contains(needle, case_sensitivity);
+ }
+
+ ModelIndex create_index(int row, int column, const void* data = nullptr) const;
+
+private:
+ HashTable<AbstractView*> m_views;
+ HashTable<ModelClient*> m_clients;
+};
+
+inline ModelIndex ModelIndex::parent() const
+{
+ return m_model ? m_model->parent_index(*this) : ModelIndex();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ModelEditingDelegate.h b/Userland/Libraries/LibGUI/ModelEditingDelegate.h
new file mode 100644
index 0000000000..2f3c0b2d8f
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ModelEditingDelegate.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Model.h>
+#include <LibGUI/TextBox.h>
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class ModelEditingDelegate {
+public:
+ virtual ~ModelEditingDelegate() { }
+
+ void bind(Model& model, const ModelIndex& index)
+ {
+ if (m_model.ptr() == &model && m_index == index)
+ return;
+ m_model = model;
+ m_index = index;
+ m_widget = create_widget();
+ }
+
+ Widget* widget() { return m_widget; }
+ const Widget* widget() const { return m_widget; }
+
+ Function<void()> on_commit;
+ Function<void()> on_rollback;
+
+ virtual Variant value() const = 0;
+ virtual void set_value(const Variant&) = 0;
+
+ virtual void will_begin_editing() { }
+
+protected:
+ ModelEditingDelegate() { }
+
+ virtual RefPtr<Widget> create_widget() = 0;
+ void commit()
+ {
+ if (on_commit)
+ on_commit();
+ }
+ void rollback()
+ {
+ if (on_rollback)
+ on_rollback();
+ }
+
+ const ModelIndex& index() const { return m_index; }
+
+private:
+ RefPtr<Model> m_model;
+ ModelIndex m_index;
+ RefPtr<Widget> m_widget;
+};
+
+class StringModelEditingDelegate : public ModelEditingDelegate {
+public:
+ StringModelEditingDelegate() { }
+ virtual ~StringModelEditingDelegate() override { }
+
+ virtual RefPtr<Widget> create_widget() override
+ {
+ auto textbox = TextBox::construct();
+ textbox->on_return_pressed = [this] {
+ commit();
+ };
+ textbox->on_escape_pressed = [this] {
+ rollback();
+ };
+ return textbox;
+ }
+ virtual Variant value() const override { return static_cast<const TextBox*>(widget())->text(); }
+ virtual void set_value(const Variant& value) override
+ {
+ auto& textbox = static_cast<TextBox&>(*widget());
+ textbox.set_text(value.to_string());
+ textbox.select_all();
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ModelIndex.cpp b/Userland/Libraries/LibGUI/ModelIndex.cpp
new file mode 100644
index 0000000000..ea6a97c28f
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ModelIndex.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/Variant.h>
+
+namespace GUI {
+
+Variant ModelIndex::data(ModelRole role) const
+{
+ if (!is_valid())
+ return {};
+
+ ASSERT(model());
+ return model()->data(*this, role);
+}
+
+const LogStream& operator<<(const LogStream& stream, const ModelIndex& value)
+{
+ if (value.internal_data())
+ return stream << String::formatted("ModelIndex({},{},{:p})", value.row(), value.column(), value.internal_data());
+ return stream << String::formatted("ModelIndex({},{})", value.row(), value.column());
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ModelIndex.h b/Userland/Libraries/LibGUI/ModelIndex.h
new file mode 100644
index 0000000000..5f8e088798
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ModelIndex.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Traits.h>
+#include <LibGUI/Forward.h>
+#include <LibGUI/ModelRole.h>
+
+namespace GUI {
+
+class ModelIndex {
+ friend class Model;
+
+public:
+ ModelIndex() { }
+
+ bool is_valid() const { return m_model && m_row != -1 && m_column != -1; }
+ int row() const { return m_row; }
+ int column() const { return m_column; }
+
+ void* internal_data() const { return m_internal_data; }
+
+ ModelIndex parent() const;
+
+ bool operator==(const ModelIndex& other) const
+ {
+ return m_model == other.m_model && m_row == other.m_row && m_column == other.m_column && m_internal_data == other.m_internal_data;
+ }
+
+ bool operator!=(const ModelIndex& other) const
+ {
+ return !(*this == other);
+ }
+
+ const Model* model() const { return m_model; }
+
+ Variant data(ModelRole = ModelRole::Display) const;
+
+private:
+ ModelIndex(const Model& model, int row, int column, void* internal_data)
+ : m_model(&model)
+ , m_row(row)
+ , m_column(column)
+ , m_internal_data(internal_data)
+ {
+ }
+
+ const Model* m_model { nullptr };
+ int m_row { -1 };
+ int m_column { -1 };
+ void* m_internal_data { nullptr };
+};
+
+const LogStream& operator<<(const LogStream&, const ModelIndex&);
+
+}
+
+namespace AK {
+
+template<>
+struct Formatter<GUI::ModelIndex> : Formatter<FormatString> {
+ void format(FormatBuilder& builder, const GUI::ModelIndex& value)
+ {
+ if (value.internal_data())
+ return Formatter<FormatString>::format(builder, "ModelIndex({},{},{})", value.row(), value.column(), value.internal_data());
+ else
+ return Formatter<FormatString>::format(builder, "ModelIndex({},{})", value.row(), value.column());
+ }
+};
+
+template<>
+struct Traits<GUI::ModelIndex> : public GenericTraits<GUI::ModelIndex> {
+ static unsigned hash(const GUI::ModelIndex& index) { return pair_int_hash(index.row(), index.column()); }
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ModelRole.h b/Userland/Libraries/LibGUI/ModelRole.h
new file mode 100644
index 0000000000..8978808481
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ModelRole.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace GUI {
+
+enum class ModelRole {
+ Display,
+ Sort,
+ ForegroundColor,
+ BackgroundColor,
+ Icon,
+ Font,
+ MimeData,
+ TextAlignment,
+ Search,
+ Custom = 0x100, // Applications are free to use roles above this number as they please
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ModelSelection.cpp b/Userland/Libraries/LibGUI/ModelSelection.cpp
new file mode 100644
index 0000000000..2a22336855
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ModelSelection.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Badge.h>
+#include <LibGUI/AbstractView.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/ModelSelection.h>
+
+namespace GUI {
+
+void ModelSelection::remove_matching(Function<bool(const ModelIndex&)> filter)
+{
+ Vector<ModelIndex> to_remove;
+ for (auto& index : m_indexes) {
+ if (filter(index))
+ to_remove.append(index);
+ }
+ if (!to_remove.is_empty()) {
+ for (auto& index : to_remove)
+ m_indexes.remove(index);
+ notify_selection_changed();
+ }
+}
+
+void ModelSelection::set(const ModelIndex& index)
+{
+ ASSERT(index.is_valid());
+ if (m_indexes.size() == 1 && m_indexes.contains(index))
+ return;
+ m_indexes.clear();
+ m_indexes.set(index);
+ notify_selection_changed();
+}
+
+void ModelSelection::add(const ModelIndex& index)
+{
+ ASSERT(index.is_valid());
+ if (m_indexes.contains(index))
+ return;
+ m_indexes.set(index);
+ notify_selection_changed();
+}
+
+void ModelSelection::add_all(const Vector<ModelIndex>& indices)
+{
+ {
+ TemporaryChange notify_change { m_disable_notify, true };
+ for (auto& index : indices)
+ add(index);
+ }
+
+ if (m_notify_pending)
+ notify_selection_changed();
+}
+
+void ModelSelection::toggle(const ModelIndex& index)
+{
+ ASSERT(index.is_valid());
+ if (m_indexes.contains(index))
+ m_indexes.remove(index);
+ else
+ m_indexes.set(index);
+ notify_selection_changed();
+}
+
+bool ModelSelection::remove(const ModelIndex& index)
+{
+ ASSERT(index.is_valid());
+ if (!m_indexes.contains(index))
+ return false;
+ m_indexes.remove(index);
+ notify_selection_changed();
+ return true;
+}
+
+void ModelSelection::clear()
+{
+ if (m_indexes.is_empty())
+ return;
+ m_indexes.clear();
+ notify_selection_changed();
+}
+
+void ModelSelection::notify_selection_changed()
+{
+ if (!m_disable_notify) {
+ m_view.notify_selection_changed({});
+ m_notify_pending = false;
+ } else {
+ m_notify_pending = true;
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ModelSelection.h b/Userland/Libraries/LibGUI/ModelSelection.h
new file mode 100644
index 0000000000..78e0585250
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ModelSelection.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Badge.h>
+#include <AK/HashTable.h>
+#include <AK/Noncopyable.h>
+#include <AK/TemporaryChange.h>
+#include <AK/Vector.h>
+#include <LibGUI/ModelIndex.h>
+
+namespace GUI {
+
+class ModelSelection {
+ AK_MAKE_NONCOPYABLE(ModelSelection);
+ AK_MAKE_NONMOVABLE(ModelSelection);
+
+public:
+ ModelSelection(AbstractView& view)
+ : m_view(view)
+ {
+ }
+
+ int size() const { return m_indexes.size(); }
+ bool is_empty() const { return m_indexes.is_empty(); }
+ bool contains(const ModelIndex& index) const { return m_indexes.contains(index); }
+ bool contains_row(int row) const
+ {
+ for (auto& index : m_indexes) {
+ if (index.row() == row)
+ return true;
+ }
+ return false;
+ }
+
+ void set(const ModelIndex&);
+ void add(const ModelIndex&);
+ void add_all(const Vector<ModelIndex>&);
+ void toggle(const ModelIndex&);
+ bool remove(const ModelIndex&);
+ void clear();
+
+ template<typename Callback>
+ void for_each_index(Callback callback)
+ {
+ for (auto& index : indexes())
+ callback(index);
+ }
+
+ template<typename Callback>
+ void for_each_index(Callback callback) const
+ {
+ for (auto& index : indexes())
+ callback(index);
+ }
+
+ Vector<ModelIndex> indexes() const
+ {
+ Vector<ModelIndex> selected_indexes;
+
+ for (auto& index : m_indexes)
+ selected_indexes.append(index);
+
+ return selected_indexes;
+ }
+
+ // FIXME: This doesn't guarantee that what you get is the lowest or "first" index selected..
+ ModelIndex first() const
+ {
+ if (m_indexes.is_empty())
+ return {};
+ return *m_indexes.begin();
+ }
+
+ void remove_matching(Function<bool(const ModelIndex&)>);
+
+ template<typename Function>
+ void change_from_model(Badge<SortingProxyModel>, Function f)
+ {
+ {
+ TemporaryChange change(m_disable_notify, true);
+ m_notify_pending = false;
+ f(*this);
+ }
+ if (m_notify_pending)
+ notify_selection_changed();
+ }
+
+private:
+ void notify_selection_changed();
+
+ AbstractView& m_view;
+ HashTable<ModelIndex> m_indexes;
+ bool m_disable_notify { false };
+ bool m_notify_pending { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/MultiView.cpp b/Userland/Libraries/LibGUI/MultiView.cpp
new file mode 100644
index 0000000000..2df586390f
--- /dev/null
+++ b/Userland/Libraries/LibGUI/MultiView.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/ActionGroup.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/MultiView.h>
+#include <LibGUI/Window.h>
+#include <stdio.h>
+#include <unistd.h>
+
+namespace GUI {
+
+MultiView::MultiView()
+{
+ set_active_widget(nullptr);
+ set_content_margins({ 2, 2, 2, 2 });
+ m_icon_view = add<IconView>();
+ m_table_view = add<TableView>();
+ m_columns_view = add<ColumnsView>();
+
+ for_each_view_implementation([&](auto& view) {
+ view.set_should_hide_unnecessary_scrollbars(true);
+ view.on_activation = [this](auto& index) {
+ if (on_activation)
+ on_activation(index);
+ };
+ view.on_selection_change = [this] {
+ if (on_selection_change)
+ on_selection_change();
+ };
+ view.on_context_menu_request = [this](auto& index, auto& event) {
+ if (on_context_menu_request)
+ on_context_menu_request(index, event);
+ };
+ view.on_drop = [this](auto& index, auto& event) {
+ if (on_drop)
+ on_drop(index, event);
+ };
+ });
+
+ build_actions();
+ set_view_mode(ViewMode::Icon);
+}
+
+MultiView::~MultiView()
+{
+}
+
+void MultiView::set_view_mode(ViewMode mode)
+{
+ if (m_view_mode == mode)
+ return;
+ m_view_mode = mode;
+ update();
+ if (mode == ViewMode::Table) {
+ set_active_widget(m_table_view);
+ m_view_as_table_action->set_checked(true);
+ return;
+ }
+ if (mode == ViewMode::Columns) {
+ set_active_widget(m_columns_view);
+ m_view_as_columns_action->set_checked(true);
+ return;
+ }
+ if (mode == ViewMode::Icon) {
+ set_active_widget(m_icon_view);
+ m_view_as_icons_action->set_checked(true);
+ return;
+ }
+ ASSERT_NOT_REACHED();
+}
+
+void MultiView::set_model(RefPtr<Model> model)
+{
+ if (m_model == model)
+ return;
+ m_model = model;
+ for_each_view_implementation([&](auto& view) {
+ view.set_model(model);
+ });
+}
+
+void MultiView::set_model_column(int column)
+{
+ if (m_model_column == column)
+ return;
+ m_model_column = column;
+ m_icon_view->set_model_column(column);
+ m_columns_view->set_model_column(column);
+}
+
+void MultiView::set_column_hidden(int column_index, bool hidden)
+{
+ m_table_view->set_column_hidden(column_index, hidden);
+}
+
+void MultiView::build_actions()
+{
+ m_view_as_table_action = Action::create_checkable(
+ "Table view", Gfx::Bitmap::load_from_file("/res/icons/16x16/table-view.png"), [this](auto&) {
+ set_view_mode(ViewMode::Table);
+ });
+
+ m_view_as_icons_action = Action::create_checkable(
+ "Icon view", Gfx::Bitmap::load_from_file("/res/icons/16x16/icon-view.png"), [this](auto&) {
+ set_view_mode(ViewMode::Icon);
+ });
+
+ m_view_as_columns_action = Action::create_checkable(
+ "Columns view", Gfx::Bitmap::load_from_file("/res/icons/16x16/columns-view.png"), [this](auto&) {
+ set_view_mode(ViewMode::Columns);
+ });
+
+ m_view_type_action_group = make<ActionGroup>();
+ m_view_type_action_group->set_exclusive(true);
+ m_view_type_action_group->add_action(*m_view_as_table_action);
+ m_view_type_action_group->add_action(*m_view_as_icons_action);
+ m_view_type_action_group->add_action(*m_view_as_columns_action);
+}
+
+AbstractView::SelectionMode MultiView::selection_mode() const
+{
+ return m_table_view->selection_mode();
+}
+
+void MultiView::set_selection_mode(AbstractView::SelectionMode selection_mode)
+{
+ m_table_view->set_selection_mode(selection_mode);
+ m_icon_view->set_selection_mode(selection_mode);
+ m_columns_view->set_selection_mode(selection_mode);
+}
+
+void MultiView::set_key_column_and_sort_order(int column, SortOrder sort_order)
+{
+ for_each_view_implementation([&](auto& view) {
+ view.set_key_column_and_sort_order(column, sort_order);
+ });
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/MultiView.h b/Userland/Libraries/LibGUI/MultiView.h
new file mode 100644
index 0000000000..e835bca6eb
--- /dev/null
+++ b/Userland/Libraries/LibGUI/MultiView.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Action.h>
+#include <LibGUI/ColumnsView.h>
+#include <LibGUI/IconView.h>
+#include <LibGUI/StackWidget.h>
+#include <LibGUI/TableView.h>
+
+namespace GUI {
+
+class MultiView final : public GUI::StackWidget {
+ C_OBJECT(MultiView)
+public:
+ virtual ~MultiView() override;
+
+ void refresh();
+
+ Function<void()> on_selection_change;
+ Function<void(const ModelIndex&)> on_activation;
+ Function<void(const ModelIndex&)> on_selection;
+ Function<void(const ModelIndex&, const ContextMenuEvent&)> on_context_menu_request;
+ Function<void(const ModelIndex&, const DropEvent&)> on_drop;
+
+ enum ViewMode {
+ Invalid,
+ Table,
+ Columns,
+ Icon
+ };
+ void set_view_mode(ViewMode);
+ ViewMode view_mode() const { return m_view_mode; }
+
+ int model_column() const { return m_model_column; }
+ void set_model_column(int);
+
+ void set_column_hidden(int column_index, bool hidden);
+
+ void set_key_column_and_sort_order(int column, SortOrder);
+
+ GUI::AbstractView& current_view()
+ {
+ switch (m_view_mode) {
+ case ViewMode::Table:
+ return *m_table_view;
+ case ViewMode::Columns:
+ return *m_columns_view;
+ case ViewMode::Icon:
+ return *m_icon_view;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ const ModelSelection& selection() const { return const_cast<MultiView&>(*this).current_view().selection(); }
+ ModelSelection& selection() { return current_view().selection(); }
+
+ template<typename Callback>
+ void for_each_view_implementation(Callback callback)
+ {
+ callback(*m_table_view);
+ callback(*m_icon_view);
+ callback(*m_columns_view);
+ }
+
+ Model* model() { return m_model; }
+ const Model* model() const { return m_model; }
+
+ void set_model(RefPtr<Model>);
+
+ Action& view_as_table_action() { return *m_view_as_table_action; }
+ Action& view_as_icons_action() { return *m_view_as_icons_action; }
+ Action& view_as_columns_action() { return *m_view_as_columns_action; }
+
+ AbstractView::SelectionMode selection_mode() const;
+ void set_selection_mode(AbstractView::SelectionMode);
+
+private:
+ MultiView();
+
+ void build_actions();
+
+ ViewMode m_view_mode { Invalid };
+ int m_model_column { 0 };
+
+ RefPtr<Model> m_model;
+
+ RefPtr<TableView> m_table_view;
+ RefPtr<IconView> m_icon_view;
+ RefPtr<ColumnsView> m_columns_view;
+
+ RefPtr<Action> m_view_as_table_action;
+ RefPtr<Action> m_view_as_icons_action;
+ RefPtr<Action> m_view_as_columns_action;
+
+ OwnPtr<ActionGroup> m_view_type_action_group;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Notification.cpp b/Userland/Libraries/LibGUI/Notification.cpp
new file mode 100644
index 0000000000..2dc4fa2149
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Notification.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Notification.h>
+#include <LibIPC/ServerConnection.h>
+#include <NotificationServer/NotificationClientEndpoint.h>
+#include <NotificationServer/NotificationServerEndpoint.h>
+
+namespace GUI {
+
+class NotificationServerConnection : public IPC::ServerConnection<NotificationClientEndpoint, NotificationServerEndpoint>
+ , public NotificationClientEndpoint {
+ C_OBJECT(NotificationServerConnection)
+public:
+ virtual void handshake() override
+ {
+ auto response = send_sync<Messages::NotificationServer::Greet>();
+ set_my_client_id(response->client_id());
+ }
+
+private:
+ NotificationServerConnection()
+ : IPC::ServerConnection<NotificationClientEndpoint, NotificationServerEndpoint>(*this, "/tmp/portal/notify")
+ {
+ }
+ virtual void handle(const Messages::NotificationClient::Dummy&) override { }
+};
+
+Notification::Notification()
+{
+}
+
+Notification::~Notification()
+{
+}
+
+void Notification::show()
+{
+ auto connection = NotificationServerConnection::construct();
+ connection->send_sync<Messages::NotificationServer::ShowNotification>(m_text, m_title, m_icon ? m_icon->to_shareable_bitmap(connection->server_pid()) : Gfx::ShareableBitmap());
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Notification.h b/Userland/Libraries/LibGUI/Notification.h
new file mode 100644
index 0000000000..1b86bc8199
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Notification.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibCore/Object.h>
+#include <LibGfx/Bitmap.h>
+
+namespace GUI {
+
+class Notification : public Core::Object {
+ C_OBJECT(Notification);
+
+public:
+ virtual ~Notification() override;
+
+ const String& text() const { return m_text; }
+ void set_text(const String& text) { m_text = text; }
+
+ const String& title() const { return m_title; }
+ void set_title(const String& title) { m_title = title; }
+
+ const Gfx::Bitmap* icon() const { return m_icon; }
+ void set_icon(const Gfx::Bitmap* icon) { m_icon = icon; }
+
+ void show();
+
+private:
+ Notification();
+
+ String m_title;
+ String m_text;
+ RefPtr<Gfx::Bitmap> m_icon;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/OpacitySlider.cpp b/Userland/Libraries/LibGUI/OpacitySlider.cpp
new file mode 100644
index 0000000000..2766226203
--- /dev/null
+++ b/Userland/Libraries/LibGUI/OpacitySlider.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/OpacitySlider.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+REGISTER_WIDGET(GUI, OpacitySlider)
+
+namespace GUI {
+
+OpacitySlider::OpacitySlider(Gfx::Orientation orientation)
+ : AbstractSlider(orientation)
+{
+ // FIXME: Implement vertical mode.
+ ASSERT(orientation == Gfx::Orientation::Horizontal);
+
+ set_min(0);
+ set_max(100);
+ set_value(100);
+ set_fixed_height(20);
+}
+
+OpacitySlider::~OpacitySlider()
+{
+}
+
+Gfx::IntRect OpacitySlider::frame_inner_rect() const
+{
+ return rect().shrunken(4, 4);
+}
+
+void OpacitySlider::paint_event(PaintEvent& event)
+{
+ GUI::Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ auto inner_rect = frame_inner_rect();
+
+ // Grid pattern
+ Gfx::StylePainter::paint_transparency_grid(painter, inner_rect, palette());
+
+ // Alpha gradient
+ for (int x = inner_rect.left(); x <= inner_rect.right(); ++x) {
+ float relative_offset = (float)x / (float)width();
+ float alpha = relative_offset * 255.0f;
+ Color color { 0, 0, 0, (u8)alpha };
+ painter.fill_rect({ x, inner_rect.y(), 1, inner_rect.height() }, color);
+ }
+
+ constexpr int notch_size = 3;
+ int notch_y_top = inner_rect.top() + notch_size;
+ int notch_y_bottom = inner_rect.bottom() - notch_size;
+ int notch_x = inner_rect.left() + ((float)value() / (float)max() * (float)inner_rect.width());
+
+ // Top notch
+ painter.set_pixel(notch_x, notch_y_top, palette().threed_shadow2());
+ for (int i = notch_size; i >= 0; --i) {
+ painter.set_pixel(notch_x - (i + 1), notch_y_top - i - 1, palette().threed_highlight());
+ for (int j = 0; j < i * 2; ++j) {
+ painter.set_pixel(notch_x - (i + 1) + j + 1, notch_y_top - i - 1, palette().button());
+ }
+ painter.set_pixel(notch_x + (i + 0), notch_y_top - i - 1, palette().threed_shadow1());
+ painter.set_pixel(notch_x + (i + 1), notch_y_top - i - 1, palette().threed_shadow2());
+ }
+
+ // Bottom notch
+ painter.set_pixel(notch_x, notch_y_bottom, palette().threed_shadow2());
+ for (int i = 0; i < notch_size; ++i) {
+ painter.set_pixel(notch_x - (i + 1), notch_y_bottom + i + 1, palette().threed_highlight());
+ for (int j = 0; j < i * 2; ++j) {
+ painter.set_pixel(notch_x - (i + 1) + j + 1, notch_y_bottom + i + 1, palette().button());
+ }
+ painter.set_pixel(notch_x + (i + 0), notch_y_bottom + i + 1, palette().threed_shadow1());
+ painter.set_pixel(notch_x + (i + 1), notch_y_bottom + i + 1, palette().threed_shadow2());
+ }
+
+ // Hairline
+ // NOTE: If we're in the whiter part of the gradient, the notch is painted as shadow between the notches.
+ // If we're in the darker part, the notch is painted as highlight.
+ // We adjust the hairline's x position so it lines up with the shadow/highlight of the notches.
+ u8 h = ((float)value() / (float)max()) * 255.0f;
+ if (h < 128)
+ painter.draw_line({ notch_x, notch_y_top }, { notch_x, notch_y_bottom }, Color(h, h, h, h));
+ else
+ painter.draw_line({ notch_x - 1, notch_y_top }, { notch_x - 1, notch_y_bottom }, Color(h, h, h, h));
+
+ // Text label
+ auto percent_text = String::formatted("{}%", (int)((float)value() / (float)max() * 100.0f));
+ painter.draw_text(inner_rect.translated(1, 1), percent_text, Gfx::TextAlignment::Center, Color::Black);
+ painter.draw_text(inner_rect, percent_text, Gfx::TextAlignment::Center, Color::White);
+
+ // Frame
+ Gfx::StylePainter::paint_frame(painter, rect(), palette(), Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2);
+}
+
+int OpacitySlider::value_at(const Gfx::IntPoint& position) const
+{
+ auto inner_rect = frame_inner_rect();
+ if (position.x() < inner_rect.left())
+ return min();
+ if (position.x() > inner_rect.right())
+ return max();
+ float relative_offset = (float)(position.x() - inner_rect.x()) / (float)inner_rect.width();
+ return relative_offset * (float)max();
+}
+
+void OpacitySlider::mousedown_event(MouseEvent& event)
+{
+ if (event.button() == MouseButton::Left) {
+ m_dragging = true;
+ set_value(value_at(event.position()));
+ return;
+ }
+ AbstractSlider::mousedown_event(event);
+}
+
+void OpacitySlider::mousemove_event(MouseEvent& event)
+{
+ if (m_dragging) {
+ set_value(value_at(event.position()));
+ return;
+ }
+ AbstractSlider::mousemove_event(event);
+}
+
+void OpacitySlider::mouseup_event(MouseEvent& event)
+{
+ if (event.button() == MouseButton::Left) {
+ m_dragging = false;
+ return;
+ }
+ AbstractSlider::mouseup_event(event);
+}
+
+void OpacitySlider::mousewheel_event(MouseEvent& event)
+{
+ set_value(value() - event.wheel_delta());
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/OpacitySlider.h b/Userland/Libraries/LibGUI/OpacitySlider.h
new file mode 100644
index 0000000000..7f61018f0a
--- /dev/null
+++ b/Userland/Libraries/LibGUI/OpacitySlider.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/AbstractSlider.h>
+
+namespace GUI {
+
+class OpacitySlider : public AbstractSlider {
+ C_OBJECT(OpacitySlider);
+
+public:
+ virtual ~OpacitySlider() override;
+
+protected:
+ virtual void paint_event(PaintEvent&) override;
+ virtual void mousedown_event(MouseEvent&) override;
+ virtual void mousemove_event(MouseEvent&) override;
+ virtual void mouseup_event(MouseEvent&) override;
+ virtual void mousewheel_event(MouseEvent&) override;
+
+private:
+ explicit OpacitySlider(Gfx::Orientation = Gfx::Orientation::Horizontal);
+
+ Gfx::IntRect frame_inner_rect() const;
+
+ int value_at(const Gfx::IntPoint&) const;
+
+ bool m_dragging { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Painter.cpp b/Userland/Libraries/LibGUI/Painter.cpp
new file mode 100644
index 0000000000..c0c2720c40
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Painter.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Painter.h>
+#include <LibGUI/Widget.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/Bitmap.h>
+
+namespace GUI {
+Painter::Painter(Gfx::Bitmap& bitmap)
+ : Gfx::Painter(bitmap)
+{
+}
+
+Painter::Painter(Widget& widget)
+ : Painter(*widget.window()->back_bitmap())
+{
+ state().font = &widget.font();
+ auto origin_rect = widget.window_relative_rect();
+ state().translation = origin_rect.location();
+ state().clip_rect = origin_rect;
+ m_clip_origin = origin_rect;
+ state().clip_rect.intersect(m_target->rect());
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Painter.h b/Userland/Libraries/LibGUI/Painter.h
new file mode 100644
index 0000000000..ca30244505
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Painter.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Forward.h>
+#include <LibGfx/Painter.h>
+
+namespace GUI {
+
+class Painter : public Gfx::Painter {
+public:
+ explicit Painter(Widget&);
+ explicit Painter(Gfx::Bitmap&);
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ProcessChooser.cpp b/Userland/Libraries/LibGUI/ProcessChooser.cpp
new file mode 100644
index 0000000000..2da94b08bf
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ProcessChooser.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/MessageBox.h>
+#include <LibGUI/ProcessChooser.h>
+#include <LibGUI/RunningProcessesModel.h>
+#include <LibGUI/SortingProxyModel.h>
+#include <LibGUI/TableView.h>
+
+namespace GUI {
+
+ProcessChooser::ProcessChooser(const StringView& window_title, const StringView& button_label, const Gfx::Bitmap* window_icon, GUI::Window* parent_window)
+ : Dialog(parent_window)
+ , m_window_title(window_title)
+ , m_button_label(button_label)
+ , m_window_icon(window_icon)
+{
+ set_title(m_window_title);
+
+ if (m_window_icon)
+ set_icon(m_window_icon);
+ else if (parent_window)
+ set_icon(parent_window->icon());
+
+ resize(300, 340);
+ center_on_screen();
+
+ auto& widget = set_main_widget<GUI::Widget>();
+ widget.set_fill_with_background_color(true);
+ widget.set_layout<GUI::VerticalBoxLayout>();
+ widget.layout()->set_margins({ 0, 0, 0, 2 });
+
+ m_table_view = widget.add<GUI::TableView>();
+ auto sorting_model = GUI::SortingProxyModel::create(RunningProcessesModel::create());
+ sorting_model->set_sort_role(GUI::ModelRole::Display);
+ m_table_view->set_model(sorting_model);
+ m_table_view->set_key_column_and_sort_order(RunningProcessesModel::Column::PID, GUI::SortOrder::Descending);
+
+ m_table_view->on_activation = [this](const ModelIndex& index) { set_pid_from_index_and_close(index); };
+
+ auto& button_container = widget.add<GUI::Widget>();
+ button_container.set_fixed_height(30);
+ button_container.set_layout<GUI::HorizontalBoxLayout>();
+ button_container.layout()->set_margins({ 0, 0, 4, 0 });
+ button_container.layout()->add_spacer();
+
+ auto& select_button = button_container.add<GUI::Button>(m_button_label);
+ select_button.set_fixed_size(80, 24);
+ select_button.on_click = [this](auto) {
+ if (m_table_view->selection().is_empty()) {
+ GUI::MessageBox::show(this, "No process selected!", m_window_title, GUI::MessageBox::Type::Error);
+ return;
+ }
+ auto index = m_table_view->selection().first();
+ set_pid_from_index_and_close(index);
+ };
+ auto& cancel_button = button_container.add<GUI::Button>("Cancel");
+ cancel_button.set_fixed_size(80, 24);
+ cancel_button.on_click = [this](auto) {
+ done(ExecCancel);
+ };
+
+ m_table_view->model()->update();
+
+ m_refresh_timer = add<Core::Timer>();
+
+ m_refresh_timer->start(m_refresh_interval); // Start the timer to update the processes
+ m_refresh_timer->on_timeout = [this] {
+ auto previous_selected_pid = -1; // Store the selection index to not to clear the selection upon update.
+ if (!m_table_view->selection().is_empty()) {
+ auto pid_as_variant = m_table_view->selection().first().data(GUI::ModelRole::Custom);
+ previous_selected_pid = pid_as_variant.as_i32();
+ }
+
+ m_table_view->model()->update();
+
+ if (previous_selected_pid == -1) {
+ return;
+ }
+
+ auto model = m_table_view->model();
+ auto row_count = model->row_count();
+ auto column_index = 1; // Corresponds to PID column in the m_table_view.
+ for (int row_index = 0; row_index < row_count; ++row_index) {
+ auto cell_index = model->index(row_index, column_index);
+ auto pid_as_variant = cell_index.data(GUI::ModelRole::Custom);
+ if (previous_selected_pid == pid_as_variant.as_i32()) {
+ m_table_view->selection().set(cell_index); // Set only if PIDs are matched.
+ }
+ }
+ };
+}
+
+void ProcessChooser::set_pid_from_index_and_close(const ModelIndex& index)
+{
+ m_pid = index.data(GUI::ModelRole::Custom).as_i32();
+ done(ExecOK);
+}
+
+ProcessChooser::~ProcessChooser()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ProcessChooser.h b/Userland/Libraries/LibGUI/ProcessChooser.h
new file mode 100644
index 0000000000..b3b14a0ea9
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ProcessChooser.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibCore/Timer.h>
+#include <LibGUI/Dialog.h>
+
+namespace GUI {
+
+class ProcessChooser final : public GUI::Dialog {
+ C_OBJECT(ProcessChooser);
+
+public:
+ virtual ~ProcessChooser() override;
+
+ pid_t pid() const { return m_pid; }
+
+private:
+ ProcessChooser(const StringView& window_title = "Process Chooser", const StringView& button_label = "Select", const Gfx::Bitmap* window_icon = nullptr, GUI::Window* parent_window = nullptr);
+
+ void set_pid_from_index_and_close(const ModelIndex&);
+
+ pid_t m_pid { 0 };
+
+ String m_window_title;
+ String m_button_label;
+ RefPtr<Gfx::Bitmap> m_window_icon;
+ RefPtr<TableView> m_table_view;
+
+ bool m_refresh_enabled { true };
+ unsigned m_refresh_interval { 1000 };
+ RefPtr<Core::Timer> m_refresh_timer;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ProgressBar.cpp b/Userland/Libraries/LibGUI/ProgressBar.cpp
new file mode 100644
index 0000000000..0e6cd4acbe
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ProgressBar.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/StringBuilder.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ProgressBar.h>
+#include <LibGfx/Palette.h>
+
+REGISTER_WIDGET(GUI, ProgressBar)
+
+namespace GUI {
+
+ProgressBar::ProgressBar()
+{
+ REGISTER_STRING_PROPERTY("text", text, set_text);
+ REGISTER_ENUM_PROPERTY("format", format, set_format, Format,
+ { Format::NoText, "NoText" },
+ { Format::Percentage, "Percentage" },
+ { Format::ValueSlashMax, "ValueSlashMax" });
+ REGISTER_INT_PROPERTY("min", min, set_min);
+ REGISTER_INT_PROPERTY("max", max, set_max);
+}
+
+ProgressBar::~ProgressBar()
+{
+}
+
+void ProgressBar::set_value(int value)
+{
+ if (m_value == value)
+ return;
+ m_value = value;
+ update();
+}
+
+void ProgressBar::set_range(int min, int max)
+{
+ ASSERT(min < max);
+ m_min = min;
+ m_max = max;
+ m_value = clamp(m_value, m_min, m_max);
+}
+
+void ProgressBar::paint_event(PaintEvent& event)
+{
+ Frame::paint_event(event);
+
+ Painter painter(*this);
+ auto rect = frame_inner_rect();
+ painter.add_clip_rect(rect);
+ painter.add_clip_rect(event.rect());
+
+ String progress_text;
+ if (m_format != Format::NoText) {
+ // Then we draw the progress text over the gradient.
+ // We draw it twice, once offset (1, 1) for a drop shadow look.
+ StringBuilder builder;
+ builder.append(m_text);
+ if (m_format == Format::Percentage) {
+ float range_size = m_max - m_min;
+ float progress = (m_value - m_min) / range_size;
+ builder.appendf("%d%%", (int)(progress * 100));
+ } else if (m_format == Format::ValueSlashMax) {
+ builder.appendf("%d/%d", m_value, m_max);
+ }
+ progress_text = builder.to_string();
+ }
+
+ Gfx::StylePainter::paint_progress_bar(painter, rect, palette(), m_min, m_max, m_value, progress_text);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ProgressBar.h b/Userland/Libraries/LibGUI/ProgressBar.h
new file mode 100644
index 0000000000..9c9fddacd7
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ProgressBar.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Frame.h>
+
+namespace GUI {
+
+class ProgressBar : public Frame {
+ C_OBJECT(ProgressBar)
+public:
+ virtual ~ProgressBar() override;
+
+ void set_range(int min, int max);
+ void set_min(int min) { set_range(min, max()); }
+ void set_max(int max) { set_range(min(), max); }
+ void set_value(int);
+
+ int value() const { return m_value; }
+ int min() const { return m_min; }
+ int max() const { return m_max; }
+
+ String text() const { return m_text; }
+ void set_text(String text) { m_text = move(text); }
+
+ enum Format {
+ NoText,
+ Percentage,
+ ValueSlashMax
+ };
+ Format format() const { return m_format; }
+ void set_format(Format format) { m_format = format; }
+
+protected:
+ ProgressBar();
+
+ virtual void paint_event(PaintEvent&) override;
+
+private:
+ Format m_format { Percentage };
+ int m_min { 0 };
+ int m_max { 100 };
+ int m_value { 0 };
+ String m_text;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/RadioButton.cpp b/Userland/Libraries/LibGUI/RadioButton.cpp
new file mode 100644
index 0000000000..0ee428fe3c
--- /dev/null
+++ b/Userland/Libraries/LibGUI/RadioButton.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Painter.h>
+#include <LibGUI/RadioButton.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+REGISTER_WIDGET(GUI, RadioButton)
+
+namespace GUI {
+
+RadioButton::RadioButton(String text)
+ : AbstractButton(move(text))
+{
+ set_exclusive(true);
+ set_min_width(32);
+ set_fixed_height(22);
+}
+
+RadioButton::~RadioButton()
+{
+}
+
+Gfx::IntSize RadioButton::circle_size()
+{
+ return { 12, 12 };
+}
+
+void RadioButton::paint_event(PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ if (fill_with_background_color())
+ painter.fill_rect(rect(), palette().window());
+
+ if (is_enabled() && is_hovered())
+ painter.fill_rect(rect(), palette().hover_highlight());
+
+ Gfx::IntRect circle_rect { { 2, 0 }, circle_size() };
+ circle_rect.center_vertically_within(rect());
+
+ Gfx::StylePainter::paint_radio_button(painter, circle_rect, palette(), is_checked(), is_being_pressed());
+
+ Gfx::IntRect text_rect { circle_rect.right() + 4, 0, font().width(text()), font().glyph_height() };
+ text_rect.center_vertically_within(rect());
+ paint_text(painter, text_rect, font(), Gfx::TextAlignment::TopLeft);
+
+ if (is_focused())
+ painter.draw_focus_rect(text_rect.inflated(6, 6), palette().focus_outline());
+}
+
+void RadioButton::click(unsigned)
+{
+ if (!is_enabled())
+ return;
+ set_checked(true);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/RadioButton.h b/Userland/Libraries/LibGUI/RadioButton.h
new file mode 100644
index 0000000000..deca8c3a0f
--- /dev/null
+++ b/Userland/Libraries/LibGUI/RadioButton.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/AbstractButton.h>
+
+namespace GUI {
+
+class RadioButton : public AbstractButton {
+ C_OBJECT(RadioButton);
+
+public:
+ virtual ~RadioButton() override;
+
+ virtual void click(unsigned modifiers = 0) override;
+
+protected:
+ explicit RadioButton(String text = {});
+ virtual void paint_event(PaintEvent&) override;
+
+private:
+ // These don't make sense for a radio button, so hide them.
+ using AbstractButton::auto_repeat_interval;
+ using AbstractButton::set_auto_repeat_interval;
+
+ static Gfx::IntSize circle_size();
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/RegularEditingEngine.cpp b/Userland/Libraries/LibGUI/RegularEditingEngine.cpp
new file mode 100644
index 0000000000..ffcbe941cd
--- /dev/null
+++ b/Userland/Libraries/LibGUI/RegularEditingEngine.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/QuickSort.h>
+#include <LibGUI/RegularEditingEngine.h>
+#include <LibGUI/TextEditor.h>
+
+namespace GUI {
+
+CursorWidth RegularEditingEngine::cursor_width() const
+{
+ return CursorWidth::NARROW;
+}
+
+bool RegularEditingEngine::on_key(const KeyEvent& event)
+{
+ if (EditingEngine::on_key(event))
+ return true;
+
+ if (event.key() == KeyCode::Key_Escape) {
+ if (m_editor->on_escape_pressed)
+ m_editor->on_escape_pressed();
+ return true;
+ }
+
+ if (event.alt() && event.shift() && event.key() == KeyCode::Key_S) {
+ sort_selected_lines();
+ return true;
+ }
+
+ return false;
+}
+
+static int strcmp_utf32(const u32* s1, const u32* s2, size_t n)
+{
+ while (n-- > 0) {
+ if (*s1++ != *s2++)
+ return s1[-1] < s2[-1] ? -1 : 1;
+ }
+ return 0;
+}
+
+void RegularEditingEngine::sort_selected_lines()
+{
+ if (!m_editor->is_editable())
+ return;
+
+ if (!m_editor->has_selection())
+ return;
+
+ size_t first_line;
+ size_t last_line;
+ get_selection_line_boundaries(first_line, last_line);
+
+ auto& lines = m_editor->document().lines();
+
+ auto start = lines.begin() + (int)first_line;
+ auto end = lines.begin() + (int)last_line + 1;
+
+ quick_sort(start, end, [](auto& a, auto& b) {
+ return strcmp_utf32(a.code_points(), b.code_points(), min(a.length(), b.length())) < 0;
+ });
+
+ m_editor->did_change();
+ m_editor->update();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/RegularEditingEngine.h b/Userland/Libraries/LibGUI/RegularEditingEngine.h
new file mode 100644
index 0000000000..87e51b08d8
--- /dev/null
+++ b/Userland/Libraries/LibGUI/RegularEditingEngine.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2021, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/EditingEngine.h>
+
+namespace GUI {
+
+class RegularEditingEngine final : public EditingEngine {
+
+public:
+ virtual CursorWidth cursor_width() const override;
+
+ virtual bool on_key(const KeyEvent& event) override;
+
+private:
+ void sort_selected_lines();
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ResizeCorner.cpp b/Userland/Libraries/LibGUI/ResizeCorner.cpp
new file mode 100644
index 0000000000..b1b2f5e1d0
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ResizeCorner.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Painter.h>
+#include <LibGUI/ResizeCorner.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/CharacterBitmap.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+static const char* s_resize_corner_shadows_data = {
+ " "
+ " ## "
+ " # "
+ " "
+ " ## ## "
+ " # # "
+ " "
+ " ## ## ## "
+ " # # # "
+ " "
+ " ## ## ## ## "
+ " # # # # "
+ " "
+ " ## ## ## ## ## "
+ " # # # # # "
+ " "
+};
+
+static const char* s_resize_corner_highlights_data = {
+ " "
+ " "
+ " # "
+ " "
+ " "
+ " # # "
+ " "
+ " "
+ " # # # "
+ " "
+ " "
+ " # # # # "
+ " "
+ " "
+ " # # # # # "
+ " "
+};
+
+static Gfx::CharacterBitmap* s_resize_corner_shadows_bitmap;
+static Gfx::CharacterBitmap* s_resize_corner_highlights_bitmap;
+static const int s_resize_corner_bitmap_width = 16;
+static const int s_resize_corner_bitmap_height = 16;
+
+ResizeCorner::ResizeCorner()
+{
+ set_override_cursor(Gfx::StandardCursor::ResizeDiagonalTLBR);
+ set_background_role(ColorRole::Button);
+ set_fixed_size(16, 18);
+}
+
+ResizeCorner::~ResizeCorner()
+{
+}
+
+void ResizeCorner::paint_event(PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+ painter.fill_rect(rect(), palette().color(background_role()));
+
+ if (!s_resize_corner_shadows_bitmap)
+ s_resize_corner_shadows_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_resize_corner_shadows_data, s_resize_corner_bitmap_width, s_resize_corner_bitmap_height).leak_ref();
+ painter.draw_bitmap({ 0, 2 }, *s_resize_corner_shadows_bitmap, palette().threed_shadow1());
+
+ if (!s_resize_corner_highlights_bitmap)
+ s_resize_corner_highlights_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_resize_corner_highlights_data, s_resize_corner_bitmap_width, s_resize_corner_bitmap_height).leak_ref();
+ painter.draw_bitmap({ 0, 2 }, *s_resize_corner_highlights_bitmap, palette().threed_highlight());
+
+ Widget::paint_event(event);
+}
+
+void ResizeCorner::mousedown_event(MouseEvent& event)
+{
+ if (event.button() == MouseButton::Left)
+ window()->start_wm_resize();
+ Widget::mousedown_event(event);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ResizeCorner.h b/Userland/Libraries/LibGUI/ResizeCorner.h
new file mode 100644
index 0000000000..68990eb205
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ResizeCorner.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class ResizeCorner : public Widget {
+ C_OBJECT(ResizeCorner)
+public:
+ virtual ~ResizeCorner() override;
+
+protected:
+ ResizeCorner();
+
+ virtual void paint_event(PaintEvent&) override;
+ virtual void mousedown_event(MouseEvent&) override;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/RunningProcessesModel.cpp b/Userland/Libraries/LibGUI/RunningProcessesModel.cpp
new file mode 100644
index 0000000000..bcb87df4e5
--- /dev/null
+++ b/Userland/Libraries/LibGUI/RunningProcessesModel.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/SharedBuffer.h>
+#include <LibCore/ProcessStatisticsReader.h>
+#include <LibGUI/FileIconProvider.h>
+#include <LibGUI/RunningProcessesModel.h>
+
+namespace GUI {
+
+NonnullRefPtr<RunningProcessesModel> RunningProcessesModel::create()
+{
+ return adopt(*new RunningProcessesModel);
+}
+
+RunningProcessesModel::RunningProcessesModel()
+{
+}
+
+RunningProcessesModel::~RunningProcessesModel()
+{
+}
+
+void RunningProcessesModel::update()
+{
+ m_processes.clear();
+
+ Core::ProcessStatisticsReader reader;
+ auto processes = reader.get_all();
+ if (processes.has_value()) {
+ for (auto& it : processes.value()) {
+ Process process;
+ process.pid = it.value.pid;
+ process.uid = it.value.uid;
+ process.icon = FileIconProvider::icon_for_executable(it.value.executable).bitmap_for_size(16);
+ process.name = it.value.name;
+ m_processes.append(move(process));
+ }
+ }
+
+ did_update();
+}
+
+int RunningProcessesModel::row_count(const GUI::ModelIndex&) const
+{
+ return m_processes.size();
+}
+
+int RunningProcessesModel::column_count(const GUI::ModelIndex&) const
+{
+ return Column::__Count;
+}
+
+String RunningProcessesModel::column_name(int column_index) const
+{
+ switch (column_index) {
+ case Column::Icon:
+ return {};
+ case Column::PID:
+ return "PID";
+ case Column::UID:
+ return "UID";
+ case Column::Name:
+ return "Name";
+ }
+ ASSERT_NOT_REACHED();
+}
+
+GUI::Variant RunningProcessesModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
+{
+ auto& process = m_processes[index.row()];
+
+ if (role == ModelRole::Custom) {
+ return process.pid;
+ }
+
+ if (role == ModelRole::Display) {
+ switch (index.column()) {
+ case Column::Icon:
+ if (!process.icon)
+ return GUI::Icon();
+ return GUI::Icon(*process.icon);
+ case Column::PID:
+ return process.pid;
+ case Column::UID:
+ return process.uid;
+ case Column::Name:
+ return process.name;
+ }
+ ASSERT_NOT_REACHED();
+ }
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/RunningProcessesModel.h b/Userland/Libraries/LibGUI/RunningProcessesModel.h
new file mode 100644
index 0000000000..3d6ec20ceb
--- /dev/null
+++ b/Userland/Libraries/LibGUI/RunningProcessesModel.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Model.h>
+#include <LibGfx/Bitmap.h>
+
+namespace GUI {
+
+class RunningProcessesModel final : public GUI::Model {
+public:
+ static NonnullRefPtr<RunningProcessesModel> create();
+ virtual ~RunningProcessesModel() override;
+
+ enum Column {
+ Icon,
+ PID,
+ UID,
+ Name,
+ __Count,
+ };
+
+ virtual int row_count(const GUI::ModelIndex&) const override;
+ virtual int column_count(const GUI::ModelIndex&) const override;
+ virtual String column_name(int column_index) const override;
+ virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
+ virtual void update() override;
+
+private:
+ RunningProcessesModel();
+
+ struct Process {
+ pid_t pid;
+ uid_t uid;
+ RefPtr<Gfx::Bitmap> icon;
+ String name;
+ };
+ Vector<Process> m_processes;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ScrollBar.cpp b/Userland/Libraries/LibGUI/ScrollBar.cpp
new file mode 100644
index 0000000000..ad8bf27e48
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ScrollBar.cpp
@@ -0,0 +1,400 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/Timer.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGfx/CharacterBitmap.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+REGISTER_WIDGET(GUI, ScrollBar)
+
+namespace GUI {
+
+static const char* s_up_arrow_bitmap_data = {
+ " "
+ " # "
+ " ### "
+ " ##### "
+ " ####### "
+ " ### "
+ " ### "
+ " ### "
+ " "
+};
+
+static const char* s_down_arrow_bitmap_data = {
+ " "
+ " ### "
+ " ### "
+ " ### "
+ " ####### "
+ " ##### "
+ " ### "
+ " # "
+ " "
+};
+
+static const char* s_left_arrow_bitmap_data = {
+ " "
+ " # "
+ " ## "
+ " ###### "
+ " ####### "
+ " ###### "
+ " ## "
+ " # "
+ " "
+};
+
+static const char* s_right_arrow_bitmap_data = {
+ " "
+ " # "
+ " ## "
+ " ###### "
+ " ####### "
+ " ###### "
+ " ## "
+ " # "
+ " "
+};
+
+static Gfx::CharacterBitmap* s_up_arrow_bitmap;
+static Gfx::CharacterBitmap* s_down_arrow_bitmap;
+static Gfx::CharacterBitmap* s_left_arrow_bitmap;
+static Gfx::CharacterBitmap* s_right_arrow_bitmap;
+
+ScrollBar::ScrollBar(Orientation orientation)
+ : AbstractSlider(orientation)
+{
+ m_automatic_scrolling_timer = add<Core::Timer>();
+ if (!s_up_arrow_bitmap)
+ s_up_arrow_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_up_arrow_bitmap_data, 9, 9).leak_ref();
+ if (!s_down_arrow_bitmap)
+ s_down_arrow_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_down_arrow_bitmap_data, 9, 9).leak_ref();
+ if (!s_left_arrow_bitmap)
+ s_left_arrow_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_left_arrow_bitmap_data, 9, 9).leak_ref();
+ if (!s_right_arrow_bitmap)
+ s_right_arrow_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_right_arrow_bitmap_data, 9, 9).leak_ref();
+
+ if (orientation == Orientation::Vertical) {
+ set_fixed_width(16);
+ } else {
+ set_fixed_height(16);
+ }
+
+ m_automatic_scrolling_timer->set_interval(100);
+ m_automatic_scrolling_timer->on_timeout = [this] {
+ on_automatic_scrolling_timer_fired();
+ };
+}
+
+ScrollBar::~ScrollBar()
+{
+}
+
+Gfx::IntRect ScrollBar::decrement_button_rect() const
+{
+ return { 0, 0, button_width(), button_height() };
+}
+
+Gfx::IntRect ScrollBar::increment_button_rect() const
+{
+ if (orientation() == Orientation::Vertical)
+ return { 0, height() - button_height(), button_width(), button_height() };
+ else
+ return { width() - button_width(), 0, button_width(), button_height() };
+}
+
+Gfx::IntRect ScrollBar::decrement_gutter_rect() const
+{
+ if (orientation() == Orientation::Vertical)
+ return { 0, button_height(), button_width(), scrubber_rect().top() - button_height() };
+ else
+ return { button_width(), 0, scrubber_rect().x() - button_width(), button_height() };
+}
+
+Gfx::IntRect ScrollBar::increment_gutter_rect() const
+{
+ auto scrubber_rect = this->scrubber_rect();
+ if (orientation() == Orientation::Vertical)
+ return { 0, scrubber_rect.bottom() + 1, button_width(), height() - button_height() - scrubber_rect.bottom() - 1 };
+ else
+ return { scrubber_rect.right() + 1, 0, width() - button_width() - scrubber_rect.right() - 1, button_width() };
+}
+
+int ScrollBar::scrubbable_range_in_pixels() const
+{
+ if (orientation() == Orientation::Vertical)
+ return height() - button_height() * 2 - visible_scrubber_size();
+ else
+ return width() - button_width() * 2 - visible_scrubber_size();
+}
+
+bool ScrollBar::has_scrubber() const
+{
+ return max() != min();
+}
+
+int ScrollBar::unclamped_scrubber_size() const
+{
+ int pixel_range = length(orientation()) - button_size() * 2;
+ int value_range = max() - min();
+
+ int scrubber_size = 0;
+ if (value_range > 0) {
+ // Scrubber size should be proportional to the visible portion
+ // (page) in relation to the content (value range + page)
+ scrubber_size = (page_step() * pixel_range) / (value_range + page_step());
+ }
+ return scrubber_size;
+}
+
+int ScrollBar::visible_scrubber_size() const
+{
+ return ::max(unclamped_scrubber_size(), button_size());
+}
+
+Gfx::IntRect ScrollBar::scrubber_rect() const
+{
+ if (!has_scrubber() || length(orientation()) <= (button_size() * 2) + visible_scrubber_size())
+ return {};
+ float x_or_y;
+ if (value() == min())
+ x_or_y = button_size();
+ else if (value() == max())
+ x_or_y = (length(orientation()) - button_size() - visible_scrubber_size()) + 1;
+ else {
+ float range_size = max() - min();
+ float available = scrubbable_range_in_pixels();
+ float step = available / range_size;
+ x_or_y = (button_size() + (step * value()));
+ }
+
+ if (orientation() == Orientation::Vertical)
+ return { 0, (int)x_or_y, button_width(), visible_scrubber_size() };
+ else
+ return { (int)x_or_y, 0, visible_scrubber_size(), button_height() };
+}
+
+void ScrollBar::paint_event(PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ Component hovered_component_for_painting = m_hovered_component;
+ if (!has_scrubber() || (m_pressed_component != Component::None && m_hovered_component != m_pressed_component))
+ hovered_component_for_painting = Component::None;
+
+ painter.fill_rect_with_dither_pattern(rect(), palette().button().lightened(1.3f), palette().button());
+
+ bool decrement_pressed = m_pressed_component == Component::DecrementButton;
+ bool increment_pressed = m_pressed_component == Component::IncrementButton;
+
+ Gfx::StylePainter::paint_button(painter, decrement_button_rect(), palette(), Gfx::ButtonStyle::Normal, decrement_pressed, hovered_component_for_painting == Component::DecrementButton);
+ Gfx::StylePainter::paint_button(painter, increment_button_rect(), palette(), Gfx::ButtonStyle::Normal, increment_pressed, hovered_component_for_painting == Component::IncrementButton);
+
+ if (length(orientation()) > default_button_size()) {
+ auto decrement_location = decrement_button_rect().location().translated(3, 3);
+ if (decrement_pressed)
+ decrement_location.move_by(1, 1);
+ if (!has_scrubber() || !is_enabled())
+ painter.draw_bitmap(decrement_location.translated(1, 1), orientation() == Orientation::Vertical ? *s_up_arrow_bitmap : *s_left_arrow_bitmap, palette().threed_highlight());
+ painter.draw_bitmap(decrement_location, orientation() == Orientation::Vertical ? *s_up_arrow_bitmap : *s_left_arrow_bitmap, (has_scrubber() && is_enabled()) ? palette().button_text() : palette().threed_shadow1());
+
+ auto increment_location = increment_button_rect().location().translated(3, 3);
+ if (increment_pressed)
+ increment_location.move_by(1, 1);
+ if (!has_scrubber() || !is_enabled())
+ painter.draw_bitmap(increment_location.translated(1, 1), orientation() == Orientation::Vertical ? *s_down_arrow_bitmap : *s_right_arrow_bitmap, palette().threed_highlight());
+ painter.draw_bitmap(increment_location, orientation() == Orientation::Vertical ? *s_down_arrow_bitmap : *s_right_arrow_bitmap, (has_scrubber() && is_enabled()) ? palette().button_text() : palette().threed_shadow1());
+ }
+
+ if (has_scrubber())
+ Gfx::StylePainter::paint_button(painter, scrubber_rect(), palette(), Gfx::ButtonStyle::Normal, false, hovered_component_for_painting == Component::Scrubber || m_pressed_component == Component::Scrubber);
+}
+
+void ScrollBar::on_automatic_scrolling_timer_fired()
+{
+ if (m_pressed_component == Component::DecrementButton && component_at_position(m_last_mouse_position) == Component::DecrementButton) {
+ set_value(value() - step());
+ return;
+ }
+ if (m_pressed_component == Component::IncrementButton && component_at_position(m_last_mouse_position) == Component::IncrementButton) {
+ set_value(value() + step());
+ return;
+ }
+ if (m_pressed_component == Component::Gutter && component_at_position(m_last_mouse_position) == Component::Gutter) {
+ scroll_by_page(m_last_mouse_position);
+ m_hovered_component = component_at_position(m_last_mouse_position);
+ return;
+ }
+}
+
+void ScrollBar::mousedown_event(MouseEvent& event)
+{
+ if (event.button() != MouseButton::Left)
+ return;
+ if (!has_scrubber())
+ return;
+
+ m_last_mouse_position = event.position();
+ m_pressed_component = component_at_position(m_last_mouse_position);
+
+ if (m_pressed_component == Component::DecrementButton) {
+ set_automatic_scrolling_active(true, Component::DecrementButton);
+ update();
+ return;
+ }
+ if (m_pressed_component == Component::IncrementButton) {
+ set_automatic_scrolling_active(true, Component::IncrementButton);
+ update();
+ return;
+ }
+
+ if (event.shift()) {
+ scroll_to_position(event.position());
+ m_pressed_component = component_at_position(event.position());
+ ASSERT(m_pressed_component == Component::Scrubber);
+ }
+ if (m_pressed_component == Component::Scrubber) {
+ m_scrub_start_value = value();
+ m_scrub_origin = event.position();
+ update();
+ return;
+ }
+ ASSERT(!event.shift());
+
+ ASSERT(m_pressed_component == Component::Gutter);
+ set_automatic_scrolling_active(true, Component::Gutter);
+ update();
+}
+
+void ScrollBar::mouseup_event(MouseEvent& event)
+{
+ if (event.button() != MouseButton::Left)
+ return;
+ set_automatic_scrolling_active(false, Component::None);
+ update();
+}
+
+void ScrollBar::mousewheel_event(MouseEvent& event)
+{
+ if (!is_scrollable())
+ return;
+ set_value(value() + event.wheel_delta() * step());
+ Widget::mousewheel_event(event);
+}
+
+void ScrollBar::set_automatic_scrolling_active(bool active, Component pressed_component)
+{
+ m_pressed_component = pressed_component;
+ if (m_pressed_component == Component::Gutter)
+ m_automatic_scrolling_timer->set_interval(200);
+ else
+ m_automatic_scrolling_timer->set_interval(100);
+
+ if (active) {
+ on_automatic_scrolling_timer_fired();
+ m_automatic_scrolling_timer->start();
+ } else {
+ m_automatic_scrolling_timer->stop();
+ }
+}
+
+void ScrollBar::scroll_by_page(const Gfx::IntPoint& click_position)
+{
+ float range_size = max() - min();
+ float available = scrubbable_range_in_pixels();
+ float rel_scrubber_size = unclamped_scrubber_size() / available;
+ float page_increment = range_size * rel_scrubber_size;
+
+ if (click_position.primary_offset_for_orientation(orientation()) < scrubber_rect().primary_offset_for_orientation(orientation()))
+ set_value(value() - page_increment);
+ else
+ set_value(value() + page_increment);
+}
+
+void ScrollBar::scroll_to_position(const Gfx::IntPoint& click_position)
+{
+ float range_size = max() - min();
+ float available = scrubbable_range_in_pixels();
+
+ float x_or_y = ::max(0, click_position.primary_offset_for_orientation(orientation()) - button_width() - button_width() / 2);
+ float rel_x_or_y = x_or_y / available;
+ set_value(min() + rel_x_or_y * range_size);
+}
+
+ScrollBar::Component ScrollBar::component_at_position(const Gfx::IntPoint& position)
+{
+ if (scrubber_rect().contains(position))
+ return Component::Scrubber;
+ if (decrement_button_rect().contains(position))
+ return Component::DecrementButton;
+ if (increment_button_rect().contains(position))
+ return Component::IncrementButton;
+ if (rect().contains(position))
+ return Component::Gutter;
+ return Component::None;
+}
+
+void ScrollBar::mousemove_event(MouseEvent& event)
+{
+ m_last_mouse_position = event.position();
+
+ auto old_hovered_component = m_hovered_component;
+ m_hovered_component = component_at_position(m_last_mouse_position);
+ if (old_hovered_component != m_hovered_component) {
+ update();
+ }
+ if (m_pressed_component != Component::Scrubber)
+ return;
+ float delta = orientation() == Orientation::Vertical ? (event.y() - m_scrub_origin.y()) : (event.x() - m_scrub_origin.x());
+ float scrubbable_range = scrubbable_range_in_pixels();
+ float value_steps_per_scrubbed_pixel = (max() - min()) / scrubbable_range;
+ float new_value = m_scrub_start_value + (value_steps_per_scrubbed_pixel * delta);
+ set_value(new_value);
+}
+
+void ScrollBar::leave_event(Core::Event&)
+{
+ if (m_hovered_component != Component::None) {
+ m_hovered_component = Component::None;
+ update();
+ }
+}
+
+void ScrollBar::change_event(Event& event)
+{
+ if (event.type() == Event::Type::EnabledChange) {
+ if (!is_enabled())
+ set_automatic_scrolling_active(false, Component::None);
+ }
+ return Widget::change_event(event);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ScrollBar.h b/Userland/Libraries/LibGUI/ScrollBar.h
new file mode 100644
index 0000000000..cfe46b4d64
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ScrollBar.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <LibGUI/AbstractSlider.h>
+
+namespace GUI {
+
+class ScrollBar final : public AbstractSlider {
+ C_OBJECT(ScrollBar);
+
+public:
+ virtual ~ScrollBar() override;
+
+ bool is_scrollable() const { return max() != min(); }
+
+ bool has_scrubber() const;
+
+ enum Component {
+ None,
+ DecrementButton,
+ IncrementButton,
+ Gutter,
+ Scrubber,
+ };
+
+private:
+ explicit ScrollBar(Gfx::Orientation = Gfx::Orientation::Vertical);
+
+ virtual void paint_event(PaintEvent&) override;
+ virtual void mousedown_event(MouseEvent&) override;
+ virtual void mouseup_event(MouseEvent&) override;
+ virtual void mousemove_event(MouseEvent&) override;
+ virtual void mousewheel_event(MouseEvent&) override;
+ virtual void leave_event(Core::Event&) override;
+ virtual void change_event(Event&) override;
+
+ int default_button_size() const { return 16; }
+ int button_size() const { return length(orientation()) <= (default_button_size() * 2) ? length(orientation()) / 2 : default_button_size(); }
+ int button_width() const { return orientation() == Orientation::Vertical ? width() : button_size(); }
+ int button_height() const { return orientation() == Orientation::Horizontal ? height() : button_size(); }
+ Gfx::IntRect decrement_button_rect() const;
+ Gfx::IntRect increment_button_rect() const;
+ Gfx::IntRect decrement_gutter_rect() const;
+ Gfx::IntRect increment_gutter_rect() const;
+ Gfx::IntRect scrubber_rect() const;
+ int unclamped_scrubber_size() const;
+ int visible_scrubber_size() const;
+ int scrubbable_range_in_pixels() const;
+ void on_automatic_scrolling_timer_fired();
+ void set_automatic_scrolling_active(bool, Component);
+
+ void scroll_to_position(const Gfx::IntPoint&);
+ void scroll_by_page(const Gfx::IntPoint&);
+
+ Component component_at_position(const Gfx::IntPoint&);
+
+ int m_scrub_start_value { 0 };
+ Gfx::IntPoint m_scrub_origin;
+
+ Component m_hovered_component { Component::None };
+ Component m_pressed_component { Component::None };
+ Gfx::IntPoint m_last_mouse_position;
+
+ RefPtr<Core::Timer> m_automatic_scrolling_timer;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ScrollableWidget.cpp b/Userland/Libraries/LibGUI/ScrollableWidget.cpp
new file mode 100644
index 0000000000..9e724a6c40
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ScrollableWidget.cpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/ScrollBar.h>
+#include <LibGUI/ScrollableWidget.h>
+
+namespace GUI {
+
+ScrollableWidget::ScrollableWidget()
+{
+ m_vertical_scrollbar = add<ScrollBar>(Orientation::Vertical);
+ m_vertical_scrollbar->set_step(4);
+ m_vertical_scrollbar->on_change = [this](int) {
+ did_scroll();
+ update();
+ };
+
+ m_horizontal_scrollbar = add<ScrollBar>(Orientation::Horizontal);
+ m_horizontal_scrollbar->set_step(4);
+ m_horizontal_scrollbar->set_page_step(30);
+ m_horizontal_scrollbar->on_change = [this](int) {
+ did_scroll();
+ update();
+ };
+
+ m_corner_widget = add<Widget>();
+ m_corner_widget->set_fill_with_background_color(true);
+}
+
+ScrollableWidget::~ScrollableWidget()
+{
+}
+
+void ScrollableWidget::mousewheel_event(MouseEvent& event)
+{
+ if (!m_scrollbars_enabled) {
+ event.ignore();
+ return;
+ }
+ // FIXME: The wheel delta multiplier should probably come from... somewhere?
+ if (event.shift()) {
+ horizontal_scrollbar().set_value(horizontal_scrollbar().value() + event.wheel_delta() * 60);
+ } else {
+ vertical_scrollbar().set_value(vertical_scrollbar().value() + event.wheel_delta() * 20);
+ }
+}
+
+void ScrollableWidget::custom_layout()
+{
+ auto inner_rect = frame_inner_rect_for_size(size());
+ int height_wanted_by_horizontal_scrollbar = m_horizontal_scrollbar->is_visible() ? m_horizontal_scrollbar->min_height() : 0;
+ int width_wanted_by_vertical_scrollbar = m_vertical_scrollbar->is_visible() ? m_vertical_scrollbar->min_width() : 0;
+
+ m_vertical_scrollbar->set_relative_rect(
+ inner_rect.right() + 1 - m_vertical_scrollbar->min_width(),
+ inner_rect.top(),
+ m_vertical_scrollbar->min_width(),
+ inner_rect.height() - height_wanted_by_horizontal_scrollbar);
+
+ m_horizontal_scrollbar->set_relative_rect(
+ inner_rect.left(),
+ inner_rect.bottom() + 1 - m_horizontal_scrollbar->min_height(),
+ inner_rect.width() - width_wanted_by_vertical_scrollbar,
+ m_horizontal_scrollbar->min_height());
+
+ m_corner_widget->set_visible(m_vertical_scrollbar->is_visible() && m_horizontal_scrollbar->is_visible());
+ if (m_corner_widget->is_visible()) {
+ Gfx::IntRect corner_rect { m_horizontal_scrollbar->relative_rect().right() + 1, m_vertical_scrollbar->relative_rect().bottom() + 1, width_occupied_by_vertical_scrollbar(), height_occupied_by_horizontal_scrollbar() };
+ m_corner_widget->set_relative_rect(corner_rect);
+ }
+}
+
+void ScrollableWidget::resize_event(ResizeEvent& event)
+{
+ Frame::resize_event(event);
+ update_scrollbar_ranges();
+}
+
+Gfx::IntSize ScrollableWidget::available_size() const
+{
+ unsigned available_width = max(frame_inner_rect().width() - m_size_occupied_by_fixed_elements.width() - width_occupied_by_vertical_scrollbar(), 0);
+ unsigned available_height = max(frame_inner_rect().height() - m_size_occupied_by_fixed_elements.height() - height_occupied_by_horizontal_scrollbar(), 0);
+ return { available_width, available_height };
+}
+
+void ScrollableWidget::update_scrollbar_ranges()
+{
+ auto available_size = this->available_size();
+
+ int excess_height = max(0, m_content_size.height() - available_size.height());
+ m_vertical_scrollbar->set_range(0, excess_height);
+ m_vertical_scrollbar->set_page_step(available_size.height());
+
+ if (should_hide_unnecessary_scrollbars())
+ m_vertical_scrollbar->set_visible(excess_height > 0);
+
+ int excess_width = max(0, m_content_size.width() - available_size.width());
+ m_horizontal_scrollbar->set_range(0, excess_width);
+ m_horizontal_scrollbar->set_page_step(available_size.width());
+
+ if (should_hide_unnecessary_scrollbars())
+ m_horizontal_scrollbar->set_visible(excess_width > 0);
+
+ m_vertical_scrollbar->set_page_step(visible_content_rect().height() - m_vertical_scrollbar->step());
+}
+
+void ScrollableWidget::set_content_size(const Gfx::IntSize& size)
+{
+ if (m_content_size == size)
+ return;
+ m_content_size = size;
+ update_scrollbar_ranges();
+}
+
+void ScrollableWidget::set_size_occupied_by_fixed_elements(const Gfx::IntSize& size)
+{
+ if (m_size_occupied_by_fixed_elements == size)
+ return;
+ m_size_occupied_by_fixed_elements = size;
+ update_scrollbar_ranges();
+}
+
+int ScrollableWidget::height_occupied_by_horizontal_scrollbar() const
+{
+ return m_horizontal_scrollbar->is_visible() ? m_horizontal_scrollbar->height() : 0;
+}
+
+int ScrollableWidget::width_occupied_by_vertical_scrollbar() const
+{
+ return m_vertical_scrollbar->is_visible() ? m_vertical_scrollbar->width() : 0;
+}
+
+Gfx::IntRect ScrollableWidget::visible_content_rect() const
+{
+ Gfx::IntRect rect {
+ m_horizontal_scrollbar->value(),
+ m_vertical_scrollbar->value(),
+ min(m_content_size.width(), frame_inner_rect().width() - width_occupied_by_vertical_scrollbar() - m_size_occupied_by_fixed_elements.width()),
+ min(m_content_size.height(), frame_inner_rect().height() - height_occupied_by_horizontal_scrollbar() - m_size_occupied_by_fixed_elements.height())
+ };
+ if (rect.is_empty())
+ return {};
+ return rect;
+}
+
+void ScrollableWidget::scroll_into_view(const Gfx::IntRect& rect, Orientation orientation)
+{
+ if (orientation == Orientation::Vertical)
+ return scroll_into_view(rect, false, true);
+ return scroll_into_view(rect, true, false);
+}
+
+void ScrollableWidget::scroll_into_view(const Gfx::IntRect& rect, bool scroll_horizontally, bool scroll_vertically)
+{
+ auto visible_content_rect = this->visible_content_rect();
+ if (visible_content_rect.contains(rect))
+ return;
+
+ if (scroll_vertically) {
+ if (rect.top() < visible_content_rect.top()) {
+ m_vertical_scrollbar->set_value(rect.top());
+ } else if (rect.top() > visible_content_rect.top() && rect.bottom() > visible_content_rect.bottom()) {
+ m_vertical_scrollbar->set_value(rect.bottom() - visible_content_rect.height() + 1);
+ }
+ }
+ if (scroll_horizontally) {
+ if (rect.left() < visible_content_rect.left()) {
+ m_horizontal_scrollbar->set_value(rect.left());
+ } else if (rect.left() > visible_content_rect.left() && rect.right() > visible_content_rect.right()) {
+ m_horizontal_scrollbar->set_value(rect.right() - visible_content_rect.width() + 1);
+ }
+ }
+}
+
+void ScrollableWidget::set_scrollbars_enabled(bool scrollbars_enabled)
+{
+ if (m_scrollbars_enabled == scrollbars_enabled)
+ return;
+ m_scrollbars_enabled = scrollbars_enabled;
+ m_vertical_scrollbar->set_visible(m_scrollbars_enabled);
+ m_horizontal_scrollbar->set_visible(m_scrollbars_enabled);
+ m_corner_widget->set_visible(m_scrollbars_enabled);
+}
+
+void ScrollableWidget::scroll_to_top()
+{
+ scroll_into_view({}, Orientation::Vertical);
+}
+
+void ScrollableWidget::scroll_to_bottom()
+{
+ scroll_into_view({ 0, content_height(), 0, 0 }, Orientation::Vertical);
+}
+
+Gfx::IntRect ScrollableWidget::widget_inner_rect() const
+{
+ auto rect = frame_inner_rect();
+ rect.set_width(rect.width() - width_occupied_by_vertical_scrollbar());
+ rect.set_height(rect.height() - height_occupied_by_horizontal_scrollbar());
+ return rect;
+}
+
+Gfx::IntPoint ScrollableWidget::to_content_position(const Gfx::IntPoint& widget_position) const
+{
+ auto content_position = widget_position;
+ content_position.move_by(horizontal_scrollbar().value(), vertical_scrollbar().value());
+ content_position.move_by(-frame_thickness(), -frame_thickness());
+ return content_position;
+}
+
+Gfx::IntPoint ScrollableWidget::to_widget_position(const Gfx::IntPoint& content_position) const
+{
+ auto widget_position = content_position;
+ widget_position.move_by(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
+ widget_position.move_by(frame_thickness(), frame_thickness());
+ return widget_position;
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ScrollableWidget.h b/Userland/Libraries/LibGUI/ScrollableWidget.h
new file mode 100644
index 0000000000..349a16a5c7
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ScrollableWidget.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Frame.h>
+
+namespace GUI {
+
+class ScrollableWidget : public Frame {
+ C_OBJECT(ScrollableWidget)
+public:
+ virtual ~ScrollableWidget() override;
+
+ Gfx::IntSize content_size() const { return m_content_size; }
+ int content_width() const { return m_content_size.width(); }
+ int content_height() const { return m_content_size.height(); }
+
+ Gfx::IntRect visible_content_rect() const;
+
+ Gfx::IntRect widget_inner_rect() const;
+
+ Gfx::IntRect viewport_rect_in_content_coordinates() const
+ {
+ auto viewport_rect = visible_content_rect();
+ viewport_rect.set_size(widget_inner_rect().size());
+ return viewport_rect;
+ }
+
+ void scroll_into_view(const Gfx::IntRect&, Orientation);
+ void scroll_into_view(const Gfx::IntRect&, bool scroll_horizontally, bool scroll_vertically);
+
+ void set_scrollbars_enabled(bool);
+ bool is_scrollbars_enabled() const { return m_scrollbars_enabled; }
+
+ Gfx::IntSize available_size() const;
+
+ ScrollBar& vertical_scrollbar() { return *m_vertical_scrollbar; }
+ const ScrollBar& vertical_scrollbar() const { return *m_vertical_scrollbar; }
+ ScrollBar& horizontal_scrollbar() { return *m_horizontal_scrollbar; }
+ const ScrollBar& horizontal_scrollbar() const { return *m_horizontal_scrollbar; }
+ Widget& corner_widget() { return *m_corner_widget; }
+ const Widget& corner_widget() const { return *m_corner_widget; }
+
+ void scroll_to_top();
+ void scroll_to_bottom();
+
+ int width_occupied_by_vertical_scrollbar() const;
+ int height_occupied_by_horizontal_scrollbar() const;
+
+ void set_should_hide_unnecessary_scrollbars(bool b) { m_should_hide_unnecessary_scrollbars = b; }
+ bool should_hide_unnecessary_scrollbars() const { return m_should_hide_unnecessary_scrollbars; }
+
+ Gfx::IntPoint to_content_position(const Gfx::IntPoint& widget_position) const;
+ Gfx::IntPoint to_widget_position(const Gfx::IntPoint& content_position) const;
+
+ Gfx::IntRect to_content_rect(const Gfx::IntRect& widget_rect) const { return { to_content_position(widget_rect.location()), widget_rect.size() }; }
+ Gfx::IntRect to_widget_rect(const Gfx::IntRect& content_rect) const { return { to_widget_position(content_rect.location()), content_rect.size() }; }
+
+protected:
+ ScrollableWidget();
+ virtual void custom_layout() override;
+ virtual void resize_event(ResizeEvent&) override;
+ virtual void mousewheel_event(MouseEvent&) override;
+ virtual void did_scroll() { }
+ void set_content_size(const Gfx::IntSize&);
+ void set_size_occupied_by_fixed_elements(const Gfx::IntSize&);
+
+private:
+ void update_scrollbar_ranges();
+
+ RefPtr<ScrollBar> m_vertical_scrollbar;
+ RefPtr<ScrollBar> m_horizontal_scrollbar;
+ RefPtr<Widget> m_corner_widget;
+ Gfx::IntSize m_content_size;
+ Gfx::IntSize m_size_occupied_by_fixed_elements;
+ bool m_scrollbars_enabled { true };
+ bool m_should_hide_unnecessary_scrollbars { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/SeparatorWidget.cpp b/Userland/Libraries/LibGUI/SeparatorWidget.cpp
new file mode 100644
index 0000000000..81478728f4
--- /dev/null
+++ b/Userland/Libraries/LibGUI/SeparatorWidget.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Painter.h>
+#include <LibGUI/SeparatorWidget.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+SeparatorWidget::SeparatorWidget(Gfx::Orientation orientation)
+ : m_orientation(orientation)
+{
+ if (m_orientation == Gfx::Orientation::Vertical)
+ set_fixed_width(8);
+ else
+ set_fixed_height(8);
+}
+
+SeparatorWidget::~SeparatorWidget()
+{
+}
+
+void SeparatorWidget::paint_event(PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ if (m_orientation == Gfx::Orientation::Vertical) {
+ painter.translate(rect().center().x() - 1, 0);
+ painter.draw_line({ 0, 0 }, { 0, rect().bottom() }, palette().threed_shadow1());
+ painter.draw_line({ 1, 0 }, { 1, rect().bottom() }, palette().threed_highlight());
+ } else {
+ painter.translate(0, rect().center().y() - 1);
+ painter.draw_line({ 0, 0 }, { rect().right(), 0 }, palette().threed_shadow1());
+ painter.draw_line({ 0, 1 }, { rect().right(), 1 }, palette().threed_highlight());
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/SeparatorWidget.h b/Userland/Libraries/LibGUI/SeparatorWidget.h
new file mode 100644
index 0000000000..5df53a74ef
--- /dev/null
+++ b/Userland/Libraries/LibGUI/SeparatorWidget.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class SeparatorWidget final : public Widget {
+ C_OBJECT(SeparatorWidget);
+
+public:
+ virtual ~SeparatorWidget() override;
+
+private:
+ explicit SeparatorWidget(Gfx::Orientation);
+
+ virtual void paint_event(PaintEvent&) override;
+
+ const Gfx::Orientation m_orientation;
+};
+}
diff --git a/Userland/Libraries/LibGUI/ShellSyntaxHighlighter.cpp b/Userland/Libraries/LibGUI/ShellSyntaxHighlighter.cpp
new file mode 100644
index 0000000000..8f3c27fa8e
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ShellSyntaxHighlighter.cpp
@@ -0,0 +1,549 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/TemporaryChange.h>
+#include <LibGUI/ShellSyntaxHighlighter.h>
+#include <LibGUI/TextEditor.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Palette.h>
+#include <Shell/NodeVisitor.h>
+#include <Shell/Parser.h>
+
+namespace GUI {
+
+using namespace Shell;
+
+enum class AugmentedTokenKind : u32 {
+ __TokenTypeCount = (u32)AST::Node::Kind::__Count,
+ OpenParen,
+ CloseParen,
+};
+
+class HighlightVisitor : public AST::NodeVisitor {
+public:
+ HighlightVisitor(Vector<GUI::TextDocumentSpan>& spans, const Gfx::Palette& palette, const TextDocument& document)
+ : m_spans(spans)
+ , m_palette(palette)
+ , m_document(document)
+ {
+ }
+
+private:
+ struct PositionAndLine {
+ size_t position { 0 };
+ size_t line { 0 };
+ size_t offset { 0 };
+ };
+
+ AST::Position::Line offset_line(const AST::Position::Line& line, size_t offset)
+ {
+ // We need to look at the line(s) above.
+ AST::Position::Line new_line { line };
+ while (new_line.line_column < offset) {
+ offset -= new_line.line_column;
+ --offset;
+
+ ASSERT(new_line.line_number > 0);
+ --new_line.line_number;
+
+ auto line = m_document.line(new_line.line_number);
+ new_line.line_column = line.length();
+ }
+ if (offset > 0)
+ new_line.line_column -= offset;
+
+ return new_line;
+ }
+ void set_offset_range_end(TextRange& range, const AST::Position::Line& line, size_t offset = 1)
+ {
+ auto new_line = offset_line(line, offset);
+ range.set_end({ new_line.line_number, new_line.line_column });
+ }
+ void set_offset_range_start(TextRange& range, const AST::Position::Line& line, size_t offset = 1)
+ {
+ auto new_line = offset_line(line, offset);
+ range.set_start({ new_line.line_number, new_line.line_column });
+ }
+
+ GUI::TextDocumentSpan& span_for_node(const AST::Node* node)
+ {
+ GUI::TextDocumentSpan span;
+ span.range.set_start({ node->position().start_line.line_number, node->position().start_line.line_column });
+ set_offset_range_end(span.range, node->position().end_line);
+ span.data = (void*)static_cast<size_t>(node->kind());
+ span.is_skippable = false;
+ m_spans.append(move(span));
+
+ return m_spans.last();
+ }
+
+ virtual void visit(const AST::PathRedirectionNode* node) override
+ {
+ if (node->path()->is_bareword()) {
+ auto& span = span_for_node(node->path());
+ span.attributes.color = m_palette.link();
+ span.attributes.underline = true;
+ } else {
+ NodeVisitor::visit(node);
+ }
+ }
+ virtual void visit(const AST::And* node) override
+ {
+ {
+ ScopedValueRollback first_in_command { m_is_first_in_command };
+ node->left()->visit(*this);
+ }
+ {
+ ScopedValueRollback first_in_command { m_is_first_in_command };
+ node->right()->visit(*this);
+ }
+
+ auto& span = span_for_node(node);
+ span.range.set_start({ node->and_position().start_line.line_number, node->and_position().start_line.line_column });
+ set_offset_range_end(span.range, node->and_position().end_line);
+ span.attributes.color = m_palette.syntax_punctuation();
+ span.attributes.bold = true;
+ }
+ virtual void visit(const AST::ListConcatenate* node) override
+ {
+ NodeVisitor::visit(node);
+ }
+ virtual void visit(const AST::Background* node) override
+ {
+ NodeVisitor::visit(node);
+
+ auto& span = span_for_node(node);
+ set_offset_range_start(span.range, node->position().end_line);
+ span.attributes.color = m_palette.syntax_punctuation();
+ span.attributes.bold = true;
+ }
+ virtual void visit(const AST::BraceExpansion* node) override
+ {
+ NodeVisitor::visit(node);
+ }
+ virtual void visit(const AST::BarewordLiteral* node) override
+ {
+ NodeVisitor::visit(node);
+
+ auto& span = span_for_node(node);
+ if (m_is_first_in_command) {
+ span.attributes.color = m_palette.syntax_keyword();
+ span.attributes.bold = true;
+ m_is_first_in_command = false;
+ } else if (node->text().starts_with("-")) {
+ span.attributes.color = m_palette.syntax_preprocessor_statement();
+ }
+ }
+ virtual void visit(const AST::CastToCommand* node) override
+ {
+ NodeVisitor::visit(node);
+ }
+ virtual void visit(const AST::CastToList* node) override
+ {
+ NodeVisitor::visit(node);
+
+ auto& start_span = span_for_node(node);
+ start_span.attributes.color = m_palette.syntax_punctuation();
+ start_span.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column + 1 });
+ start_span.data = (void*)static_cast<size_t>(AugmentedTokenKind::OpenParen);
+
+ auto& end_span = span_for_node(node);
+ end_span.attributes.color = m_palette.syntax_punctuation();
+ set_offset_range_start(end_span.range, node->position().end_line);
+ end_span.data = (void*)static_cast<size_t>(AugmentedTokenKind::CloseParen);
+ }
+ virtual void visit(const AST::CloseFdRedirection* node) override
+ {
+ NodeVisitor::visit(node);
+ }
+ virtual void visit(const AST::CommandLiteral* node) override
+ {
+ NodeVisitor::visit(node);
+ }
+ virtual void visit(const AST::Comment* node) override
+ {
+ NodeVisitor::visit(node);
+
+ auto& span = span_for_node(node);
+ span.attributes.color = m_palette.syntax_comment();
+ }
+ virtual void visit(const AST::ContinuationControl* node) override
+ {
+ NodeVisitor::visit(node);
+
+ auto& span = span_for_node(node);
+ span.attributes.color = m_palette.syntax_control_keyword();
+ }
+ virtual void visit(const AST::DynamicEvaluate* node) override
+ {
+ NodeVisitor::visit(node);
+
+ auto& start_span = span_for_node(node);
+ start_span.attributes.color = m_palette.syntax_punctuation();
+ start_span.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column });
+ }
+ virtual void visit(const AST::DoubleQuotedString* node) override
+ {
+ NodeVisitor::visit(node);
+
+ auto& start_span = span_for_node(node);
+ start_span.attributes.color = m_palette.syntax_string();
+ set_offset_range_end(start_span.range, node->position().start_line, 0);
+ start_span.is_skippable = true;
+
+ auto& end_span = span_for_node(node);
+ set_offset_range_start(end_span.range, node->position().end_line);
+ end_span.attributes.color = m_palette.syntax_string();
+ end_span.is_skippable = true;
+
+ if (m_is_first_in_command) {
+ start_span.attributes.bold = true;
+ end_span.attributes.bold = true;
+ }
+ m_is_first_in_command = false;
+ }
+ virtual void visit(const AST::Fd2FdRedirection* node) override
+ {
+ NodeVisitor::visit(node);
+ }
+ virtual void visit(const AST::FunctionDeclaration* node) override
+ {
+ NodeVisitor::visit(node);
+
+ // fn name
+ auto& name_span = span_for_node(node);
+ name_span.range.set_start({ node->name().position.start_line.line_number, node->name().position.start_line.line_column });
+ set_offset_range_end(name_span.range, node->name().position.end_line);
+ name_span.attributes.color = m_palette.syntax_identifier();
+
+ // arguments
+ for (auto& arg : node->arguments()) {
+ auto& name_span = span_for_node(node);
+ name_span.range.set_start({ arg.position.start_line.line_number, arg.position.start_line.line_column });
+ set_offset_range_end(name_span.range, arg.position.end_line);
+ name_span.attributes.color = m_palette.syntax_identifier();
+ }
+ }
+ virtual void visit(const AST::ForLoop* node) override
+ {
+ // The iterated expression is an expression, not a command.
+ m_is_first_in_command = false;
+ NodeVisitor::visit(node);
+
+ // "for"
+ auto& for_span = span_for_node(node);
+ // FIXME: "fo\\\nr" is valid too
+ for_span.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column + 2 });
+ for_span.attributes.color = m_palette.syntax_keyword();
+
+ // "in"
+ if (auto maybe_position = node->in_keyword_position(); maybe_position.has_value()) {
+ auto& position = maybe_position.value();
+
+ auto& in_span = span_for_node(node);
+ in_span.range.set_start({ position.start_line.line_number, position.start_line.line_column });
+ set_offset_range_end(in_span.range, position.end_line);
+ in_span.attributes.color = m_palette.syntax_keyword();
+ }
+ }
+ virtual void visit(const AST::Glob* node) override
+ {
+ NodeVisitor::visit(node);
+
+ auto& span = span_for_node(node);
+ span.attributes.color = m_palette.syntax_preprocessor_value();
+ }
+ virtual void visit(const AST::Execute* node) override
+ {
+ TemporaryChange first { m_is_first_in_command, true };
+ NodeVisitor::visit(node);
+
+ if (node->does_capture_stdout()) {
+ auto& start_span = span_for_node(node);
+ start_span.attributes.color = m_palette.syntax_punctuation();
+ start_span.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column + 1 });
+ start_span.data = (void*)static_cast<size_t>(AugmentedTokenKind::OpenParen);
+
+ auto& end_span = span_for_node(node);
+ end_span.attributes.color = m_palette.syntax_punctuation();
+ set_offset_range_start(end_span.range, node->position().end_line);
+ end_span.data = (void*)static_cast<size_t>(AugmentedTokenKind::CloseParen);
+ }
+ }
+ virtual void visit(const AST::IfCond* node) override
+ {
+ m_is_first_in_command = false;
+ NodeVisitor::visit(node);
+
+ // "if"
+ auto& if_span = span_for_node(node);
+ // FIXME: "i\\\nf" is valid too
+ if_span.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column + 1 });
+ if_span.attributes.color = m_palette.syntax_keyword();
+
+ // "else"
+ if (auto maybe_position = node->else_position(); maybe_position.has_value()) {
+ auto& position = maybe_position.value();
+
+ auto& else_span = span_for_node(node);
+ else_span.range.set_start({ position.start_line.line_number, position.start_line.line_column });
+ set_offset_range_end(else_span.range, node->position().end_line);
+ else_span.attributes.color = m_palette.syntax_keyword();
+ }
+ }
+ virtual void visit(const AST::Join* node) override
+ {
+ NodeVisitor::visit(node);
+ }
+ virtual void visit(const AST::MatchExpr* node) override
+ {
+ // The matched expression is an expression, not a command.
+ m_is_first_in_command = false;
+ NodeVisitor::visit(node);
+
+ // "match"
+ auto& match_expr = span_for_node(node);
+ // FIXME: "mat\\\nch" is valid too
+ match_expr.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column + 4 });
+ match_expr.attributes.color = m_palette.syntax_keyword();
+
+ // "as"
+ if (auto maybe_position = node->as_position(); maybe_position.has_value()) {
+ auto& position = maybe_position.value();
+
+ auto& as_span = span_for_node(node);
+ as_span.range.set_start({ position.start_line.line_number, position.start_line.line_column });
+ as_span.range.set_end({ position.end_line.line_number, position.end_line.line_column });
+ as_span.attributes.color = m_palette.syntax_keyword();
+ }
+ }
+ virtual void visit(const AST::Or* node) override
+ {
+ {
+ ScopedValueRollback first_in_command { m_is_first_in_command };
+ node->left()->visit(*this);
+ }
+ {
+ ScopedValueRollback first_in_command { m_is_first_in_command };
+ node->right()->visit(*this);
+ }
+
+ auto& span = span_for_node(node);
+ span.range.set_start({ node->or_position().start_line.line_number, node->or_position().start_line.line_column });
+ set_offset_range_end(span.range, node->or_position().end_line);
+ span.attributes.color = m_palette.syntax_punctuation();
+ span.attributes.bold = true;
+ }
+ virtual void visit(const AST::Pipe* node) override
+ {
+ NodeVisitor::visit(node);
+ }
+ virtual void visit(const AST::Range* node) override
+ {
+ NodeVisitor::visit(node);
+
+ auto& span = span_for_node(node->start());
+ span.range.set_start(span.range.end());
+ set_offset_range_end(span.range, node->start()->position().end_line, 2);
+ span.attributes.color = m_palette.syntax_punctuation();
+ }
+ virtual void visit(const AST::ReadRedirection* node) override
+ {
+ NodeVisitor::visit(node);
+ }
+ virtual void visit(const AST::ReadWriteRedirection* node) override
+ {
+ NodeVisitor::visit(node);
+ }
+ virtual void visit(const AST::Sequence* node) override
+ {
+ {
+ ScopedValueRollback first_in_command { m_is_first_in_command };
+ node->left()->visit(*this);
+ }
+ {
+ ScopedValueRollback first_in_command { m_is_first_in_command };
+ node->right()->visit(*this);
+ }
+
+ auto& span = span_for_node(node);
+ span.range.set_start({ node->separator_position().start_line.line_number, node->separator_position().start_line.line_column });
+ set_offset_range_end(span.range, node->separator_position().end_line);
+ span.attributes.color = m_palette.syntax_punctuation();
+ span.attributes.bold = true;
+ span.is_skippable = true;
+ }
+ virtual void visit(const AST::Subshell* node) override
+ {
+ NodeVisitor::visit(node);
+ }
+ virtual void visit(const AST::SimpleVariable* node) override
+ {
+ NodeVisitor::visit(node);
+
+ auto& span = span_for_node(node);
+ span.attributes.color = m_palette.syntax_identifier();
+ }
+ virtual void visit(const AST::SpecialVariable* node) override
+ {
+ NodeVisitor::visit(node);
+
+ auto& span = span_for_node(node);
+ span.attributes.color = m_palette.syntax_identifier();
+ }
+ virtual void visit(const AST::Juxtaposition* node) override
+ {
+ NodeVisitor::visit(node);
+ }
+ virtual void visit(const AST::StringLiteral* node) override
+ {
+ NodeVisitor::visit(node);
+
+ if (node->text().is_empty())
+ return;
+
+ auto& span = span_for_node(node);
+ span.attributes.color = m_palette.syntax_string();
+ if (m_is_first_in_command)
+ span.attributes.bold = true;
+ m_is_first_in_command = false;
+ }
+ virtual void visit(const AST::StringPartCompose* node) override
+ {
+ NodeVisitor::visit(node);
+ }
+ virtual void visit(const AST::SyntaxError* node) override
+ {
+ NodeVisitor::visit(node);
+
+ auto& span = span_for_node(node);
+ span.attributes.underline = true;
+ span.attributes.background_color = Color(Color::NamedColor::MidRed).lightened(1.3f).with_alpha(128);
+ }
+ virtual void visit(const AST::Tilde* node) override
+ {
+ NodeVisitor::visit(node);
+
+ auto& span = span_for_node(node);
+ span.attributes.color = m_palette.link();
+ }
+ virtual void visit(const AST::VariableDeclarations* node) override
+ {
+ TemporaryChange first_in_command { m_is_first_in_command, false };
+ for (auto& decl : node->variables()) {
+ auto& name_span = span_for_node(decl.name);
+ name_span.attributes.color = m_palette.syntax_identifier();
+
+ decl.name->visit(*this);
+ decl.value->visit(*this);
+
+ auto& start_span = span_for_node(decl.name);
+ start_span.range.set_start({ decl.name->position().end_line.line_number, decl.name->position().end_line.line_column });
+ start_span.range.set_end({ decl.value->position().start_line.line_number, decl.value->position().start_line.line_column });
+ start_span.attributes.color = m_palette.syntax_punctuation();
+ start_span.data = (void*)static_cast<size_t>(AugmentedTokenKind::OpenParen);
+ }
+ }
+ virtual void visit(const AST::WriteAppendRedirection* node) override
+ {
+ NodeVisitor::visit(node);
+ }
+ virtual void visit(const AST::WriteRedirection* node) override
+ {
+ NodeVisitor::visit(node);
+ }
+
+ Vector<GUI::TextDocumentSpan>& m_spans;
+ const Gfx::Palette& m_palette;
+ const TextDocument& m_document;
+ bool m_is_first_in_command { false };
+};
+
+bool ShellSyntaxHighlighter::is_identifier(void* token) const
+{
+ if (!token)
+ return false;
+
+ auto kind = (size_t)token;
+ return kind == (size_t)AST::Node::Kind::BarewordLiteral
+ || kind == (size_t)AST::Node::Kind::StringLiteral
+ || kind == (size_t)AST::Node::Kind::Tilde;
+}
+
+bool ShellSyntaxHighlighter::is_navigatable(void* token) const
+{
+ if (!token)
+ return false;
+
+ auto kind = static_cast<AugmentedTokenKind>((size_t)token);
+ return (size_t)kind == (size_t)AST::Node::Kind::BarewordLiteral;
+}
+
+void ShellSyntaxHighlighter::rehighlight(Gfx::Palette palette)
+{
+ ASSERT(m_editor);
+ auto text = m_editor->text();
+
+ Parser parser(text);
+ auto ast = parser.parse();
+
+ Vector<GUI::TextDocumentSpan> spans;
+ GUI::TextPosition position { 0, 0 };
+ HighlightVisitor visitor { spans, palette, m_editor->document() };
+
+ if (ast)
+ ast->visit(visitor);
+
+ quick_sort(spans, [](auto& a, auto& b) { return a.range.start() < b.range.start(); });
+
+ m_editor->document().set_spans(spans);
+ m_has_brace_buddies = false;
+ highlight_matching_token_pair();
+ m_editor->update();
+}
+
+Vector<SyntaxHighlighter::MatchingTokenPair> ShellSyntaxHighlighter::matching_token_pairs() const
+{
+ static Vector<SyntaxHighlighter::MatchingTokenPair> pairs;
+ if (pairs.is_empty()) {
+ pairs.append({
+ (void*)static_cast<size_t>(AugmentedTokenKind::OpenParen),
+ (void*)static_cast<size_t>(AugmentedTokenKind::CloseParen),
+ });
+ }
+ return pairs;
+}
+
+bool ShellSyntaxHighlighter::token_types_equal(void* token0, void* token1) const
+{
+ return token0 == token1;
+}
+
+ShellSyntaxHighlighter::~ShellSyntaxHighlighter()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ShellSyntaxHighlighter.h b/Userland/Libraries/LibGUI/ShellSyntaxHighlighter.h
new file mode 100644
index 0000000000..7a1cf520d0
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ShellSyntaxHighlighter.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/SyntaxHighlighter.h>
+
+namespace GUI {
+
+class ShellSyntaxHighlighter : public SyntaxHighlighter {
+public:
+ ShellSyntaxHighlighter() { }
+ virtual ~ShellSyntaxHighlighter() override;
+
+ virtual bool is_identifier(void*) const override;
+ virtual bool is_navigatable(void*) const override;
+
+ virtual SyntaxLanguage language() const override { return SyntaxLanguage::Shell; }
+ virtual void rehighlight(Gfx::Palette) override;
+
+protected:
+ virtual Vector<MatchingTokenPair> matching_token_pairs() const override;
+ virtual bool token_types_equal(void*, void*) const override;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Shortcut.cpp b/Userland/Libraries/LibGUI/Shortcut.cpp
new file mode 100644
index 0000000000..5dfc3a5931
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Shortcut.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/Vector.h>
+#include <LibGUI/Shortcut.h>
+
+namespace GUI {
+
+String Shortcut::to_string() const
+{
+ Vector<String, 8> parts;
+
+ if (m_modifiers & Mod_Ctrl)
+ parts.append("Ctrl");
+ if (m_modifiers & Mod_Shift)
+ parts.append("Shift");
+ if (m_modifiers & Mod_Alt)
+ parts.append("Alt");
+ if (m_modifiers & Mod_Logo)
+ parts.append("Logo");
+
+ if (auto* key_name = key_code_to_string(m_key))
+ parts.append(key_name);
+ else
+ parts.append("(Invalid)");
+
+ StringBuilder builder;
+ builder.join('+', parts);
+ return builder.to_string();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Shortcut.h b/Userland/Libraries/LibGUI/Shortcut.h
new file mode 100644
index 0000000000..4283a65b7d
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Shortcut.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Traits.h>
+#include <Kernel/API/KeyCode.h>
+
+namespace GUI {
+
+class Shortcut {
+public:
+ Shortcut() { }
+ Shortcut(u8 modifiers, KeyCode key)
+ : m_modifiers(modifiers)
+ , m_key(key)
+ {
+ }
+
+ bool is_valid() const { return m_key != KeyCode::Key_Invalid; }
+ u8 modifiers() const { return m_modifiers; }
+ KeyCode key() const { return m_key; }
+ String to_string() const;
+
+ bool operator==(const Shortcut& other) const
+ {
+ return m_modifiers == other.m_modifiers
+ && m_key == other.m_key;
+ }
+
+private:
+ u8 m_modifiers { 0 };
+ KeyCode m_key { KeyCode::Key_Invalid };
+};
+
+}
+
+namespace AK {
+
+template<>
+struct Traits<GUI::Shortcut> : public GenericTraits<GUI::Shortcut> {
+ static unsigned hash(const GUI::Shortcut& shortcut)
+ {
+ return pair_int_hash(shortcut.modifiers(), shortcut.key());
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Slider.cpp b/Userland/Libraries/LibGUI/Slider.cpp
new file mode 100644
index 0000000000..9aac47ee11
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Slider.cpp
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/StdLibExtras.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/Slider.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+REGISTER_WIDGET(GUI, HorizontalSlider)
+REGISTER_WIDGET(GUI, Slider)
+REGISTER_WIDGET(GUI, VerticalSlider)
+
+namespace GUI {
+
+Slider::Slider(Orientation orientation)
+ : AbstractSlider(orientation)
+{
+ REGISTER_ENUM_PROPERTY("knob_size_mode", knob_size_mode, set_knob_size_mode, KnobSizeMode,
+ { KnobSizeMode::Fixed, "Fixed" },
+ { KnobSizeMode::Proportional, "Proportional" });
+}
+
+Slider::~Slider()
+{
+}
+
+void Slider::paint_event(PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ Gfx::IntRect track_rect;
+
+ if (orientation() == Orientation::Horizontal) {
+ track_rect = { inner_rect().x(), 0, inner_rect().width(), track_size() };
+ track_rect.center_vertically_within(inner_rect());
+ } else {
+ track_rect = { 0, inner_rect().y(), track_size(), inner_rect().height() };
+ track_rect.center_horizontally_within(inner_rect());
+ }
+ Gfx::StylePainter::paint_frame(painter, track_rect, palette(), Gfx::FrameShape::Panel, Gfx::FrameShadow::Sunken, 1);
+ if (is_enabled())
+ Gfx::StylePainter::paint_button(painter, knob_rect(), palette(), Gfx::ButtonStyle::Normal, false, m_knob_hovered);
+ else
+ Gfx::StylePainter::paint_button(painter, knob_rect(), palette(), Gfx::ButtonStyle::Normal, true, m_knob_hovered);
+}
+
+Gfx::IntRect Slider::knob_rect() const
+{
+ auto inner_rect = this->inner_rect();
+ Gfx::IntRect rect;
+ rect.set_secondary_offset_for_orientation(orientation(), 0);
+ rect.set_secondary_size_for_orientation(orientation(), knob_secondary_size());
+
+ if (knob_size_mode() == KnobSizeMode::Fixed) {
+ if (max() - min()) {
+ float scale = (float)inner_rect.primary_size_for_orientation(orientation()) / (float)(max() - min());
+ rect.set_primary_offset_for_orientation(orientation(), inner_rect.primary_offset_for_orientation(orientation()) + ((int)((value() - min()) * scale)) - (knob_fixed_primary_size() / 2));
+ } else
+ rect.set_primary_size_for_orientation(orientation(), 0);
+ rect.set_primary_size_for_orientation(orientation(), knob_fixed_primary_size());
+ } else {
+ float scale = (float)inner_rect.primary_size_for_orientation(orientation()) / (float)(max() - min() + 1);
+ rect.set_primary_offset_for_orientation(orientation(), inner_rect.primary_offset_for_orientation(orientation()) + ((int)((value() - min()) * scale)));
+ if (max() - min())
+ rect.set_primary_size_for_orientation(orientation(), ::max((int)(scale), knob_fixed_primary_size()));
+ else
+ rect.set_primary_size_for_orientation(orientation(), inner_rect.primary_size_for_orientation(orientation()));
+ }
+ if (orientation() == Orientation::Horizontal)
+ rect.center_vertically_within(inner_rect);
+ else
+ rect.center_horizontally_within(inner_rect);
+ return rect;
+}
+
+void Slider::mousedown_event(MouseEvent& event)
+{
+ if (event.button() == MouseButton::Left) {
+ if (knob_rect().contains(event.position())) {
+ m_dragging = true;
+ m_drag_origin = event.position();
+ m_drag_origin_value = value();
+ return;
+ } else {
+ if (event.position().primary_offset_for_orientation(orientation()) > knob_rect().last_edge_for_orientation(orientation()))
+ set_value(value() + page_step());
+ else if (event.position().primary_offset_for_orientation(orientation()) < knob_rect().first_edge_for_orientation(orientation()))
+ set_value(value() - page_step());
+ }
+ }
+ return Widget::mousedown_event(event);
+}
+
+void Slider::mousemove_event(MouseEvent& event)
+{
+ set_knob_hovered(knob_rect().contains(event.position()));
+ if (m_dragging) {
+ float delta = event.position().primary_offset_for_orientation(orientation()) - m_drag_origin.primary_offset_for_orientation(orientation());
+ float scrubbable_range = inner_rect().primary_size_for_orientation(orientation());
+ float value_steps_per_scrubbed_pixel = (max() - min()) / scrubbable_range;
+ float new_value = m_drag_origin_value + (value_steps_per_scrubbed_pixel * delta);
+ set_value((int)new_value);
+ return;
+ }
+ return Widget::mousemove_event(event);
+}
+
+void Slider::mouseup_event(MouseEvent& event)
+{
+ if (event.button() == MouseButton::Left) {
+ m_dragging = false;
+ return;
+ }
+
+ return Widget::mouseup_event(event);
+}
+
+void Slider::mousewheel_event(MouseEvent& event)
+{
+ auto acceleration_modifier = step();
+
+ if (event.modifiers() == KeyModifier::Mod_Ctrl && knob_size_mode() == KnobSizeMode::Fixed)
+ acceleration_modifier *= 6;
+
+ if (orientation() == Orientation::Horizontal)
+ set_value(value() - event.wheel_delta() * acceleration_modifier);
+ else
+ set_value(value() + event.wheel_delta() * acceleration_modifier);
+
+ Widget::mousewheel_event(event);
+}
+
+void Slider::leave_event(Core::Event& event)
+{
+ if (!is_enabled())
+ return;
+ set_knob_hovered(false);
+ Widget::leave_event(event);
+}
+
+void Slider::change_event(Event& event)
+{
+ if (event.type() == Event::Type::EnabledChange) {
+ if (!is_enabled())
+ m_dragging = false;
+ }
+ Widget::change_event(event);
+}
+
+void Slider::set_knob_hovered(bool hovered)
+{
+ if (m_knob_hovered == hovered)
+ return;
+ m_knob_hovered = hovered;
+ update(knob_rect());
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Slider.h b/Userland/Libraries/LibGUI/Slider.h
new file mode 100644
index 0000000000..65362b6b0a
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Slider.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/AbstractSlider.h>
+
+namespace GUI {
+
+class Slider : public AbstractSlider {
+ C_OBJECT(Slider);
+
+public:
+ enum class KnobSizeMode {
+ Fixed,
+ Proportional,
+ };
+
+ virtual ~Slider() override;
+
+ void set_knob_size_mode(KnobSizeMode mode) { m_knob_size_mode = mode; }
+ KnobSizeMode knob_size_mode() const { return m_knob_size_mode; }
+
+ int track_size() const { return 2; }
+ int knob_fixed_primary_size() const { return 8; }
+ int knob_secondary_size() const { return 20; }
+
+ bool knob_dragging() const { return m_dragging; }
+ Gfx::IntRect knob_rect() const;
+
+ Gfx::IntRect inner_rect() const
+ {
+ if (orientation() == Orientation::Horizontal)
+ return rect().shrunken(20, 0);
+ return rect().shrunken(0, 20);
+ }
+
+protected:
+ explicit Slider(Orientation = Orientation::Vertical);
+
+ virtual void paint_event(PaintEvent&) override;
+ virtual void mousedown_event(MouseEvent&) override;
+ virtual void mousemove_event(MouseEvent&) override;
+ virtual void mouseup_event(MouseEvent&) override;
+ virtual void mousewheel_event(MouseEvent&) override;
+ virtual void leave_event(Core::Event&) override;
+ virtual void change_event(Event&) override;
+
+private:
+ void set_knob_hovered(bool);
+
+ bool m_knob_hovered { false };
+ bool m_dragging { false };
+ int m_drag_origin_value { 0 };
+ Gfx::IntPoint m_drag_origin;
+ KnobSizeMode m_knob_size_mode { KnobSizeMode::Fixed };
+};
+
+class VerticalSlider final : public Slider {
+ C_OBJECT(VerticalSlider);
+
+public:
+ virtual ~VerticalSlider() override { }
+
+private:
+ VerticalSlider()
+ : Slider(Orientation::Vertical)
+ {
+ }
+};
+
+class HorizontalSlider final : public Slider {
+ C_OBJECT(HorizontalSlider);
+
+public:
+ virtual ~HorizontalSlider() override { }
+
+private:
+ HorizontalSlider()
+ : Slider(Orientation::Horizontal)
+ {
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/SortingProxyModel.cpp b/Userland/Libraries/LibGUI/SortingProxyModel.cpp
new file mode 100644
index 0000000000..a0618ae410
--- /dev/null
+++ b/Userland/Libraries/LibGUI/SortingProxyModel.cpp
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/QuickSort.h>
+#include <LibGUI/AbstractView.h>
+#include <LibGUI/SortingProxyModel.h>
+
+namespace GUI {
+
+SortingProxyModel::SortingProxyModel(NonnullRefPtr<Model> source)
+ : m_source(move(source))
+{
+ m_source->register_client(*this);
+ invalidate();
+}
+
+SortingProxyModel::~SortingProxyModel()
+{
+ m_source->unregister_client(*this);
+}
+
+void SortingProxyModel::invalidate(unsigned int flags)
+{
+ if (flags == UpdateFlag::DontInvalidateIndexes) {
+ sort(m_last_key_column, m_last_sort_order);
+ } else {
+ m_mappings.clear();
+
+ // FIXME: This is really harsh, but without precise invalidation, not much we can do.
+ for_each_view([&](auto& view) {
+ view.set_cursor({}, AbstractView::SelectionUpdate::None);
+ view.selection().clear();
+ });
+ }
+ did_update(flags);
+}
+
+void SortingProxyModel::model_did_update(unsigned flags)
+{
+ invalidate(flags);
+}
+
+bool SortingProxyModel::accepts_drag(const ModelIndex& proxy_index, const Vector<String>& mime_types) const
+{
+ return source().accepts_drag(map_to_source(proxy_index), mime_types);
+}
+
+int SortingProxyModel::row_count(const ModelIndex& proxy_index) const
+{
+ return source().row_count(map_to_source(proxy_index));
+}
+
+int SortingProxyModel::column_count(const ModelIndex& proxy_index) const
+{
+ return source().column_count(map_to_source(proxy_index));
+}
+
+ModelIndex SortingProxyModel::map_to_source(const ModelIndex& proxy_index) const
+{
+ if (!proxy_index.is_valid())
+ return {};
+
+ ASSERT(proxy_index.model() == this);
+ ASSERT(proxy_index.internal_data());
+
+ auto& index_mapping = *static_cast<Mapping*>(proxy_index.internal_data());
+ auto it = m_mappings.find(index_mapping.source_parent);
+ ASSERT(it != m_mappings.end());
+
+ auto& mapping = *it->value;
+ if (static_cast<size_t>(proxy_index.row()) >= mapping.source_rows.size() || proxy_index.column() >= column_count())
+ return {};
+ int source_row = mapping.source_rows[proxy_index.row()];
+ int source_column = proxy_index.column();
+ return source().index(source_row, source_column, it->key);
+}
+
+ModelIndex SortingProxyModel::map_to_proxy(const ModelIndex& source_index) const
+{
+ if (!source_index.is_valid())
+ return {};
+
+ ASSERT(source_index.model() == m_source);
+
+ auto source_parent = source_index.parent();
+ auto it = const_cast<SortingProxyModel*>(this)->build_mapping(source_parent);
+
+ auto& mapping = *it->value;
+
+ if (source_index.row() >= static_cast<int>(mapping.proxy_rows.size()) || source_index.column() >= column_count())
+ return {};
+
+ int proxy_row = mapping.proxy_rows[source_index.row()];
+ int proxy_column = source_index.column();
+ if (proxy_row < 0 || proxy_column < 0)
+ return {};
+ return create_index(proxy_row, proxy_column, &mapping);
+}
+
+String SortingProxyModel::column_name(int column) const
+{
+ return source().column_name(column);
+}
+
+Variant SortingProxyModel::data(const ModelIndex& proxy_index, ModelRole role) const
+{
+ return source().data(map_to_source(proxy_index), role);
+}
+
+void SortingProxyModel::update()
+{
+ source().update();
+}
+
+StringView SortingProxyModel::drag_data_type() const
+{
+ return source().drag_data_type();
+}
+
+bool SortingProxyModel::less_than(const ModelIndex& index1, const ModelIndex& index2) const
+{
+ auto data1 = index1.data(m_sort_role);
+ auto data2 = index2.data(m_sort_role);
+ if (data1.is_string() && data2.is_string())
+ return data1.as_string().to_lowercase() < data2.as_string().to_lowercase();
+ return data1 < data2;
+}
+
+ModelIndex SortingProxyModel::index(int row, int column, const ModelIndex& parent) const
+{
+ if (row < 0 || column < 0)
+ return {};
+
+ auto source_parent = map_to_source(parent);
+ const_cast<SortingProxyModel*>(this)->build_mapping(source_parent);
+
+ auto it = m_mappings.find(source_parent);
+ ASSERT(it != m_mappings.end());
+ auto& mapping = *it->value;
+ if (row >= static_cast<int>(mapping.source_rows.size()) || column >= column_count())
+ return {};
+ return create_index(row, column, &mapping);
+}
+
+ModelIndex SortingProxyModel::parent_index(const ModelIndex& proxy_index) const
+{
+ if (!proxy_index.is_valid())
+ return {};
+
+ ASSERT(proxy_index.model() == this);
+ ASSERT(proxy_index.internal_data());
+
+ auto& index_mapping = *static_cast<Mapping*>(proxy_index.internal_data());
+ auto it = m_mappings.find(index_mapping.source_parent);
+ ASSERT(it != m_mappings.end());
+
+ return map_to_proxy(it->value->source_parent);
+}
+
+void SortingProxyModel::sort_mapping(Mapping& mapping, int column, SortOrder sort_order)
+{
+ if (column == -1) {
+ int row_count = source().row_count(mapping.source_parent);
+ for (int i = 0; i < row_count; ++i) {
+ mapping.source_rows[i] = i;
+ mapping.proxy_rows[i] = i;
+ }
+ return;
+ }
+
+ auto old_source_rows = mapping.source_rows;
+
+ int row_count = source().row_count(mapping.source_parent);
+ for (int i = 0; i < row_count; ++i)
+ mapping.source_rows[i] = i;
+
+ quick_sort(mapping.source_rows, [&](auto row1, auto row2) -> bool {
+ bool is_less_than = less_than(source().index(row1, column, mapping.source_parent), source().index(row2, column, mapping.source_parent));
+ return sort_order == SortOrder::Ascending ? is_less_than : !is_less_than;
+ });
+
+ for (int i = 0; i < row_count; ++i)
+ mapping.proxy_rows[mapping.source_rows[i]] = i;
+
+ // FIXME: I really feel like this should be done at the view layer somehow.
+ for_each_view([&](AbstractView& view) {
+ // Update the view's cursor.
+ auto cursor = view.cursor_index();
+ if (cursor.is_valid() && cursor.parent() == mapping.source_parent) {
+ for (size_t i = 0; i < mapping.source_rows.size(); ++i) {
+ if (mapping.source_rows[i] == view.cursor_index().row()) {
+ auto new_source_index = this->index(i, view.cursor_index().column(), mapping.source_parent);
+ view.set_cursor(new_source_index, AbstractView::SelectionUpdate::None, false);
+ break;
+ }
+ }
+ }
+
+ // Update the view's selection.
+ view.selection().change_from_model({}, [&](ModelSelection& selection) {
+ Vector<ModelIndex> selected_indexes_in_source;
+ Vector<ModelIndex> stale_indexes_in_selection;
+ selection.for_each_index([&](const ModelIndex& index) {
+ if (index.parent() == mapping.source_parent) {
+ stale_indexes_in_selection.append(index);
+ selected_indexes_in_source.append(source().index(old_source_rows[index.row()], index.column(), mapping.source_parent));
+ }
+ });
+
+ for (auto& index : stale_indexes_in_selection) {
+ selection.remove(index);
+ }
+
+ for (auto& index : selected_indexes_in_source) {
+ for (size_t i = 0; i < mapping.source_rows.size(); ++i) {
+ if (mapping.source_rows[i] == index.row()) {
+ auto new_source_index = this->index(i, index.column(), mapping.source_parent);
+ selection.add(new_source_index);
+ break;
+ }
+ }
+ }
+ });
+ });
+}
+
+void SortingProxyModel::sort(int column, SortOrder sort_order)
+{
+ for (auto& it : m_mappings) {
+ auto& mapping = *it.value;
+ sort_mapping(mapping, column, sort_order);
+ }
+
+ m_last_key_column = column;
+ m_last_sort_order = sort_order;
+
+ did_update(UpdateFlag::DontInvalidateIndexes);
+}
+
+SortingProxyModel::InternalMapIterator SortingProxyModel::build_mapping(const ModelIndex& source_parent)
+{
+ auto it = m_mappings.find(source_parent);
+ if (it != m_mappings.end())
+ return it;
+
+ auto mapping = make<Mapping>();
+
+ mapping->source_parent = source_parent;
+
+ int row_count = source().row_count(source_parent);
+ mapping->source_rows.resize(row_count);
+ mapping->proxy_rows.resize(row_count);
+
+ sort_mapping(*mapping, m_last_key_column, m_last_sort_order);
+
+ if (source_parent.is_valid()) {
+ auto source_grand_parent = source_parent.parent();
+ build_mapping(source_grand_parent);
+ }
+
+ m_mappings.set(source_parent, move(mapping));
+ return m_mappings.find(source_parent);
+}
+
+bool SortingProxyModel::is_column_sortable(int column_index) const
+{
+ return source().is_column_sortable(column_index);
+}
+
+bool SortingProxyModel::is_editable(const ModelIndex& proxy_index) const
+{
+ return source().is_editable(map_to_source(proxy_index));
+}
+
+void SortingProxyModel::set_data(const ModelIndex& proxy_index, const Variant& data)
+{
+ source().set_data(map_to_source(proxy_index), data);
+}
+
+bool SortingProxyModel::is_searchable() const
+{
+ return source().is_searchable();
+}
+
+Vector<ModelIndex, 1> SortingProxyModel::matches(const StringView& searching, unsigned flags, const ModelIndex& proxy_index)
+{
+ auto found_indexes = source().matches(searching, flags, map_to_source(proxy_index));
+ for (size_t i = 0; i < found_indexes.size(); i++)
+ found_indexes[i] = map_to_proxy(found_indexes[i]);
+ return found_indexes;
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/SortingProxyModel.h b/Userland/Libraries/LibGUI/SortingProxyModel.h
new file mode 100644
index 0000000000..3398c6a89f
--- /dev/null
+++ b/Userland/Libraries/LibGUI/SortingProxyModel.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Model.h>
+
+namespace GUI {
+
+class SortingProxyModel
+ : public Model
+ , private ModelClient {
+public:
+ static NonnullRefPtr<SortingProxyModel> create(NonnullRefPtr<Model> source) { return adopt(*new SortingProxyModel(move(source))); }
+ virtual ~SortingProxyModel() override;
+
+ virtual int row_count(const ModelIndex& = ModelIndex()) const override;
+ virtual int column_count(const ModelIndex& = ModelIndex()) const override;
+ virtual String column_name(int) const override;
+ virtual Variant data(const ModelIndex&, ModelRole = ModelRole::Display) const override;
+ virtual void update() override;
+ virtual StringView drag_data_type() const override;
+ virtual ModelIndex parent_index(const ModelIndex&) const override;
+ virtual ModelIndex index(int row, int column, const ModelIndex& parent) const override;
+ virtual bool is_editable(const ModelIndex&) const override;
+ virtual bool is_searchable() const override;
+ virtual void set_data(const ModelIndex&, const Variant&) override;
+ virtual Vector<ModelIndex, 1> matches(const StringView&, unsigned = MatchesFlag::AllMatching, const ModelIndex& = ModelIndex()) override;
+ virtual bool accepts_drag(const ModelIndex&, const Vector<String>& mime_types) const override;
+
+ virtual bool is_column_sortable(int column_index) const override;
+
+ virtual bool less_than(const ModelIndex&, const ModelIndex&) const;
+
+ ModelIndex map_to_source(const ModelIndex&) const;
+ ModelIndex map_to_proxy(const ModelIndex&) const;
+
+ ModelRole sort_role() const { return m_sort_role; }
+ void set_sort_role(ModelRole role) { m_sort_role = role; }
+
+ virtual void sort(int column, SortOrder) override;
+
+private:
+ explicit SortingProxyModel(NonnullRefPtr<Model> source);
+
+ // NOTE: The internal_data() of indexes points to the corresponding Mapping object for that index.
+ struct Mapping {
+ Vector<int> source_rows;
+ Vector<int> proxy_rows;
+ ModelIndex source_parent;
+ };
+
+ using InternalMapIterator = HashMap<ModelIndex, NonnullOwnPtr<Mapping>>::IteratorType;
+
+ void sort_mapping(Mapping&, int column, SortOrder);
+
+ // ^ModelClient
+ virtual void model_did_update(unsigned) override;
+
+ Model& source() { return *m_source; }
+ const Model& source() const { return *m_source; }
+
+ void invalidate(unsigned flags = Model::UpdateFlag::DontInvalidateIndexes);
+ InternalMapIterator build_mapping(const ModelIndex& proxy_index);
+
+ NonnullRefPtr<Model> m_source;
+
+ HashMap<ModelIndex, NonnullOwnPtr<Mapping>> m_mappings;
+ ModelRole m_sort_role { ModelRole::Sort };
+ int m_last_key_column { -1 };
+ SortOrder m_last_sort_order { SortOrder::Ascending };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/SpinBox.cpp b/Userland/Libraries/LibGUI/SpinBox.cpp
new file mode 100644
index 0000000000..943497c918
--- /dev/null
+++ b/Userland/Libraries/LibGUI/SpinBox.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/ControlBoxButton.h>
+#include <LibGUI/SpinBox.h>
+#include <LibGUI/TextBox.h>
+
+REGISTER_WIDGET(GUI, SpinBox)
+
+namespace GUI {
+
+SpinBox::SpinBox()
+{
+ set_min_width(32);
+ set_fixed_height(22);
+ m_editor = add<TextBox>();
+ m_editor->set_text("0");
+ m_editor->on_change = [this] {
+ auto value = m_editor->text().to_uint();
+ if (value.has_value())
+ set_value(value.value());
+ else
+ m_editor->set_text(String::number(m_value));
+ };
+ m_editor->on_up_pressed = [this] {
+ set_value(m_value + 1);
+ };
+ m_editor->on_down_pressed = [this] {
+ set_value(m_value - 1);
+ };
+
+ m_increment_button = add<ControlBoxButton>(ControlBoxButton::UpArrow);
+ m_increment_button->set_focus_policy(GUI::FocusPolicy::NoFocus);
+ m_increment_button->on_click = [this](auto) { set_value(m_value + 1); };
+ m_increment_button->set_auto_repeat_interval(150);
+ m_decrement_button = add<ControlBoxButton>(ControlBoxButton::DownArrow);
+ m_decrement_button->set_focus_policy(GUI::FocusPolicy::NoFocus);
+ m_decrement_button->on_click = [this](auto) { set_value(m_value - 1); };
+ m_decrement_button->set_auto_repeat_interval(150);
+
+ REGISTER_INT_PROPERTY("min", min, set_min);
+ REGISTER_INT_PROPERTY("max", max, set_max);
+}
+
+SpinBox::~SpinBox()
+{
+}
+
+void SpinBox::set_value(int value)
+{
+ value = clamp(value, m_min, m_max);
+ if (m_value == value)
+ return;
+ m_value = value;
+ m_editor->set_text(String::number(value));
+ update();
+ if (on_change)
+ on_change(value);
+}
+
+void SpinBox::set_range(int min, int max)
+{
+ ASSERT(min <= max);
+ if (m_min == min && m_max == max)
+ return;
+
+ m_min = min;
+ m_max = max;
+
+ int old_value = m_value;
+ m_value = clamp(m_value, m_min, m_max);
+ if (m_value != old_value) {
+ m_editor->set_text(String::number(m_value));
+ if (on_change)
+ on_change(m_value);
+ }
+
+ update();
+}
+
+void SpinBox::mousewheel_event(MouseEvent& event)
+{
+ set_value(m_value - event.wheel_delta());
+}
+
+void SpinBox::resize_event(ResizeEvent& event)
+{
+ int frame_thickness = m_editor->frame_thickness();
+ int button_height = (event.size().height() / 2) - frame_thickness;
+ int button_width = 15;
+ m_increment_button->set_relative_rect(width() - button_width - frame_thickness, frame_thickness, button_width, button_height);
+ m_decrement_button->set_relative_rect(width() - button_width - frame_thickness, frame_thickness + button_height, button_width, button_height);
+ m_editor->set_relative_rect(0, 0, width(), height());
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/SpinBox.h b/Userland/Libraries/LibGUI/SpinBox.h
new file mode 100644
index 0000000000..4fb4a8fcf9
--- /dev/null
+++ b/Userland/Libraries/LibGUI/SpinBox.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class ControlBoxButton;
+
+class SpinBox : public Widget {
+ C_OBJECT(SpinBox)
+public:
+ virtual ~SpinBox() override;
+
+ int value() const { return m_value; }
+ void set_value(int);
+
+ int min() const { return m_min; }
+ int max() const { return m_max; }
+ void set_min(int min) { set_range(min, max()); }
+ void set_max(int max) { set_range(min(), max); }
+ void set_range(int min, int max);
+
+ Function<void(int value)> on_change;
+
+protected:
+ SpinBox();
+
+ virtual void mousewheel_event(MouseEvent&) override;
+ virtual void resize_event(ResizeEvent&) override;
+
+private:
+ RefPtr<TextEditor> m_editor;
+ RefPtr<ControlBoxButton> m_increment_button;
+ RefPtr<ControlBoxButton> m_decrement_button;
+
+ int m_min { 0 };
+ int m_max { 100 };
+ int m_value { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Splitter.cpp b/Userland/Libraries/LibGUI/Splitter.cpp
new file mode 100644
index 0000000000..246ade1ac4
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Splitter.cpp
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/Splitter.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/Palette.h>
+
+REGISTER_WIDGET(GUI, HorizontalSplitter)
+REGISTER_WIDGET(GUI, VerticalSplitter)
+
+namespace GUI {
+
+Splitter::Splitter(Orientation orientation)
+ : m_orientation(orientation)
+{
+ set_background_role(ColorRole::Button);
+ set_layout<BoxLayout>(orientation);
+ set_fill_with_background_color(true);
+ layout()->set_spacing(3);
+}
+
+Splitter::~Splitter()
+{
+}
+
+void Splitter::paint_event(PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+ painter.fill_rect(m_grabbable_rect, palette().hover_highlight());
+}
+
+void Splitter::resize_event(ResizeEvent& event)
+{
+ Widget::resize_event(event);
+ m_grabbable_rect = {};
+}
+
+void Splitter::override_cursor(bool do_override)
+{
+ if (do_override) {
+ if (!m_overriding_cursor) {
+ set_override_cursor(m_orientation == Orientation::Horizontal ? Gfx::StandardCursor::ResizeColumn : Gfx::StandardCursor::ResizeRow);
+ m_overriding_cursor = true;
+ }
+ } else {
+ if (m_overriding_cursor) {
+ set_override_cursor(Gfx::StandardCursor::None);
+ m_overriding_cursor = false;
+ }
+ }
+}
+
+void Splitter::leave_event(Core::Event&)
+{
+ if (!m_resizing)
+ override_cursor(false);
+ if (!m_grabbable_rect.is_empty()) {
+ m_grabbable_rect = {};
+ update();
+ }
+}
+
+bool Splitter::get_resize_candidates_at(const Gfx::IntPoint& position, Widget*& first, Widget*& second)
+{
+ int x_or_y = position.primary_offset_for_orientation(m_orientation);
+ Widget* previous_widget = nullptr;
+ bool found_candidates = false;
+ for_each_child_widget([&](auto& child_widget) {
+ if (!child_widget.is_visible()) {
+ // We need to skip over widgets that are not visible as they
+ // are not necessarily in the correct location (anymore)
+ return IterationDecision::Continue;
+ }
+ if (!previous_widget) {
+ previous_widget = &child_widget;
+ return IterationDecision::Continue;
+ }
+
+ if (x_or_y > previous_widget->content_rect().last_edge_for_orientation(m_orientation)
+ && x_or_y <= child_widget.content_rect().first_edge_for_orientation(m_orientation)) {
+ first = previous_widget;
+ second = &child_widget;
+ found_candidates = true;
+ return IterationDecision::Break;
+ }
+
+ previous_widget = &child_widget;
+ return IterationDecision::Continue;
+ });
+ return found_candidates;
+}
+
+void Splitter::mousedown_event(MouseEvent& event)
+{
+ if (event.button() != MouseButton::Left)
+ return;
+ m_resizing = true;
+
+ Widget* first { nullptr };
+ Widget* second { nullptr };
+ if (!get_resize_candidates_at(event.position(), first, second))
+ return;
+
+ m_first_resizee = *first;
+ m_second_resizee = *second;
+ m_first_resizee_start_size = first->size();
+ m_second_resizee_start_size = second->size();
+ m_resize_origin = event.position();
+}
+
+void Splitter::recompute_grabbable_rect(const Widget& first, const Widget& second)
+{
+ auto first_edge = first.content_rect().primary_offset_for_orientation(m_orientation) + first.content_rect().primary_size_for_orientation(m_orientation);
+ auto second_edge = second.content_rect().primary_offset_for_orientation(m_orientation);
+ Gfx::IntRect rect;
+ rect.set_primary_offset_for_orientation(m_orientation, first_edge);
+ rect.set_primary_size_for_orientation(m_orientation, second_edge - first_edge);
+ rect.set_secondary_offset_for_orientation(m_orientation, first.content_rect().secondary_offset_for_orientation(m_orientation));
+ rect.set_secondary_size_for_orientation(m_orientation, first.content_rect().secondary_size_for_orientation(m_orientation));
+
+ if (m_grabbable_rect != rect) {
+ m_grabbable_rect = rect;
+ update();
+ }
+}
+
+void Splitter::mousemove_event(MouseEvent& event)
+{
+ if (!m_resizing) {
+ Widget* first { nullptr };
+ Widget* second { nullptr };
+ if (!get_resize_candidates_at(event.position(), first, second)) {
+ override_cursor(false);
+ return;
+ }
+ recompute_grabbable_rect(*first, *second);
+ override_cursor(m_grabbable_rect.contains(event.position()));
+ return;
+ }
+ auto delta = event.position() - m_resize_origin;
+ if (!m_first_resizee || !m_second_resizee) {
+ // One or both of the resizees were deleted during an ongoing resize, screw this.
+ m_resizing = false;
+ return;
+ }
+ int minimum_size = 0;
+ auto new_first_resizee_size = m_first_resizee_start_size;
+ auto new_second_resizee_size = m_second_resizee_start_size;
+
+ new_first_resizee_size.set_primary_size_for_orientation(m_orientation, new_first_resizee_size.primary_size_for_orientation(m_orientation) + delta.primary_offset_for_orientation(m_orientation));
+ new_second_resizee_size.set_primary_size_for_orientation(m_orientation, new_second_resizee_size.primary_size_for_orientation(m_orientation) - delta.primary_offset_for_orientation(m_orientation));
+
+ if (new_first_resizee_size.primary_size_for_orientation(m_orientation) < minimum_size) {
+ int correction = minimum_size - new_first_resizee_size.primary_size_for_orientation(m_orientation);
+ new_first_resizee_size.set_primary_size_for_orientation(m_orientation, new_first_resizee_size.primary_size_for_orientation(m_orientation) + correction);
+ new_second_resizee_size.set_primary_size_for_orientation(m_orientation, new_second_resizee_size.primary_size_for_orientation(m_orientation) - correction);
+ }
+ if (new_second_resizee_size.primary_size_for_orientation(m_orientation) < minimum_size) {
+ int correction = minimum_size - new_second_resizee_size.primary_size_for_orientation(m_orientation);
+ new_second_resizee_size.set_primary_size_for_orientation(m_orientation, new_second_resizee_size.primary_size_for_orientation(m_orientation) + correction);
+ new_first_resizee_size.set_primary_size_for_orientation(m_orientation, new_first_resizee_size.primary_size_for_orientation(m_orientation) - correction);
+ }
+
+ if (m_orientation == Orientation::Horizontal) {
+ m_first_resizee->set_fixed_width(new_first_resizee_size.width());
+ m_second_resizee->set_fixed_width(-1);
+ } else {
+ m_first_resizee->set_fixed_height(new_first_resizee_size.height());
+ m_second_resizee->set_fixed_height(-1);
+ }
+
+ invalidate_layout();
+}
+
+void Splitter::did_layout()
+{
+ if (m_first_resizee && m_second_resizee)
+ recompute_grabbable_rect(*m_first_resizee, *m_second_resizee);
+}
+
+void Splitter::mouseup_event(MouseEvent& event)
+{
+ if (event.button() != MouseButton::Left)
+ return;
+ m_resizing = false;
+ m_first_resizee = nullptr;
+ m_second_resizee = nullptr;
+ if (!rect().contains(event.position()))
+ set_override_cursor(Gfx::StandardCursor::None);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Splitter.h b/Userland/Libraries/LibGUI/Splitter.h
new file mode 100644
index 0000000000..d8a97023bb
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Splitter.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class Splitter : public Widget {
+ C_OBJECT(Splitter);
+
+public:
+ virtual ~Splitter() override;
+
+protected:
+ explicit Splitter(Gfx::Orientation);
+
+ virtual void paint_event(PaintEvent&) override;
+ virtual void resize_event(ResizeEvent&) override;
+ virtual void mousedown_event(MouseEvent&) override;
+ virtual void mousemove_event(MouseEvent&) override;
+ virtual void mouseup_event(MouseEvent&) override;
+ virtual void leave_event(Core::Event&) override;
+
+ virtual void did_layout() override;
+
+private:
+ void recompute_grabbable_rect(const Widget&, const Widget&);
+ bool get_resize_candidates_at(const Gfx::IntPoint&, Widget*&, Widget*&);
+ void override_cursor(bool do_override);
+
+ Gfx::Orientation m_orientation;
+ bool m_resizing { false };
+ bool m_overriding_cursor { false };
+ Gfx::IntPoint m_resize_origin;
+ WeakPtr<Widget> m_first_resizee;
+ WeakPtr<Widget> m_second_resizee;
+ Gfx::IntSize m_first_resizee_start_size;
+ Gfx::IntSize m_second_resizee_start_size;
+ Gfx::IntRect m_grabbable_rect;
+};
+
+class VerticalSplitter final : public Splitter {
+ C_OBJECT(VerticalSplitter)
+public:
+ virtual ~VerticalSplitter() override { }
+
+private:
+ VerticalSplitter()
+ : Splitter(Gfx::Orientation::Vertical)
+ {
+ }
+};
+
+class HorizontalSplitter final : public Splitter {
+ C_OBJECT(HorizontalSplitter)
+public:
+ virtual ~HorizontalSplitter() override { }
+
+private:
+ HorizontalSplitter()
+ : Splitter(Gfx::Orientation::Horizontal)
+ {
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/StackWidget.cpp b/Userland/Libraries/LibGUI/StackWidget.cpp
new file mode 100644
index 0000000000..531346195e
--- /dev/null
+++ b/Userland/Libraries/LibGUI/StackWidget.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/StackWidget.h>
+#include <LibGUI/Window.h>
+
+namespace GUI {
+
+StackWidget::StackWidget()
+{
+}
+
+StackWidget::~StackWidget()
+{
+}
+
+void StackWidget::set_active_widget(Widget* widget)
+{
+ if (widget == m_active_widget)
+ return;
+
+ bool active_widget_had_focus = m_active_widget && m_active_widget->has_focus_within();
+
+ if (m_active_widget)
+ m_active_widget->set_visible(false);
+ m_active_widget = widget;
+ if (m_active_widget) {
+ m_active_widget->set_relative_rect(rect());
+ if (active_widget_had_focus)
+ m_active_widget->set_focus(true);
+ m_active_widget->set_visible(true);
+ }
+
+ set_focus_proxy(m_active_widget);
+
+ if (on_active_widget_change)
+ on_active_widget_change(m_active_widget);
+}
+
+void StackWidget::resize_event(ResizeEvent& event)
+{
+ if (!m_active_widget)
+ return;
+ m_active_widget->set_relative_rect({ {}, event.size() });
+}
+
+void StackWidget::child_event(Core::ChildEvent& event)
+{
+ if (!event.child() || !is<Widget>(*event.child()))
+ return Widget::child_event(event);
+ auto& child = downcast<Widget>(*event.child());
+ if (event.type() == Event::ChildAdded) {
+ if (!m_active_widget)
+ set_active_widget(&child);
+ else if (m_active_widget != &child)
+ child.set_visible(false);
+ } else if (event.type() == Event::ChildRemoved) {
+ if (m_active_widget == &child) {
+ Widget* new_active_widget = nullptr;
+ for_each_child_widget([&](auto& new_child) {
+ new_active_widget = &new_child;
+ return IterationDecision::Break;
+ });
+ set_active_widget(new_active_widget);
+ }
+ }
+ Widget::child_event(event);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/StackWidget.h b/Userland/Libraries/LibGUI/StackWidget.h
new file mode 100644
index 0000000000..0d1d66b31f
--- /dev/null
+++ b/Userland/Libraries/LibGUI/StackWidget.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class StackWidget : public Widget {
+ C_OBJECT(StackWidget)
+public:
+ virtual ~StackWidget() override;
+
+ Widget* active_widget() { return m_active_widget.ptr(); }
+ const Widget* active_widget() const { return m_active_widget.ptr(); }
+ void set_active_widget(Widget*);
+
+ Function<void(Widget*)> on_active_widget_change;
+
+protected:
+ StackWidget();
+ virtual void child_event(Core::ChildEvent&) override;
+ virtual void resize_event(ResizeEvent&) override;
+
+private:
+ RefPtr<Widget> m_active_widget;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/StatusBar.cpp b/Userland/Libraries/LibGUI/StatusBar.cpp
new file mode 100644
index 0000000000..c32b45cf0b
--- /dev/null
+++ b/Userland/Libraries/LibGUI/StatusBar.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ResizeCorner.h>
+#include <LibGUI/StatusBar.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+REGISTER_WIDGET(GUI, StatusBar)
+
+namespace GUI {
+
+StatusBar::StatusBar(int label_count)
+{
+ set_fixed_height(18);
+ set_layout<HorizontalBoxLayout>();
+ layout()->set_margins({ 0, 0, 0, 0 });
+ layout()->set_spacing(2);
+
+ if (label_count < 1)
+ label_count = 1;
+
+ for (auto i = 0; i < label_count; i++)
+ m_labels.append(create_label());
+
+ m_corner = add<ResizeCorner>();
+
+ REGISTER_STRING_PROPERTY("text", text, set_text);
+}
+
+StatusBar::~StatusBar()
+{
+}
+
+NonnullRefPtr<Label> StatusBar::create_label()
+{
+ auto& label = add<Label>();
+ label.set_frame_shadow(Gfx::FrameShadow::Sunken);
+ label.set_frame_shape(Gfx::FrameShape::Panel);
+ label.set_frame_thickness(1);
+ label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
+ return label;
+}
+
+void StatusBar::set_text(const StringView& text)
+{
+ m_labels.first().set_text(text);
+}
+
+String StatusBar::text() const
+{
+ return m_labels.first().text();
+}
+
+void StatusBar::set_text(int index, const StringView& text)
+{
+ m_labels.at(index).set_text(text);
+}
+
+String StatusBar::text(int index) const
+{
+ return m_labels.at(index).text();
+}
+
+void StatusBar::paint_event(PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+ painter.fill_rect(rect(), palette().button());
+}
+
+void StatusBar::resize_event(ResizeEvent& event)
+{
+ if (window())
+ m_corner->set_visible(window()->is_maximized() ? false : true);
+
+ Widget::resize_event(event);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/StatusBar.h b/Userland/Libraries/LibGUI/StatusBar.h
new file mode 100644
index 0000000000..9615d9374f
--- /dev/null
+++ b/Userland/Libraries/LibGUI/StatusBar.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class StatusBar : public Widget {
+ C_OBJECT(StatusBar)
+public:
+ virtual ~StatusBar() override;
+
+ String text() const;
+ String text(int index) const;
+ void set_text(const StringView&);
+ void set_text(int index, const StringView&);
+
+protected:
+ explicit StatusBar(int label_count = 1);
+ virtual void paint_event(PaintEvent&) override;
+ virtual void resize_event(ResizeEvent&) override;
+
+private:
+ NonnullRefPtr<Label> create_label();
+ NonnullRefPtrVector<Label> m_labels;
+ RefPtr<ResizeCorner> m_corner;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/SyntaxHighlighter.cpp b/Userland/Libraries/LibGUI/SyntaxHighlighter.cpp
new file mode 100644
index 0000000000..032adc65d7
--- /dev/null
+++ b/Userland/Libraries/LibGUI/SyntaxHighlighter.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/SyntaxHighlighter.h>
+#include <LibGUI/TextEditor.h>
+
+namespace GUI {
+
+SyntaxHighlighter::~SyntaxHighlighter()
+{
+}
+
+void SyntaxHighlighter::highlight_matching_token_pair()
+{
+ ASSERT(m_editor);
+ auto& document = m_editor->document();
+
+ enum class Direction {
+ Forward,
+ Backward,
+ };
+
+ auto find_span_of_type = [&](auto i, void* type, void* not_type, Direction direction) -> Optional<size_t> {
+ size_t nesting_level = 0;
+ bool forward = direction == Direction::Forward;
+
+ if (forward) {
+ ++i;
+ if (i >= document.spans().size())
+ return {};
+ } else {
+ if (i == 0)
+ return {};
+ --i;
+ }
+
+ for (;;) {
+ auto& span = document.spans().at(i);
+ auto span_token_type = span.data;
+ if (token_types_equal(span_token_type, not_type)) {
+ ++nesting_level;
+ } else if (token_types_equal(span_token_type, type)) {
+ if (nesting_level-- <= 0)
+ return i;
+ }
+
+ if (forward) {
+ ++i;
+ if (i >= document.spans().size())
+ return {};
+ } else {
+ if (i == 0)
+ return {};
+ --i;
+ }
+ }
+
+ return {};
+ };
+
+ auto make_buddies = [&](int index0, int index1) {
+ auto& buddy0 = document.spans()[index0];
+ auto& buddy1 = document.spans()[index1];
+ m_has_brace_buddies = true;
+ m_brace_buddies[0].index = index0;
+ m_brace_buddies[1].index = index1;
+ m_brace_buddies[0].span_backup = buddy0;
+ m_brace_buddies[1].span_backup = buddy1;
+ buddy0.attributes.background_color = Color::DarkCyan;
+ buddy1.attributes.background_color = Color::DarkCyan;
+ buddy0.attributes.color = Color::White;
+ buddy1.attributes.color = Color::White;
+ m_editor->update();
+ };
+
+ auto pairs = matching_token_pairs();
+
+ for (size_t i = 0; i < document.spans().size(); ++i) {
+ auto& span = const_cast<GUI::TextDocumentSpan&>(document.spans().at(i));
+ auto token_type = span.data;
+
+ for (auto& pair : pairs) {
+ if (token_types_equal(token_type, pair.open) && span.range.start() == m_editor->cursor()) {
+ auto buddy = find_span_of_type(i, pair.close, pair.open, Direction::Forward);
+ if (buddy.has_value())
+ make_buddies(i, buddy.value());
+ return;
+ }
+ }
+
+ auto right_of_end = span.range.end();
+ right_of_end.set_column(right_of_end.column() + 1);
+
+ for (auto& pair : pairs) {
+ if (token_types_equal(token_type, pair.close) && right_of_end == m_editor->cursor()) {
+ auto buddy = find_span_of_type(i, pair.open, pair.close, Direction::Backward);
+ if (buddy.has_value())
+ make_buddies(i, buddy.value());
+ return;
+ }
+ }
+ }
+}
+
+void SyntaxHighlighter::attach(TextEditor& editor)
+{
+ ASSERT(!m_editor);
+ m_editor = editor;
+}
+
+void SyntaxHighlighter::detach()
+{
+ ASSERT(m_editor);
+ m_editor = nullptr;
+}
+
+void SyntaxHighlighter::cursor_did_change()
+{
+ ASSERT(m_editor);
+ auto& document = m_editor->document();
+ if (m_has_brace_buddies) {
+ if (m_brace_buddies[0].index >= 0 && m_brace_buddies[0].index < static_cast<int>(document.spans().size()))
+ document.set_span_at_index(m_brace_buddies[0].index, m_brace_buddies[0].span_backup);
+ if (m_brace_buddies[1].index >= 0 && m_brace_buddies[1].index < static_cast<int>(document.spans().size()))
+ document.set_span_at_index(m_brace_buddies[1].index, m_brace_buddies[1].span_backup);
+ m_has_brace_buddies = false;
+ m_editor->update();
+ }
+ highlight_matching_token_pair();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/SyntaxHighlighter.h b/Userland/Libraries/LibGUI/SyntaxHighlighter.h
new file mode 100644
index 0000000000..1ab1f51c42
--- /dev/null
+++ b/Userland/Libraries/LibGUI/SyntaxHighlighter.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Noncopyable.h>
+#include <AK/WeakPtr.h>
+#include <LibGUI/TextDocument.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+enum class SyntaxLanguage {
+ PlainText,
+ Cpp,
+ JavaScript,
+ INI,
+ Shell,
+};
+
+struct TextStyle {
+ const Color color;
+ const bool bold { false };
+};
+
+class SyntaxHighlighter {
+ AK_MAKE_NONCOPYABLE(SyntaxHighlighter);
+ AK_MAKE_NONMOVABLE(SyntaxHighlighter);
+
+public:
+ virtual ~SyntaxHighlighter();
+
+ virtual SyntaxLanguage language() const = 0;
+ virtual void rehighlight(Gfx::Palette) = 0;
+ virtual void highlight_matching_token_pair();
+
+ virtual bool is_identifier(void*) const { return false; };
+ virtual bool is_navigatable(void*) const { return false; };
+
+ void attach(TextEditor& editor);
+ void detach();
+ void cursor_did_change();
+
+protected:
+ SyntaxHighlighter() { }
+
+ WeakPtr<TextEditor> m_editor;
+
+ struct MatchingTokenPair {
+ void* open;
+ void* close;
+ };
+
+ virtual Vector<MatchingTokenPair> matching_token_pairs() const = 0;
+ virtual bool token_types_equal(void*, void*) const = 0;
+
+ struct BuddySpan {
+ int index { -1 };
+ GUI::TextDocumentSpan span_backup;
+ };
+
+ bool m_has_brace_buddies { false };
+ BuddySpan m_brace_buddies[2];
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/TabWidget.cpp b/Userland/Libraries/LibGUI/TabWidget.cpp
new file mode 100644
index 0000000000..5e46f267d0
--- /dev/null
+++ b/Userland/Libraries/LibGUI/TabWidget.cpp
@@ -0,0 +1,469 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/JsonObject.h>
+#include <AK/JsonValue.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/TabWidget.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+REGISTER_WIDGET(GUI, TabWidget)
+
+namespace GUI {
+
+TabWidget::TabWidget()
+{
+ set_focus_policy(FocusPolicy::NoFocus);
+
+ REGISTER_INT_PROPERTY("container_padding", container_padding, set_container_padding);
+ REGISTER_BOOL_PROPERTY("uniform_tabs", uniform_tabs, set_uniform_tabs);
+
+ register_property(
+ "text_alignment",
+ [this] { return Gfx::to_string(text_alignment()); },
+ [this](auto& value) {
+ auto alignment = Gfx::text_alignment_from_string(value.to_string());
+ if (alignment.has_value()) {
+ set_text_alignment(alignment.value());
+ return true;
+ }
+ return false;
+ });
+}
+
+TabWidget::~TabWidget()
+{
+}
+
+void TabWidget::add_widget(const StringView& title, Widget& widget)
+{
+ m_tabs.append({ title, nullptr, &widget });
+ add_child(widget);
+ update_focus_policy();
+}
+
+void TabWidget::remove_widget(Widget& widget)
+{
+ if (active_widget() == &widget)
+ activate_next_tab();
+ m_tabs.remove_first_matching([&widget](auto& entry) { return &widget == entry.widget; });
+ remove_child(widget);
+ update_focus_policy();
+}
+
+void TabWidget::update_focus_policy()
+{
+ FocusPolicy policy;
+ if (is_bar_visible() && !m_tabs.is_empty())
+ policy = FocusPolicy::TabFocus;
+ else
+ policy = FocusPolicy::NoFocus;
+ set_focus_policy(policy);
+}
+
+void TabWidget::set_active_widget(Widget* widget)
+{
+ if (widget == m_active_widget)
+ return;
+
+ bool active_widget_had_focus = m_active_widget && m_active_widget->has_focus_within();
+
+ if (m_active_widget)
+ m_active_widget->set_visible(false);
+ m_active_widget = widget;
+ if (m_active_widget) {
+ m_active_widget->set_relative_rect(child_rect_for_size(size()));
+ if (active_widget_had_focus)
+ m_active_widget->set_focus(true);
+ m_active_widget->set_visible(true);
+ deferred_invoke([this](auto&) {
+ if (on_change)
+ on_change(*m_active_widget);
+ });
+ }
+
+ update_bar();
+}
+
+void TabWidget::set_tab_index(int index)
+{
+ if (m_tabs.at(index).widget == m_active_widget)
+ return;
+ set_active_widget(m_tabs.at(index).widget);
+
+ update_bar();
+}
+
+void TabWidget::resize_event(ResizeEvent& event)
+{
+ if (!m_active_widget)
+ return;
+ m_active_widget->set_relative_rect(child_rect_for_size(event.size()));
+}
+
+Gfx::IntRect TabWidget::child_rect_for_size(const Gfx::IntSize& size) const
+{
+ Gfx::IntRect rect;
+ switch (m_tab_position) {
+ case TabPosition::Top:
+ rect = { { container_padding(), bar_height() + container_padding() }, { size.width() - container_padding() * 2, size.height() - bar_height() - container_padding() * 2 } };
+ break;
+ case TabPosition::Bottom:
+ rect = { { container_padding(), container_padding() }, { size.width() - container_padding() * 2, size.height() - bar_height() - container_padding() * 2 } };
+ break;
+ }
+ if (rect.is_empty())
+ return {};
+ return rect;
+}
+
+void TabWidget::child_event(Core::ChildEvent& event)
+{
+ if (!event.child() || !is<Widget>(*event.child()))
+ return Widget::child_event(event);
+ auto& child = downcast<Widget>(*event.child());
+ if (event.type() == Event::ChildAdded) {
+ if (!m_active_widget)
+ set_active_widget(&child);
+ else if (m_active_widget != &child)
+ child.set_visible(false);
+ } else if (event.type() == Event::ChildRemoved) {
+ if (m_active_widget == &child) {
+ Widget* new_active_widget = nullptr;
+ for_each_child_widget([&](auto& new_child) {
+ new_active_widget = &new_child;
+ return IterationDecision::Break;
+ });
+ set_active_widget(new_active_widget);
+ }
+ }
+ Widget::child_event(event);
+}
+
+Gfx::IntRect TabWidget::bar_rect() const
+{
+ switch (m_tab_position) {
+ case TabPosition::Top:
+ return { 0, 0, width(), bar_height() };
+ case TabPosition::Bottom:
+ return { 0, height() - bar_height(), width(), bar_height() };
+ }
+ ASSERT_NOT_REACHED();
+}
+
+Gfx::IntRect TabWidget::container_rect() const
+{
+ switch (m_tab_position) {
+ case TabPosition::Top:
+ return { 0, bar_height(), width(), height() - bar_height() };
+ case TabPosition::Bottom:
+ return { 0, 0, width(), height() - bar_height() };
+ }
+ ASSERT_NOT_REACHED();
+}
+
+void TabWidget::paint_event(PaintEvent& event)
+{
+ if (!m_bar_visible)
+ return;
+
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ auto container_rect = this->container_rect();
+ auto padding_rect = container_rect;
+ for (int i = 0; i < container_padding(); ++i) {
+ painter.draw_rect(padding_rect, palette().button());
+ padding_rect.shrink(2, 2);
+ }
+
+ if (container_padding() > 0)
+ Gfx::StylePainter::paint_frame(painter, container_rect, palette(), Gfx::FrameShape::Container, Gfx::FrameShadow::Raised, 2);
+
+ auto paint_tab_icon_if_needed = [&](auto& icon, auto& button_rect, auto& text_rect) {
+ if (!icon)
+ return;
+ Gfx::IntRect icon_rect { button_rect.x(), button_rect.y(), 16, 16 };
+ icon_rect.move_by(4, 3);
+ painter.draw_scaled_bitmap(icon_rect, *icon, icon->rect());
+ text_rect.set_x(icon_rect.right() + 1 + 4);
+ text_rect.intersect(button_rect);
+ };
+
+ for (size_t i = 0; i < m_tabs.size(); ++i) {
+ if (m_tabs[i].widget == m_active_widget)
+ continue;
+ bool hovered = static_cast<int>(i) == m_hovered_tab_index;
+ auto button_rect = this->button_rect(i);
+ Gfx::StylePainter::paint_tab_button(painter, button_rect, palette(), false, hovered, m_tabs[i].widget->is_enabled(), m_tab_position == TabPosition::Top);
+ auto tab_button_content_rect = button_rect.translated(0, m_tab_position == TabPosition::Top ? 1 : 0);
+ paint_tab_icon_if_needed(m_tabs[i].icon, button_rect, tab_button_content_rect);
+
+ Gfx::IntRect text_rect { 0, 0, min(tab_button_content_rect.width(), font().width(m_tabs[i].title)), font().glyph_height() };
+ text_rect.inflate(6, 4);
+ text_rect.align_within(tab_button_content_rect, m_text_alignment);
+ text_rect.intersect(tab_button_content_rect);
+
+ painter.draw_text(text_rect, m_tabs[i].title, Gfx::TextAlignment::Center, palette().button_text(), Gfx::TextElision::Right);
+ }
+
+ for (size_t i = 0; i < m_tabs.size(); ++i) {
+ if (m_tabs[i].widget != m_active_widget)
+ continue;
+ bool hovered = static_cast<int>(i) == m_hovered_tab_index;
+ auto button_rect = this->button_rect(i);
+ Gfx::StylePainter::paint_tab_button(painter, button_rect, palette(), true, hovered, m_tabs[i].widget->is_enabled(), m_tab_position == TabPosition::Top);
+ auto tab_button_content_rect = button_rect.translated(0, m_tab_position == TabPosition::Top ? 1 : 0);
+ paint_tab_icon_if_needed(m_tabs[i].icon, button_rect, tab_button_content_rect);
+
+ Gfx::IntRect text_rect { 0, 0, min(tab_button_content_rect.width(), font().width(m_tabs[i].title)), font().glyph_height() };
+ text_rect.inflate(6, 4);
+ text_rect.align_within(tab_button_content_rect, m_text_alignment);
+ text_rect.intersect(tab_button_content_rect);
+
+ painter.draw_text(text_rect, m_tabs[i].title, Gfx::TextAlignment::Center, palette().button_text(), Gfx::TextElision::Right);
+
+ if (is_focused()) {
+ painter.draw_focus_rect(text_rect, palette().focus_outline());
+ }
+
+ if (m_tab_position == TabPosition::Top) {
+ painter.draw_line(button_rect.bottom_left().translated(1, 1), button_rect.bottom_right().translated(-1, 1), palette().button());
+ } else if (m_tab_position == TabPosition::Bottom) {
+ painter.set_pixel(button_rect.top_left().translated(0, -1), palette().threed_highlight());
+ painter.set_pixel(button_rect.top_right().translated(-1, -1), palette().threed_shadow1());
+ painter.draw_line(button_rect.top_left().translated(1, -1), button_rect.top_right().translated(-2, -1), palette().button());
+ painter.draw_line(button_rect.top_left().translated(1, -2), button_rect.top_right().translated(-2, -2), palette().button());
+ }
+ break;
+ }
+}
+
+int TabWidget::uniform_tab_width() const
+{
+ int minimum_tab_width = 24;
+ int maximum_tab_width = 160;
+ int total_tab_width = m_tabs.size() * maximum_tab_width;
+ int tab_width = maximum_tab_width;
+ if (total_tab_width > width())
+ tab_width = width() / m_tabs.size();
+ return max(tab_width, minimum_tab_width);
+}
+
+void TabWidget::set_bar_visible(bool bar_visible)
+{
+ m_bar_visible = bar_visible;
+ if (m_active_widget)
+ m_active_widget->set_relative_rect(child_rect_for_size(size()));
+ update_bar();
+}
+
+Gfx::IntRect TabWidget::button_rect(int index) const
+{
+ int x_offset = 2;
+ for (int i = 0; i < index; ++i) {
+ auto tab_width = m_uniform_tabs ? uniform_tab_width() : m_tabs[i].width(font());
+ x_offset += tab_width;
+ }
+ Gfx::IntRect rect { x_offset, 0, m_uniform_tabs ? uniform_tab_width() : m_tabs[index].width(font()), bar_height() };
+ if (m_tabs[index].widget != m_active_widget) {
+ rect.move_by(0, m_tab_position == TabPosition::Top ? 2 : 0);
+ rect.set_height(rect.height() - 2);
+ } else {
+ rect.move_by(-2, 0);
+ rect.set_width(rect.width() + 4);
+ }
+ rect.move_by(bar_rect().location());
+ return rect;
+}
+
+int TabWidget::TabData::width(const Gfx::Font& font) const
+{
+ return 16 + font.width(title) + (icon ? (16 + 4) : 0);
+}
+
+void TabWidget::mousedown_event(MouseEvent& event)
+{
+ for (size_t i = 0; i < m_tabs.size(); ++i) {
+ auto button_rect = this->button_rect(i);
+ if (!button_rect.contains(event.position()))
+ continue;
+ if (event.button() == MouseButton::Left) {
+ set_active_widget(m_tabs[i].widget);
+ } else if (event.button() == MouseButton::Middle) {
+ auto* widget = m_tabs[i].widget;
+ deferred_invoke([this, widget](auto&) {
+ if (on_middle_click && widget)
+ on_middle_click(*widget);
+ });
+ }
+ return;
+ }
+}
+
+void TabWidget::mousemove_event(MouseEvent& event)
+{
+ int hovered_tab = -1;
+ for (size_t i = 0; i < m_tabs.size(); ++i) {
+ auto button_rect = this->button_rect(i);
+ if (!button_rect.contains(event.position()))
+ continue;
+ hovered_tab = i;
+ if (m_tabs[i].widget == m_active_widget)
+ break;
+ }
+ if (hovered_tab == m_hovered_tab_index)
+ return;
+ m_hovered_tab_index = hovered_tab;
+ update_bar();
+}
+
+void TabWidget::leave_event(Core::Event&)
+{
+ if (m_hovered_tab_index != -1) {
+ m_hovered_tab_index = -1;
+ update_bar();
+ }
+}
+
+void TabWidget::update_bar()
+{
+ auto invalidation_rect = bar_rect();
+ invalidation_rect.set_height(invalidation_rect.height() + 1);
+ update(invalidation_rect);
+}
+
+void TabWidget::set_tab_position(TabPosition tab_position)
+{
+ if (m_tab_position == tab_position)
+ return;
+ m_tab_position = tab_position;
+ if (m_active_widget)
+ m_active_widget->set_relative_rect(child_rect_for_size(size()));
+ update();
+}
+
+int TabWidget::active_tab_index() const
+{
+ for (size_t i = 0; i < m_tabs.size(); i++) {
+ if (m_tabs.at(i).widget == m_active_widget)
+ return i;
+ }
+ return -1;
+}
+
+void TabWidget::set_tab_title(Widget& tab, const StringView& title)
+{
+ for (auto& t : m_tabs) {
+ if (t.widget == &tab) {
+ if (t.title != title) {
+ t.title = title;
+ update();
+ }
+ return;
+ }
+ }
+}
+
+void TabWidget::set_tab_icon(Widget& tab, const Gfx::Bitmap* icon)
+{
+ for (auto& t : m_tabs) {
+ if (t.widget == &tab) {
+ t.icon = icon;
+ update();
+ return;
+ }
+ }
+}
+
+void TabWidget::activate_next_tab()
+{
+ if (m_tabs.size() <= 1)
+ return;
+ int index = active_tab_index();
+ ++index;
+ if (index >= (int)m_tabs.size())
+ index = 0;
+ set_active_widget(m_tabs.at(index).widget);
+}
+
+void TabWidget::activate_previous_tab()
+{
+ if (m_tabs.size() <= 1)
+ return;
+ int index = active_tab_index();
+ --index;
+ if (index < 0)
+ index = m_tabs.size() - 1;
+ set_active_widget(m_tabs.at(index).widget);
+}
+
+void TabWidget::keydown_event(KeyEvent& event)
+{
+ if (event.ctrl() && event.key() == Key_Tab) {
+ if (event.shift())
+ activate_previous_tab();
+ else
+ activate_next_tab();
+ event.accept();
+ return;
+ }
+ if (is_focused()) {
+ if (!event.modifiers() && event.key() == Key_Left) {
+ activate_previous_tab();
+ event.accept();
+ return;
+ }
+ if (!event.modifiers() && event.key() == Key_Right) {
+ activate_next_tab();
+ event.accept();
+ return;
+ }
+ }
+ Widget::keydown_event(event);
+}
+
+void TabWidget::context_menu_event(ContextMenuEvent& context_menu_event)
+{
+ for (size_t i = 0; i < m_tabs.size(); ++i) {
+ auto button_rect = this->button_rect(i);
+ if (!button_rect.contains(context_menu_event.position()))
+ continue;
+ auto* widget = m_tabs[i].widget;
+ deferred_invoke([this, widget, context_menu_event](auto&) {
+ if (on_context_menu_request && widget)
+ on_context_menu_request(*widget, context_menu_event);
+ });
+ return;
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/TabWidget.h b/Userland/Libraries/LibGUI/TabWidget.h
new file mode 100644
index 0000000000..b9b2df63ff
--- /dev/null
+++ b/Userland/Libraries/LibGUI/TabWidget.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class TabWidget : public Widget {
+ C_OBJECT(TabWidget)
+public:
+ enum TabPosition {
+ Top,
+ Bottom,
+ };
+
+ virtual ~TabWidget() override;
+
+ TabPosition tab_position() const { return m_tab_position; }
+ void set_tab_position(TabPosition);
+
+ int active_tab_index() const;
+
+ Widget* active_widget() { return m_active_widget.ptr(); }
+ const Widget* active_widget() const { return m_active_widget.ptr(); }
+ void set_active_widget(Widget*);
+ void set_tab_index(int);
+
+ int bar_height() const { return m_bar_visible ? 21 : 0; }
+
+ int container_padding() const { return m_container_padding; }
+ void set_container_padding(int padding) { m_container_padding = padding; }
+
+ void add_widget(const StringView&, Widget&);
+ void remove_widget(Widget&);
+
+ template<class T, class... Args>
+ T& add_tab(const StringView& title, Args&&... args)
+ {
+ auto t = T::construct(forward<Args>(args)...);
+ add_widget(title, *t);
+ return *t;
+ }
+
+ void remove_tab(Widget& tab) { remove_widget(tab); }
+
+ void set_tab_title(Widget& tab, const StringView& title);
+ void set_tab_icon(Widget& tab, const Gfx::Bitmap*);
+
+ void activate_next_tab();
+ void activate_previous_tab();
+
+ void set_text_alignment(Gfx::TextAlignment alignment) { m_text_alignment = alignment; }
+ Gfx::TextAlignment text_alignment() const { return m_text_alignment; }
+
+ bool uniform_tabs() const { return m_uniform_tabs; }
+ void set_uniform_tabs(bool uniform_tabs) { m_uniform_tabs = uniform_tabs; }
+ int uniform_tab_width() const;
+
+ void set_bar_visible(bool bar_visible);
+ bool is_bar_visible() const { return m_bar_visible; };
+
+ Function<void(Widget&)> on_change;
+ Function<void(Widget&)> on_middle_click;
+ Function<void(Widget&, const ContextMenuEvent&)> on_context_menu_request;
+
+protected:
+ TabWidget();
+
+ virtual void paint_event(PaintEvent&) override;
+ virtual void child_event(Core::ChildEvent&) override;
+ virtual void resize_event(ResizeEvent&) override;
+ virtual void mousedown_event(MouseEvent&) override;
+ virtual void mousemove_event(MouseEvent&) override;
+ virtual void leave_event(Core::Event&) override;
+ virtual void keydown_event(KeyEvent&) override;
+ virtual void context_menu_event(ContextMenuEvent&) override;
+
+private:
+ Gfx::IntRect child_rect_for_size(const Gfx::IntSize&) const;
+ Gfx::IntRect button_rect(int index) const;
+ Gfx::IntRect bar_rect() const;
+ Gfx::IntRect container_rect() const;
+ void update_bar();
+ void update_focus_policy();
+
+ RefPtr<Widget> m_active_widget;
+
+ struct TabData {
+ int width(const Gfx::Font&) const;
+ String title;
+ RefPtr<Gfx::Bitmap> icon;
+ Widget* widget { nullptr };
+ };
+ Vector<TabData> m_tabs;
+ TabPosition m_tab_position { TabPosition::Top };
+ int m_hovered_tab_index { -1 };
+ int m_container_padding { 2 };
+ Gfx::TextAlignment m_text_alignment { Gfx::TextAlignment::Center };
+ bool m_uniform_tabs { false };
+ bool m_bar_visible { true };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/TableView.cpp b/Userland/Libraries/LibGUI/TableView.cpp
new file mode 100644
index 0000000000..af3bba352f
--- /dev/null
+++ b/Userland/Libraries/LibGUI/TableView.cpp
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/HeaderView.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/ModelEditingDelegate.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGUI/TableView.h>
+#include <LibGUI/TextBox.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+TableView::TableView()
+{
+ set_fill_with_background_color(true);
+ set_background_role(ColorRole::Base);
+ set_foreground_role(ColorRole::BaseText);
+}
+
+TableView::~TableView()
+{
+}
+
+void TableView::paint_event(PaintEvent& event)
+{
+ Color widget_background_color = palette().color(background_role());
+ Frame::paint_event(event);
+
+ Painter painter(*this);
+ painter.add_clip_rect(frame_inner_rect());
+ painter.add_clip_rect(event.rect());
+ if (fill_with_background_color())
+ painter.fill_rect(event.rect(), widget_background_color);
+ painter.translate(frame_thickness(), frame_thickness());
+ painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
+
+ if (!model())
+ return;
+
+ auto selection_color = is_focused() ? palette().selection() : palette().inactive_selection();
+
+ int exposed_width = max(content_size().width(), width());
+ int x_offset = row_header().is_visible() ? row_header().width() : 0;
+ int y_offset = column_header().is_visible() ? column_header().height() : 0;
+
+ bool dummy;
+ int first_visible_row = index_at_event_position(frame_inner_rect().top_left(), dummy).row();
+ int last_visible_row = index_at_event_position(frame_inner_rect().bottom_right(), dummy).row();
+
+ if (first_visible_row == -1)
+ first_visible_row = 0;
+ if (last_visible_row == -1)
+ last_visible_row = model()->row_count() - 1;
+
+ int painted_item_index = first_visible_row;
+
+ for (int row_index = first_visible_row; row_index <= last_visible_row; ++row_index) {
+ bool is_selected_row = selection().contains_row(row_index);
+ int y = y_offset + painted_item_index * row_height();
+
+ Color background_color;
+ Color key_column_background_color;
+ if (is_selected_row && highlight_selected_rows()) {
+ background_color = selection_color;
+ key_column_background_color = selection_color;
+ } else {
+ if (alternating_row_colors() && (painted_item_index % 2)) {
+ background_color = widget_background_color.darkened(0.8f);
+ key_column_background_color = widget_background_color.darkened(0.7f);
+ } else {
+ background_color = widget_background_color;
+ key_column_background_color = widget_background_color.darkened(0.9f);
+ }
+ }
+
+ auto row_rect = this->row_rect(painted_item_index);
+ painter.fill_rect(row_rect, background_color);
+
+ int x = x_offset;
+ for (int column_index = 0; column_index < model()->column_count(); ++column_index) {
+ if (!column_header().is_section_visible(column_index))
+ continue;
+ int column_width = this->column_width(column_index);
+ bool is_key_column = m_key_column == column_index;
+ Gfx::IntRect cell_rect(horizontal_padding() + x, y, column_width, row_height());
+ auto cell_rect_for_fill = cell_rect.inflated(horizontal_padding() * 2, 0);
+ if (is_key_column)
+ painter.fill_rect(cell_rect_for_fill, key_column_background_color);
+ auto cell_index = model()->index(row_index, column_index);
+
+ if (auto* delegate = column_painting_delegate(column_index)) {
+ delegate->paint(painter, cell_rect, palette(), cell_index);
+ } else {
+ auto data = cell_index.data();
+ if (data.is_bitmap()) {
+ painter.blit(cell_rect.location(), data.as_bitmap(), data.as_bitmap().rect());
+ } else if (data.is_icon()) {
+ if (auto bitmap = data.as_icon().bitmap_for_size(16)) {
+ if (is_selected_row) {
+ auto tint = selection_color.with_alpha(100);
+ painter.blit_filtered(cell_rect.location(), *bitmap, bitmap->rect(), [&](auto src) { return src.blend(tint); });
+ } else if (m_hovered_index.is_valid() && cell_index.row() == m_hovered_index.row()) {
+ painter.blit_brightened(cell_rect.location(), *bitmap, bitmap->rect());
+ } else {
+ painter.blit(cell_rect.location(), *bitmap, bitmap->rect());
+ }
+ }
+ } else {
+ if (!is_selected_row) {
+ auto cell_background_color = cell_index.data(ModelRole::BackgroundColor);
+ if (cell_background_color.is_valid())
+ painter.fill_rect(cell_rect_for_fill, cell_background_color.to_color(background_color));
+ }
+
+ auto text_alignment = cell_index.data(ModelRole::TextAlignment).to_text_alignment(Gfx::TextAlignment::CenterLeft);
+ draw_item_text(painter, cell_index, is_selected_row, cell_rect, data.to_string(), font_for_index(cell_index), text_alignment, Gfx::TextElision::Right);
+ }
+ }
+
+ if (m_grid_style == GridStyle::Horizontal || m_grid_style == GridStyle::Both)
+ painter.draw_line(cell_rect_for_fill.bottom_left(), cell_rect_for_fill.bottom_right(), palette().ruler());
+ if (m_grid_style == GridStyle::Vertical || m_grid_style == GridStyle::Both)
+ painter.draw_line(cell_rect_for_fill.top_right(), cell_rect_for_fill.bottom_right(), palette().ruler());
+
+ if (selection_behavior() == SelectionBehavior::SelectItems && cell_index == cursor_index())
+ painter.draw_rect(cell_rect_for_fill, palette().text_cursor());
+
+ x += column_width + horizontal_padding() * 2;
+ }
+
+ if (is_focused() && selection_behavior() == SelectionBehavior::SelectRows && row_index == cursor_index().row()) {
+ painter.draw_rect(row_rect, widget_background_color);
+ painter.draw_focus_rect(row_rect, palette().focus_outline());
+ }
+
+ if (has_pending_drop() && selection_behavior() == SelectionBehavior::SelectRows && row_index == drop_candidate_index().row()) {
+ painter.draw_rect(row_rect, palette().selection(), true);
+ }
+ ++painted_item_index;
+ };
+
+ Gfx::IntRect unpainted_rect(0, column_header().height() + painted_item_index * row_height(), exposed_width, height());
+ if (fill_with_background_color())
+ painter.fill_rect(unpainted_rect, widget_background_color);
+}
+
+void TableView::keydown_event(KeyEvent& event)
+{
+ if (!model())
+ return AbstractTableView::keydown_event(event);
+
+ AbstractTableView::keydown_event(event);
+
+ if (event.is_accepted())
+ return;
+
+ auto is_delete = event.key() == Key_Delete || event.key() == Key_Backspace;
+ if (is_editable() && edit_triggers() & EditTrigger::AnyKeyPressed && (event.code_point() != 0 || is_delete)) {
+ begin_editing(cursor_index());
+ if (m_editing_delegate) {
+ if (is_delete)
+ m_editing_delegate->set_value(event.key() == Key_Delete ? String {} : String::empty());
+ else
+ m_editing_delegate->set_value(event.text());
+ }
+ }
+}
+
+void TableView::move_cursor(CursorMovement movement, SelectionUpdate selection_update)
+{
+ if (!model())
+ return;
+ auto& model = *this->model();
+ switch (movement) {
+ case CursorMovement::Left:
+ move_cursor_relative(0, -1, selection_update);
+ break;
+ case CursorMovement::Right:
+ move_cursor_relative(0, 1, selection_update);
+ break;
+ case CursorMovement::Up:
+ move_cursor_relative(-1, 0, selection_update);
+ break;
+ case CursorMovement::Down:
+ move_cursor_relative(1, 0, selection_update);
+ break;
+ case CursorMovement::Home: {
+ auto index = model.index(0, 0);
+ set_cursor(index, selection_update);
+ break;
+ }
+ case CursorMovement::End: {
+ auto index = model.index(model.row_count() - 1, 0);
+ set_cursor(index, selection_update);
+ break;
+ }
+ case CursorMovement::PageUp: {
+ int items_per_page = visible_content_rect().height() / row_height();
+ auto old_index = selection().first();
+ auto new_index = model.index(max(0, old_index.row() - items_per_page), old_index.column());
+ if (model.is_valid(new_index))
+ set_cursor(new_index, selection_update);
+ break;
+ }
+ case CursorMovement::PageDown: {
+ int items_per_page = visible_content_rect().height() / row_height();
+ auto old_index = selection().first();
+ auto new_index = model.index(min(model.row_count() - 1, old_index.row() + items_per_page), old_index.column());
+ if (model.is_valid(new_index))
+ set_cursor(new_index, selection_update);
+ break;
+ }
+ }
+}
+
+void TableView::set_grid_style(GridStyle style)
+{
+ if (m_grid_style == style)
+ return;
+ m_grid_style = style;
+ update();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/TableView.h b/Userland/Libraries/LibGUI/TableView.h
new file mode 100644
index 0000000000..04c44de64a
--- /dev/null
+++ b/Userland/Libraries/LibGUI/TableView.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/AbstractTableView.h>
+
+namespace GUI {
+
+class TableView : public AbstractTableView {
+ C_OBJECT(TableView)
+public:
+ virtual ~TableView() override;
+
+ enum class GridStyle {
+ None,
+ Horizontal,
+ Vertical,
+ Both,
+ };
+
+ enum class CursorStyle {
+ None,
+ Item,
+ Row,
+ };
+
+ GridStyle grid_style() const { return m_grid_style; }
+ void set_grid_style(GridStyle);
+
+ virtual void move_cursor(CursorMovement, SelectionUpdate) override;
+
+protected:
+ TableView();
+
+ virtual void keydown_event(KeyEvent&) override;
+ virtual void paint_event(PaintEvent&) override;
+
+private:
+ GridStyle m_grid_style { GridStyle::None };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/TextBox.cpp b/Userland/Libraries/LibGUI/TextBox.cpp
new file mode 100644
index 0000000000..046f8d13a7
--- /dev/null
+++ b/Userland/Libraries/LibGUI/TextBox.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/TextBox.h>
+
+REGISTER_WIDGET(GUI, TextBox)
+
+namespace GUI {
+
+TextBox::TextBox()
+ : TextEditor(TextEditor::SingleLine)
+{
+ set_min_width(32);
+ set_fixed_height(22);
+}
+
+TextBox::~TextBox()
+{
+}
+
+void TextBox::keydown_event(GUI::KeyEvent& event)
+{
+ TextEditor::keydown_event(event);
+
+ if (event.key() == Key_Up) {
+ if (on_up_pressed)
+ on_up_pressed();
+
+ if (has_no_history() || !can_go_backwards_in_history())
+ return;
+
+ if (m_history_index >= static_cast<int>(m_history.size()))
+ m_saved_input = text();
+
+ m_history_index--;
+ set_text(m_history[m_history_index]);
+ } else if (event.key() == Key_Down) {
+ if (on_down_pressed)
+ on_down_pressed();
+
+ if (has_no_history())
+ return;
+
+ if (can_go_forwards_in_history()) {
+ m_history_index++;
+ set_text(m_history[m_history_index]);
+ } else if (m_history_index < static_cast<int>(m_history.size())) {
+ m_history_index++;
+ set_text(m_saved_input);
+ }
+ }
+}
+
+void TextBox::add_current_text_to_history()
+{
+ if (!m_history_enabled)
+ return;
+
+ auto input = text();
+ if (m_history.is_empty() || m_history.last() != input)
+ add_input_to_history(input);
+ m_history_index = static_cast<int>(m_history.size());
+ m_saved_input = {};
+}
+
+void TextBox::add_input_to_history(String input)
+{
+ m_history.append(move(input));
+ m_history_index++;
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/TextBox.h b/Userland/Libraries/LibGUI/TextBox.h
new file mode 100644
index 0000000000..79be6af5c3
--- /dev/null
+++ b/Userland/Libraries/LibGUI/TextBox.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/TextEditor.h>
+
+namespace GUI {
+
+class TextBox : public TextEditor {
+ C_OBJECT(TextBox)
+public:
+ TextBox();
+ virtual ~TextBox() override;
+
+ Function<void()> on_up_pressed;
+ Function<void()> on_down_pressed;
+
+ void set_history_enabled(bool enabled) { m_history_enabled = enabled; }
+ void add_current_text_to_history();
+
+private:
+ virtual void keydown_event(GUI::KeyEvent&) override;
+
+ bool has_no_history() const { return !m_history_enabled || m_history.is_empty(); }
+ bool can_go_backwards_in_history() const { return m_history_index > 0; }
+ bool can_go_forwards_in_history() const { return m_history_index < static_cast<int>(m_history.size()) - 1; }
+ void add_input_to_history(String);
+
+ bool m_history_enabled { false };
+ Vector<String> m_history;
+ int m_history_index { -1 };
+ String m_saved_input;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/TextDocument.cpp b/Userland/Libraries/LibGUI/TextDocument.cpp
new file mode 100644
index 0000000000..986c4cd308
--- /dev/null
+++ b/Userland/Libraries/LibGUI/TextDocument.cpp
@@ -0,0 +1,848 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Badge.h>
+#include <AK/StringBuilder.h>
+#include <AK/Utf8View.h>
+#include <LibCore/Timer.h>
+#include <LibGUI/TextDocument.h>
+#include <LibGUI/TextEditor.h>
+#include <LibRegex/Regex.h>
+#include <ctype.h>
+
+namespace GUI {
+
+NonnullRefPtr<TextDocument> TextDocument::create(Client* client)
+{
+ return adopt(*new TextDocument(client));
+}
+
+TextDocument::TextDocument(Client* client)
+{
+ if (client)
+ m_clients.set(client);
+ append_line(make<TextDocumentLine>(*this));
+
+ // TODO: Instead of a repating timer, this we should call a delayed 2 sec timer when the user types.
+ m_undo_timer = Core::Timer::construct(
+ 2000, [this] {
+ update_undo_timer();
+ });
+}
+
+TextDocument::~TextDocument()
+{
+}
+
+void TextDocument::set_text(const StringView& text)
+{
+ m_client_notifications_enabled = false;
+ m_spans.clear();
+ remove_all_lines();
+
+ size_t start_of_current_line = 0;
+
+ auto add_line = [&](size_t current_position) {
+ size_t line_length = current_position - start_of_current_line;
+ auto line = make<TextDocumentLine>(*this);
+ if (line_length)
+ line->set_text(*this, text.substring_view(start_of_current_line, current_position - start_of_current_line));
+ append_line(move(line));
+ start_of_current_line = current_position + 1;
+ };
+ size_t i = 0;
+ for (i = 0; i < text.length(); ++i) {
+ if (text[i] == '\n')
+ add_line(i);
+ }
+ add_line(i);
+ m_client_notifications_enabled = true;
+
+ for (auto* client : m_clients)
+ client->document_did_set_text();
+}
+
+size_t TextDocumentLine::first_non_whitespace_column() const
+{
+ for (size_t i = 0; i < length(); ++i) {
+ auto code_point = code_points()[i];
+ if (!isspace(code_point))
+ return i;
+ }
+ return length();
+}
+
+Optional<size_t> TextDocumentLine::last_non_whitespace_column() const
+{
+ for (ssize_t i = length() - 1; i >= 0; --i) {
+ auto code_point = code_points()[i];
+ if (!isspace(code_point))
+ return i;
+ }
+ return {};
+}
+
+bool TextDocumentLine::ends_in_whitespace() const
+{
+ if (!length())
+ return false;
+ return isspace(code_points()[length() - 1]);
+}
+
+size_t TextDocumentLine::leading_spaces() const
+{
+ size_t count = 0;
+ for (; count < m_text.size(); ++count) {
+ if (m_text[count] != ' ') {
+ break;
+ }
+ }
+ return count;
+}
+
+String TextDocumentLine::to_utf8() const
+{
+ StringBuilder builder;
+ builder.append(view());
+ return builder.to_string();
+}
+
+TextDocumentLine::TextDocumentLine(TextDocument& document)
+{
+ clear(document);
+}
+
+TextDocumentLine::TextDocumentLine(TextDocument& document, const StringView& text)
+{
+ set_text(document, text);
+}
+
+void TextDocumentLine::clear(TextDocument& document)
+{
+ m_text.clear();
+ document.update_views({});
+}
+
+void TextDocumentLine::set_text(TextDocument& document, const Vector<u32> text)
+{
+ m_text = move(text);
+ document.update_views({});
+}
+
+void TextDocumentLine::set_text(TextDocument& document, const StringView& text)
+{
+ if (text.is_empty()) {
+ clear(document);
+ return;
+ }
+ m_text.clear();
+ Utf8View utf8_view(text);
+ for (auto code_point : utf8_view)
+ m_text.append(code_point);
+ document.update_views({});
+}
+
+void TextDocumentLine::append(TextDocument& document, const u32* code_points, size_t length)
+{
+ if (length == 0)
+ return;
+ m_text.append(code_points, length);
+ document.update_views({});
+}
+
+void TextDocumentLine::append(TextDocument& document, u32 code_point)
+{
+ insert(document, length(), code_point);
+}
+
+void TextDocumentLine::prepend(TextDocument& document, u32 code_point)
+{
+ insert(document, 0, code_point);
+}
+
+void TextDocumentLine::insert(TextDocument& document, size_t index, u32 code_point)
+{
+ if (index == length()) {
+ m_text.append(code_point);
+ } else {
+ m_text.insert(index, code_point);
+ }
+ document.update_views({});
+}
+
+void TextDocumentLine::remove(TextDocument& document, size_t index)
+{
+ if (index == length()) {
+ m_text.take_last();
+ } else {
+ m_text.remove(index);
+ }
+ document.update_views({});
+}
+
+void TextDocumentLine::remove_range(TextDocument& document, size_t start, size_t length)
+{
+ ASSERT(length <= m_text.size());
+
+ Vector<u32> new_data;
+ new_data.ensure_capacity(m_text.size() - length);
+ for (size_t i = 0; i < start; ++i)
+ new_data.append(m_text[i]);
+ for (size_t i = (start + length); i < m_text.size(); ++i)
+ new_data.append(m_text[i]);
+ m_text = move(new_data);
+ document.update_views({});
+}
+
+void TextDocumentLine::truncate(TextDocument& document, size_t length)
+{
+ m_text.resize(length);
+ document.update_views({});
+}
+
+void TextDocument::append_line(NonnullOwnPtr<TextDocumentLine> line)
+{
+ lines().append(move(line));
+ if (m_client_notifications_enabled) {
+ for (auto* client : m_clients)
+ client->document_did_append_line();
+ }
+}
+
+void TextDocument::insert_line(size_t line_index, NonnullOwnPtr<TextDocumentLine> line)
+{
+ lines().insert((int)line_index, move(line));
+ if (m_client_notifications_enabled) {
+ for (auto* client : m_clients)
+ client->document_did_insert_line(line_index);
+ }
+}
+
+void TextDocument::remove_line(size_t line_index)
+{
+ lines().remove((int)line_index);
+ if (m_client_notifications_enabled) {
+ for (auto* client : m_clients)
+ client->document_did_remove_line(line_index);
+ }
+}
+
+void TextDocument::remove_all_lines()
+{
+ lines().clear();
+ if (m_client_notifications_enabled) {
+ for (auto* client : m_clients)
+ client->document_did_remove_all_lines();
+ }
+}
+
+TextDocument::Client::~Client()
+{
+}
+
+void TextDocument::register_client(Client& client)
+{
+ m_clients.set(&client);
+}
+
+void TextDocument::unregister_client(Client& client)
+{
+ m_clients.remove(&client);
+}
+
+void TextDocument::update_views(Badge<TextDocumentLine>)
+{
+ notify_did_change();
+}
+
+void TextDocument::notify_did_change()
+{
+ if (m_client_notifications_enabled) {
+ for (auto* client : m_clients)
+ client->document_did_change();
+ }
+
+ m_regex_needs_update = true;
+}
+
+void TextDocument::set_all_cursors(const TextPosition& position)
+{
+ if (m_client_notifications_enabled) {
+ for (auto* client : m_clients)
+ client->document_did_set_cursor(position);
+ }
+}
+
+String TextDocument::text() const
+{
+ StringBuilder builder;
+ for (size_t i = 0; i < line_count(); ++i) {
+ auto& line = this->line(i);
+ builder.append(line.view());
+ if (i != line_count() - 1)
+ builder.append('\n');
+ }
+ return builder.to_string();
+}
+
+String TextDocument::text_in_range(const TextRange& a_range) const
+{
+ auto range = a_range.normalized();
+
+ StringBuilder builder;
+ for (size_t i = range.start().line(); i <= range.end().line(); ++i) {
+ auto& line = this->line(i);
+ size_t selection_start_column_on_line = range.start().line() == i ? range.start().column() : 0;
+ size_t selection_end_column_on_line = range.end().line() == i ? range.end().column() : line.length();
+ builder.append(Utf32View(line.code_points() + selection_start_column_on_line, selection_end_column_on_line - selection_start_column_on_line));
+ if (i != range.end().line())
+ builder.append('\n');
+ }
+
+ return builder.to_string();
+}
+
+u32 TextDocument::code_point_at(const TextPosition& position) const
+{
+ ASSERT(position.line() < line_count());
+ auto& line = this->line(position.line());
+ if (position.column() == line.length())
+ return '\n';
+ return line.code_points()[position.column()];
+}
+
+TextPosition TextDocument::next_position_after(const TextPosition& position, SearchShouldWrap should_wrap) const
+{
+ auto& line = this->line(position.line());
+ if (position.column() == line.length()) {
+ if (position.line() == line_count() - 1) {
+ if (should_wrap == SearchShouldWrap::Yes)
+ return { 0, 0 };
+ return {};
+ }
+ return { position.line() + 1, 0 };
+ }
+ return { position.line(), position.column() + 1 };
+}
+
+TextPosition TextDocument::previous_position_before(const TextPosition& position, SearchShouldWrap should_wrap) const
+{
+ if (position.column() == 0) {
+ if (position.line() == 0) {
+ if (should_wrap == SearchShouldWrap::Yes) {
+ auto& last_line = this->line(line_count() - 1);
+ return { line_count() - 1, last_line.length() };
+ }
+ return {};
+ }
+ auto& prev_line = this->line(position.line() - 1);
+ return { position.line() - 1, prev_line.length() };
+ }
+ return { position.line(), position.column() - 1 };
+}
+
+void TextDocument::update_regex_matches(const StringView& needle)
+{
+ if (m_regex_needs_update || needle != m_regex_needle) {
+ Regex<PosixExtended> re(needle);
+
+ Vector<RegexStringView> views;
+
+ for (size_t line = 0; line < m_lines.size(); ++line) {
+ views.append(m_lines.at(line).view());
+ }
+ re.search(views, m_regex_result);
+ m_regex_needs_update = false;
+ m_regex_needle = String { needle };
+ m_regex_result_match_index = -1;
+ m_regex_result_match_capture_group_index = -1;
+ }
+}
+
+TextRange TextDocument::find_next(const StringView& needle, const TextPosition& start, SearchShouldWrap should_wrap, bool regmatch)
+{
+ if (needle.is_empty())
+ return {};
+
+ if (regmatch) {
+ if (!m_regex_result.matches.size())
+ return {};
+
+ regex::Match match;
+ bool use_whole_match { false };
+
+ auto next_match = [&] {
+ m_regex_result_match_capture_group_index = 0;
+ if (m_regex_result_match_index == m_regex_result.matches.size() - 1) {
+ if (should_wrap == SearchShouldWrap::Yes)
+ m_regex_result_match_index = 0;
+ else
+ ++m_regex_result_match_index;
+ } else
+ ++m_regex_result_match_index;
+ };
+
+ if (m_regex_result.n_capture_groups) {
+ if (m_regex_result_match_index >= m_regex_result.capture_group_matches.size())
+ next_match();
+ else {
+ // check if last capture group has been reached
+ if (m_regex_result_match_capture_group_index >= m_regex_result.capture_group_matches.at(m_regex_result_match_index).size()) {
+ next_match();
+ } else {
+ // get to the next capture group item
+ ++m_regex_result_match_capture_group_index;
+ }
+ }
+
+ // use whole match, if there is no capture group for current index
+ if (m_regex_result_match_index >= m_regex_result.capture_group_matches.size())
+ use_whole_match = true;
+ else if (m_regex_result_match_capture_group_index >= m_regex_result.capture_group_matches.at(m_regex_result_match_index).size())
+ next_match();
+
+ } else {
+ next_match();
+ }
+
+ if (use_whole_match || !m_regex_result.capture_group_matches.at(m_regex_result_match_index).size())
+ match = m_regex_result.matches.at(m_regex_result_match_index);
+ else
+ match = m_regex_result.capture_group_matches.at(m_regex_result_match_index).at(m_regex_result_match_capture_group_index);
+
+ return TextRange { { match.line, match.column }, { match.line, match.column + match.view.length() } };
+ }
+
+ TextPosition position = start.is_valid() ? start : TextPosition(0, 0);
+ TextPosition original_position = position;
+
+ TextPosition start_of_potential_match;
+ size_t needle_index = 0;
+
+ do {
+ auto ch = code_point_at(position);
+ // FIXME: This is not the right way to use a Unicode needle!
+ if (ch == (u32)needle[needle_index]) {
+ if (needle_index == 0)
+ start_of_potential_match = position;
+ ++needle_index;
+ if (needle_index >= needle.length())
+ return { start_of_potential_match, next_position_after(position, should_wrap) };
+ } else {
+ if (needle_index > 0)
+ position = start_of_potential_match;
+ needle_index = 0;
+ }
+ position = next_position_after(position, should_wrap);
+ } while (position.is_valid() && position != original_position);
+
+ return {};
+}
+
+TextRange TextDocument::find_previous(const StringView& needle, const TextPosition& start, SearchShouldWrap should_wrap, bool regmatch)
+{
+ if (needle.is_empty())
+ return {};
+
+ if (regmatch) {
+ if (!m_regex_result.matches.size())
+ return {};
+
+ regex::Match match;
+ bool use_whole_match { false };
+
+ auto next_match = [&] {
+ if (m_regex_result_match_index == 0) {
+ if (should_wrap == SearchShouldWrap::Yes)
+ m_regex_result_match_index = m_regex_result.matches.size() - 1;
+ else
+ --m_regex_result_match_index;
+ } else
+ --m_regex_result_match_index;
+
+ m_regex_result_match_capture_group_index = m_regex_result.capture_group_matches.at(m_regex_result_match_index).size() - 1;
+ };
+
+ if (m_regex_result.n_capture_groups) {
+ if (m_regex_result_match_index >= m_regex_result.capture_group_matches.size())
+ next_match();
+ else {
+ // check if last capture group has been reached
+ if (m_regex_result_match_capture_group_index >= m_regex_result.capture_group_matches.at(m_regex_result_match_index).size()) {
+ next_match();
+ } else {
+ // get to the next capture group item
+ --m_regex_result_match_capture_group_index;
+ }
+ }
+
+ // use whole match, if there is no capture group for current index
+ if (m_regex_result_match_index >= m_regex_result.capture_group_matches.size())
+ use_whole_match = true;
+ else if (m_regex_result_match_capture_group_index >= m_regex_result.capture_group_matches.at(m_regex_result_match_index).size())
+ next_match();
+
+ } else {
+ next_match();
+ }
+
+ if (use_whole_match || !m_regex_result.capture_group_matches.at(m_regex_result_match_index).size())
+ match = m_regex_result.matches.at(m_regex_result_match_index);
+ else
+ match = m_regex_result.capture_group_matches.at(m_regex_result_match_index).at(m_regex_result_match_capture_group_index);
+
+ return TextRange { { match.line, match.column }, { match.line, match.column + match.view.length() } };
+ }
+
+ TextPosition position = start.is_valid() ? start : TextPosition(0, 0);
+ position = previous_position_before(position, should_wrap);
+ TextPosition original_position = position;
+
+ TextPosition end_of_potential_match;
+ size_t needle_index = needle.length() - 1;
+
+ do {
+ auto ch = code_point_at(position);
+ // FIXME: This is not the right way to use a Unicode needle!
+ if (ch == (u32)needle[needle_index]) {
+ if (needle_index == needle.length() - 1)
+ end_of_potential_match = position;
+ if (needle_index == 0)
+ return { position, next_position_after(end_of_potential_match, should_wrap) };
+ --needle_index;
+ } else {
+ if (needle_index < needle.length() - 1)
+ position = end_of_potential_match;
+ needle_index = needle.length() - 1;
+ }
+ position = previous_position_before(position, should_wrap);
+ } while (position.is_valid() && position != original_position);
+
+ return {};
+}
+
+Vector<TextRange> TextDocument::find_all(const StringView& needle, bool regmatch)
+{
+ Vector<TextRange> ranges;
+
+ TextPosition position;
+ for (;;) {
+ auto range = find_next(needle, position, SearchShouldWrap::No, regmatch);
+ if (!range.is_valid())
+ break;
+ ranges.append(range);
+ position = range.end();
+ }
+ return ranges;
+}
+
+Optional<TextDocumentSpan> TextDocument::first_non_skippable_span_before(const TextPosition& position) const
+{
+ for (int i = m_spans.size() - 1; i >= 0; --i) {
+ if (!m_spans[i].range.contains(position))
+ continue;
+ while ((i - 1) >= 0 && m_spans[i - 1].is_skippable)
+ --i;
+ if (i <= 0)
+ return {};
+ return m_spans[i - 1];
+ }
+ return {};
+}
+
+Optional<TextDocumentSpan> TextDocument::first_non_skippable_span_after(const TextPosition& position) const
+{
+ for (size_t i = 0; i < m_spans.size(); ++i) {
+ if (!m_spans[i].range.contains(position))
+ continue;
+ while ((i + 1) < m_spans.size() && m_spans[i + 1].is_skippable)
+ ++i;
+ if (i >= (m_spans.size() - 1))
+ return {};
+ return m_spans[i + 1];
+ }
+ return {};
+}
+
+TextPosition TextDocument::first_word_break_before(const TextPosition& position, bool start_at_column_before) const
+{
+ if (position.column() == 0) {
+ if (position.line() == 0) {
+ return TextPosition(0, 0);
+ }
+ auto previous_line = this->line(position.line() - 1);
+ return TextPosition(position.line() - 1, previous_line.length());
+ }
+
+ auto target = position;
+ auto line = this->line(target.line());
+ auto is_start_alphanumeric = isalnum(line.code_points()[target.column() - (start_at_column_before ? 1 : 0)]);
+
+ while (target.column() > 0) {
+ auto prev_code_point = line.code_points()[target.column() - 1];
+ if ((is_start_alphanumeric && !isalnum(prev_code_point)) || (!is_start_alphanumeric && isalnum(prev_code_point)))
+ break;
+ target.set_column(target.column() - 1);
+ }
+
+ return target;
+}
+
+TextPosition TextDocument::first_word_break_after(const TextPosition& position) const
+{
+ auto target = position;
+ auto line = this->line(target.line());
+
+ if (position.column() >= line.length()) {
+ if (position.line() >= this->line_count() - 1) {
+ return position;
+ }
+ return TextPosition(position.line() + 1, 0);
+ }
+
+ auto is_start_alphanumeric = isalnum(line.code_points()[target.column()]);
+
+ while (target.column() < line.length()) {
+ auto next_code_point = line.code_points()[target.column()];
+ if ((is_start_alphanumeric && !isalnum(next_code_point)) || (!is_start_alphanumeric && isalnum(next_code_point)))
+ break;
+ target.set_column(target.column() + 1);
+ }
+
+ return target;
+}
+
+void TextDocument::undo()
+{
+ if (!can_undo())
+ return;
+ m_undo_stack.undo();
+ notify_did_change();
+}
+
+void TextDocument::redo()
+{
+ if (!can_redo())
+ return;
+ m_undo_stack.redo();
+ notify_did_change();
+}
+
+void TextDocument::add_to_undo_stack(NonnullOwnPtr<TextDocumentUndoCommand> undo_command)
+{
+ m_undo_stack.push(move(undo_command));
+}
+
+TextDocumentUndoCommand::TextDocumentUndoCommand(TextDocument& document)
+ : m_document(document)
+{
+}
+
+TextDocumentUndoCommand::~TextDocumentUndoCommand()
+{
+}
+
+InsertTextCommand::InsertTextCommand(TextDocument& document, const String& text, const TextPosition& position)
+ : TextDocumentUndoCommand(document)
+ , m_text(text)
+ , m_range({ position, position })
+{
+}
+
+void InsertTextCommand::perform_formatting(const TextDocument::Client& client)
+{
+ const size_t tab_width = client.soft_tab_width();
+ const auto& dest_line = m_document.line(m_range.start().line());
+ const bool should_auto_indent = client.is_automatic_indentation_enabled();
+
+ StringBuilder builder;
+ size_t column = m_range.start().column();
+ size_t line_indentation = dest_line.leading_spaces();
+ bool at_start_of_line = line_indentation == column;
+
+ for (auto input_char : m_text) {
+ if (input_char == '\n') {
+ builder.append('\n');
+ column = 0;
+ if (should_auto_indent) {
+ for (; column < line_indentation; ++column) {
+ builder.append(' ');
+ }
+ }
+ at_start_of_line = true;
+ } else if (input_char == '\t') {
+ size_t next_soft_tab_stop = ((column + tab_width) / tab_width) * tab_width;
+ size_t spaces_to_insert = next_soft_tab_stop - column;
+ for (size_t i = 0; i < spaces_to_insert; ++i) {
+ builder.append(' ');
+ }
+ column = next_soft_tab_stop;
+ if (at_start_of_line) {
+ line_indentation = column;
+ }
+ } else {
+ if (input_char == ' ') {
+ if (at_start_of_line) {
+ ++line_indentation;
+ }
+ } else {
+ at_start_of_line = false;
+ }
+ builder.append(input_char);
+ ++column;
+ }
+ }
+ m_text = builder.build();
+}
+
+void InsertTextCommand::redo()
+{
+ auto new_cursor = m_document.insert_at(m_range.start(), m_text, m_client);
+ // NOTE: We don't know where the range ends until after doing redo().
+ // This is okay since we always do redo() after adding this to the undo stack.
+ m_range.set_end(new_cursor);
+ m_document.set_all_cursors(new_cursor);
+}
+
+void InsertTextCommand::undo()
+{
+ m_document.remove(m_range);
+ m_document.set_all_cursors(m_range.start());
+}
+
+RemoveTextCommand::RemoveTextCommand(TextDocument& document, const String& text, const TextRange& range)
+ : TextDocumentUndoCommand(document)
+ , m_text(text)
+ , m_range(range)
+{
+}
+
+void RemoveTextCommand::redo()
+{
+ m_document.remove(m_range);
+ m_document.set_all_cursors(m_range.start());
+}
+
+void RemoveTextCommand::undo()
+{
+ auto new_cursor = m_document.insert_at(m_range.start(), m_text);
+ m_document.set_all_cursors(new_cursor);
+}
+
+void TextDocument::update_undo_timer()
+{
+ m_undo_stack.finalize_current_combo();
+}
+
+TextPosition TextDocument::insert_at(const TextPosition& position, const StringView& text, const Client* client)
+{
+ TextPosition cursor = position;
+ Utf8View utf8_view(text);
+ for (auto code_point : utf8_view)
+ cursor = insert_at(cursor, code_point, client);
+ return cursor;
+}
+
+TextPosition TextDocument::insert_at(const TextPosition& position, u32 code_point, const Client*)
+{
+ if (code_point == '\n') {
+ auto new_line = make<TextDocumentLine>(*this);
+ new_line->append(*this, line(position.line()).code_points() + position.column(), line(position.line()).length() - position.column());
+ line(position.line()).truncate(*this, position.column());
+ insert_line(position.line() + 1, move(new_line));
+ notify_did_change();
+ return { position.line() + 1, 0 };
+ } else {
+ line(position.line()).insert(*this, position.column(), code_point);
+ notify_did_change();
+ return { position.line(), position.column() + 1 };
+ }
+}
+
+void TextDocument::remove(const TextRange& unnormalized_range)
+{
+ if (!unnormalized_range.is_valid())
+ return;
+
+ auto range = unnormalized_range.normalized();
+
+ // First delete all the lines in between the first and last one.
+ for (size_t i = range.start().line() + 1; i < range.end().line();) {
+ remove_line(i);
+ range.end().set_line(range.end().line() - 1);
+ }
+
+ if (range.start().line() == range.end().line()) {
+ // Delete within same line.
+ auto& line = this->line(range.start().line());
+ bool whole_line_is_selected = range.start().column() == 0 && range.end().column() == line.length();
+
+ if (whole_line_is_selected) {
+ line.clear(*this);
+ } else {
+ line.remove_range(*this, range.start().column(), range.end().column() - range.start().column());
+ }
+ } else {
+ // Delete across a newline, merging lines.
+ ASSERT(range.start().line() == range.end().line() - 1);
+ auto& first_line = line(range.start().line());
+ auto& second_line = line(range.end().line());
+ Vector<u32> code_points;
+ code_points.append(first_line.code_points(), range.start().column());
+ code_points.append(second_line.code_points() + range.end().column(), second_line.length() - range.end().column());
+ first_line.set_text(*this, move(code_points));
+ remove_line(range.end().line());
+ }
+
+ if (lines().is_empty()) {
+ append_line(make<TextDocumentLine>(*this));
+ }
+
+ notify_did_change();
+}
+
+bool TextDocument::is_empty() const
+{
+ return line_count() == 1 && line(0).is_empty();
+}
+
+TextRange TextDocument::range_for_entire_line(size_t line_index) const
+{
+ if (line_index >= line_count())
+ return {};
+ return { { line_index, 0 }, { line_index, line(line_index).length() } };
+}
+
+const TextDocumentSpan* TextDocument::span_at(const TextPosition& position) const
+{
+ for (auto& span : m_spans) {
+ if (span.range.contains(position))
+ return &span;
+ }
+ return nullptr;
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/TextDocument.h b/Userland/Libraries/LibGUI/TextDocument.h
new file mode 100644
index 0000000000..eb20cbdb77
--- /dev/null
+++ b/Userland/Libraries/LibGUI/TextDocument.h
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashTable.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <AK/NonnullRefPtr.h>
+#include <AK/Optional.h>
+#include <AK/RefCounted.h>
+#include <AK/Utf32View.h>
+#include <LibCore/Forward.h>
+#include <LibGUI/Command.h>
+#include <LibGUI/Forward.h>
+#include <LibGUI/TextRange.h>
+#include <LibGUI/UndoStack.h>
+#include <LibGfx/Color.h>
+#include <LibGfx/TextAttributes.h>
+#include <LibRegex/Regex.h>
+
+namespace GUI {
+
+struct TextDocumentSpan {
+ TextRange range;
+ Gfx::TextAttributes attributes;
+ void* data { nullptr };
+ bool is_skippable { false };
+};
+
+class TextDocument : public RefCounted<TextDocument> {
+public:
+ enum class SearchShouldWrap {
+ No = 0,
+ Yes
+ };
+
+ class Client {
+ public:
+ virtual ~Client();
+ virtual void document_did_append_line() = 0;
+ virtual void document_did_insert_line(size_t) = 0;
+ virtual void document_did_remove_line(size_t) = 0;
+ virtual void document_did_remove_all_lines() = 0;
+ virtual void document_did_change() = 0;
+ virtual void document_did_set_text() = 0;
+ virtual void document_did_set_cursor(const TextPosition&) = 0;
+
+ virtual bool is_automatic_indentation_enabled() const = 0;
+ virtual int soft_tab_width() const = 0;
+ };
+
+ static NonnullRefPtr<TextDocument> create(Client* client = nullptr);
+ virtual ~TextDocument();
+
+ size_t line_count() const { return m_lines.size(); }
+ const TextDocumentLine& line(size_t line_index) const { return m_lines[line_index]; }
+ TextDocumentLine& line(size_t line_index) { return m_lines[line_index]; }
+
+ void set_spans(const Vector<TextDocumentSpan>& spans) { m_spans = spans; }
+
+ void set_text(const StringView&);
+
+ const NonnullOwnPtrVector<TextDocumentLine>& lines() const { return m_lines; }
+ NonnullOwnPtrVector<TextDocumentLine>& lines() { return m_lines; }
+
+ bool has_spans() const { return !m_spans.is_empty(); }
+ Vector<TextDocumentSpan>& spans() { return m_spans; }
+ const Vector<TextDocumentSpan>& spans() const { return m_spans; }
+ void set_span_at_index(size_t index, TextDocumentSpan span) { m_spans[index] = move(span); }
+
+ const TextDocumentSpan* span_at(const TextPosition&) const;
+
+ void append_line(NonnullOwnPtr<TextDocumentLine>);
+ void remove_line(size_t line_index);
+ void remove_all_lines();
+ void insert_line(size_t line_index, NonnullOwnPtr<TextDocumentLine>);
+
+ void register_client(Client&);
+ void unregister_client(Client&);
+
+ void update_views(Badge<TextDocumentLine>);
+
+ String text() const;
+ String text_in_range(const TextRange&) const;
+
+ Vector<TextRange> find_all(const StringView& needle, bool regmatch = false);
+
+ void update_regex_matches(const StringView&);
+ TextRange find_next(const StringView&, const TextPosition& start = {}, SearchShouldWrap = SearchShouldWrap::Yes, bool regmatch = false);
+ TextRange find_previous(const StringView&, const TextPosition& start = {}, SearchShouldWrap = SearchShouldWrap::Yes, bool regmatch = false);
+
+ TextPosition next_position_after(const TextPosition&, SearchShouldWrap = SearchShouldWrap::Yes) const;
+ TextPosition previous_position_before(const TextPosition&, SearchShouldWrap = SearchShouldWrap::Yes) const;
+
+ u32 code_point_at(const TextPosition&) const;
+
+ TextRange range_for_entire_line(size_t line_index) const;
+
+ Optional<TextDocumentSpan> first_non_skippable_span_before(const TextPosition&) const;
+ Optional<TextDocumentSpan> first_non_skippable_span_after(const TextPosition&) const;
+
+ TextPosition first_word_break_before(const TextPosition&, bool start_at_column_before) const;
+ TextPosition first_word_break_after(const TextPosition&) const;
+
+ void add_to_undo_stack(NonnullOwnPtr<TextDocumentUndoCommand>);
+
+ bool can_undo() const { return m_undo_stack.can_undo(); }
+ bool can_redo() const { return m_undo_stack.can_redo(); }
+ void undo();
+ void redo();
+
+ void notify_did_change();
+ void set_all_cursors(const TextPosition&);
+
+ TextPosition insert_at(const TextPosition&, u32, const Client* = nullptr);
+ TextPosition insert_at(const TextPosition&, const StringView&, const Client* = nullptr);
+ void remove(const TextRange&);
+
+ virtual bool is_code_document() const { return false; }
+
+ bool is_empty() const;
+
+protected:
+ explicit TextDocument(Client* client);
+
+private:
+ void update_undo_timer();
+
+ NonnullOwnPtrVector<TextDocumentLine> m_lines;
+ Vector<TextDocumentSpan> m_spans;
+
+ HashTable<Client*> m_clients;
+ bool m_client_notifications_enabled { true };
+
+ UndoStack m_undo_stack;
+ RefPtr<Core::Timer> m_undo_timer;
+
+ RegexResult m_regex_result;
+ size_t m_regex_result_match_index { 0 };
+ size_t m_regex_result_match_capture_group_index { 0 };
+
+ bool m_regex_needs_update { true };
+ String m_regex_needle;
+};
+
+class TextDocumentLine {
+public:
+ explicit TextDocumentLine(TextDocument&);
+ explicit TextDocumentLine(TextDocument&, const StringView&);
+
+ String to_utf8() const;
+
+ Utf32View view() const { return { code_points(), length() }; }
+ const u32* code_points() const { return m_text.data(); }
+ size_t length() const { return m_text.size(); }
+ void set_text(TextDocument&, const StringView&);
+ void set_text(TextDocument&, Vector<u32>);
+ void append(TextDocument&, u32);
+ void prepend(TextDocument&, u32);
+ void insert(TextDocument&, size_t index, u32);
+ void remove(TextDocument&, size_t index);
+ void append(TextDocument&, const u32*, size_t);
+ void truncate(TextDocument&, size_t length);
+ void clear(TextDocument&);
+ void remove_range(TextDocument&, size_t start, size_t length);
+
+ size_t first_non_whitespace_column() const;
+ Optional<size_t> last_non_whitespace_column() const;
+ bool ends_in_whitespace() const;
+ bool is_empty() const { return length() == 0; }
+ size_t leading_spaces() const;
+
+private:
+ // NOTE: This vector is null terminated.
+ Vector<u32> m_text;
+};
+
+class TextDocumentUndoCommand : public Command {
+public:
+ TextDocumentUndoCommand(TextDocument&);
+ virtual ~TextDocumentUndoCommand();
+ virtual void perform_formatting(const TextDocument::Client&) { }
+
+ void execute_from(const TextDocument::Client& client)
+ {
+ m_client = &client;
+ redo();
+ m_client = nullptr;
+ }
+
+protected:
+ TextDocument& m_document;
+ const TextDocument::Client* m_client { nullptr };
+};
+
+class InsertTextCommand : public TextDocumentUndoCommand {
+public:
+ InsertTextCommand(TextDocument&, const String&, const TextPosition&);
+ virtual void perform_formatting(const TextDocument::Client&) override;
+ virtual void undo() override;
+ virtual void redo() override;
+ virtual bool is_insert_text() const override { return true; }
+ const String& text() const { return m_text; }
+ const TextRange& range() const { return m_range; }
+
+private:
+ String m_text;
+ TextRange m_range;
+};
+
+class RemoveTextCommand : public TextDocumentUndoCommand {
+public:
+ RemoveTextCommand(TextDocument&, const String&, const TextRange&);
+ virtual void undo() override;
+ virtual void redo() override;
+ virtual bool is_remove_text() const override { return true; }
+ const TextRange& range() const { return m_range; }
+
+private:
+ String m_text;
+ TextRange m_range;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/TextEditor.cpp b/Userland/Libraries/LibGUI/TextEditor.cpp
new file mode 100644
index 0000000000..bd2e220d19
--- /dev/null
+++ b/Userland/Libraries/LibGUI/TextEditor.cpp
@@ -0,0 +1,1668 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ScopeGuard.h>
+#include <AK/StringBuilder.h>
+#include <AK/TemporaryChange.h>
+#include <LibCore/Timer.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/AutocompleteProvider.h>
+#include <LibGUI/Clipboard.h>
+#include <LibGUI/EditingEngine.h>
+#include <LibGUI/InputBox.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/RegularEditingEngine.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGUI/SyntaxHighlighter.h>
+#include <LibGUI/TextEditor.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibGfx/Palette.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <math.h>
+#include <stdio.h>
+#include <unistd.h>
+
+//#define DEBUG_TEXTEDITOR
+
+REGISTER_WIDGET(GUI, TextEditor)
+
+namespace GUI {
+
+TextEditor::TextEditor(Type type)
+ : m_type(type)
+{
+ REGISTER_STRING_PROPERTY("text", text, set_text);
+ REGISTER_ENUM_PROPERTY("mode", mode, set_mode, Mode,
+ { Editable, "Editable" },
+ { ReadOnly, "ReadOnly" },
+ { DisplayOnly, "DisplayOnly" });
+
+ set_focus_policy(GUI::FocusPolicy::StrongFocus);
+ set_accepts_emoji_input(true);
+ set_override_cursor(Gfx::StandardCursor::IBeam);
+ set_background_role(ColorRole::Base);
+ set_foreground_role(ColorRole::BaseText);
+ set_document(TextDocument::create());
+ if (is_single_line())
+ set_visualize_trailing_whitespace(false);
+ set_scrollbars_enabled(is_multi_line());
+ if (is_multi_line())
+ set_font(Gfx::FontDatabase::default_fixed_width_font());
+ vertical_scrollbar().set_step(line_height());
+ m_cursor = { 0, 0 };
+ m_automatic_selection_scroll_timer = add<Core::Timer>(100, [this] {
+ automatic_selection_scroll_timer_fired();
+ });
+ m_automatic_selection_scroll_timer->stop();
+ create_actions();
+ set_editing_engine(make<RegularEditingEngine>());
+}
+
+TextEditor::~TextEditor()
+{
+ if (m_document)
+ m_document->unregister_client(*this);
+}
+
+void TextEditor::create_actions()
+{
+ m_undo_action = CommonActions::make_undo_action([&](auto&) { undo(); }, this);
+ m_redo_action = CommonActions::make_redo_action([&](auto&) { redo(); }, this);
+ m_undo_action->set_enabled(false);
+ m_redo_action->set_enabled(false);
+ m_cut_action = CommonActions::make_cut_action([&](auto&) { cut(); }, this);
+ m_copy_action = CommonActions::make_copy_action([&](auto&) { copy(); }, this);
+ m_paste_action = CommonActions::make_paste_action([&](auto&) { paste(); }, this);
+ m_delete_action = CommonActions::make_delete_action([&](auto&) { do_delete(); }, this);
+ if (is_multi_line()) {
+ m_go_to_line_action = Action::create(
+ "Go to line...", { Mod_Ctrl, Key_L }, Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"), [this](auto&) {
+ String value;
+ if (InputBox::show(value, window(), "Line:", "Go to line") == InputBox::ExecOK) {
+ auto line_target = value.to_uint();
+ if (line_target.has_value()) {
+ set_cursor_and_focus_line(line_target.value() - 1, 0);
+ }
+ }
+ },
+ this);
+ }
+ m_select_all_action = CommonActions::make_select_all_action([this](auto&) { select_all(); }, this);
+}
+
+void TextEditor::set_text(const StringView& text)
+{
+ m_selection.clear();
+
+ document().set_text(text);
+
+ update_content_size();
+ recompute_all_visual_lines();
+ if (is_single_line())
+ set_cursor(0, line(0).length());
+ else
+ set_cursor(0, 0);
+ did_update_selection();
+ update();
+}
+
+void TextEditor::update_content_size()
+{
+ int content_width = 0;
+ int content_height = 0;
+ for (auto& line : m_line_visual_data) {
+ content_width = max(line.visual_rect.width(), content_width);
+ content_height += line.visual_rect.height();
+ }
+ content_width += m_horizontal_content_padding * 2;
+ if (is_right_text_alignment(m_text_alignment))
+ content_width = max(frame_inner_rect().width(), content_width);
+
+ set_content_size({ content_width, content_height });
+ set_size_occupied_by_fixed_elements({ ruler_width(), 0 });
+}
+
+TextPosition TextEditor::text_position_at_content_position(const Gfx::IntPoint& content_position) const
+{
+ auto position = content_position;
+ if (is_single_line() && icon())
+ position.move_by(-(icon_size() + icon_padding()), 0);
+
+ size_t line_index = 0;
+
+ if (is_line_wrapping_enabled()) {
+ for (size_t i = 0; i < line_count(); ++i) {
+ auto& rect = m_line_visual_data[i].visual_rect;
+ if (position.y() >= rect.top() && position.y() <= rect.bottom()) {
+ line_index = i;
+ break;
+ }
+ if (position.y() > rect.bottom())
+ line_index = line_count() - 1;
+ }
+ } else {
+ line_index = (size_t)(position.y() / line_height());
+ }
+
+ line_index = max((size_t)0, min(line_index, line_count() - 1));
+
+ size_t column_index = 0;
+ switch (m_text_alignment) {
+ case Gfx::TextAlignment::CenterLeft:
+ for_each_visual_line(line_index, [&](const Gfx::IntRect& rect, auto& view, size_t start_of_line, [[maybe_unused]] bool is_last_visual_line) {
+ if (is_multi_line() && !rect.contains_vertically(position.y()) && !is_last_visual_line)
+ return IterationDecision::Continue;
+ column_index = start_of_line;
+ if (position.x() <= 0) {
+ // We're outside the text on the left side, put cursor at column 0 on this visual line.
+ } else {
+ int glyph_x = 0;
+ size_t i = 0;
+ for (; i < view.length(); ++i) {
+ int advance = font().glyph_width(view.code_points()[i]) + font().glyph_spacing();
+ if ((glyph_x + (advance / 2)) >= position.x())
+ break;
+ glyph_x += advance;
+ }
+ column_index += i;
+ }
+ return IterationDecision::Break;
+ });
+ break;
+ case Gfx::TextAlignment::CenterRight:
+ // FIXME: Support right-aligned line wrapping, I guess.
+ ASSERT(!is_line_wrapping_enabled());
+ column_index = (position.x() - content_x_for_position({ line_index, 0 }) + fixed_glyph_width() / 2) / fixed_glyph_width();
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ column_index = max((size_t)0, min(column_index, line(line_index).length()));
+ return { line_index, column_index };
+}
+
+TextPosition TextEditor::text_position_at(const Gfx::IntPoint& widget_position) const
+{
+ auto content_position = widget_position;
+ content_position.move_by(horizontal_scrollbar().value(), vertical_scrollbar().value());
+ content_position.move_by(-(m_horizontal_content_padding + ruler_width()), 0);
+ content_position.move_by(-frame_thickness(), -frame_thickness());
+ return text_position_at_content_position(content_position);
+}
+
+void TextEditor::doubleclick_event(MouseEvent& event)
+{
+ if (event.button() != MouseButton::Left)
+ return;
+
+ if (is_displayonly())
+ return;
+
+ // NOTE: This ensures that spans are updated before we look at them.
+ flush_pending_change_notification_if_needed();
+
+ m_triple_click_timer.start();
+ m_in_drag_select = false;
+
+ auto start = text_position_at(event.position());
+ auto end = start;
+
+ if (!document().has_spans()) {
+ start = document().first_word_break_before(start, false);
+ end = document().first_word_break_after(end);
+ } else {
+ for (auto& span : document().spans()) {
+ if (!span.range.contains(start))
+ continue;
+ start = span.range.start();
+ end = span.range.end();
+ end.set_column(end.column() + 1);
+ break;
+ }
+ }
+
+ m_selection.set(start, end);
+ set_cursor(end);
+ update();
+ did_update_selection();
+}
+
+void TextEditor::mousedown_event(MouseEvent& event)
+{
+ if (event.button() != MouseButton::Left) {
+ return;
+ }
+
+ if (on_mousedown)
+ on_mousedown();
+
+ if (is_displayonly())
+ return;
+
+ if (m_triple_click_timer.is_valid() && m_triple_click_timer.elapsed() < 250) {
+ m_triple_click_timer = Core::ElapsedTimer();
+
+ TextPosition start;
+ TextPosition end;
+
+ if (is_multi_line()) {
+ // select *current* line
+ start = TextPosition(m_cursor.line(), 0);
+ end = TextPosition(m_cursor.line(), line(m_cursor.line()).length());
+ } else {
+ // select *whole* line
+ start = TextPosition(0, 0);
+ end = TextPosition(line_count() - 1, line(line_count() - 1).length());
+ }
+
+ m_selection.set(start, end);
+ set_cursor(end);
+ update();
+ did_update_selection();
+ return;
+ }
+
+ if (event.modifiers() & Mod_Shift) {
+ if (!has_selection())
+ m_selection.set(m_cursor, {});
+ } else {
+ m_selection.clear();
+ }
+
+ m_in_drag_select = true;
+ m_automatic_selection_scroll_timer->start();
+
+ set_cursor(text_position_at(event.position()));
+
+ if (!(event.modifiers() & Mod_Shift)) {
+ if (!has_selection())
+ m_selection.set(m_cursor, {});
+ }
+
+ if (m_selection.start().is_valid() && m_selection.start() != m_cursor)
+ m_selection.set_end(m_cursor);
+
+ // FIXME: Only update the relevant rects.
+ update();
+ did_update_selection();
+}
+
+void TextEditor::mouseup_event(MouseEvent& event)
+{
+ if (event.button() == MouseButton::Left) {
+ if (m_in_drag_select) {
+ m_in_drag_select = false;
+ }
+ return;
+ }
+}
+
+void TextEditor::mousemove_event(MouseEvent& event)
+{
+ m_last_mousemove_position = event.position();
+ if (m_in_drag_select && (rect().contains(event.position()) || !m_automatic_selection_scroll_timer->is_active())) {
+ set_cursor(text_position_at(event.position()));
+ m_selection.set_end(m_cursor);
+ did_update_selection();
+ update();
+ return;
+ }
+}
+
+void TextEditor::automatic_selection_scroll_timer_fired()
+{
+ if (!m_in_drag_select) {
+ m_automatic_selection_scroll_timer->stop();
+ return;
+ }
+ set_cursor(text_position_at(m_last_mousemove_position));
+ m_selection.set_end(m_cursor);
+ did_update_selection();
+ update();
+}
+
+int TextEditor::ruler_width() const
+{
+ if (!m_ruler_visible)
+ return 0;
+ int line_count_digits = static_cast<int>(log10(line_count())) + 1;
+ constexpr size_t padding = 20;
+ return line_count() < 10 ? (line_count_digits + 1) * font().glyph_width('x') + padding : line_count_digits * font().glyph_width('x') + padding;
+}
+
+Gfx::IntRect TextEditor::ruler_content_rect(size_t line_index) const
+{
+ if (!m_ruler_visible)
+ return {};
+ return {
+ 0 - ruler_width() + horizontal_scrollbar().value(),
+ line_content_rect(line_index).y(),
+ ruler_width(),
+ line_content_rect(line_index).height()
+ };
+}
+
+Gfx::IntRect TextEditor::ruler_rect_in_inner_coordinates() const
+{
+ return { 0, 0, ruler_width(), height() - height_occupied_by_horizontal_scrollbar() };
+}
+
+Gfx::IntRect TextEditor::visible_text_rect_in_inner_coordinates() const
+{
+ return {
+ m_horizontal_content_padding + (m_ruler_visible ? (ruler_rect_in_inner_coordinates().right() + 1) : 0),
+ 0,
+ frame_inner_rect().width() - (m_horizontal_content_padding * 2) - width_occupied_by_vertical_scrollbar() - ruler_width(),
+ frame_inner_rect().height() - height_occupied_by_horizontal_scrollbar()
+ };
+}
+
+void TextEditor::paint_event(PaintEvent& event)
+{
+ Color widget_background_color = palette().color(is_enabled() ? background_role() : Gfx::ColorRole::Window);
+ // NOTE: This ensures that spans are updated before we look at them.
+ flush_pending_change_notification_if_needed();
+
+ Frame::paint_event(event);
+
+ Painter painter(*this);
+ painter.add_clip_rect(widget_inner_rect());
+ painter.add_clip_rect(event.rect());
+ painter.fill_rect(event.rect(), widget_background_color);
+
+ if (is_displayonly() && (is_focused() || has_visible_list())) {
+ widget_background_color = palette().selection();
+ Gfx::IntRect display_rect {
+ widget_inner_rect().x() + 1,
+ widget_inner_rect().y() + 1,
+ widget_inner_rect().width() - 2,
+ widget_inner_rect().height() - 2
+ };
+ painter.add_clip_rect(display_rect);
+ painter.add_clip_rect(event.rect());
+ painter.fill_rect(event.rect(), widget_background_color);
+ }
+
+ painter.translate(frame_thickness(), frame_thickness());
+
+ auto ruler_rect = ruler_rect_in_inner_coordinates();
+
+ if (m_ruler_visible) {
+ painter.fill_rect(ruler_rect, palette().ruler());
+ painter.draw_line(ruler_rect.top_right(), ruler_rect.bottom_right(), palette().ruler_border());
+ }
+
+ painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
+ if (m_ruler_visible)
+ painter.translate(ruler_width(), 0);
+
+ size_t first_visible_line = text_position_at(event.rect().top_left()).line();
+ size_t last_visible_line = text_position_at(event.rect().bottom_right()).line();
+
+ auto selection = normalized_selection();
+ bool has_selection = selection.is_valid();
+
+ if (m_ruler_visible) {
+ for (size_t i = first_visible_line; i <= last_visible_line; ++i) {
+ bool is_current_line = i == m_cursor.line();
+ auto ruler_line_rect = ruler_content_rect(i);
+ painter.draw_text(
+ ruler_line_rect.shrunken(2, 0).translated(0, m_line_spacing / 2),
+ String::number(i + 1),
+ is_current_line ? font().bold_variant() : font(),
+ Gfx::TextAlignment::TopRight,
+ is_current_line ? palette().ruler_active_text() : palette().ruler_inactive_text());
+ }
+ }
+
+ Gfx::IntRect text_clip_rect {
+ (m_ruler_visible ? (ruler_rect_in_inner_coordinates().right() + frame_thickness() + 1) : frame_thickness()),
+ frame_thickness(),
+ width() - width_occupied_by_vertical_scrollbar() - ruler_width(),
+ height() - height_occupied_by_horizontal_scrollbar()
+ };
+ if (m_ruler_visible)
+ text_clip_rect.move_by(-ruler_width(), 0);
+ text_clip_rect.move_by(horizontal_scrollbar().value(), vertical_scrollbar().value());
+ painter.add_clip_rect(text_clip_rect);
+
+ for (size_t line_index = first_visible_line; line_index <= last_visible_line; ++line_index) {
+ auto& line = this->line(line_index);
+
+ bool physical_line_has_selection = has_selection && line_index >= selection.start().line() && line_index <= selection.end().line();
+ size_t first_visual_line_with_selection = 0;
+ size_t last_visual_line_with_selection = 0;
+ if (physical_line_has_selection) {
+ if (selection.start().line() < line_index)
+ first_visual_line_with_selection = 0;
+ else
+ first_visual_line_with_selection = visual_line_containing(line_index, selection.start().column());
+
+ if (selection.end().line() > line_index)
+ last_visual_line_with_selection = m_line_visual_data[line_index].visual_line_breaks.size();
+ else
+ last_visual_line_with_selection = visual_line_containing(line_index, selection.end().column());
+ }
+
+ size_t selection_start_column_within_line = selection.start().line() == line_index ? selection.start().column() : 0;
+ size_t selection_end_column_within_line = selection.end().line() == line_index ? selection.end().column() : line.length();
+
+ size_t visual_line_index = 0;
+ for_each_visual_line(line_index, [&](const Gfx::IntRect& visual_line_rect, auto& visual_line_text, size_t start_of_visual_line, [[maybe_unused]] bool is_last_visual_line) {
+ if (is_multi_line() && line_index == m_cursor.line())
+ painter.fill_rect(visual_line_rect, widget_background_color.darkened(0.9f));
+#ifdef DEBUG_TEXTEDITOR
+ painter.draw_rect(visual_line_rect, Color::Cyan);
+#endif
+
+ if (!placeholder().is_empty() && document().is_empty() && !is_focused() && line_index == 0) {
+ auto line_rect = visual_line_rect;
+ line_rect.set_width(font().width(placeholder()));
+ painter.draw_text(line_rect, placeholder(), m_text_alignment, palette().color(Gfx::ColorRole::PlaceholderText));
+ } else if (!document().has_spans()) {
+ // Fast-path for plain text
+ auto color = palette().color(is_enabled() ? foreground_role() : Gfx::ColorRole::DisabledText);
+ if (is_displayonly() && (is_focused() || has_visible_list()))
+ color = palette().color(is_enabled() ? Gfx::ColorRole::SelectionText : Gfx::ColorRole::DisabledText);
+ painter.draw_text(visual_line_rect, visual_line_text, m_text_alignment, color);
+ } else {
+ Gfx::IntRect character_rect = { visual_line_rect.location(), { 0, line_height() } };
+ for (size_t i = 0; i < visual_line_text.length(); ++i) {
+ u32 code_point = visual_line_text.substring_view(i, 1).code_points()[0];
+ RefPtr<Gfx::Font> font = this->font();
+ Color color;
+ Optional<Color> background_color;
+ bool underline = false;
+ TextPosition physical_position(line_index, start_of_visual_line + i);
+ // FIXME: This is *horribly* inefficient.
+ for (auto& span : document().spans()) {
+ if (!span.range.contains(physical_position))
+ continue;
+ color = span.attributes.color;
+ if (span.attributes.bold) {
+ if (auto bold_font = Gfx::FontDatabase::the().get(font->family(), font->presentation_size(), 700))
+ font = bold_font;
+ }
+ background_color = span.attributes.background_color;
+ underline = span.attributes.underline;
+ break;
+ }
+ character_rect.set_width(font->glyph_width(code_point) + font->glyph_spacing());
+ if (background_color.has_value())
+ painter.fill_rect(character_rect, background_color.value());
+ painter.draw_text(character_rect, visual_line_text.substring_view(i, 1), *font, m_text_alignment, color);
+ if (underline) {
+ painter.draw_line(character_rect.bottom_left().translated(0, 1), character_rect.bottom_right().translated(0, 1), color);
+ }
+ character_rect.move_by(character_rect.width(), 0);
+ }
+ }
+
+ if (m_visualize_trailing_whitespace && line.ends_in_whitespace()) {
+ size_t physical_column;
+ auto last_non_whitespace_column = line.last_non_whitespace_column();
+ if (last_non_whitespace_column.has_value())
+ physical_column = last_non_whitespace_column.value() + 1;
+ else
+ physical_column = 0;
+ size_t end_of_visual_line = (start_of_visual_line + visual_line_text.length());
+ if (physical_column < end_of_visual_line) {
+ size_t visual_column = physical_column > start_of_visual_line ? (physical_column - start_of_visual_line) : 0;
+ Gfx::IntRect whitespace_rect {
+ content_x_for_position({ line_index, visual_column }),
+ visual_line_rect.y(),
+ font().width(visual_line_text.substring_view(visual_column, visual_line_text.length() - visual_column)),
+ visual_line_rect.height()
+ };
+ painter.fill_rect_with_dither_pattern(whitespace_rect, Color(), Color(255, 192, 192));
+ }
+ }
+
+ if (physical_line_has_selection) {
+ size_t start_of_selection_within_visual_line = (size_t)max(0, (int)selection_start_column_within_line - (int)start_of_visual_line);
+ size_t end_of_selection_within_visual_line = selection_end_column_within_line - start_of_visual_line;
+
+ bool current_visual_line_has_selection = start_of_selection_within_visual_line != end_of_selection_within_visual_line
+ && ((line_index != selection.start().line() && line_index != selection.end().line())
+ || (visual_line_index >= first_visual_line_with_selection && visual_line_index <= last_visual_line_with_selection));
+ if (current_visual_line_has_selection) {
+ bool selection_begins_on_current_visual_line = visual_line_index == first_visual_line_with_selection;
+ bool selection_ends_on_current_visual_line = visual_line_index == last_visual_line_with_selection;
+
+ int selection_left = selection_begins_on_current_visual_line
+ ? content_x_for_position({ line_index, (size_t)selection_start_column_within_line })
+ : m_horizontal_content_padding;
+
+ int selection_right = selection_ends_on_current_visual_line
+ ? content_x_for_position({ line_index, (size_t)selection_end_column_within_line })
+ : visual_line_rect.right() + 1;
+
+ Gfx::IntRect selection_rect {
+ selection_left,
+ visual_line_rect.y(),
+ selection_right - selection_left,
+ visual_line_rect.height()
+ };
+
+ Color background_color = is_focused() ? palette().selection() : palette().inactive_selection();
+ Color text_color = is_focused() ? palette().selection_text() : palette().inactive_selection_text();
+
+ painter.fill_rect(selection_rect, background_color);
+
+ if (visual_line_text.code_points()) {
+ Utf32View visual_selected_text {
+ visual_line_text.code_points() + start_of_selection_within_visual_line,
+ end_of_selection_within_visual_line - start_of_selection_within_visual_line
+ };
+
+ painter.draw_text(selection_rect, visual_selected_text, Gfx::TextAlignment::CenterLeft, text_color);
+ }
+ }
+ }
+
+ ++visual_line_index;
+ return IterationDecision::Continue;
+ });
+ }
+
+ if (!is_multi_line() && m_icon) {
+ Gfx::IntRect icon_rect { icon_padding(), 1, icon_size(), icon_size() };
+ painter.draw_scaled_bitmap(icon_rect, *m_icon, m_icon->rect());
+ }
+
+ if (is_focused() && m_cursor_state && !is_displayonly())
+ painter.fill_rect(cursor_content_rect(), palette().text_cursor());
+}
+
+void TextEditor::select_all()
+{
+ TextPosition start_of_document { 0, 0 };
+ TextPosition end_of_document { line_count() - 1, line(line_count() - 1).length() };
+ m_selection.set(end_of_document, start_of_document);
+ did_update_selection();
+ set_cursor(start_of_document);
+ update();
+}
+
+void TextEditor::keydown_event(KeyEvent& event)
+{
+ TemporaryChange change { m_should_keep_autocomplete_box, true };
+ if (m_autocomplete_box && m_autocomplete_box->is_visible() && (event.key() == KeyCode::Key_Return || event.key() == KeyCode::Key_Tab)) {
+ m_autocomplete_box->apply_suggestion();
+ m_autocomplete_box->close();
+ return;
+ }
+
+ if (m_autocomplete_box && m_autocomplete_box->is_visible() && event.key() == KeyCode::Key_Escape) {
+ m_autocomplete_box->close();
+ return;
+ }
+
+ if (m_autocomplete_box && m_autocomplete_box->is_visible() && event.key() == KeyCode::Key_Up) {
+ m_autocomplete_box->previous_suggestion();
+ return;
+ }
+
+ if (m_autocomplete_box && m_autocomplete_box->is_visible() && event.key() == KeyCode::Key_Down) {
+ m_autocomplete_box->next_suggestion();
+ return;
+ }
+
+ if (is_single_line()) {
+ if (event.key() == KeyCode::Key_Tab)
+ return ScrollableWidget::keydown_event(event);
+
+ if (event.key() == KeyCode::Key_Return) {
+ if (on_return_pressed)
+ on_return_pressed();
+ return;
+ }
+
+ if (event.key() == KeyCode::Key_Up) {
+ if (on_up_pressed)
+ on_up_pressed();
+ return;
+ }
+
+ if (event.key() == KeyCode::Key_Down) {
+ if (on_down_pressed)
+ on_down_pressed();
+ return;
+ }
+
+ if (event.key() == KeyCode::Key_PageUp) {
+ if (on_pageup_pressed)
+ on_pageup_pressed();
+ return;
+ }
+
+ if (event.key() == KeyCode::Key_PageDown) {
+ if (on_pagedown_pressed)
+ on_pagedown_pressed();
+ return;
+ }
+
+ } else if (is_multi_line()) {
+ ArmedScopeGuard update_autocomplete { [&] {
+ if (m_autocomplete_box && m_autocomplete_box->is_visible()) {
+ m_autocomplete_provider->provide_completions([&](auto completions) {
+ m_autocomplete_box->update_suggestions(move(completions));
+ });
+ }
+ } };
+
+ if (!event.shift() && !event.alt() && event.ctrl() && event.key() == KeyCode::Key_Space) {
+ if (m_autocomplete_provider) {
+ try_show_autocomplete();
+ update_autocomplete.disarm();
+ return;
+ }
+ }
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+
+ if (m_editing_engine->on_key(event))
+ return;
+
+ if (event.key() == KeyCode::Key_Escape) {
+ if (on_escape_pressed)
+ on_escape_pressed();
+ return;
+ }
+
+ if (event.modifiers() == Mod_Shift && event.key() == KeyCode::Key_Delete) {
+ if (m_autocomplete_box)
+ m_autocomplete_box->close();
+ return;
+ }
+
+ if (event.key() == KeyCode::Key_Delete) {
+ if (m_autocomplete_box)
+ m_autocomplete_box->close();
+ return;
+ }
+
+ if (event.key() == KeyCode::Key_Backspace) {
+ if (!is_editable())
+ return;
+ if (m_autocomplete_box)
+ m_autocomplete_box->close();
+ if (has_selection()) {
+ delete_selection();
+ did_update_selection();
+ return;
+ }
+ if (m_cursor.column() > 0) {
+ int erase_count = 1;
+ if (event.modifiers() == Mod_Ctrl) {
+ auto word_break_pos = document().first_word_break_before(m_cursor, true);
+ erase_count = m_cursor.column() - word_break_pos.column();
+ } else if (current_line().first_non_whitespace_column() >= m_cursor.column()) {
+ int new_column;
+ if (m_cursor.column() % m_soft_tab_width == 0)
+ new_column = m_cursor.column() - m_soft_tab_width;
+ else
+ new_column = (m_cursor.column() / m_soft_tab_width) * m_soft_tab_width;
+ erase_count = m_cursor.column() - new_column;
+ }
+
+ // Backspace within line
+ TextRange erased_range({ m_cursor.line(), m_cursor.column() - erase_count }, m_cursor);
+ auto erased_text = document().text_in_range(erased_range);
+ execute<RemoveTextCommand>(erased_text, erased_range);
+ return;
+ }
+ if (m_cursor.column() == 0 && m_cursor.line() != 0) {
+ // Backspace at column 0; merge with previous line
+ size_t previous_length = line(m_cursor.line() - 1).length();
+ TextRange erased_range({ m_cursor.line() - 1, previous_length }, m_cursor);
+ execute<RemoveTextCommand>("\n", erased_range);
+ return;
+ }
+ return;
+ }
+
+ if (!event.ctrl() && !event.alt() && event.code_point() != 0) {
+ add_code_point(event.code_point());
+ return;
+ }
+
+ event.ignore();
+}
+
+void TextEditor::delete_current_line()
+{
+ if (has_selection())
+ return delete_selection();
+
+ TextPosition start;
+ TextPosition end;
+ if (m_cursor.line() == 0 && line_count() == 1) {
+ start = { 0, 0 };
+ end = { 0, line(0).length() };
+ } else if (m_cursor.line() == line_count() - 1) {
+ start = { m_cursor.line() - 1, line(m_cursor.line()).length() };
+ end = { m_cursor.line(), line(m_cursor.line()).length() };
+ } else {
+ start = { m_cursor.line(), 0 };
+ end = { m_cursor.line() + 1, 0 };
+ }
+
+ TextRange erased_range(start, end);
+ execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range);
+}
+
+void TextEditor::do_delete()
+{
+ if (!is_editable())
+ return;
+
+ if (has_selection())
+ return delete_selection();
+
+ if (m_cursor.column() < current_line().length()) {
+ // Delete within line
+ TextRange erased_range(m_cursor, { m_cursor.line(), m_cursor.column() + 1 });
+ execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range);
+ return;
+ }
+ if (m_cursor.column() == current_line().length() && m_cursor.line() != line_count() - 1) {
+ // Delete at end of line; merge with next line
+ TextRange erased_range(m_cursor, { m_cursor.line() + 1, 0 });
+ execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range);
+ return;
+ }
+}
+
+void TextEditor::add_code_point(u32 code_point)
+{
+ if (!is_editable())
+ return;
+
+ StringBuilder sb;
+ sb.append_code_point(code_point);
+
+ if (should_autocomplete_automatically()) {
+ if (sb.string_view().is_whitespace())
+ m_autocomplete_timer->stop();
+ else
+ m_autocomplete_timer->start();
+ }
+ insert_at_cursor_or_replace_selection(sb.to_string());
+};
+
+void TextEditor::reset_cursor_blink()
+{
+ m_cursor_state = true;
+ update_cursor();
+ stop_timer();
+ start_timer(500);
+}
+
+void TextEditor::toggle_selection_if_needed_for_event(bool is_selecting)
+{
+ if (is_selecting && !selection()->is_valid()) {
+ selection()->set(cursor(), {});
+ did_update_selection();
+ update();
+ return;
+ }
+ if (!is_selecting && selection()->is_valid()) {
+ selection()->clear();
+ did_update_selection();
+ update();
+ return;
+ }
+ if (is_selecting && selection()->start().is_valid()) {
+ selection()->set_end(cursor());
+ did_update_selection();
+ update();
+ return;
+ }
+}
+
+int TextEditor::content_x_for_position(const TextPosition& position) const
+{
+ auto& line = this->line(position.line());
+ int x_offset = 0;
+ switch (m_text_alignment) {
+ case Gfx::TextAlignment::CenterLeft:
+ for_each_visual_line(position.line(), [&](const Gfx::IntRect&, auto& visual_line_view, size_t start_of_visual_line, [[maybe_unused]] bool is_last_visual_line) {
+ size_t offset_in_visual_line = position.column() - start_of_visual_line;
+ if (position.column() >= start_of_visual_line && (offset_in_visual_line <= visual_line_view.length())) {
+ if (offset_in_visual_line == 0) {
+ x_offset = 0;
+ } else {
+ x_offset = font().width(visual_line_view.substring_view(0, offset_in_visual_line));
+ x_offset += font().glyph_spacing();
+ }
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+ return m_horizontal_content_padding + ((is_single_line() && icon()) ? (icon_size() + icon_padding()) : 0) + x_offset;
+ case Gfx::TextAlignment::CenterRight:
+ // FIXME
+ ASSERT(!is_line_wrapping_enabled());
+ return content_width() - m_horizontal_content_padding - (line.length() * fixed_glyph_width()) + (position.column() * fixed_glyph_width());
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+Gfx::IntRect TextEditor::content_rect_for_position(const TextPosition& position) const
+{
+ if (!position.is_valid())
+ return {};
+ ASSERT(!lines().is_empty());
+ ASSERT(position.column() <= (current_line().length() + 1));
+
+ int x = content_x_for_position(position);
+
+ if (is_single_line()) {
+ Gfx::IntRect rect { x, 0, 1, line_height() };
+ rect.center_vertically_within({ {}, frame_inner_rect().size() });
+ return rect;
+ }
+
+ Gfx::IntRect rect;
+ for_each_visual_line(position.line(), [&](const Gfx::IntRect& visual_line_rect, auto& view, size_t start_of_visual_line, [[maybe_unused]] bool is_last_visual_line) {
+ if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) <= view.length())) {
+ // NOTE: We have to subtract the horizontal padding here since it's part of the visual line rect
+ // *and* included in what we get from content_x_for_position().
+ rect = {
+ visual_line_rect.x() + x - (m_horizontal_content_padding),
+ visual_line_rect.y(),
+ m_editing_engine->cursor_width() == CursorWidth::WIDE ? 7 : 1,
+ line_height()
+ };
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+ return rect;
+}
+
+Gfx::IntRect TextEditor::cursor_content_rect() const
+{
+ return content_rect_for_position(m_cursor);
+}
+
+Gfx::IntRect TextEditor::line_widget_rect(size_t line_index) const
+{
+ auto rect = line_content_rect(line_index);
+ rect.set_x(frame_thickness());
+ rect.set_width(frame_inner_rect().width());
+ rect.move_by(0, -(vertical_scrollbar().value()));
+ rect.move_by(0, frame_thickness());
+ rect.intersect(frame_inner_rect());
+ return rect;
+}
+
+void TextEditor::scroll_position_into_view(const TextPosition& position)
+{
+ auto rect = content_rect_for_position(position);
+ if (position.column() == 0)
+ rect.set_x(content_x_for_position({ position.line(), 0 }) - 2);
+ else if (position.column() == line(position.line()).length())
+ rect.set_x(content_x_for_position({ position.line(), line(position.line()).length() }) + 2);
+ scroll_into_view(rect, true, true);
+}
+
+void TextEditor::scroll_cursor_into_view()
+{
+ if (!m_reflow_deferred)
+ scroll_position_into_view(m_cursor);
+}
+
+Gfx::IntRect TextEditor::line_content_rect(size_t line_index) const
+{
+ auto& line = this->line(line_index);
+ if (is_single_line()) {
+ Gfx::IntRect line_rect = { content_x_for_position({ line_index, 0 }), 0, font().width(line.view()), font().glyph_height() + 4 };
+ line_rect.center_vertically_within({ {}, frame_inner_rect().size() });
+ return line_rect;
+ }
+ if (is_line_wrapping_enabled())
+ return m_line_visual_data[line_index].visual_rect;
+ return {
+ content_x_for_position({ line_index, 0 }),
+ (int)line_index * line_height(),
+ font().width(line.view()),
+ line_height()
+ };
+}
+
+void TextEditor::set_cursor_and_focus_line(size_t line, size_t column)
+{
+ u_int index_max = line_count() - 1;
+ set_cursor(line, column);
+ if (line > 1 && line < index_max) {
+ int headroom = frame_inner_rect().height() / 3;
+ do {
+ auto line_data = m_line_visual_data[line];
+ headroom -= line_data.visual_rect.height();
+ line--;
+ } while (line > 0 && headroom > 0);
+
+ Gfx::IntRect rect = { 0, line_content_rect(line).y(),
+ 1, frame_inner_rect().height() };
+ scroll_into_view(rect, false, true);
+ }
+}
+
+void TextEditor::update_cursor()
+{
+ update(line_widget_rect(m_cursor.line()));
+}
+
+void TextEditor::set_cursor(size_t line, size_t column)
+{
+ set_cursor({ line, column });
+}
+
+void TextEditor::set_cursor(const TextPosition& a_position)
+{
+ ASSERT(!lines().is_empty());
+
+ TextPosition position = a_position;
+
+ if (position.line() >= line_count())
+ position.set_line(line_count() - 1);
+
+ if (position.column() > lines()[position.line()].length())
+ position.set_column(lines()[position.line()].length());
+
+ if (m_cursor != position && is_visual_data_up_to_date()) {
+ // NOTE: If the old cursor is no longer valid, repaint everything just in case.
+ auto old_cursor_line_rect = m_cursor.line() < line_count()
+ ? line_widget_rect(m_cursor.line())
+ : rect();
+ m_cursor = position;
+ m_cursor_state = true;
+ scroll_cursor_into_view();
+ update(old_cursor_line_rect);
+ update_cursor();
+ } else if (m_cursor != position) {
+ m_cursor = position;
+ m_cursor_state = true;
+ }
+ cursor_did_change();
+ if (on_cursor_change)
+ on_cursor_change();
+ if (m_highlighter)
+ m_highlighter->cursor_did_change();
+}
+
+void TextEditor::focusin_event(FocusEvent& event)
+{
+ if (event.source() == FocusSource::Keyboard)
+ select_all();
+ m_cursor_state = true;
+ update_cursor();
+ stop_timer();
+ start_timer(500);
+ if (on_focusin)
+ on_focusin();
+}
+
+void TextEditor::focusout_event(FocusEvent&)
+{
+ stop_timer();
+ if (on_focusout)
+ on_focusout();
+}
+
+void TextEditor::timer_event(Core::TimerEvent&)
+{
+ m_cursor_state = !m_cursor_state;
+ if (is_focused())
+ update_cursor();
+}
+
+bool TextEditor::write_to_file(const StringView& path)
+{
+ int fd = open_with_path_length(path.characters_without_null_termination(), path.length(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0) {
+ perror("open");
+ return false;
+ }
+
+ // Compute the final file size and ftruncate() to make writing fast.
+ // FIXME: Remove this once the kernel is smart enough to do this instead.
+ off_t file_size = 0;
+ for (size_t i = 0; i < line_count(); ++i)
+ file_size += line(i).length();
+ file_size += line_count() - 1;
+
+ int rc = ftruncate(fd, file_size);
+ if (rc < 0) {
+ perror("ftruncate");
+ return false;
+ }
+
+ for (size_t i = 0; i < line_count(); ++i) {
+ auto& line = this->line(i);
+ if (line.length()) {
+ auto line_as_utf8 = line.to_utf8();
+ ssize_t nwritten = write(fd, line_as_utf8.characters(), line_as_utf8.length());
+ if (nwritten < 0) {
+ perror("write");
+ close(fd);
+ return false;
+ }
+ }
+ if (i != line_count() - 1) {
+ char ch = '\n';
+ ssize_t nwritten = write(fd, &ch, 1);
+ if (nwritten != 1) {
+ perror("write");
+ close(fd);
+ return false;
+ }
+ }
+ }
+
+ close(fd);
+ return true;
+}
+
+String TextEditor::text() const
+{
+ return document().text();
+}
+
+void TextEditor::clear()
+{
+ document().remove_all_lines();
+ document().append_line(make<TextDocumentLine>(document()));
+ m_selection.clear();
+ did_update_selection();
+ set_cursor(0, 0);
+ update();
+}
+
+String TextEditor::selected_text() const
+{
+ if (!has_selection())
+ return {};
+
+ return document().text_in_range(m_selection);
+}
+
+void TextEditor::delete_selection()
+{
+ auto selection = normalized_selection();
+ execute<RemoveTextCommand>(selected_text(), selection);
+ m_selection.clear();
+ did_update_selection();
+ did_change();
+ set_cursor(selection.start());
+ update();
+}
+
+void TextEditor::insert_at_cursor_or_replace_selection(const StringView& text)
+{
+ ReflowDeferrer defer(*this);
+ ASSERT(is_editable());
+ if (has_selection())
+ delete_selection();
+ execute<InsertTextCommand>(text, m_cursor);
+}
+
+void TextEditor::cut()
+{
+ if (!is_editable())
+ return;
+ auto selected_text = this->selected_text();
+ printf("Cut: \"%s\"\n", selected_text.characters());
+ Clipboard::the().set_plain_text(selected_text);
+ delete_selection();
+}
+
+void TextEditor::copy()
+{
+ auto selected_text = this->selected_text();
+ printf("Copy: \"%s\"\n", selected_text.characters());
+ Clipboard::the().set_plain_text(selected_text);
+}
+
+void TextEditor::paste()
+{
+ if (!is_editable())
+ return;
+
+ auto paste_text = Clipboard::the().data();
+ printf("Paste: \"%s\"\n", String::copy(paste_text).characters());
+
+ TemporaryChange change(m_automatic_indentation_enabled, false);
+ insert_at_cursor_or_replace_selection(paste_text);
+}
+
+void TextEditor::defer_reflow()
+{
+ ++m_reflow_deferred;
+}
+
+void TextEditor::undefer_reflow()
+{
+ ASSERT(m_reflow_deferred);
+ if (!--m_reflow_deferred) {
+ if (m_reflow_requested) {
+ recompute_all_visual_lines();
+ scroll_cursor_into_view();
+ }
+ }
+}
+
+void TextEditor::try_show_autocomplete()
+{
+ if (m_autocomplete_provider) {
+ m_autocomplete_provider->provide_completions([&](auto completions) {
+ auto has_completions = !completions.is_empty();
+ m_autocomplete_box->update_suggestions(move(completions));
+ auto position = content_rect_for_position(cursor()).bottom_right().translated(screen_relative_rect().top_left().translated(ruler_width(), 0).translated(10, 5));
+ if (has_completions)
+ m_autocomplete_box->show(position);
+ });
+ }
+}
+
+void TextEditor::enter_event(Core::Event&)
+{
+ m_automatic_selection_scroll_timer->stop();
+}
+
+void TextEditor::leave_event(Core::Event&)
+{
+ if (m_in_drag_select)
+ m_automatic_selection_scroll_timer->start();
+ if (m_autocomplete_timer)
+ m_autocomplete_timer->stop();
+ if (m_autocomplete_box)
+ m_autocomplete_box->close();
+}
+
+void TextEditor::did_change()
+{
+ update_content_size();
+ recompute_all_visual_lines();
+ m_undo_action->set_enabled(can_undo());
+ m_redo_action->set_enabled(can_redo());
+ if (m_autocomplete_box && !m_should_keep_autocomplete_box) {
+ m_autocomplete_timer->stop();
+ m_autocomplete_box->close();
+ }
+ if (!m_has_pending_change_notification) {
+ m_has_pending_change_notification = true;
+ deferred_invoke([this](auto&) {
+ if (!m_has_pending_change_notification)
+ return;
+ if (on_change)
+ on_change();
+ if (m_highlighter)
+ m_highlighter->rehighlight(palette());
+ m_has_pending_change_notification = false;
+ });
+ }
+}
+void TextEditor::set_mode(const Mode mode)
+{
+ if (m_mode == mode)
+ return;
+ m_mode = mode;
+ switch (mode) {
+ case Editable:
+ m_cut_action->set_enabled(true && has_selection());
+ m_delete_action->set_enabled(true);
+ m_paste_action->set_enabled(true);
+ set_accepts_emoji_input(true);
+ break;
+ case DisplayOnly:
+ case ReadOnly:
+ m_cut_action->set_enabled(false && has_selection());
+ m_delete_action->set_enabled(false);
+ m_paste_action->set_enabled(false);
+ set_accepts_emoji_input(false);
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ if (!is_displayonly())
+ set_override_cursor(Gfx::StandardCursor::IBeam);
+ else
+ set_override_cursor(Gfx::StandardCursor::None);
+}
+
+void TextEditor::set_has_visible_list(bool visible)
+{
+ if (m_has_visible_list == visible)
+ return;
+ m_has_visible_list = visible;
+}
+
+void TextEditor::did_update_selection()
+{
+ m_cut_action->set_enabled(is_editable() && has_selection());
+ m_copy_action->set_enabled(has_selection());
+ if (on_selection_change)
+ on_selection_change();
+ if (is_line_wrapping_enabled()) {
+ // FIXME: Try to repaint less.
+ update();
+ }
+}
+
+void TextEditor::context_menu_event(ContextMenuEvent& event)
+{
+ if (is_displayonly())
+ return;
+
+ if (!m_context_menu) {
+ m_context_menu = Menu::construct();
+ m_context_menu->add_action(undo_action());
+ m_context_menu->add_action(redo_action());
+ m_context_menu->add_separator();
+ m_context_menu->add_action(cut_action());
+ m_context_menu->add_action(copy_action());
+ m_context_menu->add_action(paste_action());
+ m_context_menu->add_action(delete_action());
+ m_context_menu->add_separator();
+ m_context_menu->add_action(select_all_action());
+ if (is_multi_line()) {
+ m_context_menu->add_separator();
+ m_context_menu->add_action(go_to_line_action());
+ }
+ if (!m_custom_context_menu_actions.is_empty()) {
+ m_context_menu->add_separator();
+ for (auto& action : m_custom_context_menu_actions) {
+ m_context_menu->add_action(action);
+ }
+ }
+ }
+ m_context_menu->popup(event.screen_position());
+}
+
+void TextEditor::set_text_alignment(Gfx::TextAlignment alignment)
+{
+ if (m_text_alignment == alignment)
+ return;
+ m_text_alignment = alignment;
+ update();
+}
+
+void TextEditor::resize_event(ResizeEvent& event)
+{
+ ScrollableWidget::resize_event(event);
+ update_content_size();
+ recompute_all_visual_lines();
+}
+
+void TextEditor::theme_change_event(ThemeChangeEvent& event)
+{
+ ScrollableWidget::theme_change_event(event);
+ if (m_highlighter)
+ m_highlighter->rehighlight(palette());
+}
+
+void TextEditor::set_selection(const TextRange& selection)
+{
+ if (m_selection == selection)
+ return;
+ m_selection = selection;
+ set_cursor(m_selection.end());
+ scroll_position_into_view(normalized_selection().start());
+ update();
+}
+
+void TextEditor::clear_selection()
+{
+ if (!has_selection())
+ return;
+ m_selection.clear();
+ update();
+}
+
+void TextEditor::recompute_all_visual_lines()
+{
+ if (m_reflow_deferred) {
+ m_reflow_requested = true;
+ return;
+ }
+
+ m_reflow_requested = false;
+
+ int y_offset = 0;
+ for (size_t line_index = 0; line_index < line_count(); ++line_index) {
+ recompute_visual_lines(line_index);
+ m_line_visual_data[line_index].visual_rect.set_y(y_offset);
+ y_offset += m_line_visual_data[line_index].visual_rect.height();
+ }
+
+ update_content_size();
+}
+
+void TextEditor::ensure_cursor_is_valid()
+{
+ auto new_cursor = m_cursor;
+ if (new_cursor.line() >= line_count())
+ new_cursor.set_line(line_count() - 1);
+ if (new_cursor.column() > line(new_cursor.line()).length())
+ new_cursor.set_column(line(new_cursor.line()).length());
+ if (m_cursor != new_cursor)
+ set_cursor(new_cursor);
+}
+
+size_t TextEditor::visual_line_containing(size_t line_index, size_t column) const
+{
+ size_t visual_line_index = 0;
+ for_each_visual_line(line_index, [&](const Gfx::IntRect&, auto& view, size_t start_of_visual_line, [[maybe_unused]] bool is_last_visual_line) {
+ if (column >= start_of_visual_line && ((column - start_of_visual_line) < view.length()))
+ return IterationDecision::Break;
+ ++visual_line_index;
+ return IterationDecision::Continue;
+ });
+ return visual_line_index;
+}
+
+void TextEditor::recompute_visual_lines(size_t line_index)
+{
+ auto& line = document().line(line_index);
+ auto& visual_data = m_line_visual_data[line_index];
+
+ visual_data.visual_line_breaks.clear_with_capacity();
+
+ int available_width = visible_text_rect_in_inner_coordinates().width();
+
+ if (is_line_wrapping_enabled()) {
+ int line_width_so_far = 0;
+
+ auto glyph_spacing = font().glyph_spacing();
+ for (size_t i = 0; i < line.length(); ++i) {
+ auto code_point = line.code_points()[i];
+ auto glyph_width = font().glyph_or_emoji_width(code_point);
+ if ((line_width_so_far + glyph_width + glyph_spacing) > available_width) {
+ visual_data.visual_line_breaks.append(i);
+ line_width_so_far = glyph_width + glyph_spacing;
+ continue;
+ }
+ line_width_so_far += glyph_width + glyph_spacing;
+ }
+ }
+
+ visual_data.visual_line_breaks.append(line.length());
+
+ if (is_line_wrapping_enabled())
+ visual_data.visual_rect = { m_horizontal_content_padding, 0, available_width, static_cast<int>(visual_data.visual_line_breaks.size()) * line_height() };
+ else
+ visual_data.visual_rect = { m_horizontal_content_padding, 0, font().width(line.view()), line_height() };
+}
+
+template<typename Callback>
+void TextEditor::for_each_visual_line(size_t line_index, Callback callback) const
+{
+ auto editor_visible_text_rect = visible_text_rect_in_inner_coordinates();
+ size_t start_of_line = 0;
+ size_t visual_line_index = 0;
+
+ auto& line = document().line(line_index);
+ auto& visual_data = m_line_visual_data[line_index];
+
+ for (auto visual_line_break : visual_data.visual_line_breaks) {
+ auto visual_line_view = Utf32View(line.code_points() + start_of_line, visual_line_break - start_of_line);
+ Gfx::IntRect visual_line_rect {
+ visual_data.visual_rect.x(),
+ visual_data.visual_rect.y() + ((int)visual_line_index * line_height()),
+ font().width(visual_line_view) + font().glyph_spacing(),
+ line_height()
+ };
+ if (is_right_text_alignment(text_alignment()))
+ visual_line_rect.set_right_without_resize(editor_visible_text_rect.right());
+ if (is_single_line()) {
+ visual_line_rect.center_vertically_within(editor_visible_text_rect);
+ if (m_icon)
+ visual_line_rect.move_by(icon_size() + icon_padding(), 0);
+ }
+ if (callback(visual_line_rect, visual_line_view, start_of_line, visual_line_index == visual_data.visual_line_breaks.size() - 1) == IterationDecision::Break)
+ break;
+ start_of_line = visual_line_break;
+ ++visual_line_index;
+ }
+}
+
+void TextEditor::set_line_wrapping_enabled(bool enabled)
+{
+ if (m_line_wrapping_enabled == enabled)
+ return;
+
+ m_line_wrapping_enabled = enabled;
+ horizontal_scrollbar().set_visible(!m_line_wrapping_enabled);
+ update_content_size();
+ recompute_all_visual_lines();
+ update();
+}
+
+void TextEditor::add_custom_context_menu_action(Action& action)
+{
+ m_custom_context_menu_actions.append(action);
+}
+
+void TextEditor::did_change_font()
+{
+ vertical_scrollbar().set_step(line_height());
+ recompute_all_visual_lines();
+ update();
+ ScrollableWidget::did_change_font();
+}
+
+void TextEditor::document_did_append_line()
+{
+ m_line_visual_data.append(make<LineVisualData>());
+ recompute_all_visual_lines();
+ update();
+}
+
+void TextEditor::document_did_remove_line(size_t line_index)
+{
+ m_line_visual_data.remove(line_index);
+ recompute_all_visual_lines();
+ update();
+}
+
+void TextEditor::document_did_remove_all_lines()
+{
+ m_line_visual_data.clear();
+ recompute_all_visual_lines();
+ update();
+}
+
+void TextEditor::document_did_insert_line(size_t line_index)
+{
+ m_line_visual_data.insert(line_index, make<LineVisualData>());
+ recompute_all_visual_lines();
+ update();
+}
+
+void TextEditor::document_did_change()
+{
+ did_change();
+ update();
+}
+
+void TextEditor::document_did_set_text()
+{
+ m_line_visual_data.clear();
+ for (size_t i = 0; i < m_document->line_count(); ++i)
+ m_line_visual_data.append(make<LineVisualData>());
+ document_did_change();
+}
+
+void TextEditor::document_did_set_cursor(const TextPosition& position)
+{
+ set_cursor(position);
+}
+
+void TextEditor::set_document(TextDocument& document)
+{
+ if (m_document.ptr() == &document)
+ return;
+ if (m_document)
+ m_document->unregister_client(*this);
+ m_document = document;
+ m_line_visual_data.clear();
+ for (size_t i = 0; i < m_document->line_count(); ++i) {
+ m_line_visual_data.append(make<LineVisualData>());
+ }
+ set_cursor(0, 0);
+ if (has_selection())
+ m_selection.clear();
+ recompute_all_visual_lines();
+ update();
+ m_document->register_client(*this);
+}
+
+void TextEditor::flush_pending_change_notification_if_needed()
+{
+ if (!m_has_pending_change_notification)
+ return;
+ if (on_change)
+ on_change();
+ if (m_highlighter)
+ m_highlighter->rehighlight(palette());
+ m_has_pending_change_notification = false;
+}
+
+const SyntaxHighlighter* TextEditor::syntax_highlighter() const
+{
+ return m_highlighter.ptr();
+}
+
+void TextEditor::set_syntax_highlighter(OwnPtr<SyntaxHighlighter> highlighter)
+{
+ if (m_highlighter)
+ m_highlighter->detach();
+ m_highlighter = move(highlighter);
+ if (m_highlighter) {
+ m_highlighter->attach(*this);
+ m_highlighter->rehighlight(palette());
+ } else
+ document().set_spans({});
+}
+
+const AutocompleteProvider* TextEditor::autocomplete_provider() const
+{
+ return m_autocomplete_provider.ptr();
+}
+
+void TextEditor::set_autocomplete_provider(OwnPtr<AutocompleteProvider>&& provider)
+{
+ if (m_autocomplete_provider)
+ m_autocomplete_provider->detach();
+ m_autocomplete_provider = move(provider);
+ if (m_autocomplete_provider) {
+ m_autocomplete_provider->attach(*this);
+ if (!m_autocomplete_box)
+ m_autocomplete_box = make<AutocompleteBox>(*this);
+ }
+ if (m_autocomplete_box)
+ m_autocomplete_box->close();
+}
+
+const EditingEngine* TextEditor::editing_engine() const
+{
+ return m_editing_engine.ptr();
+}
+
+void TextEditor::set_editing_engine(OwnPtr<EditingEngine> editing_engine)
+{
+ if (m_editing_engine)
+ m_editing_engine->detach();
+ m_editing_engine = move(editing_engine);
+
+ ASSERT(m_editing_engine);
+ m_editing_engine->attach(*this);
+
+ m_cursor_state = true;
+ update_cursor();
+ stop_timer();
+ start_timer(500);
+}
+
+int TextEditor::line_height() const
+{
+ return font().glyph_height() + m_line_spacing;
+}
+
+int TextEditor::fixed_glyph_width() const
+{
+ ASSERT(font().is_fixed_width());
+ return font().glyph_width(' ');
+}
+
+void TextEditor::set_icon(const Gfx::Bitmap* icon)
+{
+ if (m_icon == icon)
+ return;
+ m_icon = icon;
+ update();
+}
+
+void TextEditor::set_visualize_trailing_whitespace(bool enabled)
+{
+ if (m_visualize_trailing_whitespace == enabled)
+ return;
+ m_visualize_trailing_whitespace = enabled;
+ update();
+}
+
+void TextEditor::set_should_autocomplete_automatically(bool value)
+{
+ if (value == should_autocomplete_automatically())
+ return;
+
+ if (value) {
+ ASSERT(m_autocomplete_provider);
+ m_autocomplete_timer = Core::Timer::create_single_shot(m_automatic_autocomplete_delay_ms, [this] { try_show_autocomplete(); });
+ return;
+ }
+
+ remove_child(*m_autocomplete_timer);
+ m_autocomplete_timer = nullptr;
+}
+int TextEditor::number_of_visible_lines() const
+{
+ return visible_content_rect().height() / line_height();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/TextEditor.h b/Userland/Libraries/LibGUI/TextEditor.h
new file mode 100644
index 0000000000..1ffeb36555
--- /dev/null
+++ b/Userland/Libraries/LibGUI/TextEditor.h
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <AK/NonnullRefPtrVector.h>
+#include <LibCore/ElapsedTimer.h>
+#include <LibCore/Timer.h>
+#include <LibGUI/Forward.h>
+#include <LibGUI/ScrollableWidget.h>
+#include <LibGUI/TextDocument.h>
+#include <LibGUI/TextRange.h>
+#include <LibGfx/TextAlignment.h>
+
+namespace GUI {
+
+class TextEditor
+ : public ScrollableWidget
+ , public TextDocument::Client {
+ C_OBJECT(TextEditor)
+
+public:
+ enum Type {
+ MultiLine,
+ SingleLine
+ };
+
+ enum Mode {
+ Editable,
+ ReadOnly,
+ DisplayOnly
+ };
+
+ virtual ~TextEditor() override;
+
+ const TextDocument& document() const { return *m_document; }
+ TextDocument& document() { return *m_document; }
+
+ virtual void set_document(TextDocument&);
+
+ const String& placeholder() const { return m_placeholder; }
+ void set_placeholder(const StringView& placeholder) { m_placeholder = placeholder; }
+
+ TextDocumentLine& current_line() { return line(m_cursor.line()); }
+ const TextDocumentLine& current_line() const { return line(m_cursor.line()); }
+
+ void set_visualize_trailing_whitespace(bool);
+ bool visualize_trailing_whitespace() const { return m_visualize_trailing_whitespace; }
+
+ bool has_visible_list() const { return m_has_visible_list; }
+ void set_has_visible_list(bool);
+
+ virtual bool is_automatic_indentation_enabled() const final { return m_automatic_indentation_enabled; }
+ void set_automatic_indentation_enabled(bool enabled) { m_automatic_indentation_enabled = enabled; }
+
+ virtual int soft_tab_width() const final { return m_soft_tab_width; }
+
+ bool is_line_wrapping_enabled() const { return m_line_wrapping_enabled; }
+ void set_line_wrapping_enabled(bool);
+
+ Gfx::TextAlignment text_alignment() const { return m_text_alignment; }
+ void set_text_alignment(Gfx::TextAlignment);
+
+ Type type() const { return m_type; }
+ bool is_single_line() const { return m_type == SingleLine; }
+ bool is_multi_line() const { return m_type == MultiLine; }
+
+ Mode mode() const { return m_mode; }
+ bool is_editable() const { return m_mode == Editable; }
+ bool is_readonly() const { return m_mode == ReadOnly; }
+ bool is_displayonly() const { return m_mode == DisplayOnly; }
+ void set_mode(const Mode);
+
+ bool is_ruler_visible() const { return m_ruler_visible; }
+ void set_ruler_visible(bool b) { m_ruler_visible = b; }
+
+ void set_icon(const Gfx::Bitmap*);
+ const Gfx::Bitmap* icon() const { return m_icon; }
+
+ Function<void()> on_cursor_change;
+ Function<void()> on_selection_change;
+ Function<void()> on_focusin;
+ Function<void()> on_focusout;
+
+ void set_text(const StringView&);
+ void scroll_cursor_into_view();
+ void scroll_position_into_view(const TextPosition&);
+ size_t line_count() const { return document().line_count(); }
+ TextDocumentLine& line(size_t index) { return document().line(index); }
+ const TextDocumentLine& line(size_t index) const { return document().line(index); }
+ NonnullOwnPtrVector<TextDocumentLine>& lines() { return document().lines(); }
+ const NonnullOwnPtrVector<TextDocumentLine>& lines() const { return document().lines(); }
+ int line_spacing() const { return m_line_spacing; }
+ int line_height() const;
+ TextPosition cursor() const { return m_cursor; }
+ TextRange normalized_selection() const { return m_selection.normalized(); }
+
+ void insert_at_cursor_or_replace_selection(const StringView&);
+ bool write_to_file(const StringView& path);
+ bool has_selection() const { return m_selection.is_valid(); }
+ String selected_text() const;
+ void set_selection(const TextRange&);
+ void clear_selection();
+ bool can_undo() const { return document().can_undo(); }
+ bool can_redo() const { return document().can_redo(); }
+
+ String text() const;
+
+ void clear();
+
+ void cut();
+ void copy();
+ void paste();
+ void do_delete();
+ void delete_current_line();
+ void select_all();
+ virtual void undo() { document().undo(); }
+ virtual void redo() { document().redo(); }
+
+ Function<void()> on_change;
+ Function<void()> on_mousedown;
+ Function<void()> on_return_pressed;
+ Function<void()> on_escape_pressed;
+ Function<void()> on_up_pressed;
+ Function<void()> on_down_pressed;
+ Function<void()> on_pageup_pressed;
+ Function<void()> on_pagedown_pressed;
+
+ Action& undo_action() { return *m_undo_action; }
+ Action& redo_action() { return *m_redo_action; }
+ Action& cut_action() { return *m_cut_action; }
+ Action& copy_action() { return *m_copy_action; }
+ Action& paste_action() { return *m_paste_action; }
+ Action& delete_action() { return *m_delete_action; }
+ Action& go_to_line_action() { return *m_go_to_line_action; }
+ Action& select_all_action() { return *m_select_all_action; }
+
+ void add_custom_context_menu_action(Action&);
+
+ void set_cursor_and_focus_line(size_t line, size_t column);
+ void set_cursor(size_t line, size_t column);
+ void set_cursor(const TextPosition&);
+
+ const SyntaxHighlighter* syntax_highlighter() const;
+ void set_syntax_highlighter(OwnPtr<SyntaxHighlighter>);
+
+ const AutocompleteProvider* autocomplete_provider() const;
+ void set_autocomplete_provider(OwnPtr<AutocompleteProvider>&&);
+
+ const EditingEngine* editing_engine() const;
+ void set_editing_engine(OwnPtr<EditingEngine>);
+
+ bool should_autocomplete_automatically() const { return m_autocomplete_timer; }
+ void set_should_autocomplete_automatically(bool);
+
+ bool is_in_drag_select() const { return m_in_drag_select; }
+
+ TextRange* selection() { return &m_selection; };
+ void did_update_selection();
+ void did_change();
+ void update_cursor();
+
+ void add_code_point(u32 code_point);
+ void reset_cursor_blink();
+ void toggle_selection_if_needed_for_event(bool is_selecting);
+
+ int number_of_visible_lines() const;
+ Gfx::IntRect cursor_content_rect() const;
+ TextPosition text_position_at_content_position(const Gfx::IntPoint&) const;
+
+protected:
+ explicit TextEditor(Type = Type::MultiLine);
+
+ virtual void did_change_font() override;
+ virtual void paint_event(PaintEvent&) override;
+ virtual void mousedown_event(MouseEvent&) override;
+ virtual void mouseup_event(MouseEvent&) override;
+ virtual void mousemove_event(MouseEvent&) override;
+ virtual void doubleclick_event(MouseEvent&) override;
+ virtual void keydown_event(KeyEvent&) override;
+ virtual void focusin_event(FocusEvent&) override;
+ virtual void focusout_event(FocusEvent&) override;
+ virtual void timer_event(Core::TimerEvent&) override;
+ virtual void enter_event(Core::Event&) override;
+ virtual void leave_event(Core::Event&) override;
+ virtual void context_menu_event(ContextMenuEvent&) override;
+ virtual void resize_event(ResizeEvent&) override;
+ virtual void theme_change_event(ThemeChangeEvent&) override;
+ virtual void cursor_did_change() { }
+ Gfx::IntRect ruler_content_rect(size_t line) const;
+
+ TextPosition text_position_at(const Gfx::IntPoint&) const;
+ bool ruler_visible() const { return m_ruler_visible; }
+ Gfx::IntRect content_rect_for_position(const TextPosition&) const;
+ int ruler_width() const;
+
+private:
+ friend class TextDocumentLine;
+
+ // ^TextDocument::Client
+ virtual void document_did_append_line() override;
+ virtual void document_did_insert_line(size_t) override;
+ virtual void document_did_remove_line(size_t) override;
+ virtual void document_did_remove_all_lines() override;
+ virtual void document_did_change() override;
+ virtual void document_did_set_text() override;
+ virtual void document_did_set_cursor(const TextPosition&) override;
+
+ void create_actions();
+ void paint_ruler(Painter&);
+ void update_content_size();
+ int fixed_glyph_width() const;
+
+ void defer_reflow();
+ void undefer_reflow();
+
+ void try_show_autocomplete();
+
+ int icon_size() const { return 16; }
+ int icon_padding() const { return 2; }
+
+ class ReflowDeferrer {
+ public:
+ ReflowDeferrer(TextEditor& editor)
+ : m_editor(editor)
+ {
+ m_editor.defer_reflow();
+ }
+ ~ReflowDeferrer()
+ {
+ m_editor.undefer_reflow();
+ }
+
+ private:
+ TextEditor& m_editor;
+ };
+
+ Gfx::IntRect line_content_rect(size_t item_index) const;
+ Gfx::IntRect line_widget_rect(size_t line_index) const;
+ void delete_selection();
+ int content_x_for_position(const TextPosition&) const;
+ Gfx::IntRect ruler_rect_in_inner_coordinates() const;
+ Gfx::IntRect visible_text_rect_in_inner_coordinates() const;
+ void recompute_all_visual_lines();
+ void ensure_cursor_is_valid();
+ void flush_pending_change_notification_if_needed();
+
+ size_t visual_line_containing(size_t line_index, size_t column) const;
+ void recompute_visual_lines(size_t line_index);
+
+ void automatic_selection_scroll_timer_fired();
+
+ template<class T, class... Args>
+ inline void execute(Args&&... args)
+ {
+ auto command = make<T>(*m_document, forward<Args>(args)...);
+ command->perform_formatting(*this);
+ on_edit_action(*command);
+ command->execute_from(*this);
+ m_document->add_to_undo_stack(move(command));
+ }
+
+ virtual void on_edit_action(const Command&) { }
+
+ Type m_type { MultiLine };
+ Mode m_mode { Editable };
+
+ TextPosition m_cursor;
+ Gfx::TextAlignment m_text_alignment { Gfx::TextAlignment::CenterLeft };
+ bool m_cursor_state { true };
+ bool m_in_drag_select { false };
+ bool m_ruler_visible { false };
+ bool m_has_pending_change_notification { false };
+ bool m_automatic_indentation_enabled { false };
+ bool m_line_wrapping_enabled { false };
+ bool m_has_visible_list { false };
+ bool m_visualize_trailing_whitespace { true };
+ int m_line_spacing { 4 };
+ size_t m_soft_tab_width { 4 };
+ int m_horizontal_content_padding { 3 };
+ TextRange m_selection;
+ RefPtr<Menu> m_context_menu;
+ RefPtr<Action> m_undo_action;
+ RefPtr<Action> m_redo_action;
+ RefPtr<Action> m_cut_action;
+ RefPtr<Action> m_copy_action;
+ RefPtr<Action> m_paste_action;
+ RefPtr<Action> m_delete_action;
+ RefPtr<Action> m_go_to_line_action;
+ RefPtr<Action> m_select_all_action;
+ Core::ElapsedTimer m_triple_click_timer;
+ NonnullRefPtrVector<Action> m_custom_context_menu_actions;
+
+ size_t m_reflow_deferred { 0 };
+ bool m_reflow_requested { false };
+
+ bool is_visual_data_up_to_date() const { return !m_reflow_requested; }
+
+ RefPtr<TextDocument> m_document;
+
+ String m_placeholder { "" };
+
+ template<typename Callback>
+ void for_each_visual_line(size_t line_index, Callback) const;
+
+ struct LineVisualData {
+ Vector<size_t, 1> visual_line_breaks;
+ Gfx::IntRect visual_rect;
+ };
+
+ NonnullOwnPtrVector<LineVisualData> m_line_visual_data;
+
+ OwnPtr<SyntaxHighlighter> m_highlighter;
+ OwnPtr<AutocompleteProvider> m_autocomplete_provider;
+ OwnPtr<AutocompleteBox> m_autocomplete_box;
+ bool m_should_keep_autocomplete_box { false };
+ size_t m_automatic_autocomplete_delay_ms { 800 };
+
+ RefPtr<Core::Timer> m_automatic_selection_scroll_timer;
+ RefPtr<Core::Timer> m_autocomplete_timer;
+
+ OwnPtr<EditingEngine> m_editing_engine;
+
+ Gfx::IntPoint m_last_mousemove_position;
+
+ RefPtr<Gfx::Bitmap> m_icon;
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/TextPosition.h b/Userland/Libraries/LibGUI/TextPosition.h
new file mode 100644
index 0000000000..47ccc71e47
--- /dev/null
+++ b/Userland/Libraries/LibGUI/TextPosition.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/LogStream.h>
+#include <AK/String.h>
+
+namespace GUI {
+
+class TextPosition {
+public:
+ TextPosition() { }
+ TextPosition(size_t line, size_t column)
+ : m_line(line)
+ , m_column(column)
+ {
+ }
+
+ bool is_valid() const { return m_line != 0xffffffffu && m_column != 0xffffffffu; }
+
+ size_t line() const { return m_line; }
+ size_t column() const { return m_column; }
+
+ void set_line(size_t line) { m_line = line; }
+ void set_column(size_t column) { m_column = column; }
+
+ bool operator==(const TextPosition& other) const { return m_line == other.m_line && m_column == other.m_column; }
+ bool operator!=(const TextPosition& other) const { return m_line != other.m_line || m_column != other.m_column; }
+ bool operator<(const TextPosition& other) const { return m_line < other.m_line || (m_line == other.m_line && m_column < other.m_column); }
+
+private:
+ size_t m_line { 0xffffffff };
+ size_t m_column { 0xffffffff };
+};
+
+inline const LogStream& operator<<(const LogStream& stream, const TextPosition& value)
+{
+ if (!value.is_valid())
+ return stream << "GUI::TextPosition(Invalid)";
+ return stream << String::formatted("({},{})", value.line(), value.column());
+}
+
+}
+
+template<>
+struct AK::Formatter<GUI::TextPosition> : AK::Formatter<FormatString> {
+ void format(FormatBuilder& builder, const GUI::TextPosition& value)
+ {
+ if (value.is_valid())
+ Formatter<FormatString>::format(builder, "({},{})", value.line(), value.column());
+ else
+ Formatter<FormatString>::format(builder, "GUI::TextPosition(Invalid)");
+ }
+};
diff --git a/Userland/Libraries/LibGUI/TextRange.h b/Userland/Libraries/LibGUI/TextRange.h
new file mode 100644
index 0000000000..222edbca01
--- /dev/null
+++ b/Userland/Libraries/LibGUI/TextRange.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/LogStream.h>
+#include <LibGUI/TextPosition.h>
+
+namespace GUI {
+
+class TextRange {
+public:
+ TextRange() { }
+ TextRange(const TextPosition& start, const TextPosition& end)
+ : m_start(start)
+ , m_end(end)
+ {
+ }
+
+ bool is_valid() const { return m_start.is_valid() && m_end.is_valid(); }
+ void clear()
+ {
+ m_start = {};
+ m_end = {};
+ }
+
+ TextPosition& start() { return m_start; }
+ TextPosition& end() { return m_end; }
+ const TextPosition& start() const { return m_start; }
+ const TextPosition& end() const { return m_end; }
+
+ TextRange normalized() const { return TextRange(normalized_start(), normalized_end()); }
+
+ void set_start(const TextPosition& position) { m_start = position; }
+ void set_end(const TextPosition& position) { m_end = position; }
+
+ void set(const TextPosition& start, const TextPosition& end)
+ {
+ m_start = start;
+ m_end = end;
+ }
+
+ bool operator==(const TextRange& other) const
+ {
+ return m_start == other.m_start && m_end == other.m_end;
+ }
+
+ bool contains(const TextPosition& position) const
+ {
+ if (!(position.line() > m_start.line() || (position.line() == m_start.line() && position.column() >= m_start.column())))
+ return false;
+ if (!(position.line() < m_end.line() || (position.line() == m_end.line() && position.column() <= m_end.column())))
+ return false;
+ return true;
+ }
+
+private:
+ TextPosition normalized_start() const { return m_start < m_end ? m_start : m_end; }
+ TextPosition normalized_end() const { return m_start < m_end ? m_end : m_start; }
+
+ TextPosition m_start;
+ TextPosition m_end;
+};
+
+inline const LogStream& operator<<(const LogStream& stream, const TextRange& value)
+{
+ if (!value.is_valid())
+ return stream << "GUI::TextRange(Invalid)";
+ return stream << value.start() << '-' << value.end();
+}
+
+}
+
+template<>
+struct AK::Formatter<GUI::TextRange> : AK::Formatter<FormatString> {
+ void format(FormatBuilder& builder, const GUI::TextRange& value)
+ {
+ if (value.is_valid())
+ return Formatter<FormatString>::format(builder, "{}-{}", value.start(), value.end());
+ else
+ return Formatter<FormatString>::format(builder, "GUI::TextRange(Invalid)");
+ }
+};
diff --git a/Userland/Libraries/LibGUI/ToolBar.cpp b/Userland/Libraries/LibGUI/ToolBar.cpp
new file mode 100644
index 0000000000..c2486ee884
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ToolBar.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/ActionGroup.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/SeparatorWidget.h>
+#include <LibGUI/ToolBar.h>
+#include <LibGfx/Palette.h>
+
+REGISTER_WIDGET(GUI, ToolBar)
+
+namespace GUI {
+
+ToolBar::ToolBar(Orientation orientation, int button_size)
+ : m_orientation(orientation)
+ , m_button_size(button_size)
+{
+ if (m_orientation == Orientation::Horizontal) {
+ set_fixed_height(button_size + 8);
+ } else {
+ set_fixed_width(button_size + 8);
+ }
+ set_layout<BoxLayout>(orientation);
+ layout()->set_spacing(0);
+ layout()->set_margins({ 2, 2, 2, 2 });
+}
+
+ToolBar::~ToolBar()
+{
+}
+
+class ToolBarButton final : public Button {
+ C_OBJECT(ToolBarButton);
+
+public:
+ virtual ~ToolBarButton() override { }
+
+private:
+ explicit ToolBarButton(Action& action)
+ {
+ if (action.group() && action.group()->is_exclusive())
+ set_exclusive(true);
+ set_action(action);
+ set_tooltip(tooltip(action));
+ set_focus_policy(FocusPolicy::TabFocus);
+ if (action.icon())
+ set_icon(action.icon());
+ else
+ set_text(action.text());
+ set_button_style(Gfx::ButtonStyle::CoolBar);
+ }
+ String tooltip(const Action& action) const
+ {
+ StringBuilder builder;
+ builder.append(action.text());
+ if (action.shortcut().is_valid()) {
+ builder.append(" (");
+ builder.append(action.shortcut().to_string());
+ builder.append(")");
+ }
+ return builder.to_string();
+ }
+};
+
+void ToolBar::add_action(Action& action)
+{
+ auto item = make<Item>();
+ item->type = Item::Type::Action;
+ item->action = action;
+
+ auto& button = add<ToolBarButton>(action);
+ button.set_fixed_size(m_button_size + 8, m_button_size + 8);
+
+ m_items.append(move(item));
+}
+
+void ToolBar::add_separator()
+{
+ auto item = make<Item>();
+ item->type = Item::Type::Separator;
+ add<SeparatorWidget>(m_orientation == Gfx::Orientation::Horizontal ? Gfx::Orientation::Vertical : Gfx::Orientation::Horizontal);
+ m_items.append(move(item));
+}
+
+void ToolBar::paint_event(PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ painter.fill_rect(event.rect(), palette().button());
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ToolBar.h b/Userland/Libraries/LibGUI/ToolBar.h
new file mode 100644
index 0000000000..e6e6790d7c
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ToolBar.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullOwnPtrVector.h>
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class ToolBar : public Widget {
+ C_OBJECT(ToolBar)
+public:
+ virtual ~ToolBar() override;
+
+ void add_action(Action&);
+ void add_separator();
+
+ bool has_frame() const { return m_has_frame; }
+ void set_has_frame(bool has_frame) { m_has_frame = has_frame; }
+
+protected:
+ explicit ToolBar(Gfx::Orientation = Gfx::Orientation::Horizontal, int button_size = 16);
+
+ virtual void paint_event(PaintEvent&) override;
+
+private:
+ struct Item {
+ enum class Type {
+ Invalid,
+ Separator,
+ Action
+ };
+ Type type { Type::Invalid };
+ RefPtr<Action> action;
+ };
+ NonnullOwnPtrVector<Item> m_items;
+ const Gfx::Orientation m_orientation;
+ int m_button_size { 16 };
+ bool m_has_frame { true };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/ToolBarContainer.cpp b/Userland/Libraries/LibGUI/ToolBarContainer.cpp
new file mode 100644
index 0000000000..d2f6b5252c
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ToolBarContainer.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ToolBarContainer.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+REGISTER_WIDGET(GUI, ToolBarContainer)
+
+namespace GUI {
+
+ToolBarContainer::ToolBarContainer(Gfx::Orientation orientation)
+ : m_orientation(orientation)
+{
+ set_fill_with_background_color(true);
+
+ set_frame_thickness(2);
+ set_frame_shape(Gfx::FrameShape::Box);
+ set_frame_shadow(Gfx::FrameShadow::Sunken);
+
+ auto& layout = set_layout<VerticalBoxLayout>();
+ layout.set_spacing(2);
+ layout.set_margins({ 2, 2, 2, 2 });
+
+ set_shrink_to_fit(true);
+}
+
+void ToolBarContainer::paint_event(GUI::PaintEvent& event)
+{
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ for_each_child_of_type<ToolBar>([&](auto& toolbar) {
+ if (toolbar.is_visible()) {
+ auto rect = toolbar.relative_rect();
+ painter.draw_line(rect.top_left().translated(0, -1), rect.top_right().translated(0, -1), palette().threed_highlight());
+ painter.draw_line(rect.bottom_left().translated(0, 1), rect.bottom_right().translated(0, 1), palette().threed_shadow1());
+ }
+ return IterationDecision::Continue;
+ });
+
+ Frame::paint_event(event);
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/ToolBarContainer.h b/Userland/Libraries/LibGUI/ToolBarContainer.h
new file mode 100644
index 0000000000..46db33d269
--- /dev/null
+++ b/Userland/Libraries/LibGUI/ToolBarContainer.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Frame.h>
+#include <LibGUI/ToolBar.h>
+
+namespace GUI {
+
+class ToolBarContainer : public Frame {
+ C_OBJECT(ToolBarContainer);
+
+public:
+private:
+ explicit ToolBarContainer(Gfx::Orientation = Gfx::Orientation::Horizontal);
+
+ virtual void paint_event(GUI::PaintEvent&) override;
+
+ Gfx::Orientation m_orientation { Gfx::Orientation::Horizontal };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/TreeView.cpp b/Userland/Libraries/LibGUI/TreeView.cpp
new file mode 100644
index 0000000000..ca6455024f
--- /dev/null
+++ b/Userland/Libraries/LibGUI/TreeView.cpp
@@ -0,0 +1,633 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/HeaderView.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGUI/TreeView.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Palette.h>
+
+//#define DEBUG_ITEM_RECTS
+
+REGISTER_WIDGET(GUI, TreeView)
+
+namespace GUI {
+
+struct TreeView::MetadataForIndex {
+ bool open { false };
+};
+
+TreeView::MetadataForIndex& TreeView::ensure_metadata_for_index(const ModelIndex& index) const
+{
+ ASSERT(index.is_valid());
+ auto it = m_view_metadata.find(index.internal_data());
+ if (it != m_view_metadata.end())
+ return *it->value;
+ auto new_metadata = make<MetadataForIndex>();
+ auto& new_metadata_ref = *new_metadata;
+ m_view_metadata.set(index.internal_data(), move(new_metadata));
+ return new_metadata_ref;
+}
+
+TreeView::TreeView()
+{
+ set_fill_with_background_color(true);
+ set_background_role(ColorRole::Base);
+ set_foreground_role(ColorRole::BaseText);
+ set_column_headers_visible(false);
+ m_expand_bitmap = Gfx::Bitmap::load_from_file("/res/icons/serenity/treeview-expand.png");
+ m_collapse_bitmap = Gfx::Bitmap::load_from_file("/res/icons/serenity/treeview-collapse.png");
+}
+
+TreeView::~TreeView()
+{
+}
+
+ModelIndex TreeView::index_at_event_position(const Gfx::IntPoint& a_position, bool& is_toggle) const
+{
+ auto position = a_position.translated(0, -column_header().height()).translated(horizontal_scrollbar().value() - frame_thickness(), vertical_scrollbar().value() - frame_thickness());
+ is_toggle = false;
+ if (!model())
+ return {};
+ ModelIndex result;
+ traverse_in_paint_order([&](const ModelIndex& index, const Gfx::IntRect& rect, const Gfx::IntRect& toggle_rect, int) {
+ if (toggle_rect.contains(position)) {
+ result = index;
+ is_toggle = true;
+ return IterationDecision::Break;
+ }
+ if (rect.contains_vertically(position.y())) {
+ result = index;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+ return result;
+}
+
+void TreeView::doubleclick_event(MouseEvent& event)
+{
+ if (!model())
+ return;
+ auto& model = *this->model();
+ bool is_toggle;
+ auto index = index_at_event_position(event.position(), is_toggle);
+ if (!index.is_valid())
+ return;
+
+ if (event.button() == MouseButton::Left) {
+ set_cursor(index, SelectionUpdate::Set);
+
+ if (model.row_count(index))
+ toggle_index(index);
+ else
+ activate(index);
+ }
+}
+
+void TreeView::set_open_state_of_all_in_subtree(const ModelIndex& root, bool open)
+{
+ if (root.is_valid()) {
+ ensure_metadata_for_index(root).open = open;
+ if (model()->row_count(root)) {
+ if (on_toggle)
+ on_toggle(root, open);
+ }
+ }
+ int row_count = model()->row_count(root);
+ int column = model()->tree_column();
+ for (int row = 0; row < row_count; ++row) {
+ auto index = model()->index(row, column, root);
+ set_open_state_of_all_in_subtree(index, open);
+ }
+}
+
+void TreeView::expand_all_parents_of(const ModelIndex& index)
+{
+ if (!model())
+ return;
+
+ auto current = index;
+ while (current.is_valid()) {
+ ensure_metadata_for_index(current).open = true;
+ if (on_toggle)
+ on_toggle(current, true);
+ current = current.parent();
+ }
+ update_column_sizes();
+ update_content_size();
+ update();
+}
+
+void TreeView::expand_tree(const ModelIndex& root)
+{
+ if (!model())
+ return;
+ set_open_state_of_all_in_subtree(root, true);
+ update_column_sizes();
+ update_content_size();
+ update();
+}
+
+void TreeView::collapse_tree(const ModelIndex& root)
+{
+ if (!model())
+ return;
+ set_open_state_of_all_in_subtree(root, false);
+ update_column_sizes();
+ update_content_size();
+ update();
+}
+
+void TreeView::toggle_index(const ModelIndex& index)
+{
+ ASSERT(model()->row_count(index));
+ auto& metadata = ensure_metadata_for_index(index);
+ metadata.open = !metadata.open;
+ if (on_toggle)
+ on_toggle(index, metadata.open);
+ update_column_sizes();
+ update_content_size();
+ update();
+}
+
+template<typename Callback>
+void TreeView::traverse_in_paint_order(Callback callback) const
+{
+ ASSERT(model());
+ auto& model = *this->model();
+ int indent_level = 1;
+ int y_offset = 0;
+ int tree_column_x_offset = this->tree_column_x_offset();
+
+ Function<IterationDecision(const ModelIndex&)> traverse_index = [&](const ModelIndex& index) {
+ int row_count_at_index = model.row_count(index);
+ if (index.is_valid()) {
+ auto& metadata = ensure_metadata_for_index(index);
+ int x_offset = tree_column_x_offset + horizontal_padding() + indent_level * indent_width_in_pixels();
+ auto node_text = index.data().to_string();
+ Gfx::IntRect rect = {
+ x_offset, y_offset,
+ icon_size() + icon_spacing() + text_padding() + font_for_index(index)->width(node_text) + text_padding(), row_height()
+ };
+ Gfx::IntRect toggle_rect;
+ if (row_count_at_index > 0) {
+ int toggle_x = tree_column_x_offset + horizontal_padding() + (indent_width_in_pixels() * indent_level) - (icon_size() / 2) - 4;
+ toggle_rect = { toggle_x, rect.y(), toggle_size(), toggle_size() };
+ toggle_rect.center_vertically_within(rect);
+ }
+ if (callback(index, rect, toggle_rect, indent_level) == IterationDecision::Break)
+ return IterationDecision::Break;
+ y_offset += row_height();
+ // NOTE: Skip traversing children if this index is closed!
+ if (!metadata.open)
+ return IterationDecision::Continue;
+ }
+
+ if (indent_level > 0 && !index.is_valid())
+ return IterationDecision::Continue;
+
+ ++indent_level;
+ int row_count = model.row_count(index);
+ for (int i = 0; i < row_count; ++i) {
+ if (traverse_index(model.index(i, model.tree_column(), index)) == IterationDecision::Break)
+ return IterationDecision::Break;
+ }
+ --indent_level;
+ return IterationDecision::Continue;
+ };
+ int root_count = model.row_count();
+ for (int root_index = 0; root_index < root_count; ++root_index) {
+ if (traverse_index(model.index(root_index, model.tree_column(), ModelIndex())) == IterationDecision::Break)
+ break;
+ }
+}
+
+void TreeView::paint_event(PaintEvent& event)
+{
+ Frame::paint_event(event);
+ Painter painter(*this);
+ painter.add_clip_rect(frame_inner_rect());
+ painter.add_clip_rect(event.rect());
+ if (fill_with_background_color())
+ painter.fill_rect(event.rect(), palette().color(background_role()));
+
+ if (!model())
+ return;
+ auto& model = *this->model();
+
+ painter.translate(frame_inner_rect().location());
+ painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
+
+ auto visible_content_rect = this->visible_content_rect();
+ int tree_column = model.tree_column();
+ int tree_column_x_offset = this->tree_column_x_offset();
+
+ int y_offset = column_header().height();
+
+ int painted_row_index = 0;
+
+ traverse_in_paint_order([&](const ModelIndex& index, const Gfx::IntRect& a_rect, const Gfx::IntRect& a_toggle_rect, int indent_level) {
+ if (!a_rect.intersects_vertically(visible_content_rect))
+ return IterationDecision::Continue;
+
+ auto rect = a_rect.translated(0, y_offset);
+ auto toggle_rect = a_toggle_rect.translated(0, y_offset);
+
+#ifdef DEBUG_ITEM_RECTS
+ painter.fill_rect(rect, Color::WarmGray);
+#endif
+
+ bool is_selected_row = selection().contains(index);
+
+ Color text_color = palette().color(foreground_role());
+ if (is_selected_row && should_fill_selected_rows())
+ text_color = is_focused() ? palette().selection_text() : palette().inactive_selection_text();
+
+ Color background_color;
+ Color key_column_background_color;
+ if (is_selected_row) {
+ background_color = is_focused() ? palette().selection() : palette().inactive_selection();
+ key_column_background_color = is_focused() ? palette().selection() : palette().inactive_selection();
+ } else {
+ if (alternating_row_colors() && (painted_row_index % 2)) {
+ background_color = Color(220, 220, 220);
+ key_column_background_color = Color(200, 200, 200);
+ } else {
+ background_color = palette().color(background_role());
+ key_column_background_color = Color(220, 220, 220);
+ }
+ }
+
+ int row_width = 0;
+ for (int column_index = 0; column_index < model.column_count(); ++column_index) {
+ if (!column_header().is_section_visible(column_index))
+ continue;
+ row_width += this->column_width(column_index) + horizontal_padding() * 2;
+ }
+ if (frame_inner_rect().width() > row_width) {
+ row_width = frame_inner_rect().width();
+ }
+
+ Gfx::IntRect row_rect { 0, rect.y(), row_width, rect.height() };
+
+ if (!is_selected_row || should_fill_selected_rows())
+ painter.fill_rect(row_rect, background_color);
+
+ int x_offset = 0;
+ for (int column_index = 0; column_index < model.column_count(); ++column_index) {
+ if (!column_header().is_section_visible(column_index))
+ continue;
+ int column_width = this->column_width(column_index);
+
+ painter.draw_rect(toggle_rect, text_color);
+
+ if (column_index != tree_column) {
+ Gfx::IntRect cell_rect(horizontal_padding() + x_offset, rect.y(), column_width, row_height());
+ auto cell_index = model.index(index.row(), column_index, index.parent());
+
+ if (auto* delegate = column_painting_delegate(column_index)) {
+ delegate->paint(painter, cell_rect, palette(), cell_index);
+ } else {
+ auto data = cell_index.data();
+
+ if (data.is_bitmap()) {
+ painter.blit(cell_rect.location(), data.as_bitmap(), data.as_bitmap().rect());
+ } else if (data.is_icon()) {
+ if (auto bitmap = data.as_icon().bitmap_for_size(16))
+ painter.blit(cell_rect.location(), *bitmap, bitmap->rect());
+ } else {
+ auto text_alignment = cell_index.data(ModelRole::TextAlignment).to_text_alignment(Gfx::TextAlignment::CenterLeft);
+ draw_item_text(painter, cell_index, is_selected_row, cell_rect, data.to_string(), font_for_index(cell_index), text_alignment, Gfx::TextElision::Right);
+ }
+ }
+ } else {
+ // It's the tree column!
+ Gfx::IntRect icon_rect = { rect.x(), rect.y(), icon_size(), icon_size() };
+ Gfx::IntRect text_rect = {
+ icon_rect.right() + 1 + icon_spacing(), rect.y(),
+ rect.width() - icon_size() - icon_spacing(), rect.height()
+ };
+
+ painter.fill_rect(text_rect, background_color);
+
+ auto icon = index.data(ModelRole::Icon);
+ if (icon.is_icon()) {
+ if (auto* bitmap = icon.as_icon().bitmap_for_size(icon_size())) {
+ if (m_hovered_index.is_valid() && m_hovered_index.parent() == index.parent() && m_hovered_index.row() == index.row())
+ painter.blit_brightened(icon_rect.location(), *bitmap, bitmap->rect());
+ else
+ painter.blit(icon_rect.location(), *bitmap, bitmap->rect());
+ }
+ }
+ draw_item_text(painter, index, is_selected_row, text_rect, index.data().to_string(), font_for_index(index), Gfx::TextAlignment::Center, Gfx::TextElision::None);
+
+ if (is_focused() && index == cursor_index()) {
+ painter.draw_rect(text_rect, palette().color(background_role()));
+ painter.draw_focus_rect(text_rect, palette().focus_outline());
+ }
+
+ auto index_at_indent = index;
+ for (int i = indent_level; i > 0; --i) {
+ auto parent_of_index_at_indent = index_at_indent.parent();
+ bool index_at_indent_is_last_in_parent = index_at_indent.row() == model.row_count(parent_of_index_at_indent) - 1;
+ Gfx::IntPoint a { tree_column_x_offset + horizontal_padding() + indent_width_in_pixels() * i - icon_size() / 2, rect.y() - 2 };
+ Gfx::IntPoint b { a.x(), a.y() + row_height() - 1 };
+ if (index_at_indent_is_last_in_parent)
+ b.set_y(rect.center().y());
+ if (!(i != indent_level && index_at_indent_is_last_in_parent))
+ painter.draw_line(a, b, Color::MidGray);
+
+ if (i == indent_level) {
+ Gfx::IntPoint c { a.x(), rect.center().y() };
+ Gfx::IntPoint d { c.x() + icon_size() / 2, c.y() };
+ painter.draw_line(c, d, Color::MidGray);
+ }
+ index_at_indent = parent_of_index_at_indent;
+ }
+
+ if (!toggle_rect.is_empty()) {
+ auto& metadata = ensure_metadata_for_index(index);
+ if (metadata.open)
+ painter.blit(toggle_rect.location(), *m_collapse_bitmap, m_collapse_bitmap->rect());
+ else
+ painter.blit(toggle_rect.location(), *m_expand_bitmap, m_expand_bitmap->rect());
+ }
+
+ if (has_pending_drop() && index == drop_candidate_index()) {
+ painter.draw_rect(rect, palette().selection(), true);
+ }
+ }
+ x_offset += column_width + horizontal_padding() * 2;
+ }
+
+ return IterationDecision::Continue;
+ });
+}
+
+void TreeView::scroll_into_view(const ModelIndex& a_index, bool scroll_horizontally, bool scroll_vertically)
+{
+ if (!a_index.is_valid())
+ return;
+ Gfx::IntRect found_rect;
+ traverse_in_paint_order([&](const ModelIndex& index, const Gfx::IntRect& rect, const Gfx::IntRect&, int) {
+ if (index == a_index) {
+ found_rect = rect;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+ ScrollableWidget::scroll_into_view(found_rect, scroll_horizontally, scroll_vertically);
+}
+
+void TreeView::model_did_update(unsigned flags)
+{
+ m_view_metadata.clear();
+ AbstractTableView::model_did_update(flags);
+}
+
+void TreeView::did_update_selection()
+{
+ AbstractView::did_update_selection();
+ if (!model())
+ return;
+ auto index = selection().first();
+ if (!index.is_valid())
+ return;
+#if 0
+ bool opened_any = false;
+ for (auto current = index; current.is_valid(); current = current.parent()) {
+ auto& metadata_for_ancestor = ensure_metadata_for_index(current);
+ if (!metadata_for_ancestor.open) {
+ metadata_for_ancestor.open = true;
+ opened_any = true;
+ }
+ }
+ if (opened_any)
+ update_content_size();
+ update();
+#endif
+ if (activates_on_selection())
+ activate(index);
+}
+
+void TreeView::keydown_event(KeyEvent& event)
+{
+ if (!model())
+ return AbstractTableView::keydown_event(event);
+
+ if (event.key() == KeyCode::Key_Space) {
+ if (model()->row_count(cursor_index()))
+ toggle_index(cursor_index());
+ return;
+ }
+
+ auto open_tree_node = [&](bool open, MetadataForIndex& metadata) {
+ if (on_toggle)
+ on_toggle(cursor_index(), open);
+ metadata.open = open;
+ update_column_sizes();
+ update_content_size();
+ update();
+ };
+
+ if (event.key() == KeyCode::Key_Left) {
+ if (cursor_index().is_valid() && model()->row_count(cursor_index())) {
+ if (event.ctrl()) {
+ collapse_tree(cursor_index());
+ return;
+ }
+
+ auto& metadata = ensure_metadata_for_index(cursor_index());
+ if (metadata.open) {
+ open_tree_node(false, metadata);
+ return;
+ }
+ }
+ if (cursor_index().is_valid() && cursor_index().parent().is_valid()) {
+ set_cursor(cursor_index().parent(), SelectionUpdate::Set);
+ return;
+ }
+ }
+
+ if (event.key() == KeyCode::Key_Right) {
+ if (cursor_index().is_valid() && model()->row_count(cursor_index())) {
+ if (event.ctrl()) {
+ expand_tree(cursor_index());
+ return;
+ }
+
+ auto& metadata = ensure_metadata_for_index(cursor_index());
+ if (!metadata.open) {
+ open_tree_node(true, metadata);
+ return;
+ }
+
+ auto new_cursor = model()->index(0, model()->tree_column(), cursor_index());
+ set_cursor(new_cursor, SelectionUpdate::Set);
+ return;
+ }
+ }
+
+ if (event.key() == KeyCode::Key_Return) {
+ if (cursor_index().is_valid() && model()->row_count(cursor_index())) {
+ toggle_index(cursor_index());
+ return;
+ }
+ }
+
+ AbstractTableView::keydown_event(event);
+}
+
+void TreeView::move_cursor(CursorMovement movement, SelectionUpdate selection_update)
+{
+ switch (movement) {
+ case CursorMovement::Up: {
+ ModelIndex previous_index;
+ ModelIndex found_index;
+ traverse_in_paint_order([&](const ModelIndex& index, const Gfx::IntRect&, const Gfx::IntRect&, int) {
+ if (index == cursor_index()) {
+ found_index = previous_index;
+ return IterationDecision::Break;
+ }
+ previous_index = index;
+ return IterationDecision::Continue;
+ });
+ if (found_index.is_valid())
+ set_cursor(found_index, selection_update);
+ break;
+ }
+ case CursorMovement::Down: {
+ ModelIndex previous_index;
+ ModelIndex found_index;
+ traverse_in_paint_order([&](const ModelIndex& index, const Gfx::IntRect&, const Gfx::IntRect&, int) {
+ if (previous_index == cursor_index()) {
+ found_index = index;
+ return IterationDecision::Break;
+ }
+ previous_index = index;
+ return IterationDecision::Continue;
+ });
+ if (found_index.is_valid())
+ set_cursor(found_index, selection_update);
+ return;
+ }
+
+ case CursorMovement::Home:
+ // FIXME: Implement.
+ break;
+
+ case CursorMovement::End:
+ // FIXME: Implement.
+ break;
+
+ case CursorMovement::PageUp:
+ // FIXME: Implement.
+ break;
+
+ case CursorMovement::PageDown:
+ // FIXME: Implement.
+ break;
+
+ case CursorMovement::Left:
+ case CursorMovement::Right:
+ // There is no left/right in a treeview, those keys expand/collapse items instead.
+ break;
+ }
+}
+
+int TreeView::item_count() const
+{
+ int count = 0;
+ traverse_in_paint_order([&](const ModelIndex&, const Gfx::IntRect&, const Gfx::IntRect&, int) {
+ ++count;
+ return IterationDecision::Continue;
+ });
+ return count;
+}
+
+void TreeView::update_column_sizes()
+{
+ if (!model())
+ return;
+
+ auto& model = *this->model();
+ int column_count = model.column_count();
+ int row_count = model.row_count();
+ int tree_column = model.tree_column();
+ int tree_column_x_offset = 0;
+
+ for (int column = 0; column < column_count; ++column) {
+ if (column == tree_column)
+ continue;
+ if (!column_header().is_section_visible(column))
+ continue;
+ int header_width = column_header().font().width(model.column_name(column));
+ int column_width = header_width;
+
+ for (int row = 0; row < row_count; ++row) {
+ auto cell_data = model.index(row, column).data();
+ int cell_width = 0;
+ if (cell_data.is_bitmap()) {
+ cell_width = cell_data.as_bitmap().width();
+ } else {
+ cell_width = font().width(cell_data.to_string());
+ }
+ column_width = max(column_width, cell_width);
+ }
+
+ set_column_width(column, max(this->column_width(column), column_width));
+
+ if (column < tree_column)
+ tree_column_x_offset += column_width;
+ }
+
+ int tree_column_header_width = column_header().font().width(model.column_name(tree_column));
+ int tree_column_width = tree_column_header_width;
+ traverse_in_paint_order([&](const ModelIndex&, const Gfx::IntRect& rect, const Gfx::IntRect&, int) {
+ tree_column_width = max(rect.right() - tree_column_x_offset, tree_column_width);
+ return IterationDecision::Continue;
+ });
+
+ set_column_width(tree_column, max(this->column_width(tree_column), tree_column_width));
+}
+
+int TreeView::tree_column_x_offset() const
+{
+ int tree_column = model()->tree_column();
+ int offset = 0;
+ for (int i = 0; i < tree_column; ++i) {
+ if (column_header().is_section_visible(i)) {
+ offset += column_width(i);
+ offset += horizontal_padding();
+ }
+ }
+ return offset;
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/TreeView.h b/Userland/Libraries/LibGUI/TreeView.h
new file mode 100644
index 0000000000..76b1754302
--- /dev/null
+++ b/Userland/Libraries/LibGUI/TreeView.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <LibGUI/AbstractTableView.h>
+
+namespace GUI {
+
+class TreeView : public AbstractTableView {
+ C_OBJECT(TreeView)
+public:
+ virtual ~TreeView() override;
+
+ virtual void scroll_into_view(const ModelIndex&, bool scroll_horizontally, bool scroll_vertically) override;
+
+ virtual int item_count() const override;
+ virtual void toggle_index(const ModelIndex&) override;
+
+ void expand_tree(const ModelIndex& root = {});
+ void collapse_tree(const ModelIndex& root = {});
+
+ void expand_all_parents_of(const ModelIndex&);
+
+ Function<void(const ModelIndex&, const bool)> on_toggle;
+
+ void set_should_fill_selected_rows(bool fill) { m_should_fill_selected_rows = fill; }
+ bool should_fill_selected_rows() const { return m_should_fill_selected_rows; }
+
+protected:
+ TreeView();
+
+ virtual void paint_event(PaintEvent&) override;
+ virtual void doubleclick_event(MouseEvent&) override;
+ virtual void keydown_event(KeyEvent&) override;
+
+ virtual void did_update_selection() override;
+ virtual void model_did_update(unsigned flags) override;
+ virtual void move_cursor(CursorMovement, SelectionUpdate) override;
+
+private:
+ virtual ModelIndex index_at_event_position(const Gfx::IntPoint&, bool& is_toggle) const override;
+
+ int row_height() const { return 16; }
+ int max_item_width() const { return frame_inner_rect().width(); }
+ int indent_width_in_pixels() const { return 16; }
+ int icon_size() const { return 16; }
+ int icon_spacing() const { return 2; }
+ int toggle_size() const { return 9; }
+ int text_padding() const { return 2; }
+ int tree_column_x_offset() const;
+ virtual void update_column_sizes() override;
+
+ template<typename Callback>
+ void traverse_in_paint_order(Callback) const;
+
+ struct MetadataForIndex;
+
+ MetadataForIndex& ensure_metadata_for_index(const ModelIndex&) const;
+ void set_open_state_of_all_in_subtree(const ModelIndex& root, bool open);
+
+ mutable HashMap<void*, NonnullOwnPtr<MetadataForIndex>> m_view_metadata;
+
+ RefPtr<Gfx::Bitmap> m_expand_bitmap;
+ RefPtr<Gfx::Bitmap> m_collapse_bitmap;
+
+ bool m_should_fill_selected_rows { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/UndoStack.cpp b/Userland/Libraries/LibGUI/UndoStack.cpp
new file mode 100644
index 0000000000..775997ef47
--- /dev/null
+++ b/Userland/Libraries/LibGUI/UndoStack.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Command.h>
+#include <LibGUI/UndoStack.h>
+
+namespace GUI {
+
+UndoStack::UndoStack()
+{
+}
+
+UndoStack::~UndoStack()
+{
+}
+
+void UndoStack::undo()
+{
+ if (!can_undo())
+ return;
+
+ auto pop_container_and_undo = [this]() {
+ for (;;) {
+ if (m_stack_index >= m_stack.size())
+ break;
+
+ auto& container = m_stack[m_stack_index++];
+ if (container.m_undo_vector.size() == 0)
+ continue;
+ for (auto& command : container.m_undo_vector)
+ command.undo();
+ break;
+ }
+ };
+
+ // If this is the first undo, finish off our current combo
+ if (m_stack_index == 0)
+ finalize_current_combo();
+ pop_container_and_undo();
+}
+
+void UndoStack::redo()
+{
+ if (!can_redo())
+ return;
+
+ m_stack_index -= 1;
+ auto& vector = m_stack[m_stack_index].m_undo_vector;
+ for (int i = vector.size() - 1; i >= 0; i--)
+ vector[i].redo();
+}
+
+void UndoStack::push(NonnullOwnPtr<Command>&& command)
+{
+ if (m_stack.is_empty())
+ finalize_current_combo();
+
+ if (m_stack_index > 0) {
+ for (size_t i = 0; i < m_stack_index; i++)
+ m_stack.remove(0);
+ m_stack_index = 0;
+ finalize_current_combo();
+ }
+
+ auto& current_vector = m_stack.first().m_undo_vector;
+ current_vector.prepend(move(command));
+}
+
+void UndoStack::finalize_current_combo()
+{
+ if (m_stack_index > 0)
+ return;
+ if (m_stack.size() != 0 && m_stack.first().m_undo_vector.size() == 0)
+ return;
+
+ auto undo_commands_container = make<UndoCommandsContainer>();
+ m_stack.prepend(move(undo_commands_container));
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/UndoStack.h b/Userland/Libraries/LibGUI/UndoStack.h
new file mode 100644
index 0000000000..9c6d0504e1
--- /dev/null
+++ b/Userland/Libraries/LibGUI/UndoStack.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullOwnPtrVector.h>
+#include <LibGUI/Forward.h>
+
+namespace GUI {
+
+class UndoStack {
+public:
+ UndoStack();
+ ~UndoStack();
+
+ void push(NonnullOwnPtr<Command>&&);
+
+ bool can_undo() const { return m_stack_index < m_stack.size() && !m_stack.is_empty(); }
+ bool can_redo() const { return m_stack_index > 0 && !m_stack.is_empty(); }
+
+ void undo();
+ void redo();
+
+ void finalize_current_combo();
+
+private:
+ struct UndoCommandsContainer {
+ NonnullOwnPtrVector<Command> m_undo_vector;
+ };
+
+ NonnullOwnPtrVector<UndoCommandsContainer> m_stack;
+ size_t m_stack_index { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Variant.cpp b/Userland/Libraries/LibGUI/Variant.cpp
new file mode 100644
index 0000000000..5d3ddac2b2
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Variant.cpp
@@ -0,0 +1,462 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/FlyString.h>
+#include <AK/JsonValue.h>
+#include <AK/RefPtr.h>
+#include <LibGUI/Variant.h>
+
+namespace GUI {
+
+const char* to_string(Variant::Type type)
+{
+ switch (type) {
+ case Variant::Type::Invalid:
+ return "Invalid";
+ case Variant::Type::Bool:
+ return "Bool";
+ case Variant::Type::Int32:
+ return "Int32";
+ case Variant::Type::Int64:
+ return "Int64";
+ case Variant::Type::UnsignedInt:
+ return "UnsignedInt";
+ case Variant::Type::Float:
+ return "Float";
+ case Variant::Type::String:
+ return "String";
+ case Variant::Type::Bitmap:
+ return "Bitmap";
+ case Variant::Type::Color:
+ return "Color";
+ case Variant::Type::Icon:
+ return "Icon";
+ case Variant::Type::Point:
+ return "Point";
+ case Variant::Type::Size:
+ return "Size";
+ case Variant::Type::Rect:
+ return "Rect";
+ case Variant::Type::Font:
+ return "Font";
+ case Variant::Type::TextAlignment:
+ return "TextAlignment";
+ }
+ ASSERT_NOT_REACHED();
+}
+
+Variant::Variant()
+{
+ m_value.as_string = nullptr;
+}
+
+Variant::~Variant()
+{
+ clear();
+}
+
+void Variant::clear()
+{
+ switch (m_type) {
+ case Type::String:
+ AK::unref_if_not_null(m_value.as_string);
+ break;
+ case Type::Bitmap:
+ AK::unref_if_not_null(m_value.as_bitmap);
+ break;
+ case Type::Icon:
+ AK::unref_if_not_null(m_value.as_icon);
+ break;
+ default:
+ break;
+ }
+ m_type = Type::Invalid;
+ m_value.as_string = nullptr;
+}
+
+Variant::Variant(Gfx::TextAlignment value)
+ : m_type(Type::TextAlignment)
+{
+ m_value.as_text_alignment = value;
+}
+
+Variant::Variant(i32 value)
+ : m_type(Type::Int32)
+{
+ m_value.as_i32 = value;
+}
+
+Variant::Variant(i64 value)
+ : m_type(Type::Int64)
+{
+ m_value.as_i64 = value;
+}
+
+Variant::Variant(unsigned value)
+ : m_type(Type::UnsignedInt)
+{
+ m_value.as_uint = value;
+}
+
+Variant::Variant(float value)
+ : m_type(Type::Float)
+{
+ m_value.as_float = value;
+}
+
+Variant::Variant(bool value)
+ : m_type(Type::Bool)
+{
+ m_value.as_bool = value;
+}
+
+Variant::Variant(const char* cstring)
+ : Variant(String(cstring))
+{
+}
+
+Variant::Variant(const FlyString& value)
+ : Variant(String(value.impl()))
+{
+}
+
+Variant::Variant(const String& value)
+ : m_type(Type::String)
+{
+ m_value.as_string = const_cast<StringImpl*>(value.impl());
+ AK::ref_if_not_null(m_value.as_string);
+}
+
+Variant::Variant(const JsonValue& value)
+{
+ if (value.is_null()) {
+ m_value.as_string = nullptr;
+ return;
+ }
+
+ if (value.is_i32()) {
+ m_type = Type::Int32;
+ m_value.as_i32 = value.as_i32();
+ return;
+ }
+
+ if (value.is_u32()) {
+ m_type = Type::UnsignedInt;
+ m_value.as_uint = value.as_u32();
+ return;
+ }
+
+ if (value.is_i64()) {
+ m_type = Type::Int64;
+ m_value.as_i64 = value.as_i64();
+ return;
+ }
+
+ if (value.is_u64()) {
+ // FIXME: Variant should have a 64-bit internal type.
+ m_type = Type::UnsignedInt;
+ m_value.as_uint = value.to_u32();
+ return;
+ }
+
+ if (value.is_string()) {
+ m_type = Type::String;
+ m_value.as_string = value.as_string().impl();
+ m_value.as_string->ref();
+ return;
+ }
+
+ if (value.is_bool()) {
+ m_type = Type::Bool;
+ m_value.as_bool = value.as_bool();
+ return;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+Variant::Variant(const Gfx::Bitmap& value)
+ : m_type(Type::Bitmap)
+{
+ m_value.as_bitmap = const_cast<Gfx::Bitmap*>(&value);
+ AK::ref_if_not_null(m_value.as_bitmap);
+}
+
+Variant::Variant(const GUI::Icon& value)
+ : m_type(Type::Icon)
+{
+ m_value.as_icon = &const_cast<GUI::IconImpl&>(value.impl());
+ AK::ref_if_not_null(m_value.as_icon);
+}
+
+Variant::Variant(const Gfx::Font& value)
+ : m_type(Type::Font)
+{
+ m_value.as_font = &const_cast<Gfx::Font&>(value);
+ AK::ref_if_not_null(m_value.as_font);
+}
+
+Variant::Variant(Color color)
+ : m_type(Type::Color)
+{
+ m_value.as_color = color.value();
+}
+
+Variant::Variant(const Gfx::IntPoint& point)
+ : m_type(Type::Point)
+{
+ m_value.as_point = { point.x(), point.y() };
+}
+
+Variant::Variant(const Gfx::IntSize& size)
+ : m_type(Type::Size)
+{
+ m_value.as_size = { size.width(), size.height() };
+}
+
+Variant::Variant(const Gfx::IntRect& rect)
+ : m_type(Type::Rect)
+{
+ m_value.as_rect = (const RawRect&)rect;
+}
+
+Variant& Variant::operator=(const Variant& other)
+{
+ if (&other == this)
+ return *this;
+ clear();
+ copy_from(other);
+ return *this;
+}
+
+Variant& Variant::operator=(Variant&& other)
+{
+ if (&other == this)
+ return *this;
+ clear();
+ move_from(AK::move(other));
+ return *this;
+}
+
+Variant::Variant(const Variant& other)
+{
+ copy_from(other);
+}
+
+void Variant::move_from(Variant&& other)
+{
+ m_type = other.m_type;
+ m_value = other.m_value;
+ other.m_type = Type::Invalid;
+ other.m_value.as_string = nullptr;
+}
+
+void Variant::copy_from(const Variant& other)
+{
+ ASSERT(!is_valid());
+ m_type = other.m_type;
+ switch (m_type) {
+ case Type::Bool:
+ m_value.as_bool = other.m_value.as_bool;
+ break;
+ case Type::Int32:
+ m_value.as_i32 = other.m_value.as_i32;
+ break;
+ case Type::Int64:
+ m_value.as_i64 = other.m_value.as_i64;
+ break;
+ case Type::UnsignedInt:
+ m_value.as_uint = other.m_value.as_uint;
+ break;
+ case Type::Float:
+ m_value.as_float = other.m_value.as_float;
+ break;
+ case Type::String:
+ m_value.as_string = other.m_value.as_string;
+ AK::ref_if_not_null(m_value.as_bitmap);
+ break;
+ case Type::Bitmap:
+ m_value.as_bitmap = other.m_value.as_bitmap;
+ AK::ref_if_not_null(m_value.as_bitmap);
+ break;
+ case Type::Icon:
+ m_value.as_icon = other.m_value.as_icon;
+ AK::ref_if_not_null(m_value.as_icon);
+ break;
+ case Type::Font:
+ m_value.as_font = other.m_value.as_font;
+ AK::ref_if_not_null(m_value.as_font);
+ break;
+ case Type::Color:
+ m_value.as_color = other.m_value.as_color;
+ break;
+ case Type::Point:
+ m_value.as_point = other.m_value.as_point;
+ break;
+ case Type::Size:
+ m_value.as_size = other.m_value.as_size;
+ break;
+ case Type::Rect:
+ m_value.as_rect = other.m_value.as_rect;
+ break;
+ case Type::TextAlignment:
+ m_value.as_text_alignment = other.m_value.as_text_alignment;
+ break;
+ case Type::Invalid:
+ break;
+ }
+}
+
+bool Variant::operator==(const Variant& other) const
+{
+ if (m_type != other.m_type)
+ return to_string() == other.to_string();
+ switch (m_type) {
+ case Type::Bool:
+ return as_bool() == other.as_bool();
+ case Type::Int32:
+ return as_i32() == other.as_i32();
+ case Type::Int64:
+ return as_i64() == other.as_i64();
+ case Type::UnsignedInt:
+ return as_uint() == other.as_uint();
+ case Type::Float:
+ return as_float() == other.as_float();
+ case Type::String:
+ return as_string() == other.as_string();
+ case Type::Bitmap:
+ return m_value.as_bitmap == other.m_value.as_bitmap;
+ case Type::Icon:
+ return m_value.as_icon == other.m_value.as_icon;
+ case Type::Color:
+ return m_value.as_color == other.m_value.as_color;
+ case Type::Point:
+ return as_point() == other.as_point();
+ case Type::Size:
+ return as_size() == other.as_size();
+ case Type::Rect:
+ return as_rect() == other.as_rect();
+ case Type::Font:
+ return &as_font() == &other.as_font();
+ case Type::TextAlignment:
+ return m_value.as_text_alignment == other.m_value.as_text_alignment;
+ case Type::Invalid:
+ return true;
+ }
+ ASSERT_NOT_REACHED();
+}
+
+bool Variant::operator<(const Variant& other) const
+{
+ if (m_type != other.m_type)
+ return to_string() < other.to_string();
+ switch (m_type) {
+ case Type::Bool:
+ return as_bool() < other.as_bool();
+ case Type::Int32:
+ return as_i32() < other.as_i32();
+ case Type::Int64:
+ return as_i64() < other.as_i64();
+ case Type::UnsignedInt:
+ return as_uint() < other.as_uint();
+ case Type::Float:
+ return as_float() < other.as_float();
+ case Type::String:
+ return as_string() < other.as_string();
+ case Type::Bitmap:
+ // FIXME: Maybe compare bitmaps somehow differently?
+ return m_value.as_bitmap < other.m_value.as_bitmap;
+ case Type::Icon:
+ // FIXME: Maybe compare icons somehow differently?
+ return m_value.as_icon < other.m_value.as_icon;
+ case Type::Color:
+ return m_value.as_color < other.m_value.as_color;
+ case Type::Point:
+ case Type::Size:
+ case Type::Rect:
+ case Type::Font:
+ case Type::TextAlignment:
+ // FIXME: Figure out how to compare these.
+ ASSERT_NOT_REACHED();
+ case Type::Invalid:
+ break;
+ }
+ ASSERT_NOT_REACHED();
+}
+
+String Variant::to_string() const
+{
+ switch (m_type) {
+ case Type::Bool:
+ return as_bool() ? "true" : "false";
+ case Type::Int32:
+ return String::number(as_i32());
+ case Type::Int64:
+ return String::number(as_i64());
+ case Type::UnsignedInt:
+ return String::number(as_uint());
+ case Type::Float:
+ return String::format("%.2f", (double)as_float());
+ case Type::String:
+ return as_string();
+ case Type::Bitmap:
+ return "[Gfx::Bitmap]";
+ case Type::Icon:
+ return "[GUI::Icon]";
+ case Type::Color:
+ return as_color().to_string();
+ case Type::Point:
+ return as_point().to_string();
+ case Type::Size:
+ return as_size().to_string();
+ case Type::Rect:
+ return as_rect().to_string();
+ case Type::Font:
+ return String::formatted("[Font: {}]", as_font().name());
+ case Type::TextAlignment: {
+ switch (m_value.as_text_alignment) {
+ case Gfx::TextAlignment::Center:
+ return "Gfx::TextAlignment::Center";
+ case Gfx::TextAlignment::CenterLeft:
+ return "Gfx::TextAlignment::CenterLeft";
+ case Gfx::TextAlignment::CenterRight:
+ return "Gfx::TextAlignment::CenterRight";
+ case Gfx::TextAlignment::TopLeft:
+ return "Gfx::TextAlignment::TopLeft";
+ case Gfx::TextAlignment::TopRight:
+ return "Gfx::TextAlignment::TopRight";
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ return "";
+ }
+ case Type::Invalid:
+ return "[null]";
+ }
+ ASSERT_NOT_REACHED();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Variant.h b/Userland/Libraries/LibGUI/Variant.h
new file mode 100644
index 0000000000..a04f7b2e17
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Variant.h
@@ -0,0 +1,292 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <LibGUI/Icon.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Font.h>
+
+namespace GUI {
+
+class Variant {
+public:
+ Variant();
+ Variant(bool);
+ Variant(float);
+ Variant(i32);
+ Variant(i64);
+ Variant(unsigned);
+ Variant(const char*);
+ Variant(const String&);
+ Variant(const FlyString&);
+ Variant(const Gfx::Bitmap&);
+ Variant(const GUI::Icon&);
+ Variant(const Gfx::IntPoint&);
+ Variant(const Gfx::IntSize&);
+ Variant(const Gfx::IntRect&);
+ Variant(const Gfx::Font&);
+ Variant(const Gfx::TextAlignment);
+ Variant(const AK::JsonValue&);
+ Variant(Color);
+
+ Variant(const Variant&);
+ Variant& operator=(const Variant&);
+
+ Variant(Variant&&) = delete;
+ Variant& operator=(Variant&&);
+
+ void clear();
+ ~Variant();
+
+ enum class Type {
+ Invalid,
+ Bool,
+ Int32,
+ Int64,
+ UnsignedInt,
+ Float,
+ String,
+ Bitmap,
+ Color,
+ Icon,
+ Point,
+ Size,
+ Rect,
+ Font,
+ TextAlignment,
+ };
+
+ bool is_valid() const { return m_type != Type::Invalid; }
+ bool is_bool() const { return m_type == Type::Bool; }
+ bool is_i32() const { return m_type == Type::Int32; }
+ bool is_i64() const { return m_type == Type::Int64; }
+ bool is_uint() const { return m_type == Type::UnsignedInt; }
+ bool is_float() const { return m_type == Type::Float; }
+ bool is_string() const { return m_type == Type::String; }
+ bool is_bitmap() const { return m_type == Type::Bitmap; }
+ bool is_color() const { return m_type == Type::Color; }
+ bool is_icon() const { return m_type == Type::Icon; }
+ bool is_point() const { return m_type == Type::Point; }
+ bool is_size() const { return m_type == Type::Size; }
+ bool is_rect() const { return m_type == Type::Rect; }
+ bool is_font() const { return m_type == Type::Font; }
+ bool is_text_alignment() const { return m_type == Type::TextAlignment; }
+ Type type() const { return m_type; }
+
+ bool as_bool() const
+ {
+ ASSERT(type() == Type::Bool);
+ return m_value.as_bool;
+ }
+
+ bool to_bool() const
+ {
+ if (type() == Type::Bool)
+ return as_bool();
+ if (type() == Type::String)
+ return !!m_value.as_string;
+ if (type() == Type::Int32)
+ return m_value.as_i32 != 0;
+ if (type() == Type::Int64)
+ return m_value.as_i64 != 0;
+ if (type() == Type::UnsignedInt)
+ return m_value.as_uint != 0;
+ if (type() == Type::Rect)
+ return !as_rect().is_null();
+ if (type() == Type::Size)
+ return !as_size().is_null();
+ if (type() == Type::Point)
+ return !as_point().is_null();
+ return is_valid();
+ }
+
+ int as_i32() const
+ {
+ ASSERT(type() == Type::Int32);
+ return m_value.as_i32;
+ }
+
+ int as_i64() const
+ {
+ ASSERT(type() == Type::Int64);
+ return m_value.as_i64;
+ }
+
+ unsigned as_uint() const
+ {
+ ASSERT(type() == Type::UnsignedInt);
+ return m_value.as_uint;
+ }
+
+ template<typename T>
+ T to_integer() const
+ {
+ if (is_i32())
+ return as_i32();
+ if (is_i64())
+ return as_i64();
+ if (is_bool())
+ return as_bool() ? 1 : 0;
+ if (is_float())
+ return (int)as_float();
+ if (is_uint()) {
+ ASSERT(as_uint() <= INT32_MAX);
+ return (int)as_uint();
+ }
+ if (is_string())
+ return as_string().to_int().value_or(0);
+ return 0;
+ }
+
+ i32 to_i32() const
+ {
+ return to_integer<i32>();
+ }
+
+ i64 to_i64() const
+ {
+ return to_integer<i64>();
+ }
+
+ float as_float() const
+ {
+ ASSERT(type() == Type::Float);
+ return m_value.as_float;
+ }
+
+ Gfx::IntPoint as_point() const
+ {
+ return { m_value.as_point.x, m_value.as_point.y };
+ }
+
+ Gfx::IntSize as_size() const
+ {
+ return { m_value.as_size.width, m_value.as_size.height };
+ }
+
+ Gfx::IntRect as_rect() const
+ {
+ return { as_point(), as_size() };
+ }
+
+ String as_string() const
+ {
+ ASSERT(type() == Type::String);
+ return m_value.as_string;
+ }
+
+ const Gfx::Bitmap& as_bitmap() const
+ {
+ ASSERT(type() == Type::Bitmap);
+ return *m_value.as_bitmap;
+ }
+
+ GUI::Icon as_icon() const
+ {
+ ASSERT(type() == Type::Icon);
+ return GUI::Icon(*m_value.as_icon);
+ }
+
+ Color as_color() const
+ {
+ ASSERT(type() == Type::Color);
+ return Color::from_rgba(m_value.as_color);
+ }
+
+ const Gfx::Font& as_font() const
+ {
+ ASSERT(type() == Type::Font);
+ return *m_value.as_font;
+ }
+
+ Gfx::TextAlignment to_text_alignment(Gfx::TextAlignment default_value) const
+ {
+ if (type() != Type::TextAlignment)
+ return default_value;
+ return m_value.as_text_alignment;
+ }
+
+ Color to_color(Color default_value = {}) const
+ {
+ if (type() == Type::Color)
+ return as_color();
+ if (type() == Type::String) {
+ auto color = Color::from_string(as_string());
+ if (color.has_value())
+ return color.value();
+ }
+ return default_value;
+ }
+
+ String to_string() const;
+
+ bool operator==(const Variant&) const;
+ bool operator<(const Variant&) const;
+
+private:
+ void copy_from(const Variant&);
+ void move_from(Variant&&);
+
+ struct RawPoint {
+ int x;
+ int y;
+ };
+
+ struct RawSize {
+ int width;
+ int height;
+ };
+
+ struct RawRect {
+ RawPoint location;
+ RawSize size;
+ };
+
+ union {
+ StringImpl* as_string;
+ Gfx::Bitmap* as_bitmap;
+ GUI::IconImpl* as_icon;
+ Gfx::Font* as_font;
+ bool as_bool;
+ i32 as_i32;
+ i64 as_i64;
+ unsigned as_uint;
+ float as_float;
+ Gfx::RGBA32 as_color;
+ Gfx::TextAlignment as_text_alignment;
+ RawPoint as_point;
+ RawSize as_size;
+ RawRect as_rect;
+ } m_value;
+
+ Type m_type { Type::Invalid };
+};
+
+const char* to_string(Variant::Type);
+
+}
diff --git a/Userland/Libraries/LibGUI/VimEditingEngine.cpp b/Userland/Libraries/LibGUI/VimEditingEngine.cpp
new file mode 100644
index 0000000000..94b9631004
--- /dev/null
+++ b/Userland/Libraries/LibGUI/VimEditingEngine.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Event.h>
+#include <LibGUI/TextEditor.h>
+#include <LibGUI/VimEditingEngine.h>
+
+namespace GUI {
+
+CursorWidth VimEditingEngine::cursor_width() const
+{
+ return m_vim_mode == VimMode::Normal ? CursorWidth::WIDE : CursorWidth::NARROW;
+}
+
+bool VimEditingEngine::on_key(const KeyEvent& event)
+{
+ if (EditingEngine::on_key(event))
+ return true;
+
+ switch (m_vim_mode) {
+ case (VimMode::Insert):
+ return on_key_in_insert_mode(event);
+ case (VimMode::Normal):
+ return on_key_in_normal_mode(event);
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ return false;
+}
+
+bool VimEditingEngine::on_key_in_insert_mode(const KeyEvent& event)
+{
+ if (event.key() == KeyCode::Key_Escape || (event.ctrl() && event.key() == KeyCode::Key_LeftBracket) || (event.ctrl() && event.key() == KeyCode::Key_C)) {
+ switch_to_normal_mode();
+ return true;
+ }
+ return false;
+}
+
+bool VimEditingEngine::on_key_in_normal_mode(const KeyEvent& event)
+{
+ if (m_previous_key == KeyCode::Key_D) {
+ if (event.key() == KeyCode::Key_D) {
+ delete_line();
+ }
+ m_previous_key = {};
+ } else if (m_previous_key == KeyCode::Key_G) {
+ if (event.key() == KeyCode::Key_G) {
+ move_to_first_line();
+ }
+ m_previous_key = {};
+ } else {
+ // Handle first any key codes that are to be applied regardless of modifiers.
+ switch (event.key()) {
+ case (KeyCode::Key_Dollar):
+ move_to_line_end(event);
+ break;
+ case (KeyCode::Key_Escape):
+ if (m_editor->on_escape_pressed)
+ m_editor->on_escape_pressed();
+ break;
+ default:
+ break;
+ }
+
+ // SHIFT is pressed.
+ if (event.shift() && !event.ctrl() && !event.alt()) {
+ switch (event.key()) {
+ case (KeyCode::Key_A):
+ move_to_line_end(event);
+ switch_to_insert_mode();
+ break;
+ case (KeyCode::Key_G):
+ move_to_last_line();
+ break;
+ case (KeyCode::Key_I):
+ move_to_line_beginning(event);
+ switch_to_insert_mode();
+ break;
+ case (KeyCode::Key_O):
+ move_to_line_beginning(event);
+ m_editor->add_code_point(0x0A);
+ move_one_up(event);
+ switch_to_insert_mode();
+ break;
+ default:
+ break;
+ }
+ }
+
+ // CTRL is pressed.
+ if (event.ctrl() && !event.shift() && !event.alt()) {
+ switch (event.key()) {
+ case (KeyCode::Key_D):
+ move_half_page_down(event);
+ break;
+ case (KeyCode::Key_R):
+ m_editor->redo();
+ break;
+ case (KeyCode::Key_U):
+ move_half_page_up(event);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // No modifier is pressed.
+ if (!event.ctrl() && !event.shift() && !event.alt()) {
+ switch (event.key()) {
+ case (KeyCode::Key_A):
+ move_one_right(event);
+ switch_to_insert_mode();
+ break;
+ case (KeyCode::Key_B):
+ move_to_previous_span(event); // FIXME: This probably isn't 100% correct.
+ break;
+ case (KeyCode::Key_Backspace):
+ case (KeyCode::Key_H):
+ case (KeyCode::Key_Left):
+ move_one_left(event);
+ break;
+ case (KeyCode::Key_D):
+ case (KeyCode::Key_G):
+ m_previous_key = event.key();
+ break;
+ case (KeyCode::Key_Down):
+ case (KeyCode::Key_J):
+ move_one_down(event);
+ break;
+ case (KeyCode::Key_I):
+ switch_to_insert_mode();
+ break;
+ case (KeyCode::Key_K):
+ case (KeyCode::Key_Up):
+ move_one_up(event);
+ break;
+ case (KeyCode::Key_L):
+ case (KeyCode::Key_Right):
+ move_one_right(event);
+ break;
+ case (KeyCode::Key_O):
+ move_to_line_end(event);
+ m_editor->add_code_point(0x0A);
+ switch_to_insert_mode();
+ break;
+ case (KeyCode::Key_U):
+ m_editor->undo();
+ break;
+ case (KeyCode::Key_W):
+ move_to_next_span(event); // FIXME: This probably isn't 100% correct.
+ break;
+ case (KeyCode::Key_X):
+ delete_char();
+ break;
+ case (KeyCode::Key_0):
+ move_to_line_beginning(event);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return true;
+}
+
+void VimEditingEngine::switch_to_normal_mode()
+{
+ m_vim_mode = VimMode::Normal;
+ m_editor->reset_cursor_blink();
+};
+
+void VimEditingEngine::switch_to_insert_mode()
+{
+ m_vim_mode = VimMode::Insert;
+ m_editor->reset_cursor_blink();
+};
+
+void VimEditingEngine::move_half_page_up(const KeyEvent& event)
+{
+ move_up(event, 0.5);
+};
+
+void VimEditingEngine::move_half_page_down(const KeyEvent& event)
+{
+ move_down(event, 0.5);
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/VimEditingEngine.h b/Userland/Libraries/LibGUI/VimEditingEngine.h
new file mode 100644
index 0000000000..5b399008b1
--- /dev/null
+++ b/Userland/Libraries/LibGUI/VimEditingEngine.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2021, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/EditingEngine.h>
+
+namespace GUI {
+
+class VimEditingEngine final : public EditingEngine {
+
+public:
+ virtual CursorWidth cursor_width() const override;
+
+ virtual bool on_key(const KeyEvent& event) override;
+
+private:
+ enum VimMode {
+ Normal,
+ Insert,
+ };
+
+ VimMode m_vim_mode { VimMode::Normal };
+
+ KeyCode m_previous_key {};
+ void switch_to_normal_mode();
+ void switch_to_insert_mode();
+ void move_half_page_up(const KeyEvent& event);
+ void move_half_page_down(const KeyEvent& event);
+
+ bool on_key_in_insert_mode(const KeyEvent& event);
+ bool on_key_in_normal_mode(const KeyEvent& event);
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/Widget.cpp b/Userland/Libraries/LibGUI/Widget.cpp
new file mode 100644
index 0000000000..1da1b220e1
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Widget.cpp
@@ -0,0 +1,1056 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/JsonObject.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Event.h>
+#include <LibGUI/GMLParser.h>
+#include <LibGUI/Layout.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/Widget.h>
+#include <LibGUI/Window.h>
+#include <LibGUI/WindowServerConnection.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibGfx/Palette.h>
+#include <unistd.h>
+
+REGISTER_WIDGET(GUI, Widget)
+
+namespace GUI {
+
+static HashMap<String, WidgetClassRegistration*>& widget_classes()
+{
+ static HashMap<String, WidgetClassRegistration*>* map;
+ if (!map)
+ map = new HashMap<String, WidgetClassRegistration*>;
+ return *map;
+}
+
+WidgetClassRegistration::WidgetClassRegistration(const String& class_name, Function<NonnullRefPtr<Widget>()> factory)
+ : m_class_name(class_name)
+ , m_factory(move(factory))
+{
+ widget_classes().set(class_name, this);
+}
+
+WidgetClassRegistration::~WidgetClassRegistration()
+{
+}
+
+void WidgetClassRegistration::for_each(Function<void(const WidgetClassRegistration&)> callback)
+{
+ for (auto& it : widget_classes()) {
+ callback(*it.value);
+ }
+}
+
+const WidgetClassRegistration* WidgetClassRegistration::find(const String& class_name)
+{
+ return widget_classes().get(class_name).value_or(nullptr);
+}
+
+Widget::Widget()
+ : Core::Object(nullptr)
+ , m_background_role(Gfx::ColorRole::Window)
+ , m_foreground_role(Gfx::ColorRole::WindowText)
+ , m_font(Gfx::FontDatabase::default_font())
+ , m_palette(Application::the()->palette().impl())
+{
+ REGISTER_RECT_PROPERTY("relative_rect", relative_rect, set_relative_rect);
+ REGISTER_BOOL_PROPERTY("fill_with_background_color", fill_with_background_color, set_fill_with_background_color);
+ REGISTER_BOOL_PROPERTY("visible", is_visible, set_visible);
+ REGISTER_BOOL_PROPERTY("focused", is_focused, set_focus);
+ REGISTER_BOOL_PROPERTY("enabled", is_enabled, set_enabled);
+ REGISTER_STRING_PROPERTY("tooltip", tooltip, set_tooltip);
+
+ REGISTER_SIZE_PROPERTY("min_size", min_size, set_min_size);
+ REGISTER_SIZE_PROPERTY("max_size", max_size, set_max_size);
+ REGISTER_INT_PROPERTY("width", width, set_width);
+ REGISTER_INT_PROPERTY("min_width", min_width, set_min_width);
+ REGISTER_INT_PROPERTY("max_width", max_width, set_max_width);
+ REGISTER_INT_PROPERTY("min_height", min_height, set_min_height);
+ REGISTER_INT_PROPERTY("height", height, set_height);
+ REGISTER_INT_PROPERTY("max_height", max_height, set_max_height);
+
+ REGISTER_INT_PROPERTY("fixed_width", dummy_fixed_width, set_fixed_width);
+ REGISTER_INT_PROPERTY("fixed_height", dummy_fixed_height, set_fixed_height);
+ REGISTER_SIZE_PROPERTY("fixed_size", dummy_fixed_size, set_fixed_size);
+
+ REGISTER_BOOL_PROPERTY("shrink_to_fit", is_shrink_to_fit, set_shrink_to_fit);
+
+ REGISTER_INT_PROPERTY("x", x, set_x);
+ REGISTER_INT_PROPERTY("y", y, set_y);
+
+ register_property(
+ "focus_policy", [this]() -> JsonValue {
+ auto policy = focus_policy();
+ if (policy == GUI::FocusPolicy::ClickFocus)
+ return "ClickFocus";
+ if (policy == GUI::FocusPolicy::NoFocus)
+ return "NoFocus";
+ if (policy == GUI::FocusPolicy::TabFocus)
+ return "TabFocus";
+ if (policy == GUI::FocusPolicy::StrongFocus)
+ return "StrongFocus";
+ return JsonValue(); },
+ [this](auto& value) {
+ if (!value.is_string())
+ return false;
+ if (value.as_string() == "ClickFocus") {
+ set_focus_policy(GUI::FocusPolicy::ClickFocus);
+ return true;
+ }
+ if (value.as_string() == "NoFocus") {
+ set_focus_policy(GUI::FocusPolicy::NoFocus);
+ return true;
+ }
+ if (value.as_string() == "TabFocus") {
+ set_focus_policy(GUI::FocusPolicy::TabFocus);
+ return true;
+ }
+ if (value.as_string() == "StrongFocus") {
+ set_focus_policy(GUI::FocusPolicy::StrongFocus);
+ return true;
+ }
+ return false;
+ });
+
+ register_property(
+ "foreground_color", [this]() -> JsonValue { return palette().color(foreground_role()).to_string(); },
+ [this](auto& value) {
+ auto c = Color::from_string(value.to_string());
+ if (c.has_value()) {
+ auto _palette = palette();
+ _palette.set_color(foreground_role(), c.value());
+ set_palette(_palette);
+ return true;
+ }
+ return false;
+ });
+
+ register_property(
+ "background_color", [this]() -> JsonValue { return palette().color(background_role()).to_string(); },
+ [this](auto& value) {
+ auto c = Color::from_string(value.to_string());
+ if (c.has_value()) {
+ auto _palette = palette();
+ _palette.set_color(background_role(), c.value());
+ set_palette(_palette);
+ return true;
+ }
+ return false;
+ });
+}
+
+Widget::~Widget()
+{
+}
+
+void Widget::child_event(Core::ChildEvent& event)
+{
+ if (event.type() == Event::ChildAdded) {
+ if (event.child() && is<Widget>(*event.child()) && layout()) {
+ if (event.insertion_before_child() && is<Widget>(event.insertion_before_child()))
+ layout()->insert_widget_before(downcast<Widget>(*event.child()), downcast<Widget>(*event.insertion_before_child()));
+ else
+ layout()->add_widget(downcast<Widget>(*event.child()));
+ }
+ if (window() && event.child() && is<Widget>(*event.child()))
+ window()->did_add_widget({}, downcast<Widget>(*event.child()));
+ }
+ if (event.type() == Event::ChildRemoved) {
+ if (layout()) {
+ if (event.child() && is<Widget>(*event.child()))
+ layout()->remove_widget(downcast<Widget>(*event.child()));
+ else
+ invalidate_layout();
+ }
+ if (window() && event.child() && is<Widget>(*event.child()))
+ window()->did_remove_widget({}, downcast<Widget>(*event.child()));
+ update();
+ }
+ return Core::Object::child_event(event);
+}
+
+void Widget::set_relative_rect(const Gfx::IntRect& a_rect)
+{
+ // Get rid of negative width/height values.
+ Gfx::IntRect rect = {
+ a_rect.x(),
+ a_rect.y(),
+ max(a_rect.width(), 0),
+ max(a_rect.height(), 0)
+ };
+
+ if (rect == m_relative_rect)
+ return;
+
+ auto old_rect = m_relative_rect;
+
+ bool size_changed = m_relative_rect.size() != rect.size();
+ m_relative_rect = rect;
+
+ if (size_changed) {
+ ResizeEvent resize_event(rect.size());
+ event(resize_event);
+ }
+
+ if (auto* parent = parent_widget())
+ parent->update(old_rect);
+ update();
+}
+
+void Widget::event(Core::Event& event)
+{
+ if (!is_enabled()) {
+ switch (event.type()) {
+ case Event::MouseUp:
+ case Event::MouseDown:
+ case Event::MouseMove:
+ case Event::MouseWheel:
+ case Event::MouseDoubleClick:
+ case Event::KeyUp:
+ case Event::KeyDown:
+ return;
+ default:
+ break;
+ }
+ }
+
+ switch (event.type()) {
+ case Event::Paint:
+ return handle_paint_event(static_cast<PaintEvent&>(event));
+ case Event::Resize:
+ return handle_resize_event(static_cast<ResizeEvent&>(event));
+ case Event::FocusIn:
+ return focusin_event(static_cast<FocusEvent&>(event));
+ case Event::FocusOut:
+ return focusout_event(static_cast<FocusEvent&>(event));
+ case Event::Show:
+ return show_event(static_cast<ShowEvent&>(event));
+ case Event::Hide:
+ return hide_event(static_cast<HideEvent&>(event));
+ case Event::KeyDown:
+ return keydown_event(static_cast<KeyEvent&>(event));
+ case Event::KeyUp:
+ return keyup_event(static_cast<KeyEvent&>(event));
+ case Event::MouseMove:
+ return mousemove_event(static_cast<MouseEvent&>(event));
+ case Event::MouseDown:
+ return handle_mousedown_event(static_cast<MouseEvent&>(event));
+ case Event::MouseDoubleClick:
+ return handle_mousedoubleclick_event(static_cast<MouseEvent&>(event));
+ case Event::MouseUp:
+ return handle_mouseup_event(static_cast<MouseEvent&>(event));
+ case Event::MouseWheel:
+ return mousewheel_event(static_cast<MouseEvent&>(event));
+ case Event::DragEnter:
+ return drag_enter_event(static_cast<DragEvent&>(event));
+ case Event::DragMove:
+ return drag_move_event(static_cast<DragEvent&>(event));
+ case Event::DragLeave:
+ return drag_leave_event(static_cast<Event&>(event));
+ case Event::Drop:
+ return drop_event(static_cast<DropEvent&>(event));
+ case Event::ThemeChange:
+ return theme_change_event(static_cast<ThemeChangeEvent&>(event));
+ case Event::Enter:
+ return handle_enter_event(event);
+ case Event::Leave:
+ return handle_leave_event(event);
+ case Event::EnabledChange:
+ return change_event(static_cast<Event&>(event));
+ default:
+ return Core::Object::event(event);
+ }
+}
+
+void Widget::handle_paint_event(PaintEvent& event)
+{
+ ASSERT(is_visible());
+ if (fill_with_background_color()) {
+ Painter painter(*this);
+ painter.fill_rect(event.rect(), palette().color(background_role()));
+ }
+ paint_event(event);
+ auto children_clip_rect = this->children_clip_rect();
+ for_each_child_widget([&](auto& child) {
+ if (!child.is_visible())
+ return IterationDecision::Continue;
+ if (child.relative_rect().intersects(event.rect())) {
+ PaintEvent local_event(event.rect().intersected(children_clip_rect).intersected(child.relative_rect()).translated(-child.relative_position()));
+ child.dispatch_event(local_event, this);
+ }
+ return IterationDecision::Continue;
+ });
+ second_paint_event(event);
+
+ auto* app = Application::the();
+
+ if (app && app->dnd_debugging_enabled() && has_pending_drop()) {
+ Painter painter(*this);
+ painter.draw_rect(rect(), Color::Blue);
+ }
+
+ if (app && app->focus_debugging_enabled() && is_focused()) {
+ Painter painter(*this);
+ painter.draw_rect(rect(), Color::Cyan);
+ }
+
+ if (is_being_inspected()) {
+ Painter painter(*this);
+ painter.draw_rect(rect(), Color::Magenta);
+ }
+}
+
+void Widget::set_layout(NonnullRefPtr<Layout> layout)
+{
+ if (m_layout) {
+ m_layout->notify_disowned({}, *this);
+ m_layout->remove_from_parent();
+ }
+ m_layout = move(layout);
+ if (m_layout) {
+ add_child(*m_layout);
+ m_layout->notify_adopted({}, *this);
+ do_layout();
+ } else {
+ update();
+ }
+}
+
+void Widget::do_layout()
+{
+ for_each_child_widget([&](auto& child) {
+ child.do_layout();
+ return IterationDecision::Continue;
+ });
+ custom_layout();
+ if (!m_layout)
+ return;
+ m_layout->run(*this);
+ did_layout();
+ update();
+}
+
+void Widget::notify_layout_changed(Badge<Layout>)
+{
+ invalidate_layout();
+}
+
+void Widget::handle_resize_event(ResizeEvent& event)
+{
+ resize_event(event);
+ do_layout();
+}
+
+void Widget::handle_mouseup_event(MouseEvent& event)
+{
+ mouseup_event(event);
+}
+
+void Widget::handle_mousedown_event(MouseEvent& event)
+{
+ if (((unsigned)focus_policy() & (unsigned)FocusPolicy::ClickFocus))
+ set_focus(true, FocusSource::Mouse);
+ mousedown_event(event);
+ if (event.button() == MouseButton::Right) {
+ ContextMenuEvent c_event(event.position(), screen_relative_rect().location().translated(event.position()));
+ context_menu_event(c_event);
+ }
+}
+
+void Widget::handle_mousedoubleclick_event(MouseEvent& event)
+{
+ doubleclick_event(event);
+}
+
+void Widget::handle_enter_event(Core::Event& event)
+{
+ if (auto* window = this->window())
+ window->update_cursor({});
+ show_or_hide_tooltip();
+ enter_event(event);
+}
+
+void Widget::handle_leave_event(Core::Event& event)
+{
+ if (auto* window = this->window())
+ window->update_cursor({});
+ Application::the()->hide_tooltip();
+ leave_event(event);
+}
+
+void Widget::doubleclick_event(MouseEvent&)
+{
+}
+
+void Widget::resize_event(ResizeEvent&)
+{
+}
+
+void Widget::paint_event(PaintEvent&)
+{
+}
+
+void Widget::second_paint_event(PaintEvent&)
+{
+}
+
+void Widget::show_event(ShowEvent&)
+{
+}
+
+void Widget::hide_event(HideEvent&)
+{
+}
+
+void Widget::keydown_event(KeyEvent& event)
+{
+ if (!event.alt() && !event.ctrl() && !event.logo()) {
+ if (event.key() == KeyCode::Key_Tab) {
+ if (event.shift())
+ focus_previous_widget(FocusSource::Keyboard, false);
+ else
+ focus_next_widget(FocusSource::Keyboard, false);
+ event.accept();
+ return;
+ }
+ if (!event.shift() && (event.key() == KeyCode::Key_Left || event.key() == KeyCode::Key_Up)) {
+ focus_previous_widget(FocusSource::Keyboard, true);
+ event.accept();
+ return;
+ }
+ if (!event.shift() && (event.key() == KeyCode::Key_Right || event.key() == KeyCode::Key_Down)) {
+ focus_next_widget(FocusSource::Keyboard, true);
+ event.accept();
+ return;
+ }
+ }
+ event.ignore();
+}
+
+void Widget::keyup_event(KeyEvent& event)
+{
+ event.ignore();
+}
+
+void Widget::mousedown_event(MouseEvent&)
+{
+}
+
+void Widget::mouseup_event(MouseEvent&)
+{
+}
+
+void Widget::mousemove_event(MouseEvent&)
+{
+}
+
+void Widget::mousewheel_event(MouseEvent&)
+{
+}
+
+void Widget::context_menu_event(ContextMenuEvent&)
+{
+}
+
+void Widget::focusin_event(FocusEvent&)
+{
+}
+
+void Widget::focusout_event(FocusEvent&)
+{
+}
+
+void Widget::enter_event(Core::Event&)
+{
+}
+
+void Widget::leave_event(Core::Event&)
+{
+}
+
+void Widget::change_event(Event&)
+{
+}
+
+void Widget::drag_move_event(DragEvent&)
+{
+}
+
+void Widget::drag_enter_event(DragEvent& event)
+{
+ StringBuilder builder;
+ builder.join(',', event.mime_types());
+ dbgln("{} {:p} DRAG ENTER @ {}, {}", class_name(), this, event.position(), builder.string_view());
+}
+
+void Widget::drag_leave_event(Event&)
+{
+ dbgln("{} {:p} DRAG LEAVE", class_name(), this);
+}
+
+void Widget::drop_event(DropEvent& event)
+{
+ dbgln("{} {:p} DROP @ {}, '{}'", class_name(), this, event.position(), event.text());
+}
+
+void Widget::theme_change_event(ThemeChangeEvent&)
+{
+}
+
+void Widget::update()
+{
+ if (rect().is_empty())
+ return;
+ update(rect());
+}
+
+void Widget::update(const Gfx::IntRect& rect)
+{
+ if (!is_visible())
+ return;
+
+ if (!updates_enabled())
+ return;
+
+ Window* window = m_window;
+ Widget* parent = parent_widget();
+ while (parent) {
+ if (!parent->updates_enabled())
+ return;
+ window = parent->m_window;
+ parent = parent->parent_widget();
+ }
+ if (window)
+ window->update(rect.translated(window_relative_rect().location()));
+}
+
+Gfx::IntRect Widget::window_relative_rect() const
+{
+ auto rect = relative_rect();
+ for (auto* parent = parent_widget(); parent; parent = parent->parent_widget()) {
+ rect.move_by(parent->relative_position());
+ }
+ return rect;
+}
+
+Gfx::IntRect Widget::screen_relative_rect() const
+{
+ auto window_position = window()->window_type() == WindowType::MenuApplet
+ ? window()->rect_in_menubar().location()
+ : window()->rect().location();
+ return window_relative_rect().translated(window_position);
+}
+
+Widget* Widget::child_at(const Gfx::IntPoint& point) const
+{
+ for (int i = children().size() - 1; i >= 0; --i) {
+ if (!is<Widget>(children()[i]))
+ continue;
+ auto& child = downcast<Widget>(children()[i]);
+ if (!child.is_visible())
+ continue;
+ if (child.content_rect().contains(point))
+ return const_cast<Widget*>(&child);
+ }
+ return nullptr;
+}
+
+Widget::HitTestResult Widget::hit_test(const Gfx::IntPoint& position, ShouldRespectGreediness should_respect_greediness)
+{
+ if (should_respect_greediness == ShouldRespectGreediness::Yes && is_greedy_for_hits())
+ return { this, position };
+ if (auto* child = child_at(position))
+ return child->hit_test(position - child->relative_position());
+ return { this, position };
+}
+
+void Widget::set_window(Window* window)
+{
+ if (m_window == window)
+ return;
+ m_window = window;
+}
+
+void Widget::set_focus_proxy(Widget* proxy)
+{
+ if (m_focus_proxy == proxy)
+ return;
+
+ m_focus_proxy = proxy;
+}
+
+FocusPolicy Widget::focus_policy() const
+{
+ if (m_focus_proxy)
+ return m_focus_proxy->focus_policy();
+ return m_focus_policy;
+}
+
+void Widget::set_focus_policy(FocusPolicy policy)
+{
+ if (m_focus_proxy)
+ return m_focus_proxy->set_focus_policy(policy);
+ m_focus_policy = policy;
+}
+
+bool Widget::is_focused() const
+{
+ if (m_focus_proxy)
+ return m_focus_proxy->is_focused();
+
+ auto* win = window();
+ if (!win)
+ return false;
+ // Accessory windows are not active despite being the active
+ // input window. So we can have focus if either we're the active
+ // input window or we're the active window
+ if (win->is_active_input() || win->is_active())
+ return win->focused_widget() == this;
+ return false;
+}
+
+void Widget::set_focus(bool focus, FocusSource source)
+{
+ if (m_focus_proxy)
+ return m_focus_proxy->set_focus(focus, source);
+
+ auto* win = window();
+ if (!win)
+ return;
+ if (focus) {
+ win->set_focused_widget(this, source);
+ } else {
+ if (win->focused_widget() == this)
+ win->set_focused_widget(nullptr, source);
+ }
+}
+
+void Widget::set_font(const Gfx::Font* font)
+{
+ if (m_font.ptr() == font)
+ return;
+
+ if (!font)
+ m_font = Gfx::FontDatabase::default_font();
+ else
+ m_font = *font;
+
+ did_change_font();
+ update();
+}
+
+void Widget::set_global_cursor_tracking(bool enabled)
+{
+ auto* win = window();
+ if (!win)
+ return;
+ win->set_global_cursor_tracking_widget(enabled ? this : nullptr);
+}
+
+bool Widget::global_cursor_tracking() const
+{
+ auto* win = window();
+ if (!win)
+ return false;
+ return win->global_cursor_tracking_widget() == this;
+}
+
+void Widget::set_min_size(const Gfx::IntSize& size)
+{
+ if (m_min_size == size)
+ return;
+ m_min_size = size;
+ invalidate_layout();
+}
+
+void Widget::set_max_size(const Gfx::IntSize& size)
+{
+ if (m_max_size == size)
+ return;
+ m_max_size = size;
+ invalidate_layout();
+}
+
+void Widget::invalidate_layout()
+{
+ if (window())
+ window()->schedule_relayout();
+}
+
+void Widget::set_visible(bool visible)
+{
+ if (visible == m_visible)
+ return;
+ m_visible = visible;
+ if (auto* parent = parent_widget())
+ parent->invalidate_layout();
+ if (m_visible)
+ update();
+
+ if (m_visible) {
+ ShowEvent e;
+ event(e);
+ } else {
+ HideEvent e;
+ event(e);
+ }
+}
+
+bool Widget::spans_entire_window_horizontally() const
+{
+ auto* w = window();
+ if (!w)
+ return false;
+ auto* main_widget = w->main_widget();
+ if (!main_widget)
+ return false;
+ if (main_widget == this)
+ return true;
+ auto wrr = window_relative_rect();
+ return wrr.left() == main_widget->rect().left() && wrr.right() == main_widget->rect().right();
+}
+
+void Widget::set_enabled(bool enabled)
+{
+ if (m_enabled == enabled)
+ return;
+ m_enabled = enabled;
+
+ for_each_child_widget([enabled](auto& child) {
+ child.set_enabled(enabled);
+ return IterationDecision::Continue;
+ });
+
+ if (!m_enabled && window() && window()->focused_widget() == this) {
+ window()->did_disable_focused_widget({});
+ }
+
+ Event e(Event::EnabledChange);
+ event(e);
+ update();
+}
+
+void Widget::move_to_front()
+{
+ auto* parent = parent_widget();
+ if (!parent)
+ return;
+ if (parent->children().size() == 1)
+ return;
+ parent->children().remove_first_matching([this](auto& entry) {
+ return entry == this;
+ });
+ parent->children().append(*this);
+ parent->update();
+}
+
+void Widget::move_to_back()
+{
+ auto* parent = parent_widget();
+ if (!parent)
+ return;
+ if (parent->children().size() == 1)
+ return;
+ parent->children().remove_first_matching([this](auto& entry) {
+ return entry == this;
+ });
+ parent->children().prepend(*this);
+ parent->update();
+}
+
+bool Widget::is_frontmost() const
+{
+ auto* parent = parent_widget();
+ if (!parent)
+ return true;
+ return &parent->children().last() == this;
+}
+
+bool Widget::is_backmost() const
+{
+ auto* parent = parent_widget();
+ if (!parent)
+ return true;
+ return &parent->children().first() == this;
+}
+
+Action* Widget::action_for_key_event(const KeyEvent& event)
+{
+ Shortcut shortcut(event.modifiers(), (KeyCode)event.key());
+
+ if (!shortcut.is_valid()) {
+ return nullptr;
+ }
+
+ Action* found_action = nullptr;
+ for_each_child_of_type<Action>([&](auto& action) {
+ if (action.shortcut() == shortcut) {
+ found_action = &action;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+ return found_action;
+}
+
+void Widget::set_updates_enabled(bool enabled)
+{
+ if (m_updates_enabled == enabled)
+ return;
+ m_updates_enabled = enabled;
+ if (enabled)
+ update();
+}
+
+void Widget::focus_previous_widget(FocusSource source, bool siblings_only)
+{
+ auto focusable_widgets = window()->focusable_widgets(source);
+ if (siblings_only)
+ focusable_widgets.remove_all_matching([this](auto& entry) { return entry->parent() != parent(); });
+ for (int i = focusable_widgets.size() - 1; i >= 0; --i) {
+ if (focusable_widgets[i] != this)
+ continue;
+ if (i > 0)
+ focusable_widgets[i - 1]->set_focus(true, source);
+ else
+ focusable_widgets.last()->set_focus(true, source);
+ }
+}
+
+void Widget::focus_next_widget(FocusSource source, bool siblings_only)
+{
+ auto focusable_widgets = window()->focusable_widgets(source);
+ if (siblings_only)
+ focusable_widgets.remove_all_matching([this](auto& entry) { return entry->parent() != parent(); });
+ for (size_t i = 0; i < focusable_widgets.size(); ++i) {
+ if (focusable_widgets[i] != this)
+ continue;
+ if (i < focusable_widgets.size() - 1)
+ focusable_widgets[i + 1]->set_focus(true, source);
+ else
+ focusable_widgets.first()->set_focus(true, source);
+ }
+}
+
+Vector<Widget*> Widget::child_widgets() const
+{
+ Vector<Widget*> widgets;
+ widgets.ensure_capacity(children().size());
+ for (auto& child : const_cast<Widget*>(this)->children()) {
+ if (is<Widget>(child))
+ widgets.append(static_cast<Widget*>(&child));
+ }
+ return widgets;
+}
+
+void Widget::set_palette(const Palette& palette)
+{
+ m_palette = palette.impl();
+}
+
+void Widget::set_background_role(ColorRole role)
+{
+ m_background_role = role;
+}
+
+void Widget::set_foreground_role(ColorRole role)
+{
+ m_foreground_role = role;
+}
+
+Gfx::Palette Widget::palette() const
+{
+ return Gfx::Palette(*m_palette);
+}
+
+void Widget::did_begin_inspection()
+{
+ update();
+}
+
+void Widget::did_end_inspection()
+{
+ update();
+}
+
+void Widget::set_content_margins(const Margins& margins)
+{
+ if (m_content_margins == margins)
+ return;
+ m_content_margins = margins;
+ invalidate_layout();
+}
+
+Gfx::IntRect Widget::content_rect() const
+{
+ auto rect = relative_rect();
+ rect.move_by(m_content_margins.left(), m_content_margins.top());
+ rect.set_width(rect.width() - (m_content_margins.left() + m_content_margins.right()));
+ rect.set_height(rect.height() - (m_content_margins.top() + m_content_margins.bottom()));
+ return rect;
+}
+
+void Widget::set_tooltip(const StringView& tooltip)
+{
+ m_tooltip = tooltip;
+ if (Application::the()->tooltip_source_widget() == this)
+ show_or_hide_tooltip();
+}
+
+void Widget::show_or_hide_tooltip()
+{
+ if (has_tooltip())
+ Application::the()->show_tooltip(m_tooltip, this);
+ else
+ Application::the()->hide_tooltip();
+}
+
+Gfx::IntRect Widget::children_clip_rect() const
+{
+ return rect();
+}
+
+void Widget::set_override_cursor(Gfx::StandardCursor cursor)
+{
+ if (m_override_cursor == cursor)
+ return;
+
+ m_override_cursor = cursor;
+ if (auto* window = this->window())
+ window->update_cursor({});
+}
+
+bool Widget::load_from_gml(const StringView& gml_string)
+{
+ return load_from_gml(gml_string, [](const String& class_name) -> RefPtr<Widget> {
+ dbgln("Class '{}' not registered", class_name);
+ return nullptr;
+ });
+}
+
+bool Widget::load_from_gml(const StringView& gml_string, RefPtr<Widget> (*unregistered_child_handler)(const String&))
+{
+ auto value = parse_gml(gml_string);
+ if (!value.is_object())
+ return false;
+ return load_from_json(value.as_object(), unregistered_child_handler);
+}
+
+bool Widget::load_from_json(const JsonObject& json, RefPtr<Widget> (*unregistered_child_handler)(const String&))
+{
+ json.for_each_member([&](auto& key, auto& value) {
+ set_property(key, value);
+ });
+
+ auto layout_value = json.get("layout");
+ if (!layout_value.is_null() && !layout_value.is_object()) {
+ dbgln("layout is not an object");
+ return false;
+ }
+ if (layout_value.is_object()) {
+ auto& layout = layout_value.as_object();
+ auto class_name = layout.get("class");
+ if (class_name.is_null()) {
+ dbgln("Invalid layout class name");
+ return false;
+ }
+
+ if (class_name.to_string() == "GUI::VerticalBoxLayout") {
+ set_layout<GUI::VerticalBoxLayout>();
+ } else if (class_name.to_string() == "GUI::HorizontalBoxLayout") {
+ set_layout<GUI::HorizontalBoxLayout>();
+ } else {
+ dbg() << "Unknown layout class: '" << class_name.to_string() << "'";
+ return false;
+ }
+
+ layout.for_each_member([&](auto& key, auto& value) {
+ this->layout()->set_property(key, value);
+ });
+ }
+
+ auto children = json.get("children");
+ if (children.is_array()) {
+ for (auto& child_json_value : children.as_array().values()) {
+ if (!child_json_value.is_object())
+ return false;
+ auto& child_json = child_json_value.as_object();
+ auto class_name = child_json.get("class");
+ if (!class_name.is_string()) {
+ dbgln("No class name in entry");
+ return false;
+ }
+
+ RefPtr<Widget> child_widget;
+ if (auto* registration = WidgetClassRegistration::find(class_name.as_string())) {
+ child_widget = registration->construct();
+ } else {
+ child_widget = unregistered_child_handler(class_name.as_string());
+ if (!child_widget)
+ return false;
+ }
+ add_child(*child_widget);
+ child_widget->load_from_json(child_json, unregistered_child_handler);
+ }
+ }
+
+ return true;
+}
+
+bool Widget::has_focus_within() const
+{
+ auto* window = this->window();
+ if (!window)
+ return false;
+ if (!window->focused_widget())
+ return false;
+ auto& effective_focus_widget = focus_proxy() ? *focus_proxy() : *this;
+ return window->focused_widget() == &effective_focus_widget || is_ancestor_of(*window->focused_widget());
+}
+
+void Widget::set_shrink_to_fit(bool b)
+{
+ if (m_shrink_to_fit == b)
+ return;
+ m_shrink_to_fit = b;
+ invalidate_layout();
+}
+
+bool Widget::has_pending_drop() const
+{
+ return Application::the()->pending_drop_widget() == this;
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Widget.h b/Userland/Libraries/LibGUI/Widget.h
new file mode 100644
index 0000000000..1f14ef14d4
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Widget.h
@@ -0,0 +1,406 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/JsonObject.h>
+#include <AK/String.h>
+#include <LibCore/Object.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/Event.h>
+#include <LibGUI/Forward.h>
+#include <LibGUI/Margins.h>
+#include <LibGfx/Color.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/Orientation.h>
+#include <LibGfx/Rect.h>
+#include <LibGfx/StandardCursor.h>
+
+#define REGISTER_WIDGET(namespace_, class_name) \
+ namespace { \
+ GUI::WidgetClassRegistration registration_##class_name(#namespace_ "::" #class_name, []() { return namespace_::class_name::construct(); }); \
+ }
+
+namespace GUI {
+
+enum class HorizontalDirection {
+ Left,
+ Right
+};
+enum class VerticalDirection {
+ Up,
+ Down
+};
+
+class WidgetClassRegistration {
+ AK_MAKE_NONCOPYABLE(WidgetClassRegistration);
+ AK_MAKE_NONMOVABLE(WidgetClassRegistration);
+
+public:
+ WidgetClassRegistration(const String& class_name, Function<NonnullRefPtr<Widget>()> factory);
+ ~WidgetClassRegistration();
+
+ String class_name() const { return m_class_name; }
+ NonnullRefPtr<Widget> construct() const { return m_factory(); }
+
+ static void for_each(Function<void(const WidgetClassRegistration&)>);
+ static const WidgetClassRegistration* find(const String& class_name);
+
+private:
+ String m_class_name;
+ Function<NonnullRefPtr<Widget>()> m_factory;
+};
+
+enum class FocusPolicy {
+ NoFocus = 0,
+ TabFocus = 0x1,
+ ClickFocus = 0x2,
+ StrongFocus = TabFocus | ClickFocus,
+};
+
+class Widget : public Core::Object {
+ C_OBJECT(Widget)
+public:
+ virtual ~Widget() override;
+
+ Layout* layout() { return m_layout.ptr(); }
+ const Layout* layout() const { return m_layout.ptr(); }
+ void set_layout(NonnullRefPtr<Layout>);
+
+ template<class T, class... Args>
+ inline T& set_layout(Args&&... args)
+ {
+ auto layout = T::construct(forward<Args>(args)...);
+ set_layout(*layout);
+ return layout;
+ }
+
+ Gfx::IntSize min_size() const { return m_min_size; }
+ void set_min_size(const Gfx::IntSize&);
+ void set_min_size(int width, int height) { set_min_size({ width, height }); }
+
+ int min_width() const { return m_min_size.width(); }
+ int min_height() const { return m_min_size.height(); }
+ void set_min_width(int width) { set_min_size(width, min_height()); }
+ void set_min_height(int height) { set_min_size(min_width(), height); }
+
+ Gfx::IntSize max_size() const { return m_max_size; }
+ void set_max_size(const Gfx::IntSize&);
+ void set_max_size(int width, int height) { set_max_size({ width, height }); }
+
+ int max_width() const { return m_max_size.width(); }
+ int max_height() const { return m_max_size.height(); }
+ void set_max_width(int width) { set_max_size(width, max_height()); }
+ void set_max_height(int height) { set_max_size(max_width(), height); }
+
+ void set_fixed_size(const Gfx::IntSize& size)
+ {
+ set_min_size(size);
+ set_max_size(size);
+ }
+
+ void set_fixed_size(int width, int height) { set_fixed_size({ width, height }); }
+
+ void set_fixed_width(int width)
+ {
+ set_min_width(width);
+ set_max_width(width);
+ }
+
+ void set_fixed_height(int height)
+ {
+ set_min_height(height);
+ set_max_height(height);
+ }
+
+ bool has_tooltip() const { return !m_tooltip.is_empty(); }
+ String tooltip() const { return m_tooltip; }
+ void set_tooltip(const StringView&);
+
+ bool is_enabled() const { return m_enabled; }
+ void set_enabled(bool);
+
+ bool updates_enabled() const { return m_updates_enabled; }
+ void set_updates_enabled(bool);
+
+ virtual void event(Core::Event&) override;
+
+ // This is called after children have been painted.
+ virtual void second_paint_event(PaintEvent&);
+
+ Gfx::IntRect relative_rect() const { return m_relative_rect; }
+ Gfx::IntPoint relative_position() const { return m_relative_rect.location(); }
+
+ Gfx::IntRect window_relative_rect() const;
+ Gfx::IntRect screen_relative_rect() const;
+
+ int x() const { return m_relative_rect.x(); }
+ int y() const { return m_relative_rect.y(); }
+ int width() const { return m_relative_rect.width(); }
+ int height() const { return m_relative_rect.height(); }
+ int length(Orientation orientation) const { return orientation == Orientation::Vertical ? height() : width(); }
+
+ Gfx::IntRect rect() const { return { 0, 0, width(), height() }; }
+ Gfx::IntSize size() const { return m_relative_rect.size(); }
+
+ void update();
+ void update(const Gfx::IntRect&);
+
+ bool is_focused() const;
+ void set_focus(bool, FocusSource = FocusSource::Programmatic);
+
+ Function<void(const bool, const FocusSource)> on_focus_change;
+
+ // Returns true if this widget or one of its descendants is focused.
+ bool has_focus_within() const;
+
+ Widget* focus_proxy() { return m_focus_proxy; }
+ const Widget* focus_proxy() const { return m_focus_proxy; }
+ void set_focus_proxy(Widget*);
+
+ void set_focus_policy(FocusPolicy policy);
+ FocusPolicy focus_policy() const;
+
+ enum class ShouldRespectGreediness {
+ No = 0,
+ Yes
+ };
+ struct HitTestResult {
+ WeakPtr<Widget> widget;
+ Gfx::IntPoint local_position;
+ };
+ HitTestResult hit_test(const Gfx::IntPoint&, ShouldRespectGreediness = ShouldRespectGreediness::Yes);
+ Widget* child_at(const Gfx::IntPoint&) const;
+
+ void set_relative_rect(const Gfx::IntRect&);
+ void set_relative_rect(int x, int y, int width, int height) { set_relative_rect({ x, y, width, height }); }
+
+ void set_x(int x) { set_relative_rect(x, y(), width(), height()); }
+ void set_y(int y) { set_relative_rect(x(), y, width(), height()); }
+ void set_width(int width) { set_relative_rect(x(), y(), width, height()); }
+ void set_height(int height) { set_relative_rect(x(), y(), width(), height); }
+
+ void move_to(const Gfx::IntPoint& point) { set_relative_rect({ point, relative_rect().size() }); }
+ void move_to(int x, int y) { move_to({ x, y }); }
+ void resize(const Gfx::IntSize& size) { set_relative_rect({ relative_rect().location(), size }); }
+ void resize(int width, int height) { resize({ width, height }); }
+
+ void move_by(int x, int y) { move_by({ x, y }); }
+ void move_by(const Gfx::IntPoint& delta) { set_relative_rect({ relative_position().translated(delta), size() }); }
+
+ Gfx::ColorRole background_role() const { return m_background_role; }
+ void set_background_role(Gfx::ColorRole);
+
+ Gfx::ColorRole foreground_role() const { return m_foreground_role; }
+ void set_foreground_role(Gfx::ColorRole);
+
+ void set_autofill(bool b) { set_fill_with_background_color(b); }
+
+ Window* window()
+ {
+ if (auto* pw = parent_widget())
+ return pw->window();
+ return m_window;
+ }
+
+ const Window* window() const
+ {
+ if (auto* pw = parent_widget())
+ return pw->window();
+ return m_window;
+ }
+
+ void set_window(Window*);
+
+ Widget* parent_widget();
+ const Widget* parent_widget() const;
+
+ void set_fill_with_background_color(bool b) { m_fill_with_background_color = b; }
+ bool fill_with_background_color() const { return m_fill_with_background_color; }
+
+ const Gfx::Font& font() const { return *m_font; }
+ void set_font(const Gfx::Font*);
+ void set_font(const Gfx::Font& font) { set_font(&font); }
+
+ void set_global_cursor_tracking(bool);
+ bool global_cursor_tracking() const;
+
+ void notify_layout_changed(Badge<Layout>);
+ void invalidate_layout();
+
+ bool is_visible() const { return m_visible; }
+ void set_visible(bool);
+
+ bool spans_entire_window_horizontally() const;
+
+ bool is_greedy_for_hits() const { return m_greedy_for_hits; }
+ void set_greedy_for_hits(bool b) { m_greedy_for_hits = b; }
+
+ void move_to_front();
+ void move_to_back();
+
+ bool is_frontmost() const;
+ bool is_backmost() const;
+
+ Action* action_for_key_event(const KeyEvent&);
+
+ template<typename Callback>
+ void for_each_child_widget(Callback callback)
+ {
+ for_each_child([&](auto& child) {
+ if (is<Widget>(child))
+ return callback(downcast<Widget>(child));
+ return IterationDecision::Continue;
+ });
+ }
+
+ Vector<Widget*> child_widgets() const;
+
+ void do_layout();
+
+ Gfx::Palette palette() const;
+ void set_palette(const Gfx::Palette&);
+
+ const Margins& content_margins() const { return m_content_margins; }
+ void set_content_margins(const Margins&);
+
+ Gfx::IntRect content_rect() const;
+
+ void set_accepts_emoji_input(bool b) { m_accepts_emoji_input = b; }
+ bool accepts_emoji_input() const { return m_accepts_emoji_input; }
+
+ virtual Gfx::IntRect children_clip_rect() const;
+
+ Gfx::StandardCursor override_cursor() const { return m_override_cursor; }
+ void set_override_cursor(Gfx::StandardCursor);
+
+ bool load_from_gml(const StringView&);
+ bool load_from_gml(const StringView&, RefPtr<Widget> (*unregistered_child_handler)(const String&));
+
+ void set_shrink_to_fit(bool);
+ bool is_shrink_to_fit() const { return m_shrink_to_fit; }
+
+ bool has_pending_drop() const;
+
+protected:
+ Widget();
+
+ virtual void custom_layout() { }
+ virtual void did_change_font() { }
+ virtual void did_layout() { }
+ virtual void paint_event(PaintEvent&);
+ virtual void resize_event(ResizeEvent&);
+ virtual void show_event(ShowEvent&);
+ virtual void hide_event(HideEvent&);
+ virtual void keydown_event(KeyEvent&);
+ virtual void keyup_event(KeyEvent&);
+ virtual void mousemove_event(MouseEvent&);
+ virtual void mousedown_event(MouseEvent&);
+ virtual void mouseup_event(MouseEvent&);
+ virtual void mousewheel_event(MouseEvent&);
+ virtual void doubleclick_event(MouseEvent&);
+ virtual void context_menu_event(ContextMenuEvent&);
+ virtual void focusin_event(FocusEvent&);
+ virtual void focusout_event(FocusEvent&);
+ virtual void enter_event(Core::Event&);
+ virtual void leave_event(Core::Event&);
+ virtual void child_event(Core::ChildEvent&) override;
+ virtual void change_event(Event&);
+ virtual void drag_enter_event(DragEvent&);
+ virtual void drag_move_event(DragEvent&);
+ virtual void drag_leave_event(Event&);
+ virtual void drop_event(DropEvent&);
+ virtual void theme_change_event(ThemeChangeEvent&);
+
+ virtual void did_begin_inspection() override;
+ virtual void did_end_inspection() override;
+
+ void show_or_hide_tooltip();
+
+private:
+ void handle_paint_event(PaintEvent&);
+ void handle_resize_event(ResizeEvent&);
+ void handle_mousedown_event(MouseEvent&);
+ void handle_mousedoubleclick_event(MouseEvent&);
+ void handle_mouseup_event(MouseEvent&);
+ void handle_enter_event(Core::Event&);
+ void handle_leave_event(Core::Event&);
+ void focus_previous_widget(FocusSource, bool siblings_only);
+ void focus_next_widget(FocusSource, bool siblings_only);
+
+ bool load_from_json(const JsonObject&, RefPtr<Widget> (*unregistered_child_handler)(const String&));
+
+ // HACK: These are used as property getters for the fixed_* size property aliases.
+ int dummy_fixed_width() { return 0; }
+ int dummy_fixed_height() { return 0; }
+ Gfx::IntSize dummy_fixed_size() { return {}; }
+
+ Window* m_window { nullptr };
+ RefPtr<Layout> m_layout;
+
+ Gfx::IntRect m_relative_rect;
+ Gfx::ColorRole m_background_role;
+ Gfx::ColorRole m_foreground_role;
+ NonnullRefPtr<Gfx::Font> m_font;
+ String m_tooltip;
+
+ Gfx::IntSize m_min_size { -1, -1 };
+ Gfx::IntSize m_max_size { -1, -1 };
+ Margins m_content_margins;
+
+ bool m_fill_with_background_color { false };
+ bool m_visible { true };
+ bool m_greedy_for_hits { false };
+ bool m_enabled { true };
+ bool m_updates_enabled { true };
+ bool m_accepts_emoji_input { false };
+ bool m_shrink_to_fit { false };
+
+ NonnullRefPtr<Gfx::PaletteImpl> m_palette;
+
+ WeakPtr<Widget> m_focus_proxy;
+ FocusPolicy m_focus_policy { FocusPolicy::NoFocus };
+
+ Gfx::StandardCursor m_override_cursor { Gfx::StandardCursor::None };
+};
+
+inline Widget* Widget::parent_widget()
+{
+ if (parent() && is<Widget>(*parent()))
+ return &downcast<Widget>(*parent());
+ return nullptr;
+}
+inline const Widget* Widget::parent_widget() const
+{
+ if (parent() && is<Widget>(*parent()))
+ return &downcast<const Widget>(*parent());
+ return nullptr;
+}
+}
+
+template<>
+struct AK::Formatter<GUI::Widget> : AK::Formatter<Core::Object> {
+};
diff --git a/Userland/Libraries/LibGUI/Window.cpp b/Userland/Libraries/LibGUI/Window.cpp
new file mode 100644
index 0000000000..62af3d07e1
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Window.cpp
@@ -0,0 +1,965 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/HashMap.h>
+#include <AK/JsonObject.h>
+#include <AK/NeverDestroyed.h>
+#include <AK/SharedBuffer.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/MimeData.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/Desktop.h>
+#include <LibGUI/Event.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/Widget.h>
+#include <LibGUI/Window.h>
+#include <LibGUI/WindowServerConnection.h>
+#include <LibGfx/Bitmap.h>
+#include <serenity.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+//#define UPDATE_COALESCING_DEBUG
+
+namespace GUI {
+
+static NeverDestroyed<HashTable<Window*>> all_windows;
+static NeverDestroyed<HashMap<int, Window*>> reified_windows;
+
+Window* Window::from_window_id(int window_id)
+{
+ auto it = reified_windows->find(window_id);
+ if (it != reified_windows->end())
+ return (*it).value;
+ return nullptr;
+}
+
+Window::Window(Core::Object* parent)
+ : Core::Object(parent)
+{
+ all_windows->set(this);
+ m_rect_when_windowless = { -5000, -5000, 140, 140 };
+ m_title_when_windowless = "GUI::Window";
+
+ register_property(
+ "title",
+ [this] { return title(); },
+ [this](auto& value) {
+ set_title(value.to_string());
+ return true;
+ });
+
+ register_property("visible", [this] { return is_visible(); });
+ register_property("active", [this] { return is_active(); });
+
+ REGISTER_BOOL_PROPERTY("minimizable", is_minimizable, set_minimizable);
+ REGISTER_BOOL_PROPERTY("resizable", is_resizable, set_resizable);
+ REGISTER_BOOL_PROPERTY("fullscreen", is_fullscreen, set_fullscreen);
+ REGISTER_RECT_PROPERTY("rect", rect, set_rect);
+ REGISTER_SIZE_PROPERTY("base_size", base_size, set_base_size);
+ REGISTER_SIZE_PROPERTY("size_increment", size_increment, set_size_increment);
+}
+
+Window::~Window()
+{
+ all_windows->remove(this);
+ hide();
+}
+
+void Window::close()
+{
+ hide();
+ if (on_close)
+ on_close();
+}
+
+void Window::move_to_front()
+{
+ if (!is_visible())
+ return;
+
+ WindowServerConnection::the().send_sync<Messages::WindowServer::MoveWindowToFront>(m_window_id);
+}
+
+void Window::show()
+{
+ if (is_visible())
+ return;
+
+ auto* parent_window = find_parent_window();
+
+ m_cursor = Gfx::StandardCursor::None;
+ auto response = WindowServerConnection::the().send_sync<Messages::WindowServer::CreateWindow>(
+ m_rect_when_windowless,
+ !m_moved_by_client,
+ m_has_alpha_channel,
+ m_modal,
+ m_minimizable,
+ m_resizable,
+ m_fullscreen,
+ m_frameless,
+ m_accessory,
+ m_opacity_when_windowless,
+ m_base_size,
+ m_size_increment,
+ m_resize_aspect_ratio,
+ (i32)m_window_type,
+ m_title_when_windowless,
+ parent_window ? parent_window->window_id() : 0);
+ m_window_id = response->window_id();
+ m_visible = true;
+
+ apply_icon();
+
+ reified_windows->set(m_window_id, this);
+ Application::the()->did_create_window({});
+ update();
+}
+
+Window* Window::find_parent_window()
+{
+ for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
+ if (is<Window>(ancestor))
+ return static_cast<Window*>(ancestor);
+ }
+ return nullptr;
+}
+
+void Window::server_did_destroy()
+{
+ reified_windows->remove(m_window_id);
+ m_window_id = 0;
+ m_visible = false;
+ m_pending_paint_event_rects.clear();
+ m_back_bitmap = nullptr;
+ m_front_bitmap = nullptr;
+ m_cursor = Gfx::StandardCursor::None;
+}
+
+void Window::hide()
+{
+ if (!is_visible())
+ return;
+ auto response = WindowServerConnection::the().send_sync<Messages::WindowServer::DestroyWindow>(m_window_id);
+ server_did_destroy();
+
+ for (auto child_window_id : response->destroyed_window_ids()) {
+ if (auto* window = Window::from_window_id(child_window_id)) {
+ window->server_did_destroy();
+ }
+ }
+
+ bool app_has_visible_windows = false;
+ for (auto& window : *all_windows) {
+ if (window->is_visible()) {
+ app_has_visible_windows = true;
+ break;
+ }
+ }
+ if (!app_has_visible_windows)
+ Application::the()->did_delete_last_window({});
+}
+
+void Window::set_title(const StringView& title)
+{
+ m_title_when_windowless = title;
+ if (!is_visible())
+ return;
+ WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowTitle>(m_window_id, title);
+}
+
+String Window::title() const
+{
+ if (!is_visible())
+ return m_title_when_windowless;
+ return WindowServerConnection::the().send_sync<Messages::WindowServer::GetWindowTitle>(m_window_id)->title();
+}
+
+Gfx::IntRect Window::rect_in_menubar() const
+{
+ ASSERT(m_window_type == WindowType::MenuApplet);
+ return WindowServerConnection::the().send_sync<Messages::WindowServer::GetWindowRectInMenubar>(m_window_id)->rect();
+}
+
+Gfx::IntRect Window::rect() const
+{
+ if (!is_visible())
+ return m_rect_when_windowless;
+ return WindowServerConnection::the().send_sync<Messages::WindowServer::GetWindowRect>(m_window_id)->rect();
+}
+
+void Window::set_rect(const Gfx::IntRect& a_rect)
+{
+ if (a_rect.location() != m_rect_when_windowless.location()) {
+ m_moved_by_client = true;
+ }
+
+ m_rect_when_windowless = a_rect;
+ if (!is_visible()) {
+ if (m_main_widget)
+ m_main_widget->resize(m_rect_when_windowless.size());
+ return;
+ }
+ auto window_rect = WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowRect>(m_window_id, a_rect)->rect();
+ if (m_back_bitmap && m_back_bitmap->size() != window_rect.size())
+ m_back_bitmap = nullptr;
+ if (m_front_bitmap && m_front_bitmap->size() != window_rect.size())
+ m_front_bitmap = nullptr;
+ if (m_main_widget)
+ m_main_widget->resize(window_rect.size());
+}
+
+void Window::center_on_screen()
+{
+ auto window_rect = rect();
+ window_rect.center_within(Desktop::the().rect());
+ set_rect(window_rect);
+}
+
+void Window::center_within(const Window& other)
+{
+ if (this == &other)
+ return;
+ auto window_rect = rect();
+ window_rect.center_within(other.rect());
+ set_rect(window_rect);
+}
+
+void Window::set_window_type(WindowType window_type)
+{
+ m_window_type = window_type;
+}
+
+void Window::set_cursor(Gfx::StandardCursor cursor)
+{
+ if (m_cursor == cursor)
+ return;
+ m_cursor = cursor;
+ m_custom_cursor = nullptr;
+ update_cursor();
+}
+
+void Window::set_cursor(const Gfx::Bitmap& cursor)
+{
+ if (m_custom_cursor == &cursor)
+ return;
+ m_cursor = Gfx::StandardCursor::None;
+ m_custom_cursor = &cursor;
+ update_cursor();
+}
+
+void Window::handle_drop_event(DropEvent& event)
+{
+ if (!m_main_widget)
+ return;
+ auto result = m_main_widget->hit_test(event.position());
+ auto local_event = make<DropEvent>(result.local_position, event.text(), event.mime_data());
+ ASSERT(result.widget);
+ result.widget->dispatch_event(*local_event, this);
+
+ Application::the()->set_drag_hovered_widget({}, nullptr);
+}
+
+void Window::handle_mouse_event(MouseEvent& event)
+{
+ if (m_global_cursor_tracking_widget) {
+ auto window_relative_rect = m_global_cursor_tracking_widget->window_relative_rect();
+ Gfx::IntPoint local_point { event.x() - window_relative_rect.x(), event.y() - window_relative_rect.y() };
+ auto local_event = make<MouseEvent>((Event::Type)event.type(), local_point, event.buttons(), event.button(), event.modifiers(), event.wheel_delta());
+ m_global_cursor_tracking_widget->dispatch_event(*local_event, this);
+ return;
+ }
+ if (m_automatic_cursor_tracking_widget) {
+ auto window_relative_rect = m_automatic_cursor_tracking_widget->window_relative_rect();
+ Gfx::IntPoint local_point { event.x() - window_relative_rect.x(), event.y() - window_relative_rect.y() };
+ auto local_event = make<MouseEvent>((Event::Type)event.type(), local_point, event.buttons(), event.button(), event.modifiers(), event.wheel_delta());
+ m_automatic_cursor_tracking_widget->dispatch_event(*local_event, this);
+ if (event.buttons() == 0)
+ m_automatic_cursor_tracking_widget = nullptr;
+ return;
+ }
+ if (!m_main_widget)
+ return;
+ auto result = m_main_widget->hit_test(event.position());
+ auto local_event = make<MouseEvent>((Event::Type)event.type(), result.local_position, event.buttons(), event.button(), event.modifiers(), event.wheel_delta());
+ ASSERT(result.widget);
+ set_hovered_widget(result.widget);
+ if (event.buttons() != 0 && !m_automatic_cursor_tracking_widget)
+ m_automatic_cursor_tracking_widget = *result.widget;
+ if (result.widget != m_global_cursor_tracking_widget.ptr())
+ return result.widget->dispatch_event(*local_event, this);
+ return;
+}
+
+void Window::handle_multi_paint_event(MultiPaintEvent& event)
+{
+ if (!is_visible())
+ return;
+ if (!m_main_widget)
+ return;
+ auto rects = event.rects();
+ ASSERT(!rects.is_empty());
+ if (m_back_bitmap && m_back_bitmap->size() != event.window_size()) {
+ // Eagerly discard the backing store if we learn from this paint event that it needs to be bigger.
+ // Otherwise we would have to wait for a resize event to tell us. This way we don't waste the
+ // effort on painting into an undersized bitmap that will be thrown away anyway.
+ m_back_bitmap = nullptr;
+ }
+ bool created_new_backing_store = !m_back_bitmap;
+ if (!m_back_bitmap) {
+ m_back_bitmap = create_backing_bitmap(event.window_size());
+ ASSERT(m_back_bitmap);
+ } else if (m_double_buffering_enabled) {
+ bool still_has_pixels = m_back_bitmap->shared_buffer()->set_nonvolatile();
+ if (!still_has_pixels) {
+ m_back_bitmap = create_backing_bitmap(event.window_size());
+ ASSERT(m_back_bitmap);
+ created_new_backing_store = true;
+ }
+ }
+
+ auto rect = rects.first();
+ if (rect.is_empty() || created_new_backing_store) {
+ rects.clear();
+ rects.append({ {}, event.window_size() });
+ }
+
+ for (auto& rect : rects) {
+ PaintEvent paint_event(rect);
+ m_main_widget->dispatch_event(paint_event, this);
+ }
+
+ if (m_double_buffering_enabled)
+ flip(rects);
+ else if (created_new_backing_store)
+ set_current_backing_bitmap(*m_back_bitmap, true);
+
+ if (is_visible()) {
+ Vector<Gfx::IntRect> rects_to_send;
+ for (auto& r : rects)
+ rects_to_send.append(r);
+ WindowServerConnection::the().post_message(Messages::WindowServer::DidFinishPainting(m_window_id, rects_to_send));
+ }
+}
+
+void Window::handle_key_event(KeyEvent& event)
+{
+ if (!m_focused_widget && event.type() == Event::KeyDown && event.key() == Key_Tab && !event.ctrl() && !event.alt() && !event.logo()) {
+ focus_a_widget_if_possible(FocusSource::Keyboard);
+ return;
+ }
+
+ if (m_focused_widget)
+ return m_focused_widget->dispatch_event(event, this);
+ if (m_main_widget)
+ return m_main_widget->dispatch_event(event, this);
+}
+
+void Window::handle_resize_event(ResizeEvent& event)
+{
+ auto new_size = event.size();
+ if (m_back_bitmap && m_back_bitmap->size() != new_size)
+ m_back_bitmap = nullptr;
+ if (!m_pending_paint_event_rects.is_empty()) {
+ m_pending_paint_event_rects.clear_with_capacity();
+ m_pending_paint_event_rects.append({ {}, new_size });
+ }
+ m_rect_when_windowless = { {}, new_size };
+ if (m_main_widget)
+ m_main_widget->set_relative_rect({ {}, new_size });
+}
+
+void Window::handle_input_entered_or_left_event(Core::Event& event)
+{
+ m_is_active_input = event.type() == Event::WindowInputEntered;
+ if (on_active_input_change)
+ on_active_input_change(m_is_active_input);
+ if (m_main_widget)
+ m_main_widget->dispatch_event(event, this);
+ if (m_focused_widget)
+ m_focused_widget->update();
+}
+
+void Window::handle_became_active_or_inactive_event(Core::Event& event)
+{
+ if (event.type() == Event::WindowBecameActive)
+ Application::the()->window_did_become_active({}, *this);
+ else
+ Application::the()->window_did_become_inactive({}, *this);
+ if (m_main_widget)
+ m_main_widget->dispatch_event(event, this);
+ if (m_focused_widget)
+ m_focused_widget->update();
+}
+
+void Window::handle_close_request()
+{
+ if (on_close_request) {
+ if (on_close_request() == Window::CloseRequestDecision::StayOpen)
+ return;
+ }
+ close();
+}
+
+void Window::handle_theme_change_event(ThemeChangeEvent& event)
+{
+ if (!m_main_widget)
+ return;
+ auto dispatch_theme_change = [&](auto& widget, auto recursive) {
+ widget.dispatch_event(event, this);
+ widget.for_each_child_widget([&](auto& widget) -> IterationDecision {
+ widget.dispatch_event(event, this);
+ recursive(widget, recursive);
+ return IterationDecision::Continue;
+ });
+ };
+ dispatch_theme_change(*m_main_widget.ptr(), dispatch_theme_change);
+}
+
+void Window::handle_drag_move_event(DragEvent& event)
+{
+ if (!m_main_widget)
+ return;
+ auto result = m_main_widget->hit_test(event.position());
+ ASSERT(result.widget);
+
+ Application::the()->set_drag_hovered_widget({}, result.widget, result.local_position, event.mime_types());
+
+ // NOTE: Setting the drag hovered widget may have executed arbitrary code, so re-check that the widget is still there.
+ if (!result.widget)
+ return;
+
+ if (result.widget->has_pending_drop()) {
+ DragEvent drag_move_event(static_cast<Event::Type>(event.type()), result.local_position, event.mime_types());
+ result.widget->dispatch_event(drag_move_event, this);
+ }
+}
+
+void Window::handle_left_event()
+{
+ set_hovered_widget(nullptr);
+ Application::the()->set_drag_hovered_widget({}, nullptr);
+}
+
+void Window::event(Core::Event& event)
+{
+ if (event.type() == Event::Drop)
+ return handle_drop_event(static_cast<DropEvent&>(event));
+
+ if (event.type() == Event::MouseUp || event.type() == Event::MouseDown || event.type() == Event::MouseDoubleClick || event.type() == Event::MouseMove || event.type() == Event::MouseWheel)
+ return handle_mouse_event(static_cast<MouseEvent&>(event));
+
+ if (event.type() == Event::MultiPaint)
+ return handle_multi_paint_event(static_cast<MultiPaintEvent&>(event));
+
+ if (event.type() == Event::KeyUp || event.type() == Event::KeyDown)
+ return handle_key_event(static_cast<KeyEvent&>(event));
+
+ if (event.type() == Event::WindowBecameActive || event.type() == Event::WindowBecameInactive)
+ return handle_became_active_or_inactive_event(event);
+
+ if (event.type() == Event::WindowInputEntered || event.type() == Event::WindowInputLeft)
+ return handle_input_entered_or_left_event(event);
+
+ if (event.type() == Event::WindowCloseRequest)
+ return handle_close_request();
+
+ if (event.type() == Event::WindowLeft)
+ return handle_left_event();
+
+ if (event.type() == Event::Resize)
+ return handle_resize_event(static_cast<ResizeEvent&>(event));
+
+ if (event.type() > Event::__Begin_WM_Events && event.type() < Event::__End_WM_Events)
+ return wm_event(static_cast<WMEvent&>(event));
+
+ if (event.type() == Event::DragMove)
+ return handle_drag_move_event(static_cast<DragEvent&>(event));
+
+ if (event.type() == Event::ThemeChange)
+ return handle_theme_change_event(static_cast<ThemeChangeEvent&>(event));
+
+ Core::Object::event(event);
+}
+
+bool Window::is_visible() const
+{
+ return m_visible;
+}
+
+void Window::update()
+{
+ auto rect = this->rect();
+ update({ 0, 0, rect.width(), rect.height() });
+}
+
+void Window::force_update()
+{
+ if (!is_visible())
+ return;
+ auto rect = this->rect();
+ WindowServerConnection::the().post_message(Messages::WindowServer::InvalidateRect(m_window_id, { { 0, 0, rect.width(), rect.height() } }, true));
+}
+
+void Window::update(const Gfx::IntRect& a_rect)
+{
+ if (!is_visible())
+ return;
+
+ for (auto& pending_rect : m_pending_paint_event_rects) {
+ if (pending_rect.contains(a_rect)) {
+#ifdef UPDATE_COALESCING_DEBUG
+ dbgln("Ignoring {} since it's contained by pending rect {}", a_rect, pending_rect);
+#endif
+ return;
+ }
+ }
+
+ if (m_pending_paint_event_rects.is_empty()) {
+ deferred_invoke([this](auto&) {
+ auto rects = move(m_pending_paint_event_rects);
+ if (rects.is_empty())
+ return;
+ Vector<Gfx::IntRect> rects_to_send;
+ for (auto& r : rects)
+ rects_to_send.append(r);
+ WindowServerConnection::the().post_message(Messages::WindowServer::InvalidateRect(m_window_id, rects_to_send, false));
+ });
+ }
+ m_pending_paint_event_rects.append(a_rect);
+}
+
+void Window::set_main_widget(Widget* widget)
+{
+ if (m_main_widget == widget)
+ return;
+ if (m_main_widget) {
+ m_main_widget->set_window(nullptr);
+ remove_child(*m_main_widget);
+ }
+ m_main_widget = widget;
+ if (m_main_widget) {
+ add_child(*widget);
+ auto new_window_rect = rect();
+ if (m_main_widget->min_width() >= 0)
+ new_window_rect.set_width(max(new_window_rect.width(), m_main_widget->min_width()));
+ if (m_main_widget->min_height() >= 0)
+ new_window_rect.set_height(max(new_window_rect.height(), m_main_widget->min_height()));
+ set_rect(new_window_rect);
+ m_main_widget->set_relative_rect({ {}, new_window_rect.size() });
+ m_main_widget->set_window(this);
+ if (m_main_widget->focus_policy() != FocusPolicy::NoFocus)
+ m_main_widget->set_focus(true);
+ }
+ update();
+}
+
+void Window::set_focused_widget(Widget* widget, FocusSource source)
+{
+ if (m_focused_widget == widget)
+ return;
+
+ WeakPtr<Widget> previously_focused_widget = m_focused_widget;
+ m_focused_widget = widget;
+
+ if (previously_focused_widget) {
+ Core::EventLoop::current().post_event(*previously_focused_widget, make<FocusEvent>(Event::FocusOut, source));
+ previously_focused_widget->update();
+ if (previously_focused_widget && previously_focused_widget->on_focus_change)
+ previously_focused_widget->on_focus_change(previously_focused_widget->is_focused(), source);
+ }
+ if (m_focused_widget) {
+ Core::EventLoop::current().post_event(*m_focused_widget, make<FocusEvent>(Event::FocusIn, source));
+ m_focused_widget->update();
+ if (m_focused_widget && m_focused_widget->on_focus_change)
+ m_focused_widget->on_focus_change(m_focused_widget->is_focused(), source);
+ }
+}
+
+void Window::set_global_cursor_tracking_widget(Widget* widget)
+{
+ if (widget == m_global_cursor_tracking_widget)
+ return;
+ m_global_cursor_tracking_widget = widget;
+}
+
+void Window::set_automatic_cursor_tracking_widget(Widget* widget)
+{
+ if (widget == m_automatic_cursor_tracking_widget)
+ return;
+ m_automatic_cursor_tracking_widget = widget;
+}
+
+void Window::set_has_alpha_channel(bool value)
+{
+ if (m_has_alpha_channel == value)
+ return;
+ m_has_alpha_channel = value;
+ if (!is_visible())
+ return;
+
+ m_pending_paint_event_rects.clear();
+ m_back_bitmap = nullptr;
+ m_front_bitmap = nullptr;
+
+ WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowHasAlphaChannel>(m_window_id, value);
+ update();
+}
+
+void Window::set_double_buffering_enabled(bool value)
+{
+ ASSERT(!is_visible());
+ m_double_buffering_enabled = value;
+}
+
+void Window::set_opacity(float opacity)
+{
+ m_opacity_when_windowless = opacity;
+ if (!is_visible())
+ return;
+ WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowOpacity>(m_window_id, opacity);
+}
+
+void Window::set_hovered_widget(Widget* widget)
+{
+ if (widget == m_hovered_widget)
+ return;
+
+ if (m_hovered_widget)
+ Core::EventLoop::current().post_event(*m_hovered_widget, make<Event>(Event::Leave));
+
+ m_hovered_widget = widget;
+
+ if (m_hovered_widget)
+ Core::EventLoop::current().post_event(*m_hovered_widget, make<Event>(Event::Enter));
+}
+
+void Window::set_current_backing_bitmap(Gfx::Bitmap& bitmap, bool flush_immediately)
+{
+ WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowBackingStore>(m_window_id, 32, bitmap.pitch(), bitmap.shbuf_id(), bitmap.has_alpha_channel(), bitmap.size(), flush_immediately);
+}
+
+void Window::flip(const Vector<Gfx::IntRect, 32>& dirty_rects)
+{
+ swap(m_front_bitmap, m_back_bitmap);
+
+ set_current_backing_bitmap(*m_front_bitmap);
+
+ if (!m_back_bitmap || m_back_bitmap->size() != m_front_bitmap->size()) {
+ m_back_bitmap = create_backing_bitmap(m_front_bitmap->size());
+ ASSERT(m_back_bitmap);
+ memcpy(m_back_bitmap->scanline(0), m_front_bitmap->scanline(0), m_front_bitmap->size_in_bytes());
+ m_back_bitmap->shared_buffer()->set_volatile();
+ return;
+ }
+
+ // Copy whatever was painted from the front to the back.
+ Painter painter(*m_back_bitmap);
+ for (auto& dirty_rect : dirty_rects)
+ painter.blit(dirty_rect.location(), *m_front_bitmap, dirty_rect);
+
+ m_back_bitmap->shared_buffer()->set_volatile();
+}
+
+RefPtr<Gfx::Bitmap> Window::create_shared_bitmap(Gfx::BitmapFormat format, const Gfx::IntSize& size)
+{
+ ASSERT(WindowServerConnection::the().server_pid());
+ ASSERT(!size.is_empty());
+ size_t pitch = Gfx::Bitmap::minimum_pitch(size.width(), format);
+ size_t size_in_bytes = size.height() * pitch;
+ auto shared_buffer = SharedBuffer::create_with_size(size_in_bytes);
+ ASSERT(shared_buffer);
+ shared_buffer->share_with(WindowServerConnection::the().server_pid());
+ return Gfx::Bitmap::create_with_shared_buffer(format, *shared_buffer, size);
+}
+
+RefPtr<Gfx::Bitmap> Window::create_backing_bitmap(const Gfx::IntSize& size)
+{
+ auto format = m_has_alpha_channel ? Gfx::BitmapFormat::RGBA32 : Gfx::BitmapFormat::RGB32;
+ return create_shared_bitmap(format, size);
+}
+
+void Window::set_modal(bool modal)
+{
+ ASSERT(!is_visible());
+ m_modal = modal;
+}
+
+void Window::wm_event(WMEvent&)
+{
+}
+
+void Window::set_icon(const Gfx::Bitmap* icon)
+{
+ if (m_icon == icon)
+ return;
+
+ Gfx::IntSize icon_size = icon ? icon->size() : Gfx::IntSize(16, 16);
+
+ m_icon = create_shared_bitmap(Gfx::BitmapFormat::RGBA32, icon_size);
+ ASSERT(m_icon);
+ if (icon) {
+ Painter painter(*m_icon);
+ painter.blit({ 0, 0 }, *icon, icon->rect());
+ }
+
+ apply_icon();
+}
+
+void Window::apply_icon()
+{
+ if (!m_icon)
+ return;
+
+ if (!is_visible())
+ return;
+
+ int rc = shbuf_seal(m_icon->shbuf_id());
+ ASSERT(rc == 0);
+
+ rc = shbuf_allow_all(m_icon->shbuf_id());
+ ASSERT(rc == 0);
+
+ WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowIconBitmap>(m_window_id, m_icon->to_shareable_bitmap(WindowServerConnection::the().server_pid()));
+}
+
+void Window::start_wm_resize()
+{
+ WindowServerConnection::the().post_message(Messages::WindowServer::WM_StartWindowResize(WindowServerConnection::the().my_client_id(), m_window_id));
+}
+
+Vector<Widget*> Window::focusable_widgets(FocusSource source) const
+{
+ if (!m_main_widget)
+ return {};
+
+ HashTable<Widget*> seen_widgets;
+ Vector<Widget*> collected_widgets;
+
+ Function<void(Widget&)> collect_focusable_widgets = [&](auto& widget) {
+ bool widget_accepts_focus = false;
+ switch (source) {
+ case FocusSource::Keyboard:
+ widget_accepts_focus = ((unsigned)widget.focus_policy() & (unsigned)FocusPolicy::TabFocus);
+ break;
+ case FocusSource::Mouse:
+ widget_accepts_focus = ((unsigned)widget.focus_policy() & (unsigned)FocusPolicy::ClickFocus);
+ break;
+ case FocusSource::Programmatic:
+ widget_accepts_focus = widget.focus_policy() != FocusPolicy::NoFocus;
+ break;
+ }
+
+ if (widget_accepts_focus) {
+ auto& effective_focus_widget = widget.focus_proxy() ? *widget.focus_proxy() : widget;
+ if (seen_widgets.set(&effective_focus_widget) == AK::HashSetResult::InsertedNewEntry)
+ collected_widgets.append(&effective_focus_widget);
+ }
+ widget.for_each_child_widget([&](auto& child) {
+ if (!child.is_visible())
+ return IterationDecision::Continue;
+ if (!child.is_enabled())
+ return IterationDecision::Continue;
+ collect_focusable_widgets(child);
+ return IterationDecision::Continue;
+ });
+ };
+
+ collect_focusable_widgets(const_cast<Widget&>(*m_main_widget));
+ return collected_widgets;
+}
+
+void Window::set_fullscreen(bool fullscreen)
+{
+ if (m_fullscreen == fullscreen)
+ return;
+ m_fullscreen = fullscreen;
+ if (!is_visible())
+ return;
+ WindowServerConnection::the().send_sync<Messages::WindowServer::SetFullscreen>(m_window_id, fullscreen);
+}
+
+bool Window::is_maximized() const
+{
+ if (!is_visible())
+ return false;
+
+ return WindowServerConnection::the().send_sync<Messages::WindowServer::IsMaximized>(m_window_id)->maximized();
+}
+
+void Window::schedule_relayout()
+{
+ if (m_layout_pending)
+ return;
+ m_layout_pending = true;
+ deferred_invoke([this](auto&) {
+ if (main_widget())
+ main_widget()->do_layout();
+ update();
+ m_layout_pending = false;
+ });
+}
+
+void Window::for_each_window(Badge<WindowServerConnection>, Function<void(Window&)> callback)
+{
+ for (auto& e : *reified_windows) {
+ ASSERT(e.value);
+ callback(*e.value);
+ }
+}
+
+void Window::update_all_windows(Badge<WindowServerConnection>)
+{
+ for (auto& e : *reified_windows) {
+ e.value->force_update();
+ }
+}
+
+void Window::notify_state_changed(Badge<WindowServerConnection>, bool minimized, bool occluded)
+{
+ m_visible_for_timer_purposes = !minimized && !occluded;
+
+ // When double buffering is enabled, minimization/occlusion means we can mark the front bitmap volatile (in addition to the back bitmap.)
+ // When double buffering is disabled, there is only the back bitmap (which we can now mark volatile!)
+ RefPtr<Gfx::Bitmap>& bitmap = m_double_buffering_enabled ? m_front_bitmap : m_back_bitmap;
+ if (!bitmap)
+ return;
+ if (minimized || occluded) {
+ bitmap->shared_buffer()->set_volatile();
+ } else {
+ if (!bitmap->shared_buffer()->set_nonvolatile()) {
+ bitmap = nullptr;
+ update();
+ }
+ }
+}
+
+Action* Window::action_for_key_event(const KeyEvent& event)
+{
+ Shortcut shortcut(event.modifiers(), (KeyCode)event.key());
+ Action* found_action = nullptr;
+ for_each_child_of_type<Action>([&](auto& action) {
+ if (action.shortcut() == shortcut) {
+ found_action = &action;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+ return found_action;
+}
+
+void Window::set_base_size(const Gfx::IntSize& base_size)
+{
+ if (m_base_size == base_size)
+ return;
+ m_base_size = base_size;
+ if (is_visible())
+ WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowBaseSizeAndSizeIncrement>(m_window_id, m_base_size, m_size_increment);
+}
+
+void Window::set_size_increment(const Gfx::IntSize& size_increment)
+{
+ if (m_size_increment == size_increment)
+ return;
+ m_size_increment = size_increment;
+ if (is_visible())
+ WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowBaseSizeAndSizeIncrement>(m_window_id, m_base_size, m_size_increment);
+}
+
+void Window::set_resize_aspect_ratio(const Optional<Gfx::IntSize>& ratio)
+{
+ if (m_resize_aspect_ratio == ratio)
+ return;
+
+ m_resize_aspect_ratio = ratio;
+ if (is_visible())
+ WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowResizeAspectRatio>(m_window_id, m_resize_aspect_ratio);
+}
+
+void Window::did_add_widget(Badge<Widget>, Widget&)
+{
+ if (!m_focused_widget)
+ focus_a_widget_if_possible(FocusSource::Mouse);
+}
+
+void Window::did_remove_widget(Badge<Widget>, Widget& widget)
+{
+ if (m_focused_widget == &widget)
+ m_focused_widget = nullptr;
+ if (m_hovered_widget == &widget)
+ m_hovered_widget = nullptr;
+ if (m_global_cursor_tracking_widget == &widget)
+ m_global_cursor_tracking_widget = nullptr;
+ if (m_automatic_cursor_tracking_widget == &widget)
+ m_automatic_cursor_tracking_widget = nullptr;
+}
+
+void Window::set_progress(int progress)
+{
+ ASSERT(m_window_id);
+ WindowServerConnection::the().post_message(Messages::WindowServer::SetWindowProgress(m_window_id, progress));
+}
+
+void Window::update_cursor()
+{
+ Gfx::StandardCursor new_cursor;
+
+ if (m_hovered_widget && m_hovered_widget->override_cursor() != Gfx::StandardCursor::None)
+ new_cursor = m_hovered_widget->override_cursor();
+ else
+ new_cursor = m_cursor;
+
+ if (m_effective_cursor == new_cursor)
+ return;
+ m_effective_cursor = new_cursor;
+
+ if (m_custom_cursor)
+ WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowCustomCursor>(m_window_id, m_custom_cursor->to_shareable_bitmap(WindowServerConnection::the().server_pid()));
+ else
+ WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowCursor>(m_window_id, (u32)m_effective_cursor);
+}
+
+void Window::focus_a_widget_if_possible(FocusSource source)
+{
+ auto focusable_widgets = this->focusable_widgets(source);
+ if (!focusable_widgets.is_empty())
+ set_focused_widget(focusable_widgets[0], source);
+}
+
+void Window::did_disable_focused_widget(Badge<Widget>)
+{
+ focus_a_widget_if_possible(FocusSource::Mouse);
+}
+
+bool Window::is_active() const
+{
+ ASSERT(Application::the());
+ return this == Application::the()->active_window();
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/Window.h b/Userland/Libraries/LibGUI/Window.h
new file mode 100644
index 0000000000..326fed14cf
--- /dev/null
+++ b/Userland/Libraries/LibGUI/Window.h
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/String.h>
+#include <AK/WeakPtr.h>
+#include <LibCore/Object.h>
+#include <LibGUI/FocusSource.h>
+#include <LibGUI/Forward.h>
+#include <LibGUI/WindowType.h>
+#include <LibGfx/Color.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/Rect.h>
+#include <LibGfx/StandardCursor.h>
+
+namespace GUI {
+
+class Window : public Core::Object {
+ C_OBJECT(Window)
+public:
+ virtual ~Window() override;
+
+ static Window* from_window_id(int);
+
+ bool is_modal() const { return m_modal; }
+ void set_modal(bool);
+
+ bool is_fullscreen() const { return m_fullscreen; }
+ void set_fullscreen(bool);
+
+ bool is_maximized() const;
+
+ bool is_frameless() const { return m_frameless; }
+ void set_frameless(bool frameless) { m_frameless = frameless; }
+
+ bool is_resizable() const { return m_resizable; }
+ void set_resizable(bool resizable) { m_resizable = resizable; }
+
+ bool is_minimizable() const { return m_minimizable; }
+ void set_minimizable(bool minimizable) { m_minimizable = minimizable; }
+
+ void set_double_buffering_enabled(bool);
+ void set_has_alpha_channel(bool);
+ void set_opacity(float);
+
+ WindowType window_type() const { return m_window_type; }
+ void set_window_type(WindowType);
+
+ int window_id() const { return m_window_id; }
+
+ String title() const;
+ void set_title(const StringView&);
+
+ Color background_color() const { return m_background_color; }
+ void set_background_color(Color color) { m_background_color = color; }
+
+ enum class CloseRequestDecision {
+ StayOpen,
+ Close,
+ };
+
+ Function<void()> on_close;
+ Function<CloseRequestDecision()> on_close_request;
+ Function<void(bool is_active_input)> on_active_input_change;
+
+ int x() const { return rect().x(); }
+ int y() const { return rect().y(); }
+ int width() const { return rect().width(); }
+ int height() const { return rect().height(); }
+
+ Gfx::IntRect rect() const;
+ Gfx::IntRect rect_in_menubar() const;
+ Gfx::IntSize size() const { return rect().size(); }
+ void set_rect(const Gfx::IntRect&);
+ void set_rect(int x, int y, int width, int height) { set_rect({ x, y, width, height }); }
+
+ Gfx::IntPoint position() const { return rect().location(); }
+
+ void move_to(int x, int y) { move_to({ x, y }); }
+ void move_to(const Gfx::IntPoint& point) { set_rect({ point, size() }); }
+
+ void resize(int width, int height) { resize({ width, height }); }
+ void resize(const Gfx::IntSize& size) { set_rect({ position(), size }); }
+
+ void center_on_screen();
+ void center_within(const Window&);
+
+ virtual void event(Core::Event&) override;
+
+ bool is_visible() const;
+ bool is_active() const;
+ bool is_active_input() const { return m_is_active_input; }
+
+ bool is_accessory() const { return m_accessory; }
+ void set_accessory(bool accessory) { m_accessory = accessory; }
+
+ void show();
+ void hide();
+ virtual void close();
+ void move_to_front();
+
+ void start_wm_resize();
+
+ Widget* main_widget() { return m_main_widget; }
+ const Widget* main_widget() const { return m_main_widget; }
+ void set_main_widget(Widget*);
+
+ template<class T, class... Args>
+ inline T& set_main_widget(Args&&... args)
+ {
+ auto widget = T::construct(forward<Args>(args)...);
+ set_main_widget(widget.ptr());
+ return *widget;
+ }
+
+ Widget* focused_widget() { return m_focused_widget; }
+ const Widget* focused_widget() const { return m_focused_widget; }
+ void set_focused_widget(Widget*, FocusSource = FocusSource::Programmatic);
+
+ void update();
+ void update(const Gfx::IntRect&);
+
+ void set_global_cursor_tracking_widget(Widget*);
+ Widget* global_cursor_tracking_widget() { return m_global_cursor_tracking_widget.ptr(); }
+ const Widget* global_cursor_tracking_widget() const { return m_global_cursor_tracking_widget.ptr(); }
+
+ void set_automatic_cursor_tracking_widget(Widget*);
+ Widget* automatic_cursor_tracking_widget() { return m_automatic_cursor_tracking_widget.ptr(); }
+ const Widget* automatic_cursor_tracking_widget() const { return m_automatic_cursor_tracking_widget.ptr(); }
+
+ Widget* hovered_widget() { return m_hovered_widget.ptr(); }
+ const Widget* hovered_widget() const { return m_hovered_widget.ptr(); }
+ void set_hovered_widget(Widget*);
+
+ Gfx::Bitmap* front_bitmap() { return m_front_bitmap.ptr(); }
+ Gfx::Bitmap* back_bitmap() { return m_back_bitmap.ptr(); }
+
+ Gfx::IntSize size_increment() const { return m_size_increment; }
+ void set_size_increment(const Gfx::IntSize&);
+ Gfx::IntSize base_size() const { return m_base_size; }
+ void set_base_size(const Gfx::IntSize&);
+ const Optional<Gfx::IntSize>& resize_aspect_ratio() const { return m_resize_aspect_ratio; }
+ void set_resize_aspect_ratio(int width, int height) { set_resize_aspect_ratio(Gfx::IntSize(width, height)); }
+ void set_no_resize_aspect_ratio() { set_resize_aspect_ratio({}); }
+ void set_resize_aspect_ratio(const Optional<Gfx::IntSize>& ratio);
+
+ void set_cursor(Gfx::StandardCursor);
+ void set_cursor(const Gfx::Bitmap&);
+
+ void set_icon(const Gfx::Bitmap*);
+ void apply_icon();
+ const Gfx::Bitmap* icon() const { return m_icon.ptr(); }
+
+ Vector<Widget*> focusable_widgets(FocusSource) const;
+
+ void schedule_relayout();
+
+ static void for_each_window(Badge<WindowServerConnection>, Function<void(Window&)>);
+ static void update_all_windows(Badge<WindowServerConnection>);
+ void notify_state_changed(Badge<WindowServerConnection>, bool minimized, bool occluded);
+
+ virtual bool is_visible_for_timer_purposes() const override { return m_visible_for_timer_purposes; }
+
+ Action* action_for_key_event(const KeyEvent&);
+
+ void did_add_widget(Badge<Widget>, Widget&);
+ void did_remove_widget(Badge<Widget>, Widget&);
+
+ Window* find_parent_window();
+
+ void set_progress(int);
+
+ void update_cursor(Badge<Widget>) { update_cursor(); }
+
+ void did_disable_focused_widget(Badge<Widget>);
+
+protected:
+ Window(Core::Object* parent = nullptr);
+ virtual void wm_event(WMEvent&);
+
+private:
+ void update_cursor();
+ void focus_a_widget_if_possible(FocusSource);
+
+ void handle_drop_event(DropEvent&);
+ void handle_mouse_event(MouseEvent&);
+ void handle_multi_paint_event(MultiPaintEvent&);
+ void handle_key_event(KeyEvent&);
+ void handle_resize_event(ResizeEvent&);
+ void handle_input_entered_or_left_event(Core::Event&);
+ void handle_became_active_or_inactive_event(Core::Event&);
+ void handle_close_request();
+ void handle_theme_change_event(ThemeChangeEvent&);
+ void handle_drag_move_event(DragEvent&);
+ void handle_left_event();
+
+ void server_did_destroy();
+
+ RefPtr<Gfx::Bitmap> create_backing_bitmap(const Gfx::IntSize&);
+ RefPtr<Gfx::Bitmap> create_shared_bitmap(Gfx::BitmapFormat, const Gfx::IntSize&);
+ void set_current_backing_bitmap(Gfx::Bitmap&, bool flush_immediately = false);
+ void flip(const Vector<Gfx::IntRect, 32>& dirty_rects);
+ void force_update();
+
+ RefPtr<Gfx::Bitmap> m_front_bitmap;
+ RefPtr<Gfx::Bitmap> m_back_bitmap;
+ RefPtr<Gfx::Bitmap> m_icon;
+ RefPtr<Gfx::Bitmap> m_custom_cursor;
+ int m_window_id { 0 };
+ float m_opacity_when_windowless { 1.0f };
+ RefPtr<Widget> m_main_widget;
+ WeakPtr<Widget> m_focused_widget;
+ WeakPtr<Widget> m_global_cursor_tracking_widget;
+ WeakPtr<Widget> m_automatic_cursor_tracking_widget;
+ WeakPtr<Widget> m_hovered_widget;
+ Gfx::IntRect m_rect_when_windowless;
+ String m_title_when_windowless;
+ Vector<Gfx::IntRect, 32> m_pending_paint_event_rects;
+ Gfx::IntSize m_size_increment;
+ Gfx::IntSize m_base_size;
+ Color m_background_color { Color::WarmGray };
+ WindowType m_window_type { WindowType::Normal };
+ Gfx::StandardCursor m_cursor { Gfx::StandardCursor::None };
+ Gfx::StandardCursor m_effective_cursor { Gfx::StandardCursor::None };
+ bool m_is_active_input { false };
+ bool m_has_alpha_channel { false };
+ bool m_double_buffering_enabled { true };
+ bool m_modal { false };
+ bool m_resizable { true };
+ Optional<Gfx::IntSize> m_resize_aspect_ratio {};
+ bool m_minimizable { true };
+ bool m_fullscreen { false };
+ bool m_frameless { false };
+ bool m_layout_pending { false };
+ bool m_visible_for_timer_purposes { true };
+ bool m_visible { false };
+ bool m_accessory { false };
+ bool m_moved_by_client { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/WindowServerConnection.cpp b/Userland/Libraries/LibGUI/WindowServerConnection.cpp
new file mode 100644
index 0000000000..8250f134af
--- /dev/null
+++ b/Userland/Libraries/LibGUI/WindowServerConnection.cpp
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/SharedBuffer.h>
+#include <AK/StringBuilder.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/MimeData.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/Clipboard.h>
+#include <LibGUI/Desktop.h>
+#include <LibGUI/DisplayLink.h>
+#include <LibGUI/DragOperation.h>
+#include <LibGUI/EmojiInputDialog.h>
+#include <LibGUI/Event.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/Widget.h>
+#include <LibGUI/Window.h>
+#include <LibGUI/WindowServerConnection.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/SystemTheme.h>
+
+//#define KEYBOARD_SHORTCUTS_DEBUG
+
+namespace GUI {
+
+WindowServerConnection& WindowServerConnection::the()
+{
+ static WindowServerConnection* s_connection = nullptr;
+ if (!s_connection)
+ s_connection = new WindowServerConnection;
+ return *s_connection;
+}
+
+static void set_system_theme_from_shbuf_id(int id)
+{
+ auto system_theme = SharedBuffer::create_from_shbuf_id(id);
+ ASSERT(system_theme);
+ Gfx::set_system_theme(*system_theme);
+ Application::the()->set_system_palette(*system_theme);
+}
+
+void WindowServerConnection::handshake()
+{
+ auto response = send_sync<Messages::WindowServer::Greet>();
+ set_my_client_id(response->client_id());
+ set_system_theme_from_shbuf_id(response->system_theme_buffer_id());
+ Desktop::the().did_receive_screen_rect({}, response->screen_rect());
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::UpdateSystemTheme& message)
+{
+ set_system_theme_from_shbuf_id(message.system_theme_buffer_id());
+ Window::update_all_windows({});
+ Window::for_each_window({}, [](auto& window) {
+ Core::EventLoop::current().post_event(window, make<ThemeChangeEvent>());
+ });
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::Paint& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id()))
+ Core::EventLoop::current().post_event(*window, make<MultiPaintEvent>(message.rects(), message.window_size()));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::WindowResized& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id())) {
+ Core::EventLoop::current().post_event(*window, make<ResizeEvent>(message.new_rect().size()));
+ }
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::WindowActivated& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id()))
+ Core::EventLoop::current().post_event(*window, make<Event>(Event::WindowBecameActive));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::WindowDeactivated& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id()))
+ Core::EventLoop::current().post_event(*window, make<Event>(Event::WindowBecameInactive));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::WindowInputEntered& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id()))
+ Core::EventLoop::current().post_event(*window, make<Event>(Event::WindowInputEntered));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::WindowInputLeft& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id()))
+ Core::EventLoop::current().post_event(*window, make<Event>(Event::WindowInputLeft));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::WindowCloseRequest& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id()))
+ Core::EventLoop::current().post_event(*window, make<Event>(Event::WindowCloseRequest));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::WindowEntered& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id()))
+ Core::EventLoop::current().post_event(*window, make<Event>(Event::WindowEntered));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::WindowLeft& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id()))
+ Core::EventLoop::current().post_event(*window, make<Event>(Event::WindowLeft));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::KeyDown& message)
+{
+ auto* window = Window::from_window_id(message.window_id());
+ if (!window)
+ return;
+
+ auto key_event = make<KeyEvent>(Event::KeyDown, (KeyCode)message.key(), message.modifiers(), message.code_point(), message.scancode());
+ Action* action = nullptr;
+
+#ifdef KEYBOARD_SHORTCUTS_DEBUG
+ dbg() << "Looking up action for " << key_event->to_string();
+#endif
+
+ if (auto* focused_widget = window->focused_widget()) {
+ for (auto* widget = focused_widget; widget && !action; widget = widget->parent_widget()) {
+ action = widget->action_for_key_event(*key_event);
+#ifdef KEYBOARD_SHORTCUTS_DEBUG
+ dbg() << " > Focused widget " << *widget << " gave action: " << action;
+#endif
+ }
+ }
+
+ if (!action) {
+ action = window->action_for_key_event(*key_event);
+#ifdef KEYBOARD_SHORTCUTS_DEBUG
+ dbg() << " > Asked window " << *window << ", got action: " << action;
+#endif
+ }
+
+ // NOTE: Application-global shortcuts are ignored while a modal window is up.
+ if (!action && !window->is_modal()) {
+ action = Application::the()->action_for_key_event(*key_event);
+#ifdef KEYBOARD_SHORTCUTS_DEBUG
+ dbg() << " > Asked application, got action: " << action;
+#endif
+ }
+
+ if (action) {
+ if (action->is_enabled()) {
+ action->activate();
+ return;
+ }
+ if (action->swallow_key_event_when_disabled())
+ return;
+ }
+
+ bool focused_widget_accepts_emoji_input = window->focused_widget() && window->focused_widget()->accepts_emoji_input();
+ if (focused_widget_accepts_emoji_input && (message.modifiers() == (Mod_Ctrl | Mod_Alt)) && message.key() == Key_Space) {
+ auto emoji_input_dialog = EmojiInputDialog::construct(window);
+ if (emoji_input_dialog->exec() != EmojiInputDialog::ExecOK)
+ return;
+ key_event->m_key = Key_Invalid;
+ key_event->m_modifiers = 0;
+
+ AK::Utf8View m_utf8_view(emoji_input_dialog->selected_emoji_text().characters());
+ u32 code_point = *m_utf8_view.begin();
+
+ key_event->m_code_point = code_point;
+ }
+
+ Core::EventLoop::current().post_event(*window, move(key_event));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::KeyUp& message)
+{
+ auto* window = Window::from_window_id(message.window_id());
+ if (!window)
+ return;
+
+ auto key_event = make<KeyEvent>(Event::KeyUp, (KeyCode)message.key(), message.modifiers(), message.code_point(), message.scancode());
+ Core::EventLoop::current().post_event(*window, move(key_event));
+}
+
+static MouseButton to_gmousebutton(u32 button)
+{
+ switch (button) {
+ case 0:
+ return MouseButton::None;
+ case 1:
+ return MouseButton::Left;
+ case 2:
+ return MouseButton::Right;
+ case 4:
+ return MouseButton::Middle;
+ case 8:
+ return MouseButton::Back;
+ case 16:
+ return MouseButton::Forward;
+ default:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::MouseDown& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id()))
+ Core::EventLoop::current().post_event(*window, make<MouseEvent>(Event::MouseDown, message.mouse_position(), message.buttons(), to_gmousebutton(message.button()), message.modifiers(), message.wheel_delta()));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::MouseUp& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id()))
+ Core::EventLoop::current().post_event(*window, make<MouseEvent>(Event::MouseUp, message.mouse_position(), message.buttons(), to_gmousebutton(message.button()), message.modifiers(), message.wheel_delta()));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::MouseMove& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id())) {
+ if (message.is_drag())
+ Core::EventLoop::current().post_event(*window, make<DragEvent>(Event::DragMove, message.mouse_position(), message.mime_types()));
+ else
+ Core::EventLoop::current().post_event(*window, make<MouseEvent>(Event::MouseMove, message.mouse_position(), message.buttons(), to_gmousebutton(message.button()), message.modifiers(), message.wheel_delta()));
+ }
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::MouseDoubleClick& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id()))
+ Core::EventLoop::current().post_event(*window, make<MouseEvent>(Event::MouseDoubleClick, message.mouse_position(), message.buttons(), to_gmousebutton(message.button()), message.modifiers(), message.wheel_delta()));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::MouseWheel& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id()))
+ Core::EventLoop::current().post_event(*window, make<MouseEvent>(Event::MouseWheel, message.mouse_position(), message.buttons(), to_gmousebutton(message.button()), message.modifiers(), message.wheel_delta()));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::MenuItemActivated& message)
+{
+ auto* menu = Menu::from_menu_id(message.menu_id());
+ if (!menu) {
+ dbgprintf("EventLoop received event for invalid menu ID %d\n", message.menu_id());
+ return;
+ }
+ if (auto* action = menu->action_at(message.identifier()))
+ action->activate(menu);
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::WM_WindowStateChanged& message)
+{
+ if (auto* window = Window::from_window_id(message.wm_id()))
+ Core::EventLoop::current().post_event(*window, make<WMWindowStateChangedEvent>(message.client_id(), message.window_id(), message.parent_client_id(), message.parent_window_id(), message.title(), message.rect(), message.is_active(), message.is_modal(), static_cast<WindowType>(message.window_type()), message.is_minimized(), message.is_frameless(), message.progress()));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::WM_WindowRectChanged& message)
+{
+ if (auto* window = Window::from_window_id(message.wm_id()))
+ Core::EventLoop::current().post_event(*window, make<WMWindowRectChangedEvent>(message.client_id(), message.window_id(), message.rect()));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::WM_WindowIconBitmapChanged& message)
+{
+ if (auto* window = Window::from_window_id(message.wm_id()))
+ Core::EventLoop::current().post_event(*window, make<WMWindowIconBitmapChangedEvent>(message.client_id(), message.window_id(), message.icon_buffer_id(), message.icon_size()));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::WM_WindowRemoved& message)
+{
+ if (auto* window = Window::from_window_id(message.wm_id()))
+ Core::EventLoop::current().post_event(*window, make<WMWindowRemovedEvent>(message.client_id(), message.window_id()));
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::ScreenRectChanged& message)
+{
+ Desktop::the().did_receive_screen_rect({}, message.rect());
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::AsyncSetWallpaperFinished&)
+{
+ // This is handled manually by Desktop::set_wallpaper().
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::DragDropped& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id())) {
+ auto mime_data = Core::MimeData::construct(message.mime_data());
+ Core::EventLoop::current().post_event(*window, make<DropEvent>(message.mouse_position(), message.text(), mime_data));
+ }
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::DragAccepted&)
+{
+ DragOperation::notify_accepted({});
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::DragCancelled&)
+{
+ DragOperation::notify_cancelled({});
+ Application::the()->notify_drag_cancelled({});
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::WindowStateChanged& message)
+{
+ if (auto* window = Window::from_window_id(message.window_id()))
+ window->notify_state_changed({}, message.minimized(), message.occluded());
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::DisplayLinkNotification&)
+{
+ if (m_display_link_notification_pending)
+ return;
+
+ m_display_link_notification_pending = true;
+ deferred_invoke([this](auto&) {
+ DisplayLink::notify({});
+ m_display_link_notification_pending = false;
+ });
+}
+
+void WindowServerConnection::handle(const Messages::WindowClient::Ping&)
+{
+ post_message(Messages::WindowServer::Pong());
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/WindowServerConnection.h b/Userland/Libraries/LibGUI/WindowServerConnection.h
new file mode 100644
index 0000000000..49850e3839
--- /dev/null
+++ b/Userland/Libraries/LibGUI/WindowServerConnection.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibIPC/ServerConnection.h>
+#include <WindowServer/WindowClientEndpoint.h>
+#include <WindowServer/WindowServerEndpoint.h>
+
+namespace GUI {
+
+class WindowServerConnection
+ : public IPC::ServerConnection<WindowClientEndpoint, WindowServerEndpoint>
+ , public WindowClientEndpoint {
+ C_OBJECT(WindowServerConnection)
+public:
+ WindowServerConnection()
+ : IPC::ServerConnection<WindowClientEndpoint, WindowServerEndpoint>(*this, "/tmp/portal/window")
+ {
+ handshake();
+ }
+
+ virtual void handshake() override;
+ static WindowServerConnection& the();
+
+private:
+ virtual void handle(const Messages::WindowClient::Paint&) override;
+ virtual void handle(const Messages::WindowClient::MouseMove&) override;
+ virtual void handle(const Messages::WindowClient::MouseDown&) override;
+ virtual void handle(const Messages::WindowClient::MouseDoubleClick&) override;
+ virtual void handle(const Messages::WindowClient::MouseUp&) override;
+ virtual void handle(const Messages::WindowClient::MouseWheel&) override;
+ virtual void handle(const Messages::WindowClient::WindowEntered&) override;
+ virtual void handle(const Messages::WindowClient::WindowLeft&) override;
+ virtual void handle(const Messages::WindowClient::KeyDown&) override;
+ virtual void handle(const Messages::WindowClient::KeyUp&) override;
+ virtual void handle(const Messages::WindowClient::WindowActivated&) override;
+ virtual void handle(const Messages::WindowClient::WindowDeactivated&) override;
+ virtual void handle(const Messages::WindowClient::WindowInputEntered&) override;
+ virtual void handle(const Messages::WindowClient::WindowInputLeft&) override;
+ virtual void handle(const Messages::WindowClient::WindowCloseRequest&) override;
+ virtual void handle(const Messages::WindowClient::WindowResized&) override;
+ virtual void handle(const Messages::WindowClient::MenuItemActivated&) override;
+ virtual void handle(const Messages::WindowClient::ScreenRectChanged&) override;
+ virtual void handle(const Messages::WindowClient::WM_WindowRemoved&) override;
+ virtual void handle(const Messages::WindowClient::WM_WindowStateChanged&) override;
+ virtual void handle(const Messages::WindowClient::WM_WindowIconBitmapChanged&) override;
+ virtual void handle(const Messages::WindowClient::WM_WindowRectChanged&) override;
+ virtual void handle(const Messages::WindowClient::AsyncSetWallpaperFinished&) override;
+ virtual void handle(const Messages::WindowClient::DragDropped&) override;
+ virtual void handle(const Messages::WindowClient::DragAccepted&) override;
+ virtual void handle(const Messages::WindowClient::DragCancelled&) override;
+ virtual void handle(const Messages::WindowClient::UpdateSystemTheme&) override;
+ virtual void handle(const Messages::WindowClient::WindowStateChanged&) override;
+ virtual void handle(const Messages::WindowClient::DisplayLinkNotification&) override;
+ virtual void handle(const Messages::WindowClient::Ping&) override;
+
+ bool m_display_link_notification_pending { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGUI/WindowType.h b/Userland/Libraries/LibGUI/WindowType.h
new file mode 100644
index 0000000000..b3b2638aba
--- /dev/null
+++ b/Userland/Libraries/LibGUI/WindowType.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace GUI {
+
+// Keep this in sync with WindowServer::WindowType.
+enum class WindowType {
+ Invalid = 0,
+ Normal,
+ Menu,
+ WindowSwitcher,
+ Taskbar,
+ Tooltip,
+ Menubar,
+ MenuApplet,
+ Notification,
+ Desktop,
+};
+
+}
diff --git a/Userland/Libraries/LibGemini/CMakeLists.txt b/Userland/Libraries/LibGemini/CMakeLists.txt
new file mode 100644
index 0000000000..dbd453fed2
--- /dev/null
+++ b/Userland/Libraries/LibGemini/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(SOURCES
+ Document.cpp
+ GeminiJob.cpp
+ GeminiRequest.cpp
+ GeminiResponse.cpp
+ Job.cpp
+ Line.cpp
+)
+
+serenity_lib(LibGemini gemini)
+target_link_libraries(LibGemini LibCore LibTLS)
diff --git a/Userland/Libraries/LibGemini/Document.cpp b/Userland/Libraries/LibGemini/Document.cpp
new file mode 100644
index 0000000000..fcf57d4d10
--- /dev/null
+++ b/Userland/Libraries/LibGemini/Document.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/NonnullRefPtr.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/Vector.h>
+#include <LibGemini/Document.h>
+
+namespace Gemini {
+
+String Document::render_to_html() const
+{
+ StringBuilder html_builder;
+ html_builder.append("<!DOCTYPE html>\n<html>\n");
+ html_builder.append("<head>\n<title>");
+ html_builder.append(m_url.path());
+ html_builder.append("</title>\n</head>\n");
+ html_builder.append("<body>\n");
+ for (auto& line : m_lines) {
+ html_builder.append(line.render_to_html());
+ }
+ html_builder.append("</body>");
+ html_builder.append("</html>");
+ return html_builder.build();
+}
+
+NonnullRefPtr<Document> Document::parse(const StringView& lines, const URL& url)
+{
+ auto document = adopt(*new Document(url));
+ document->read_lines(lines);
+ return document;
+}
+
+void Document::read_lines(const StringView& source)
+{
+ auto close_list_if_needed = [&] {
+ if (m_inside_unordered_list) {
+ m_inside_unordered_list = false;
+ m_lines.append(make<Control>(Control::UnorderedListEnd));
+ }
+ };
+
+ for (auto& line : source.lines()) {
+ if (line.starts_with("```")) {
+ close_list_if_needed();
+
+ m_inside_preformatted_block = !m_inside_preformatted_block;
+ if (m_inside_preformatted_block) {
+ m_lines.append(make<Control>(Control::PreformattedStart));
+ } else {
+ m_lines.append(make<Control>(Control::PreformattedEnd));
+ }
+ continue;
+ }
+
+ if (m_inside_preformatted_block) {
+ m_lines.append(make<Preformatted>(move(line)));
+ continue;
+ }
+
+ if (line.starts_with("*")) {
+ if (!m_inside_unordered_list)
+ m_lines.append(make<Control>(Control::UnorderedListStart));
+ m_lines.append(make<UnorderedList>(move(line)));
+ m_inside_unordered_list = true;
+ continue;
+ }
+
+ close_list_if_needed();
+
+ if (line.starts_with("=>")) {
+ m_lines.append(make<Link>(move(line), *this));
+ continue;
+ }
+
+ if (line.starts_with("#")) {
+ size_t level = 0;
+ while (line.length() > level && line[level] == '#')
+ ++level;
+
+ m_lines.append(make<Heading>(move(line), level));
+ continue;
+ }
+
+ m_lines.append(make<Text>(move(line)));
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibGemini/Document.h b/Userland/Libraries/LibGemini/Document.h
new file mode 100644
index 0000000000..4bebc94303
--- /dev/null
+++ b/Userland/Libraries/LibGemini/Document.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <AK/String.h>
+#include <AK/URL.h>
+#include <AK/Vector.h>
+
+namespace Gemini {
+
+class Line {
+public:
+ Line(String string)
+ : m_text(move(string))
+ {
+ }
+
+ virtual ~Line();
+
+ virtual String render_to_html() const = 0;
+
+protected:
+ String m_text;
+};
+
+class Document : public RefCounted<Document> {
+public:
+ String render_to_html() const;
+
+ static NonnullRefPtr<Document> parse(const StringView& source, const URL&);
+
+ const URL& url() const { return m_url; };
+
+private:
+ explicit Document(const URL& url)
+ : m_url(url)
+ {
+ }
+
+ void read_lines(const StringView&);
+
+ NonnullOwnPtrVector<Line> m_lines;
+ URL m_url;
+ bool m_inside_preformatted_block { false };
+ bool m_inside_unordered_list { false };
+};
+
+class Text : public Line {
+public:
+ Text(String line)
+ : Line(move(line))
+ {
+ }
+ virtual ~Text() override;
+ virtual String render_to_html() const override;
+};
+
+class Link : public Line {
+public:
+ Link(String line, const Document&);
+ virtual ~Link() override;
+ virtual String render_to_html() const override;
+
+private:
+ URL m_url;
+ String m_name;
+};
+
+class Preformatted : public Line {
+public:
+ Preformatted(String line)
+ : Line(move(line))
+ {
+ }
+ virtual ~Preformatted() override;
+ virtual String render_to_html() const override;
+};
+
+class UnorderedList : public Line {
+public:
+ UnorderedList(String line)
+ : Line(move(line))
+ {
+ }
+ virtual ~UnorderedList() override;
+ virtual String render_to_html() const override;
+};
+
+class Control : public Line {
+public:
+ enum Kind {
+ UnorderedListStart,
+ UnorderedListEnd,
+ PreformattedStart,
+ PreformattedEnd,
+ };
+ Control(Kind kind)
+ : Line("")
+ , m_kind(kind)
+ {
+ }
+ virtual ~Control() override;
+ virtual String render_to_html() const override;
+
+private:
+ Kind m_kind;
+};
+
+class Heading : public Line {
+public:
+ Heading(String line, int level)
+ : Line(move(line))
+ , m_level(level)
+ {
+ }
+ virtual ~Heading() override;
+ virtual String render_to_html() const override;
+
+private:
+ int m_level { 1 };
+};
+
+}
diff --git a/Userland/Libraries/LibGemini/Forward.h b/Userland/Libraries/LibGemini/Forward.h
new file mode 100644
index 0000000000..d0f838bb53
--- /dev/null
+++ b/Userland/Libraries/LibGemini/Forward.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace Gemini {
+
+class Document;
+class GeminiRequest;
+class GeminiResponse;
+class GeminiJob;
+class Job;
+
+}
diff --git a/Userland/Libraries/LibGemini/GeminiJob.cpp b/Userland/Libraries/LibGemini/GeminiJob.cpp
new file mode 100644
index 0000000000..afba44932d
--- /dev/null
+++ b/Userland/Libraries/LibGemini/GeminiJob.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/EventLoop.h>
+#include <LibGemini/GeminiJob.h>
+#include <LibGemini/GeminiResponse.h>
+#include <LibTLS/TLSv12.h>
+#include <stdio.h>
+#include <unistd.h>
+
+//#define GEMINIJOB_DEBUG
+
+namespace Gemini {
+
+void GeminiJob::start()
+{
+ ASSERT(!m_socket);
+ m_socket = TLS::TLSv12::construct(this);
+ m_socket->set_root_certificates(m_override_ca_certificates ? *m_override_ca_certificates : DefaultRootCACertificates::the().certificates());
+ m_socket->on_tls_connected = [this] {
+#ifdef GEMINIJOB_DEBUG
+ dbgln("GeminiJob: on_connected callback");
+#endif
+ on_socket_connected();
+ };
+ m_socket->on_tls_error = [this](TLS::AlertDescription error) {
+ if (error == TLS::AlertDescription::HandshakeFailure) {
+ deferred_invoke([this](auto&) {
+ return did_fail(Core::NetworkJob::Error::ProtocolFailed);
+ });
+ } else if (error == TLS::AlertDescription::DecryptError) {
+ deferred_invoke([this](auto&) {
+ return did_fail(Core::NetworkJob::Error::ConnectionFailed);
+ });
+ } else {
+ deferred_invoke([this](auto&) {
+ return did_fail(Core::NetworkJob::Error::TransmissionFailed);
+ });
+ }
+ };
+ m_socket->on_tls_finished = [this] {
+ finish_up();
+ };
+ m_socket->on_tls_certificate_request = [this](auto&) {
+ if (on_certificate_requested)
+ on_certificate_requested(*this);
+ };
+ bool success = ((TLS::TLSv12&)*m_socket).connect(m_request.url().host(), m_request.url().port());
+ if (!success) {
+ deferred_invoke([this](auto&) {
+ return did_fail(Core::NetworkJob::Error::ConnectionFailed);
+ });
+ }
+}
+
+void GeminiJob::shutdown()
+{
+ if (!m_socket)
+ return;
+ m_socket->on_tls_ready_to_read = nullptr;
+ m_socket->on_tls_connected = nullptr;
+ remove_child(*m_socket);
+ m_socket = nullptr;
+}
+
+void GeminiJob::read_while_data_available(Function<IterationDecision()> read)
+{
+ while (m_socket->can_read()) {
+ if (read() == IterationDecision::Break)
+ break;
+ }
+}
+
+void GeminiJob::set_certificate(String certificate, String private_key)
+{
+ if (!m_socket->add_client_key(certificate.bytes(), private_key.bytes())) {
+ dbgln("LibGemini: Failed to set a client certificate");
+ // FIXME: Do something about this failure
+ ASSERT_NOT_REACHED();
+ }
+}
+
+void GeminiJob::register_on_ready_to_read(Function<void()> callback)
+{
+ m_socket->on_tls_ready_to_read = [callback = move(callback)](auto&) {
+ callback();
+ };
+}
+
+void GeminiJob::register_on_ready_to_write(Function<void()> callback)
+{
+ m_socket->on_tls_ready_to_write = [callback = move(callback)](auto&) {
+ callback();
+ };
+}
+
+bool GeminiJob::can_read_line() const
+{
+ return m_socket->can_read_line();
+}
+
+String GeminiJob::read_line(size_t size)
+{
+ return m_socket->read_line(size);
+}
+
+ByteBuffer GeminiJob::receive(size_t size)
+{
+ return m_socket->read(size);
+}
+
+bool GeminiJob::can_read() const
+{
+ return m_socket->can_read();
+}
+
+bool GeminiJob::eof() const
+{
+ return m_socket->eof();
+}
+
+bool GeminiJob::write(ReadonlyBytes bytes)
+{
+ return m_socket->write(bytes);
+}
+
+}
diff --git a/Userland/Libraries/LibGemini/GeminiJob.h b/Userland/Libraries/LibGemini/GeminiJob.h
new file mode 100644
index 0000000000..531a6368d5
--- /dev/null
+++ b/Userland/Libraries/LibGemini/GeminiJob.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibCore/NetworkJob.h>
+#include <LibGemini/GeminiRequest.h>
+#include <LibGemini/GeminiResponse.h>
+#include <LibGemini/Job.h>
+#include <LibTLS/TLSv12.h>
+
+namespace Gemini {
+
+class GeminiJob final : public Job {
+ C_OBJECT(GeminiJob)
+public:
+ explicit GeminiJob(const GeminiRequest& request, OutputStream& output_stream, const Vector<Certificate>* override_certificates = nullptr)
+ : Job(request, output_stream)
+ , m_override_ca_certificates(override_certificates)
+ {
+ }
+
+ virtual ~GeminiJob() override
+ {
+ }
+
+ virtual void start() override;
+ virtual void shutdown() override;
+ void set_certificate(String certificate, String key);
+
+ Function<void(GeminiJob&)> on_certificate_requested;
+
+protected:
+ virtual void register_on_ready_to_read(Function<void()>) override;
+ virtual void register_on_ready_to_write(Function<void()>) override;
+ virtual bool can_read_line() const override;
+ virtual String read_line(size_t) override;
+ virtual bool can_read() const override;
+ virtual ByteBuffer receive(size_t) override;
+ virtual bool eof() const override;
+ virtual bool write(ReadonlyBytes) override;
+ virtual bool is_established() const override { return m_socket->is_established(); }
+ virtual bool should_fail_on_empty_payload() const override { return false; }
+ virtual void read_while_data_available(Function<IterationDecision()>) override;
+
+private:
+ RefPtr<TLS::TLSv12> m_socket;
+ const Vector<Certificate>* m_override_ca_certificates { nullptr };
+};
+
+}
diff --git a/Userland/Libraries/LibGemini/GeminiRequest.cpp b/Userland/Libraries/LibGemini/GeminiRequest.cpp
new file mode 100644
index 0000000000..94171bcc44
--- /dev/null
+++ b/Userland/Libraries/LibGemini/GeminiRequest.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <AK/URL.h>
+#include <LibGemini/GeminiJob.h>
+#include <LibGemini/GeminiRequest.h>
+
+namespace Gemini {
+
+GeminiRequest::GeminiRequest()
+{
+}
+
+GeminiRequest::~GeminiRequest()
+{
+}
+
+ByteBuffer GeminiRequest::to_raw_request() const
+{
+ StringBuilder builder;
+ builder.append(m_url.to_string());
+ builder.append("\r\n");
+ return builder.to_byte_buffer();
+}
+
+Optional<GeminiRequest> GeminiRequest::from_raw_request(const ByteBuffer& raw_request)
+{
+ URL url = StringView(raw_request);
+ if (!url.is_valid())
+ return {};
+ GeminiRequest request;
+ request.m_url = url;
+ return request;
+}
+
+}
diff --git a/Userland/Libraries/LibGemini/GeminiRequest.h b/Userland/Libraries/LibGemini/GeminiRequest.h
new file mode 100644
index 0000000000..7d25009e0b
--- /dev/null
+++ b/Userland/Libraries/LibGemini/GeminiRequest.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Optional.h>
+#include <AK/String.h>
+#include <AK/URL.h>
+#include <LibCore/Forward.h>
+
+namespace Gemini {
+
+class GeminiRequest {
+public:
+ GeminiRequest();
+ ~GeminiRequest();
+
+ const URL& url() const { return m_url; }
+ void set_url(const URL& url) { m_url = url; }
+
+ ByteBuffer to_raw_request() const;
+
+ static Optional<GeminiRequest> from_raw_request(const ByteBuffer&);
+
+private:
+ URL m_url;
+};
+
+}
diff --git a/Userland/Libraries/LibGemini/GeminiResponse.cpp b/Userland/Libraries/LibGemini/GeminiResponse.cpp
new file mode 100644
index 0000000000..e9ed7afd32
--- /dev/null
+++ b/Userland/Libraries/LibGemini/GeminiResponse.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGemini/GeminiResponse.h>
+
+namespace Gemini {
+
+GeminiResponse::GeminiResponse(int status, String meta)
+ : m_status(status)
+ , m_meta(meta)
+{
+}
+
+GeminiResponse::~GeminiResponse()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibGemini/GeminiResponse.h b/Userland/Libraries/LibGemini/GeminiResponse.h
new file mode 100644
index 0000000000..ec410af91b
--- /dev/null
+++ b/Userland/Libraries/LibGemini/GeminiResponse.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <LibCore/NetworkResponse.h>
+
+namespace Gemini {
+
+class GeminiResponse : public Core::NetworkResponse {
+public:
+ virtual ~GeminiResponse() override;
+ static NonnullRefPtr<GeminiResponse> create(int status, String meta)
+ {
+ return adopt(*new GeminiResponse(status, meta));
+ }
+
+ int status() const { return m_status; }
+ String meta() const { return m_meta; }
+
+private:
+ GeminiResponse(int status, String);
+
+ int m_status { 0 };
+ String m_meta;
+};
+
+}
diff --git a/Userland/Libraries/LibGemini/Job.cpp b/Userland/Libraries/LibGemini/Job.cpp
new file mode 100644
index 0000000000..c260951e70
--- /dev/null
+++ b/Userland/Libraries/LibGemini/Job.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGemini/GeminiResponse.h>
+#include <LibGemini/Job.h>
+#include <stdio.h>
+#include <unistd.h>
+
+//#define JOB_DEBUG
+
+namespace Gemini {
+
+Job::Job(const GeminiRequest& request, OutputStream& output_stream)
+ : Core::NetworkJob(output_stream)
+ , m_request(request)
+{
+}
+
+Job::~Job()
+{
+}
+
+void Job::flush_received_buffers()
+{
+ for (size_t i = 0; i < m_received_buffers.size(); ++i) {
+ auto& payload = m_received_buffers[i];
+ auto written = do_write(payload);
+ m_received_size -= written;
+ if (written == payload.size()) {
+ // FIXME: Make this a take-first-friendly object?
+ m_received_buffers.take_first();
+ continue;
+ }
+ ASSERT(written < payload.size());
+ payload = payload.slice(written, payload.size() - written);
+ return;
+ }
+}
+
+void Job::on_socket_connected()
+{
+ register_on_ready_to_write([this] {
+ if (m_sent_data)
+ return;
+ m_sent_data = true;
+ auto raw_request = m_request.to_raw_request();
+#ifdef JOB_DEBUG
+ dbgln("Job: raw_request:");
+ dbg() << String::copy(raw_request).characters();
+#endif
+ bool success = write(raw_request);
+ if (!success)
+ deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::TransmissionFailed); });
+ });
+ register_on_ready_to_read([this] {
+ if (is_cancelled())
+ return;
+
+ if (m_state == State::InStatus) {
+ if (!can_read_line())
+ return;
+
+ auto line = read_line(PAGE_SIZE);
+ if (line.is_null()) {
+ fprintf(stderr, "Job: Expected status line\n");
+ return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::TransmissionFailed); });
+ }
+
+ auto parts = line.split_limit(' ', 2);
+ if (parts.size() != 2) {
+ warnln("Job: Expected 2-part status line, got '{}'", line);
+ return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); });
+ }
+
+ auto status = parts[0].to_uint();
+ if (!status.has_value()) {
+ fprintf(stderr, "Job: Expected numeric status code\n");
+ return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); });
+ }
+
+ m_status = status.value();
+ m_meta = parts[1];
+
+ if (m_status >= 10 && m_status < 20) {
+ m_state = State::Finished;
+ } else if (m_status >= 20 && m_status < 30) {
+ m_state = State::InBody;
+ } else if (m_status >= 30 && m_status < 40) {
+ m_state = State::Finished;
+ } else if (m_status >= 40 && m_status < 50) {
+ m_state = State::Finished;
+ } else if (m_status >= 50 && m_status < 60) {
+ m_state = State::Finished;
+ } else if (m_status >= 60 && m_status < 70) {
+ m_state = State::InBody;
+ } else {
+ fprintf(stderr, "Job: Expected status between 10 and 69; instead got %d\n", m_status);
+ return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); });
+ }
+
+ return;
+ }
+
+ ASSERT(m_state == State::InBody || m_state == State::Finished);
+
+ read_while_data_available([&] {
+ auto read_size = 64 * KiB;
+
+ auto payload = receive(read_size);
+ if (!payload) {
+ if (eof()) {
+ finish_up();
+ return IterationDecision::Break;
+ }
+
+ if (should_fail_on_empty_payload()) {
+ deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); });
+ return IterationDecision::Break;
+ }
+ }
+
+ m_received_buffers.append(payload);
+ m_received_size += payload.size();
+ flush_received_buffers();
+
+ deferred_invoke([this](auto&) { did_progress({}, m_received_size); });
+
+ return IterationDecision::Continue;
+ });
+
+ if (!is_established()) {
+#ifdef JOB_DEBUG
+ dbgln("Connection appears to have closed, finishing up");
+#endif
+ finish_up();
+ }
+ });
+}
+
+void Job::finish_up()
+{
+ m_state = State::Finished;
+ flush_received_buffers();
+ if (m_received_size != 0) {
+ // We have to wait for the client to consume all the downloaded data
+ // before we can actually call `did_finish`. in a normal flow, this should
+ // never be hit since the client is reading as we are writing, unless there
+ // are too many concurrent downloads going on.
+ deferred_invoke([this](auto&) {
+ finish_up();
+ });
+ return;
+ }
+
+ auto response = GeminiResponse::create(m_status, m_meta);
+ deferred_invoke([this, response](auto&) {
+ did_finish(move(response));
+ });
+}
+}
diff --git a/Userland/Libraries/LibGemini/Job.h b/Userland/Libraries/LibGemini/Job.h
new file mode 100644
index 0000000000..b138078609
--- /dev/null
+++ b/Userland/Libraries/LibGemini/Job.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Optional.h>
+#include <LibCore/NetworkJob.h>
+#include <LibCore/TCPSocket.h>
+#include <LibGemini/GeminiRequest.h>
+#include <LibGemini/GeminiResponse.h>
+
+namespace Gemini {
+
+class Job : public Core::NetworkJob {
+public:
+ explicit Job(const GeminiRequest&, OutputStream&);
+ virtual ~Job() override;
+
+ virtual void start() override = 0;
+ virtual void shutdown() override = 0;
+
+ GeminiResponse* response() { return static_cast<GeminiResponse*>(Core::NetworkJob::response()); }
+ const GeminiResponse* response() const { return static_cast<const GeminiResponse*>(Core::NetworkJob::response()); }
+
+protected:
+ void finish_up();
+ void on_socket_connected();
+ void flush_received_buffers();
+ virtual void register_on_ready_to_read(Function<void()>) = 0;
+ virtual void register_on_ready_to_write(Function<void()>) = 0;
+ virtual bool can_read_line() const = 0;
+ virtual String read_line(size_t) = 0;
+ virtual bool can_read() const = 0;
+ virtual ByteBuffer receive(size_t) = 0;
+ virtual bool eof() const = 0;
+ virtual bool write(ReadonlyBytes) = 0;
+ virtual bool is_established() const = 0;
+ virtual bool should_fail_on_empty_payload() const { return false; }
+ virtual void read_while_data_available(Function<IterationDecision()> read) { read(); };
+
+ enum class State {
+ InStatus,
+ InBody,
+ Finished,
+ };
+
+ GeminiRequest m_request;
+ State m_state { State::InStatus };
+ int m_status { -1 };
+ String m_meta;
+ Vector<ByteBuffer, 2> m_received_buffers;
+ size_t m_received_size { 0 };
+ bool m_sent_data { false };
+ bool m_should_have_payload { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGemini/Line.cpp b/Userland/Libraries/LibGemini/Line.cpp
new file mode 100644
index 0000000000..d3197176d5
--- /dev/null
+++ b/Userland/Libraries/LibGemini/Line.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibGemini/Document.h>
+
+namespace Gemini {
+
+String Text::render_to_html() const
+{
+ StringBuilder builder;
+ builder.append(escape_html_entities(m_text));
+ builder.append("<br>\n");
+ return builder.build();
+}
+
+Text::~Text()
+{
+}
+
+String Heading::render_to_html() const
+{
+ StringBuilder builder;
+ builder.appendf("<h%d>", m_level);
+ builder.append(escape_html_entities(m_text.substring_view(m_level, m_text.length() - m_level)));
+ builder.appendf("</h%d>", m_level);
+ return builder.build();
+}
+Heading::~Heading()
+{
+}
+
+String UnorderedList::render_to_html() const
+{
+ // 1.3.5.4.2 "Advanced clients can take the space of the bullet symbol into account"
+ // FIXME: The spec is unclear about what the space means, or where it goes
+ // somehow figure this out
+ StringBuilder builder;
+ builder.append("<li>");
+ builder.append(escape_html_entities(m_text.substring_view(1, m_text.length() - 1)));
+ builder.append("</li>");
+ return builder.build();
+}
+UnorderedList::~UnorderedList()
+{
+}
+
+String Control::render_to_html() const
+{
+ switch (m_kind) {
+ case Kind::PreformattedEnd:
+ return "</pre>";
+ case Kind::PreformattedStart:
+ return "<pre>";
+ case Kind::UnorderedListStart:
+ return "<ul>";
+ case Kind::UnorderedListEnd:
+ return "</ul>";
+ default:
+ dbgln("Unknown control kind _{}_", (int)m_kind);
+ ASSERT_NOT_REACHED();
+ return "";
+ }
+}
+Control::~Control()
+{
+}
+
+Link::Link(String text, const Document& document)
+ : Line(move(text))
+{
+ size_t index = 2;
+ while (index < m_text.length() && (m_text[index] == ' ' || m_text[index] == '\t'))
+ ++index;
+ auto url_string = m_text.substring_view(index, m_text.length() - index);
+ auto space_offset = url_string.find_first_of(" \t");
+ String url = url_string;
+ if (space_offset.has_value()) {
+ url = url_string.substring_view(0, space_offset.value());
+ auto offset = space_offset.value();
+ while (offset < url_string.length() && (url_string[offset] == ' ' || url_string[offset] == '\t'))
+ ++offset;
+ m_name = url_string.substring_view(offset, url_string.length() - offset);
+ }
+ m_url = document.url().complete_url(url);
+ if (m_name.is_null())
+ m_name = m_url.to_string();
+}
+Link::~Link()
+{
+}
+
+String Link::render_to_html() const
+{
+ StringBuilder builder;
+ builder.append("<a href=\"");
+ builder.append(escape_html_entities(m_url.to_string()));
+ builder.append("\">");
+ builder.append(escape_html_entities(m_name));
+ builder.append("</a><br>\n");
+ return builder.build();
+}
+
+String Preformatted::render_to_html() const
+{
+ StringBuilder builder;
+ builder.append(escape_html_entities(m_text));
+ builder.append("\n");
+
+ return builder.build();
+}
+Preformatted::~Preformatted()
+{
+}
+
+Line::~Line()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/AffineTransform.cpp b/Userland/Libraries/LibGfx/AffineTransform.cpp
new file mode 100644
index 0000000000..29236b83f0
--- /dev/null
+++ b/Userland/Libraries/LibGfx/AffineTransform.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LogStream.h>
+#include <AK/Optional.h>
+#include <LibGfx/AffineTransform.h>
+#include <LibGfx/Rect.h>
+
+namespace Gfx {
+
+bool AffineTransform::is_identity() const
+{
+ return m_values[0] == 1 && m_values[1] == 0 && m_values[2] == 0 && m_values[3] == 1 && m_values[4] == 0 && m_values[5] == 0;
+}
+
+static float hypotenuse(float x, float y)
+{
+ // FIXME: This won't handle overflow :(
+ return sqrt(x * x + y * y);
+}
+
+float AffineTransform::x_scale() const
+{
+ return hypotenuse(m_values[0], m_values[1]);
+}
+
+float AffineTransform::y_scale() const
+{
+ return hypotenuse(m_values[2], m_values[3]);
+}
+
+AffineTransform& AffineTransform::scale(float sx, float sy)
+{
+ m_values[0] *= sx;
+ m_values[1] *= sx;
+ m_values[2] *= sy;
+ m_values[3] *= sy;
+ return *this;
+}
+
+AffineTransform& AffineTransform::translate(float tx, float ty)
+{
+ m_values[4] += tx * m_values[0] + ty * m_values[2];
+ m_values[5] += tx * m_values[1] + ty * m_values[3];
+ return *this;
+}
+
+AffineTransform& AffineTransform::multiply(const AffineTransform& other)
+{
+ AffineTransform result;
+ result.m_values[0] = other.a() * a() + other.b() * c();
+ result.m_values[1] = other.a() * b() + other.b() * d();
+ result.m_values[2] = other.c() * a() + other.d() * c();
+ result.m_values[3] = other.c() * b() + other.d() * d();
+ result.m_values[4] = other.e() * a() + other.f() * c() + e();
+ result.m_values[5] = other.e() * b() + other.f() * d() + f();
+ *this = result;
+ return *this;
+}
+
+AffineTransform& AffineTransform::rotate_radians(float radians)
+{
+ float sin_angle = sinf(radians);
+ float cos_angle = cosf(radians);
+ AffineTransform rotation(cos_angle, sin_angle, -sin_angle, cos_angle, 0, 0);
+ multiply(rotation);
+ return *this;
+}
+
+void AffineTransform::map(float unmapped_x, float unmapped_y, float& mapped_x, float& mapped_y) const
+{
+ mapped_x = (m_values[0] * unmapped_x + m_values[2] * unmapped_y + m_values[4]);
+ mapped_y = (m_values[1] * unmapped_x + m_values[3] * unmapped_y + m_values[5]);
+}
+
+template<>
+IntPoint AffineTransform::map(const IntPoint& point) const
+{
+ float mapped_x;
+ float mapped_y;
+ map(point.x(), point.y(), mapped_x, mapped_y);
+ return { roundf(mapped_x), roundf(mapped_y) };
+}
+
+template<>
+FloatPoint AffineTransform::map(const FloatPoint& point) const
+{
+ float mapped_x;
+ float mapped_y;
+ map(point.x(), point.y(), mapped_x, mapped_y);
+ return { mapped_x, mapped_y };
+}
+
+template<>
+IntSize AffineTransform::map(const IntSize& size) const
+{
+ return { roundf(size.width() * x_scale()), roundf(size.height() * y_scale()) };
+}
+
+template<>
+FloatSize AffineTransform::map(const FloatSize& size) const
+{
+ return { size.width() * x_scale(), size.height() * y_scale() };
+}
+
+template<typename T>
+static T smallest_of(T p1, T p2, T p3, T p4)
+{
+ return min(min(p1, p2), min(p3, p4));
+}
+
+template<typename T>
+static T largest_of(T p1, T p2, T p3, T p4)
+{
+ return max(max(p1, p2), max(p3, p4));
+}
+
+template<>
+FloatRect AffineTransform::map(const FloatRect& rect) const
+{
+ FloatPoint p1 = map(rect.top_left());
+ FloatPoint p2 = map(rect.top_right().translated(1, 0));
+ FloatPoint p3 = map(rect.bottom_right().translated(1, 1));
+ FloatPoint p4 = map(rect.bottom_left().translated(0, 1));
+ float left = smallest_of(p1.x(), p2.x(), p3.x(), p4.x());
+ float top = smallest_of(p1.y(), p2.y(), p3.y(), p4.y());
+ float right = largest_of(p1.x(), p2.x(), p3.x(), p4.x());
+ float bottom = largest_of(p1.y(), p2.y(), p3.y(), p4.y());
+ return { left, top, right - left, bottom - top };
+}
+
+template<>
+IntRect AffineTransform::map(const IntRect& rect) const
+{
+ return enclosing_int_rect(map(FloatRect(rect)));
+}
+
+const LogStream& operator<<(const LogStream& stream, const AffineTransform& value)
+{
+ if (value.is_identity())
+ return stream << "{ Identity }";
+
+ return stream << "{ "
+ << value.a() << ", "
+ << value.b() << ", "
+ << value.c() << ", "
+ << value.d() << ", "
+ << value.e() << ", "
+ << value.f() << " }";
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/AffineTransform.h b/Userland/Libraries/LibGfx/AffineTransform.h
new file mode 100644
index 0000000000..41334eb138
--- /dev/null
+++ b/Userland/Libraries/LibGfx/AffineTransform.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/LogStream.h>
+#include <LibGfx/Forward.h>
+
+namespace Gfx {
+
+class AffineTransform {
+public:
+ AffineTransform()
+ : m_values { 1, 0, 0, 1, 0, 0 }
+ {
+ }
+
+ AffineTransform(float a, float b, float c, float d, float e, float f)
+ : m_values { a, b, c, d, e, f }
+ {
+ }
+
+ bool is_identity() const;
+
+ void map(float unmapped_x, float unmapped_y, float& mapped_x, float& mapped_y) const;
+
+ template<typename T>
+ Point<T> map(const Point<T>&) const;
+
+ template<typename T>
+ Size<T> map(const Size<T>&) const;
+
+ template<typename T>
+ Rect<T> map(const Rect<T>&) const;
+
+ float a() const { return m_values[0]; }
+ float b() const { return m_values[1]; }
+ float c() const { return m_values[2]; }
+ float d() const { return m_values[3]; }
+ float e() const { return m_values[4]; }
+ float f() const { return m_values[5]; }
+
+ float x_scale() const;
+ float y_scale() const;
+
+ AffineTransform& scale(float sx, float sy);
+ AffineTransform& translate(float tx, float ty);
+ AffineTransform& rotate_radians(float);
+ AffineTransform& multiply(const AffineTransform&);
+
+private:
+ float m_values[6] { 0 };
+};
+
+const LogStream& operator<<(const LogStream&, const AffineTransform&);
+
+}
diff --git a/Userland/Libraries/LibGfx/BMPLoader.cpp b/Userland/Libraries/LibGfx/BMPLoader.cpp
new file mode 100644
index 0000000000..2b178f0257
--- /dev/null
+++ b/Userland/Libraries/LibGfx/BMPLoader.cpp
@@ -0,0 +1,1400 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <LibGfx/BMPLoader.h>
+
+#ifndef BMP_DEBUG
+# define BMP_DEBUG 0
+#endif
+
+#define IF_BMP_DEBUG(x) \
+ if (BMP_DEBUG) \
+ x
+
+namespace Gfx {
+
+const u8 bmp_header_size = 14;
+const u32 color_palette_limit = 1024;
+
+// Compression flags
+struct Compression {
+ enum : u32 {
+ RGB = 0,
+ RLE8,
+ RLE4,
+ BITFIELDS,
+ RLE24, // doubles as JPEG for V4+, but that is unsupported
+ PNG,
+ ALPHABITFIELDS,
+ CMYK = 11,
+ CMYKRLE8,
+ CMYKRLE4,
+ };
+};
+
+struct DIBCore {
+ // u16 for BITMAPHEADERCORE, but i32 for everything else. If the dib type is
+ // BITMAPHEADERCORE, this is range checked.
+ i32 width;
+ i32 height;
+ u16 bpp;
+};
+
+struct DIBInfo {
+ u32 compression { Compression::RGB };
+ u32 image_size { 0 };
+ i32 horizontal_resolution { 0 };
+ i32 vertical_resolution { 0 };
+ u32 number_of_palette_colors { 0 };
+ u32 number_of_important_palette_colors { number_of_palette_colors };
+
+ // Introduced in the BITMAPV2INFOHEADER and would ideally be stored in the
+ // DIBV2 struct, however with a compression value of BI_BITFIELDS or
+ // BI_ALPHABITFIELDS, these can be specified with the Info header.
+ Vector<u32> masks;
+ Vector<i8> mask_shifts;
+ Vector<u8> mask_sizes;
+};
+
+struct DIBOSV2 {
+ u16 recording;
+ u16 halftoning;
+ u16 size1;
+ u16 size2;
+};
+
+template<typename T>
+struct Endpoint {
+ T x;
+ T y;
+ T z;
+};
+
+struct DIBV4 {
+ u32 color_space { 0 };
+ Endpoint<i32> red_endpoint { 0, 0, 0 };
+ Endpoint<i32> green_endpoint { 0, 0, 0 };
+ Endpoint<i32> blue_endpoint { 0, 0, 0 };
+ Endpoint<u32> gamma_endpoint { 0, 0, 0 };
+};
+
+struct DIBV5 {
+ u32 intent { 0 };
+ u32 profile_data { 0 };
+ u32 profile_size { 0 };
+};
+
+struct DIB {
+ DIBCore core;
+ DIBInfo info;
+ DIBOSV2 osv2;
+ DIBV4 v4;
+ DIBV5 v5;
+};
+
+enum class DIBType {
+ Core = 0,
+ OSV2Short,
+ OSV2,
+ Info,
+ V2,
+ V3,
+ V4,
+ V5
+};
+
+struct BMPLoadingContext {
+ enum class State {
+ NotDecoded = 0,
+ HeaderDecoded,
+ DIBDecoded,
+ ColorTableDecoded,
+ PixelDataDecoded,
+ Error,
+ };
+ State state { State::NotDecoded };
+
+ const u8* file_bytes { nullptr };
+ size_t file_size { 0 };
+ u32 data_offset { 0 };
+
+ DIB dib;
+ DIBType dib_type;
+
+ Vector<u32> color_table;
+ RefPtr<Gfx::Bitmap> bitmap;
+
+ u32 dib_size() const
+ {
+ switch (dib_type) {
+ case DIBType::Core:
+ return 12;
+ case DIBType::OSV2Short:
+ return 16;
+ case DIBType::OSV2:
+ return 64;
+ case DIBType::Info:
+ return 40;
+ case DIBType::V2:
+ return 52;
+ case DIBType::V3:
+ return 56;
+ case DIBType::V4:
+ return 108;
+ case DIBType::V5:
+ return 124;
+ }
+
+ ASSERT_NOT_REACHED();
+ }
+};
+
+static RefPtr<Bitmap> load_bmp_impl(const u8*, size_t);
+
+RefPtr<Gfx::Bitmap> load_bmp(const StringView& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return nullptr;
+ auto bitmap = load_bmp_impl((const u8*)file_or_error.value()->data(), file_or_error.value()->size());
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded BMP: {}", bitmap->size(), LexicalPath::canonicalized_path(path)));
+ return bitmap;
+}
+
+RefPtr<Gfx::Bitmap> load_bmp_from_memory(const u8* data, size_t length)
+{
+ auto bitmap = load_bmp_impl(data, length);
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded BMP: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+static const LogStream& operator<<(const LogStream& out, Endpoint<i32> ep)
+{
+ return out << "(" << ep.x << ", " << ep.y << ", " << ep.z << ")";
+}
+
+static const LogStream& operator<<(const LogStream& out, Endpoint<u32> ep)
+{
+ return out << "(" << ep.x << ", " << ep.y << ", " << ep.z << ")";
+}
+
+class Streamer {
+public:
+ Streamer(const u8* data, size_t size)
+ : m_data_ptr(data)
+ , m_size_remaining(size)
+ {
+ }
+
+ u8 read_u8()
+ {
+ ASSERT(m_size_remaining >= 1);
+ m_size_remaining--;
+ return *(m_data_ptr++);
+ }
+
+ u16 read_u16()
+ {
+ return read_u8() | (read_u8() << 8);
+ }
+
+ u32 read_u24()
+ {
+ return read_u8() | (read_u8() << 8) | (read_u8() << 16);
+ }
+
+ i32 read_i32()
+ {
+ return static_cast<i32>(read_u16() | (read_u16() << 16));
+ }
+
+ u32 read_u32()
+ {
+ return read_u16() | (read_u16() << 16);
+ }
+
+ void drop_bytes(u8 num_bytes)
+ {
+ ASSERT(m_size_remaining >= num_bytes);
+ m_size_remaining -= num_bytes;
+ m_data_ptr += num_bytes;
+ }
+
+ bool at_end() const { return !m_size_remaining; }
+
+ bool has_u8() const { return m_size_remaining >= 1; }
+ bool has_u16() const { return m_size_remaining >= 2; }
+ bool has_u24() const { return m_size_remaining >= 3; }
+ bool has_u32() const { return m_size_remaining >= 4; }
+
+ size_t remaining() const { return m_size_remaining; }
+
+private:
+ const u8* m_data_ptr { nullptr };
+ size_t m_size_remaining { 0 };
+};
+
+// Lookup table for distributing all possible 2-bit numbers evenly into 8-bit numbers
+static u8 scaling_factors_2bit[4] = {
+ 0x00,
+ 0x55,
+ 0xaa,
+ 0xff,
+};
+
+// Lookup table for distributing all possible 3-bit numbers evenly into 8-bit numbers
+static u8 scaling_factors_3bit[8] = {
+ 0x00,
+ 0x24,
+ 0x48,
+ 0x6d,
+ 0x91,
+ 0xb6,
+ 0xdb,
+ 0xff,
+};
+
+static u8 scale_masked_8bit_number(u8 number, u8 bits_set)
+{
+ // If there are more than 4 bit set, an easy way to scale the number is to
+ // just copy the most significant bits into the least significant bits
+ if (bits_set >= 4)
+ return number | (number >> bits_set);
+ if (!bits_set)
+ return 0;
+ if (bits_set == 1)
+ return number ? 0xff : 0;
+ if (bits_set == 2)
+ return scaling_factors_2bit[number >> 6];
+ return scaling_factors_3bit[number >> 5];
+}
+
+static u8 get_scaled_color(u32 data, u8 mask_size, i8 mask_shift)
+{
+ // A negative mask_shift indicates we actually need to left shift
+ // the result in order to get out a valid 8-bit color (for example, the blue
+ // value in an RGB555 encoding is XXXBBBBB, which needs to be shifted to the
+ // left by 3, hence it would have a "mask_shift" value of -3).
+ if (mask_shift < 0)
+ return scale_masked_8bit_number(data << -mask_shift, mask_size);
+ return scale_masked_8bit_number(data >> mask_shift, mask_size);
+}
+
+// Scales an 8-bit number with "mask_size" bits set (and "8 - mask_size" bits
+// ignored). This function scales the number appropriately over the entire
+// 256 value color spectrum.
+// Note that a much simpler scaling can be done by simple bit shifting. If you
+// just ignore the bottom 8-mask_size bits, then you get *close*. However,
+// consider, as an example, a 5 bit number (so the bottom 3 bits are ignored).
+// The purest white you could get is 0xf8, which is 248 in RGB-land. We need
+// to scale the values in order to reach the proper value of 255.
+static u32 int_to_scaled_rgb(BMPLoadingContext& context, u32 data)
+{
+ IF_BMP_DEBUG(dbg() << "DIB info sizes before access: #masks=" << context.dib.info.masks.size() << ", #mask_sizes=" << context.dib.info.mask_sizes.size() << ", #mask_shifts=" << context.dib.info.mask_shifts.size());
+
+ u8 r = get_scaled_color(data & context.dib.info.masks[0], context.dib.info.mask_sizes[0], context.dib.info.mask_shifts[0]);
+ u8 g = get_scaled_color(data & context.dib.info.masks[1], context.dib.info.mask_sizes[1], context.dib.info.mask_shifts[1]);
+ u8 b = get_scaled_color(data & context.dib.info.masks[2], context.dib.info.mask_sizes[2], context.dib.info.mask_shifts[2]);
+ u32 color = (r << 16) | (g << 8) | b;
+
+ if (context.dib.info.masks.size() == 4) {
+ // The bitmap has an alpha mask
+ u8 a = get_scaled_color(data & context.dib.info.masks[3], context.dib.info.mask_sizes[3], context.dib.info.mask_shifts[3]);
+ color |= (a << 24);
+ } else {
+ color |= 0xff000000;
+ }
+
+ return color;
+}
+
+static void populate_dib_mask_info_if_needed(BMPLoadingContext& context)
+{
+ if (context.dib.info.masks.is_empty())
+ return;
+
+ // Mask shift is the number of right shifts needed to align the MSb of the
+ // mask to the MSb of the LSB. Note that this can be a negative number.
+ // Mask size is the number of set bits in the mask. This is required for
+ // color scaling (for example, ensuring that a 4-bit color value spans the
+ // entire 256 value color spectrum.
+ auto& masks = context.dib.info.masks;
+ auto& mask_shifts = context.dib.info.mask_shifts;
+ auto& mask_sizes = context.dib.info.mask_sizes;
+
+ if (!mask_shifts.is_empty() && !mask_sizes.is_empty())
+ return;
+
+ ASSERT(mask_shifts.is_empty() && mask_sizes.is_empty());
+
+ mask_shifts.ensure_capacity(masks.size());
+ mask_sizes.ensure_capacity(masks.size());
+
+ for (size_t i = 0; i < masks.size(); ++i) {
+ u32 mask = masks[i];
+ if (!mask) {
+ mask_shifts.append(0);
+ mask_sizes.append(0);
+ continue;
+ }
+ int trailing_zeros = count_trailing_zeroes_32(mask);
+ int size = count_trailing_zeroes_32(~(mask >> trailing_zeros));
+ mask_shifts.append(trailing_zeros - 8);
+ mask_sizes.append(size);
+ }
+}
+
+static bool check_for_invalid_bitmask_combinations(BMPLoadingContext& context)
+{
+ auto& bpp = context.dib.core.bpp;
+ auto& compression = context.dib.info.compression;
+
+ if (compression == Compression::ALPHABITFIELDS && context.dib_type != DIBType::Info)
+ return false;
+
+ switch (context.dib_type) {
+ case DIBType::Core:
+ if (bpp == 2 || bpp == 16 || bpp == 32)
+ return false;
+ break;
+ case DIBType::Info:
+ switch (compression) {
+ case Compression::BITFIELDS:
+ case Compression::ALPHABITFIELDS:
+ if (bpp != 16 && bpp != 32)
+ return false;
+ break;
+ case Compression::RGB:
+ break;
+ case Compression::RLE8:
+ if (bpp > 8)
+ return false;
+ break;
+ case Compression::RLE4:
+ // TODO: This is a guess
+ if (bpp > 4)
+ return false;
+ break;
+ default:
+ // Other compressions are not officially supported.
+ // Technically, we could even drop ALPHABITFIELDS.
+ return false;
+ }
+ break;
+ case DIBType::OSV2Short:
+ case DIBType::OSV2:
+ case DIBType::V2:
+ case DIBType::V3:
+ case DIBType::V4:
+ case DIBType::V5:
+ if (compression == Compression::BITFIELDS && bpp != 16 && bpp != 32)
+ return false;
+ break;
+ }
+
+ return true;
+}
+
+static bool set_dib_bitmasks(BMPLoadingContext& context, Streamer& streamer)
+{
+ if (!check_for_invalid_bitmask_combinations(context))
+ return false;
+
+ auto& bpp = context.dib.core.bpp;
+ if (bpp <= 8 || bpp == 24)
+ return true;
+
+ auto& compression = context.dib.info.compression;
+ auto& type = context.dib_type;
+
+ if (type > DIBType::OSV2 && bpp == 16 && compression == Compression::RGB) {
+ context.dib.info.masks.append({ 0x7c00, 0x03e0, 0x001f });
+ context.dib.info.mask_shifts.append({ 7, 2, -3 });
+ context.dib.info.mask_sizes.append({ 5, 5, 5 });
+ } else if (type == DIBType::Info && (compression == Compression::BITFIELDS || compression == Compression::ALPHABITFIELDS)) {
+ // Consume the extra BITFIELDS bytes
+ auto number_of_mask_fields = compression == Compression::ALPHABITFIELDS ? 4 : 3;
+
+ for (auto i = 0; i < number_of_mask_fields; i++) {
+ if (!streamer.has_u32())
+ return false;
+ context.dib.info.masks.append(streamer.read_u32());
+ }
+ }
+
+ populate_dib_mask_info_if_needed(context);
+ return true;
+}
+
+static bool decode_bmp_header(BMPLoadingContext& context)
+{
+ if (context.state == BMPLoadingContext::State::Error)
+ return false;
+
+ if (context.state >= BMPLoadingContext::State::HeaderDecoded)
+ return true;
+
+ if (!context.file_bytes || context.file_size < bmp_header_size) {
+ IF_BMP_DEBUG(dbg() << "Missing BMP header");
+ context.state = BMPLoadingContext::State::Error;
+ return false;
+ }
+
+ Streamer streamer(context.file_bytes, bmp_header_size);
+
+ u16 header = streamer.read_u16();
+ if (header != 0x4d42) {
+ IF_BMP_DEBUG(dbgprintf("BMP has invalid magic header number: %04x\n", header));
+ context.state = BMPLoadingContext::State::Error;
+ return false;
+ }
+
+ // The reported size of the file in the header is actually not important
+ // for decoding the file. Some specifications say that this value should
+ // be the size of the header instead, so we just rely on the known file
+ // size, instead of a possibly-correct-but-also-possibly-incorrect reported
+ // value of the file size.
+ streamer.drop_bytes(4);
+
+ // Ignore reserved bytes
+ streamer.drop_bytes(4);
+ context.data_offset = streamer.read_u32();
+
+ IF_BMP_DEBUG(dbg() << "BMP file size: " << context.file_size);
+ IF_BMP_DEBUG(dbg() << "BMP data offset: " << context.data_offset);
+
+ if (context.data_offset >= context.file_size) {
+ IF_BMP_DEBUG(dbg() << "BMP data offset is beyond file end?!");
+ return false;
+ }
+
+ context.state = BMPLoadingContext::State::HeaderDecoded;
+ return true;
+}
+
+static bool decode_bmp_core_dib(BMPLoadingContext& context, Streamer& streamer)
+{
+ auto& core = context.dib.core;
+
+ // The width and height are u16 fields in the actual BITMAPCOREHEADER format.
+ if (context.dib_type == DIBType::Core) {
+ core.width = streamer.read_u16();
+ core.height = streamer.read_u16();
+ } else {
+ core.width = streamer.read_i32();
+ core.height = streamer.read_i32();
+ }
+
+ if (core.width < 0) {
+ IF_BMP_DEBUG(dbg() << "BMP has a negative width: " << core.width);
+ return false;
+ }
+
+ if (static_cast<size_t>(core.width) > maximum_width_for_decoded_images || static_cast<size_t>(abs(core.height)) > maximum_height_for_decoded_images) {
+ dbgln("This BMP is too large for comfort: {}x{}", core.width, abs(core.height));
+ return false;
+ }
+
+ auto color_planes = streamer.read_u16();
+ if (color_planes != 1) {
+ IF_BMP_DEBUG(dbg() << "BMP has an invalid number of color planes: " << color_planes);
+ return false;
+ }
+
+ core.bpp = streamer.read_u16();
+ switch (core.bpp) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ case 16:
+ case 24:
+ case 32:
+ break;
+ default:
+ IF_BMP_DEBUG(dbg() << "BMP has an invalid bpp: " << core.bpp);
+ context.state = BMPLoadingContext::State::Error;
+ return false;
+ }
+
+ IF_BMP_DEBUG(dbg() << "BMP width: " << core.width);
+ IF_BMP_DEBUG(dbg() << "BMP height: " << core.height);
+ IF_BMP_DEBUG(dbg() << "BMP bits_per_pixel: " << core.bpp);
+
+ return true;
+}
+
+ALWAYS_INLINE static bool is_supported_compression_format(BMPLoadingContext& context, u32 compression)
+{
+ return compression == Compression::RGB || compression == Compression::BITFIELDS
+ || compression == Compression::ALPHABITFIELDS || compression == Compression::RLE8
+ || compression == Compression::RLE4 || (compression == Compression::RLE24 && context.dib_type <= DIBType::OSV2);
+}
+
+static bool decode_bmp_osv2_dib(BMPLoadingContext& context, Streamer& streamer, bool short_variant = false)
+{
+ auto& core = context.dib.core;
+
+ core.width = streamer.read_u32();
+ core.height = streamer.read_u32();
+
+ if (core.width < 0) {
+ IF_BMP_DEBUG(dbg() << "BMP has a negative width: " << core.width);
+ return false;
+ }
+
+ auto color_planes = streamer.read_u16();
+ if (color_planes != 1) {
+ IF_BMP_DEBUG(dbg() << "BMP has an invalid number of color planes: " << color_planes);
+ return false;
+ }
+
+ core.bpp = streamer.read_u16();
+ switch (core.bpp) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ case 24:
+ break;
+ default:
+ // OS/2 didn't expect 16- or 32-bpp to be popular.
+ IF_BMP_DEBUG(dbg() << "BMP has an invalid bpp: " << core.bpp);
+ context.state = BMPLoadingContext::State::Error;
+ return false;
+ }
+
+ IF_BMP_DEBUG(dbg() << "BMP width: " << core.width);
+ IF_BMP_DEBUG(dbg() << "BMP height: " << core.height);
+ IF_BMP_DEBUG(dbg() << "BMP bpp: " << core.bpp);
+
+ if (short_variant)
+ return true;
+
+ auto& info = context.dib.info;
+ auto& osv2 = context.dib.osv2;
+
+ info.compression = streamer.read_u32();
+ info.image_size = streamer.read_u32();
+ info.horizontal_resolution = streamer.read_u32();
+ info.vertical_resolution = streamer.read_u32();
+ info.number_of_palette_colors = streamer.read_u32();
+ info.number_of_important_palette_colors = streamer.read_u32();
+
+ if (!is_supported_compression_format(context, info.compression)) {
+ IF_BMP_DEBUG(dbg() << "BMP has unsupported compression value: " << info.compression);
+ return false;
+ }
+
+ if (info.number_of_palette_colors > color_palette_limit || info.number_of_important_palette_colors > color_palette_limit) {
+ IF_BMP_DEBUG(dbg() << "BMP header indicates too many palette colors: " << info.number_of_palette_colors);
+ return false;
+ }
+
+ // Units (2) + reserved (2)
+ streamer.drop_bytes(4);
+
+ osv2.recording = streamer.read_u16();
+ osv2.halftoning = streamer.read_u16();
+ osv2.size1 = streamer.read_u32();
+ osv2.size2 = streamer.read_u32();
+
+ // ColorEncoding (4) + Identifier (4)
+ streamer.drop_bytes(8);
+
+ IF_BMP_DEBUG(dbg() << "BMP compression: " << info.compression);
+ IF_BMP_DEBUG(dbg() << "BMP image size: " << info.image_size);
+ IF_BMP_DEBUG(dbg() << "BMP horizontal res: " << info.horizontal_resolution);
+ IF_BMP_DEBUG(dbg() << "BMP vertical res: " << info.vertical_resolution);
+ IF_BMP_DEBUG(dbg() << "BMP colors: " << info.number_of_palette_colors);
+ IF_BMP_DEBUG(dbg() << "BMP important colors: " << info.number_of_important_palette_colors);
+
+ return true;
+}
+
+static bool decode_bmp_info_dib(BMPLoadingContext& context, Streamer& streamer)
+{
+ if (!decode_bmp_core_dib(context, streamer))
+ return false;
+
+ auto& info = context.dib.info;
+
+ auto compression = streamer.read_u32();
+ info.compression = compression;
+ if (!is_supported_compression_format(context, compression)) {
+ IF_BMP_DEBUG(dbg() << "BMP has unsupported compression value: " << compression);
+ return false;
+ }
+
+ info.image_size = streamer.read_u32();
+ info.horizontal_resolution = streamer.read_i32();
+ info.vertical_resolution = streamer.read_i32();
+ info.number_of_palette_colors = streamer.read_u32();
+ info.number_of_important_palette_colors = streamer.read_u32();
+
+ if (info.number_of_palette_colors > color_palette_limit || info.number_of_important_palette_colors > color_palette_limit) {
+ IF_BMP_DEBUG(dbg() << "BMP header indicates too many palette colors: " << info.number_of_palette_colors);
+ return false;
+ }
+
+ if (info.number_of_important_palette_colors == 0)
+ info.number_of_important_palette_colors = info.number_of_palette_colors;
+
+ IF_BMP_DEBUG(dbg() << "BMP compression: " << info.compression);
+ IF_BMP_DEBUG(dbg() << "BMP image size: " << info.image_size);
+ IF_BMP_DEBUG(dbg() << "BMP horizontal resolution: " << info.horizontal_resolution);
+ IF_BMP_DEBUG(dbg() << "BMP vertical resolution: " << info.vertical_resolution);
+ IF_BMP_DEBUG(dbg() << "BMP palette colors: " << info.number_of_palette_colors);
+ IF_BMP_DEBUG(dbg() << "BMP important palette colors: " << info.number_of_important_palette_colors);
+
+ return true;
+}
+
+static bool decode_bmp_v2_dib(BMPLoadingContext& context, Streamer& streamer)
+{
+ if (!decode_bmp_info_dib(context, streamer))
+ return false;
+
+ context.dib.info.masks.append(streamer.read_u32());
+ context.dib.info.masks.append(streamer.read_u32());
+ context.dib.info.masks.append(streamer.read_u32());
+
+ IF_BMP_DEBUG(dbgprintf("BMP red mask: %08x\n", context.dib.info.masks[0]));
+ IF_BMP_DEBUG(dbgprintf("BMP green mask: %08x\n", context.dib.info.masks[1]));
+ IF_BMP_DEBUG(dbgprintf("BMP blue mask: %08x\n", context.dib.info.masks[2]));
+
+ return true;
+}
+
+static bool decode_bmp_v3_dib(BMPLoadingContext& context, Streamer& streamer)
+{
+ if (!decode_bmp_v2_dib(context, streamer))
+ return false;
+
+ // There is zero documentation about when alpha masks actually get applied.
+ // Well, there's some, but it's not even close to comprehensive. So, this is
+ // in no way based off of any spec, it's simply based off of the BMP test
+ // suite results.
+ if (context.dib.info.compression == Compression::ALPHABITFIELDS) {
+ context.dib.info.masks.append(streamer.read_u32());
+ IF_BMP_DEBUG(dbgprintf("BMP alpha mask: %08x\n", context.dib.info.masks[3]));
+ } else if (context.dib_size() >= 56 && context.dib.core.bpp >= 16) {
+ auto mask = streamer.read_u32();
+ if ((context.dib.core.bpp == 32 && mask != 0) || context.dib.core.bpp == 16) {
+ context.dib.info.masks.append(mask);
+ IF_BMP_DEBUG(dbgprintf("BMP alpha mask: %08x\n", mask));
+ }
+ } else {
+ streamer.drop_bytes(4);
+ }
+
+ return true;
+}
+
+static bool decode_bmp_v4_dib(BMPLoadingContext& context, Streamer& streamer)
+{
+ if (!decode_bmp_v3_dib(context, streamer))
+ return false;
+
+ auto& v4 = context.dib.v4;
+ v4.color_space = streamer.read_u32();
+ v4.red_endpoint = { streamer.read_i32(), streamer.read_i32(), streamer.read_i32() };
+ v4.green_endpoint = { streamer.read_i32(), streamer.read_i32(), streamer.read_i32() };
+ v4.blue_endpoint = { streamer.read_i32(), streamer.read_i32(), streamer.read_i32() };
+ v4.gamma_endpoint = { streamer.read_u32(), streamer.read_u32(), streamer.read_u32() };
+
+ IF_BMP_DEBUG(dbg() << "BMP color space: " << v4.color_space);
+ IF_BMP_DEBUG(dbg() << "BMP red endpoint: " << v4.red_endpoint);
+ IF_BMP_DEBUG(dbg() << "BMP green endpoint: " << v4.green_endpoint);
+ IF_BMP_DEBUG(dbg() << "BMP blue endpoint: " << v4.blue_endpoint);
+ IF_BMP_DEBUG(dbg() << "BMP gamma endpoint: " << v4.gamma_endpoint);
+
+ return true;
+}
+
+static bool decode_bmp_v5_dib(BMPLoadingContext& context, Streamer& streamer)
+{
+ if (!decode_bmp_v4_dib(context, streamer))
+ return false;
+
+ auto& v5 = context.dib.v5;
+ v5.intent = streamer.read_u32();
+ v5.profile_data = streamer.read_u32();
+ v5.profile_size = streamer.read_u32();
+
+ IF_BMP_DEBUG(dbg() << "BMP intent: " << v5.intent);
+ IF_BMP_DEBUG(dbg() << "BMP profile data: " << v5.profile_data);
+ IF_BMP_DEBUG(dbg() << "BMP profile size: " << v5.profile_size);
+
+ return true;
+}
+
+static bool decode_bmp_dib(BMPLoadingContext& context)
+{
+ if (context.state == BMPLoadingContext::State::Error)
+ return false;
+
+ if (context.state >= BMPLoadingContext::State::DIBDecoded)
+ return true;
+
+ if (context.state < BMPLoadingContext::State::HeaderDecoded && !decode_bmp_header(context))
+ return false;
+
+ if (context.file_size < bmp_header_size + 4)
+ return false;
+
+ Streamer streamer(context.file_bytes + bmp_header_size, 4);
+ u32 dib_size = streamer.read_u32();
+
+ if (context.file_size < bmp_header_size + dib_size)
+ return false;
+ if (context.data_offset < bmp_header_size + dib_size) {
+ IF_BMP_DEBUG(dbg() << "Shenanigans! BMP pixel data and header usually don't overlap.");
+ return false;
+ }
+
+ streamer = Streamer(context.file_bytes + bmp_header_size + 4, context.data_offset - bmp_header_size - 4);
+
+ IF_BMP_DEBUG(dbg() << "BMP dib size: " << dib_size);
+
+ bool error = false;
+
+ if (dib_size == 12) {
+ context.dib_type = DIBType::Core;
+ if (!decode_bmp_core_dib(context, streamer))
+ error = true;
+ } else if (dib_size == 64) {
+ context.dib_type = DIBType::OSV2;
+ if (!decode_bmp_osv2_dib(context, streamer))
+ error = true;
+ } else if (dib_size == 16) {
+ context.dib_type = DIBType::OSV2Short;
+ if (!decode_bmp_osv2_dib(context, streamer, true))
+ error = true;
+ } else if (dib_size == 40) {
+ context.dib_type = DIBType::Info;
+ if (!decode_bmp_info_dib(context, streamer))
+ error = true;
+ } else if (dib_size == 52) {
+ context.dib_type = DIBType::V2;
+ if (!decode_bmp_v2_dib(context, streamer))
+ error = true;
+ } else if (dib_size == 56) {
+ context.dib_type = DIBType::V3;
+ if (!decode_bmp_v3_dib(context, streamer))
+ error = true;
+ } else if (dib_size == 108) {
+ context.dib_type = DIBType::V4;
+ if (!decode_bmp_v4_dib(context, streamer))
+ error = true;
+ } else if (dib_size == 124) {
+ context.dib_type = DIBType::V5;
+ if (!decode_bmp_v5_dib(context, streamer))
+ error = true;
+ } else {
+ IF_BMP_DEBUG(dbg() << "Unsupported BMP DIB size: " << dib_size);
+ error = true;
+ }
+
+ switch (context.dib.info.compression) {
+ case Compression::RGB:
+ case Compression::RLE8:
+ case Compression::RLE4:
+ case Compression::BITFIELDS:
+ case Compression::RLE24:
+ case Compression::PNG:
+ case Compression::ALPHABITFIELDS:
+ case Compression::CMYK:
+ case Compression::CMYKRLE8:
+ case Compression::CMYKRLE4:
+ break;
+ default:
+ error = true;
+ }
+
+ if (!error && !set_dib_bitmasks(context, streamer))
+ error = true;
+
+ if (error) {
+ IF_BMP_DEBUG(dbg() << "BMP has an invalid DIB");
+ context.state = BMPLoadingContext::State::Error;
+ return false;
+ }
+
+ context.state = BMPLoadingContext::State::DIBDecoded;
+
+ return true;
+}
+
+static bool decode_bmp_color_table(BMPLoadingContext& context)
+{
+ if (context.state == BMPLoadingContext::State::Error)
+ return false;
+
+ if (context.state < BMPLoadingContext::State::DIBDecoded && !decode_bmp_dib(context))
+ return false;
+
+ if (context.state >= BMPLoadingContext::State::ColorTableDecoded)
+ return true;
+
+ if (context.dib.core.bpp > 8) {
+ context.state = BMPLoadingContext::State::ColorTableDecoded;
+ return true;
+ }
+
+ auto bytes_per_color = context.dib_type == DIBType::Core ? 3 : 4;
+ u32 max_colors = 1 << context.dib.core.bpp;
+ ASSERT(context.data_offset >= bmp_header_size + context.dib_size());
+ auto size_of_color_table = context.data_offset - bmp_header_size - context.dib_size();
+
+ if (context.dib_type <= DIBType::OSV2) {
+ // Partial color tables are not supported, so the space of the color
+ // table must be at least enough for the maximum amount of colors
+ if (size_of_color_table < 3 * max_colors) {
+ // This is against the spec, but most viewers process it anyways
+ IF_BMP_DEBUG(dbg() << "BMP with CORE header does not have enough colors. Has: " << size_of_color_table << ", expected: " << (3 * max_colors));
+ }
+ }
+
+ Streamer streamer(context.file_bytes + bmp_header_size + context.dib_size(), size_of_color_table);
+ for (u32 i = 0; !streamer.at_end() && i < max_colors; ++i) {
+ if (bytes_per_color == 4) {
+ if (!streamer.has_u32())
+ return false;
+ context.color_table.append(streamer.read_u32());
+ } else {
+ if (!streamer.has_u24())
+ return false;
+ context.color_table.append(streamer.read_u24());
+ }
+ }
+
+ context.state = BMPLoadingContext::State::ColorTableDecoded;
+ return true;
+}
+
+struct RLEState {
+ enum : u8 {
+ PixelCount = 0,
+ PixelValue,
+ Meta, // Represents just consuming a null byte, which indicates something special
+ };
+};
+
+static bool uncompress_bmp_rle_data(BMPLoadingContext& context, ByteBuffer& buffer)
+{
+ // RLE-compressed images cannot be stored top-down
+ if (context.dib.core.height < 0) {
+ IF_BMP_DEBUG(dbg() << "BMP is top-down and RLE compressed");
+ context.state = BMPLoadingContext::State::Error;
+ return false;
+ }
+
+ Streamer streamer(context.file_bytes + context.data_offset, context.file_size - context.data_offset);
+
+ auto compression = context.dib.info.compression;
+
+ u32 total_rows = static_cast<u32>(context.dib.core.height);
+ u32 total_columns = round_up_to_power_of_two(static_cast<u32>(context.dib.core.width), 4);
+ u32 column = 0;
+ u32 row = 0;
+ auto currently_consuming = RLEState::PixelCount;
+ i16 pixel_count = 0;
+
+ // ByteBuffer asserts that allocating the memory never fails.
+ // FIXME: ByteBuffer should return either RefPtr<> or Optional<>.
+ // Decoding the RLE data on-the-fly might actually be faster, and avoids this topic entirely.
+ u32 buffer_size;
+ if (compression == Compression::RLE24) {
+ buffer_size = total_rows * round_up_to_power_of_two(total_columns, 4) * 4;
+ } else {
+ buffer_size = total_rows * round_up_to_power_of_two(total_columns, 4);
+ }
+ if (buffer_size > 300 * MiB) {
+ IF_BMP_DEBUG(dbg() << "Suspiciously large amount of RLE data");
+ return false;
+ }
+ buffer = ByteBuffer::create_zeroed(buffer_size);
+
+ // Avoid as many if statements as possible by pulling out
+ // compression-dependent actions into separate lambdas
+ Function<u32()> get_buffer_index;
+ Function<bool(u32, bool)> set_byte;
+ Function<Optional<u32>()> read_byte;
+
+ if (compression == Compression::RLE8) {
+ get_buffer_index = [&]() -> u32 { return row * total_columns + column; };
+ } else if (compression == Compression::RLE4) {
+ get_buffer_index = [&]() -> u32 { return (row * total_columns + column) / 2; };
+ } else {
+ get_buffer_index = [&]() -> u32 { return (row * total_columns + column) * 3; };
+ }
+
+ if (compression == Compression::RLE8) {
+ set_byte = [&](u32 color, bool) -> bool {
+ if (column >= total_columns) {
+ column = 0;
+ row++;
+ }
+ auto index = get_buffer_index();
+ if (index >= buffer.size()) {
+ IF_BMP_DEBUG(dbg() << "BMP has badly-formatted RLE data");
+ return false;
+ }
+ buffer[index] = color;
+ column++;
+ return true;
+ };
+ } else if (compression == Compression::RLE24) {
+ set_byte = [&](u32 color, bool) -> bool {
+ if (column >= total_columns) {
+ column = 0;
+ row++;
+ }
+ auto index = get_buffer_index();
+ if (index + 3 >= buffer.size()) {
+ IF_BMP_DEBUG(dbg() << "BMP has badly-formatted RLE data");
+ return false;
+ }
+ ((u32&)buffer[index]) = color;
+ column++;
+ return true;
+ };
+ } else {
+ set_byte = [&](u32 byte, bool rle4_set_second_nibble) -> bool {
+ if (column >= total_columns) {
+ column = 0;
+ row++;
+ }
+
+ u32 index = get_buffer_index();
+ if (index >= buffer.size() || (rle4_set_second_nibble && index + 1 >= buffer.size())) {
+ IF_BMP_DEBUG(dbg() << "BMP has badly-formatted RLE data");
+ return false;
+ }
+
+ if (column % 2) {
+ buffer[index] |= byte >> 4;
+ if (rle4_set_second_nibble) {
+ buffer[index + 1] |= byte << 4;
+ column++;
+ }
+ } else {
+ if (rle4_set_second_nibble) {
+ buffer[index] = byte;
+ column++;
+ } else {
+ buffer[index] |= byte & 0xf0;
+ }
+ }
+
+ column++;
+ return true;
+ };
+ }
+
+ if (compression == Compression::RLE24) {
+ read_byte = [&]() -> Optional<u32> {
+ if (!streamer.has_u24()) {
+ IF_BMP_DEBUG(dbg() << "BMP has badly-formatted RLE data");
+ return {};
+ }
+ return streamer.read_u24();
+ };
+ } else {
+ read_byte = [&]() -> Optional<u32> {
+ if (!streamer.has_u8()) {
+ IF_BMP_DEBUG(dbg() << "BMP has badly-formatted RLE data");
+ return {};
+ }
+ return streamer.read_u8();
+ };
+ }
+
+ while (true) {
+ u32 byte;
+
+ switch (currently_consuming) {
+ case RLEState::PixelCount:
+ if (!streamer.has_u8())
+ return false;
+ byte = streamer.read_u8();
+ if (!byte) {
+ currently_consuming = RLEState::Meta;
+ } else {
+ pixel_count = byte;
+ currently_consuming = RLEState::PixelValue;
+ }
+ break;
+ case RLEState::PixelValue: {
+ auto result = read_byte();
+ if (!result.has_value())
+ return false;
+ byte = result.value();
+ for (u16 i = 0; i < pixel_count; ++i) {
+ if (compression != Compression::RLE4) {
+ if (!set_byte(byte, true))
+ return false;
+ } else {
+ if (!set_byte(byte, i != pixel_count - 1))
+ return false;
+ i++;
+ }
+ }
+
+ currently_consuming = RLEState::PixelCount;
+ break;
+ }
+ case RLEState::Meta:
+ if (!streamer.has_u8())
+ return false;
+ byte = streamer.read_u8();
+ if (!byte) {
+ column = 0;
+ row++;
+ currently_consuming = RLEState::PixelCount;
+ continue;
+ }
+ if (byte == 1)
+ return true;
+ if (byte == 2) {
+ if (!streamer.has_u8())
+ return false;
+ u8 offset_x = streamer.read_u8();
+ if (!streamer.has_u8())
+ return false;
+ u8 offset_y = streamer.read_u8();
+ column += offset_x;
+ if (column >= total_columns) {
+ column -= total_columns;
+ row++;
+ }
+ row += offset_y;
+ currently_consuming = RLEState::PixelCount;
+ continue;
+ }
+
+ // Consume literal bytes
+ pixel_count = byte;
+ i16 i = byte;
+
+ while (i >= 1) {
+ auto result = read_byte();
+ if (!result.has_value())
+ return false;
+ byte = result.value();
+ if (!set_byte(byte, i != 1))
+ return false;
+ i--;
+ if (compression == Compression::RLE4)
+ i--;
+ }
+
+ // Optionally consume a padding byte
+ if (compression != Compression::RLE4) {
+ if (pixel_count % 2) {
+ if (!streamer.has_u8())
+ return false;
+ byte = streamer.read_u8();
+ }
+ } else {
+ if (((pixel_count + 1) / 2) % 2) {
+ if (!streamer.has_u8())
+ return false;
+ byte = streamer.read_u8();
+ }
+ }
+ currently_consuming = RLEState::PixelCount;
+ break;
+ }
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+static bool decode_bmp_pixel_data(BMPLoadingContext& context)
+{
+ if (context.state == BMPLoadingContext::State::Error)
+ return false;
+
+ if (context.state <= BMPLoadingContext::State::ColorTableDecoded && !decode_bmp_color_table(context))
+ return false;
+
+ const u16 bits_per_pixel = context.dib.core.bpp;
+
+ BitmapFormat format = [&]() -> BitmapFormat {
+ switch (bits_per_pixel) {
+ case 1:
+ return BitmapFormat::Indexed1;
+ case 2:
+ return BitmapFormat::Indexed2;
+ case 4:
+ return BitmapFormat::Indexed4;
+ case 8:
+ return BitmapFormat::Indexed8;
+ case 16:
+ if (context.dib.info.masks.size() == 4)
+ return BitmapFormat::RGBA32;
+ return BitmapFormat::RGB32;
+ case 24:
+ return BitmapFormat::RGB32;
+ case 32:
+ return BitmapFormat::RGBA32;
+ default:
+ return BitmapFormat::Invalid;
+ }
+ }();
+
+ if (format == BitmapFormat::Invalid) {
+ IF_BMP_DEBUG(dbg() << "BMP has invalid bpp of " << bits_per_pixel);
+ context.state = BMPLoadingContext::State::Error;
+ return false;
+ }
+
+ const u32 width = abs(context.dib.core.width);
+ const u32 height = abs(context.dib.core.height);
+ context.bitmap = Bitmap::create_purgeable(format, { static_cast<int>(width), static_cast<int>(height) });
+ if (!context.bitmap) {
+ IF_BMP_DEBUG(dbg() << "BMP appears to have overly large dimensions");
+ return false;
+ }
+
+ ByteBuffer rle_buffer;
+ ReadonlyBytes bytes { context.file_bytes + context.data_offset, context.file_size - context.data_offset };
+
+ if (context.dib.info.compression == Compression::RLE4 || context.dib.info.compression == Compression::RLE8
+ || context.dib.info.compression == Compression::RLE24) {
+ if (!uncompress_bmp_rle_data(context, rle_buffer))
+ return false;
+ bytes = rle_buffer.bytes();
+ }
+
+ Streamer streamer(bytes.data(), bytes.size());
+
+ auto process_row = [&](u32 row) -> bool {
+ u32 space_remaining_before_consuming_row = streamer.remaining();
+
+ for (u32 column = 0; column < width;) {
+ switch (bits_per_pixel) {
+ case 1: {
+ if (!streamer.has_u8())
+ return false;
+ u8 byte = streamer.read_u8();
+ u8 mask = 8;
+ while (column < width && mask > 0) {
+ mask -= 1;
+ context.bitmap->scanline_u8(row)[column++] = (byte >> mask) & 0x1;
+ }
+ break;
+ }
+ case 2: {
+ if (!streamer.has_u8())
+ return false;
+ u8 byte = streamer.read_u8();
+ u8 mask = 8;
+ while (column < width && mask > 0) {
+ mask -= 2;
+ context.bitmap->scanline_u8(row)[column++] = (byte >> mask) & 0x3;
+ }
+ break;
+ }
+ case 4: {
+ if (!streamer.has_u8())
+ return false;
+ u8 byte = streamer.read_u8();
+ context.bitmap->scanline_u8(row)[column++] = (byte >> 4) & 0xf;
+ if (column < width)
+ context.bitmap->scanline_u8(row)[column++] = byte & 0xf;
+ break;
+ }
+ case 8:
+ if (!streamer.has_u8())
+ return false;
+ context.bitmap->scanline_u8(row)[column++] = streamer.read_u8();
+ break;
+ case 16: {
+ if (!streamer.has_u16())
+ return false;
+ context.bitmap->scanline(row)[column++] = int_to_scaled_rgb(context, streamer.read_u16());
+ break;
+ }
+ case 24: {
+ if (!streamer.has_u24())
+ return false;
+ context.bitmap->scanline(row)[column++] = streamer.read_u24();
+ break;
+ }
+ case 32:
+ if (!streamer.has_u32())
+ return false;
+ if (context.dib.info.masks.is_empty()) {
+ context.bitmap->scanline(row)[column++] = streamer.read_u32() | 0xff000000;
+ } else {
+ context.bitmap->scanline(row)[column++] = int_to_scaled_rgb(context, streamer.read_u32());
+ }
+ break;
+ }
+ }
+
+ auto consumed = space_remaining_before_consuming_row - streamer.remaining();
+
+ // Calculate padding
+ u8 bytes_to_drop = [consumed]() -> u8 {
+ switch (consumed % 4) {
+ case 0:
+ return 0;
+ case 1:
+ return 3;
+ case 2:
+ return 2;
+ case 3:
+ return 1;
+ }
+ ASSERT_NOT_REACHED();
+ }();
+ if (streamer.remaining() < bytes_to_drop)
+ return false;
+ streamer.drop_bytes(bytes_to_drop);
+
+ return true;
+ };
+
+ if (context.dib.core.height < 0) {
+ // BMP is stored top-down
+ for (u32 row = 0; row < height; ++row) {
+ if (!process_row(row))
+ return false;
+ }
+ } else {
+ for (i32 row = height - 1; row >= 0; --row) {
+ if (!process_row(row))
+ return false;
+ }
+ }
+
+ for (size_t i = 0; i < context.color_table.size(); ++i)
+ context.bitmap->set_palette_color(i, Color::from_rgb(context.color_table[i]));
+
+ context.state = BMPLoadingContext::State::PixelDataDecoded;
+
+ return true;
+}
+
+static RefPtr<Bitmap> load_bmp_impl(const u8* data, size_t data_size)
+{
+ BMPLoadingContext context;
+ context.file_bytes = data;
+ context.file_size = data_size;
+
+ // Forces a decode of the header, dib, and color table as well
+ if (!decode_bmp_pixel_data(context)) {
+ context.state = BMPLoadingContext::State::Error;
+ return nullptr;
+ }
+
+ return context.bitmap;
+}
+
+BMPImageDecoderPlugin::BMPImageDecoderPlugin(const u8* data, size_t data_size)
+{
+ m_context = make<BMPLoadingContext>();
+ m_context->file_bytes = data;
+ m_context->file_size = data_size;
+}
+
+BMPImageDecoderPlugin::~BMPImageDecoderPlugin()
+{
+}
+
+IntSize BMPImageDecoderPlugin::size()
+{
+ if (m_context->state == BMPLoadingContext::State::Error)
+ return {};
+
+ if (m_context->state < BMPLoadingContext::State::DIBDecoded && !decode_bmp_dib(*m_context))
+ return {};
+
+ return { m_context->dib.core.width, abs(m_context->dib.core.height) };
+}
+
+RefPtr<Gfx::Bitmap> BMPImageDecoderPlugin::bitmap()
+{
+ if (m_context->state == BMPLoadingContext::State::Error)
+ return nullptr;
+
+ if (m_context->state < BMPLoadingContext::State::PixelDataDecoded && !decode_bmp_pixel_data(*m_context))
+ return nullptr;
+
+ ASSERT(m_context->bitmap);
+ return m_context->bitmap;
+}
+
+void BMPImageDecoderPlugin::set_volatile()
+{
+ if (m_context->bitmap)
+ m_context->bitmap->set_volatile();
+}
+
+bool BMPImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->bitmap)
+ return false;
+ return m_context->bitmap->set_nonvolatile();
+}
+
+bool BMPImageDecoderPlugin::sniff()
+{
+ return decode_bmp_header(*m_context);
+}
+
+bool BMPImageDecoderPlugin::is_animated()
+{
+ return false;
+}
+
+size_t BMPImageDecoderPlugin::loop_count()
+{
+ return 0;
+}
+
+size_t BMPImageDecoderPlugin::frame_count()
+{
+ return 1;
+}
+
+ImageFrameDescriptor BMPImageDecoderPlugin::frame(size_t i)
+{
+ if (i > 0)
+ return { bitmap(), 0 };
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/BMPLoader.h b/Userland/Libraries/LibGfx/BMPLoader.h
new file mode 100644
index 0000000000..92f54e6b5f
--- /dev/null
+++ b/Userland/Libraries/LibGfx/BMPLoader.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_bmp(const StringView& path);
+RefPtr<Gfx::Bitmap> load_bmp_from_memory(const u8*, size_t);
+
+struct BMPLoadingContext;
+
+class BMPImageDecoderPlugin final : public ImageDecoderPlugin {
+public:
+ virtual ~BMPImageDecoderPlugin() override;
+ BMPImageDecoderPlugin(const u8*, size_t);
+
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+ virtual bool sniff() override;
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<BMPLoadingContext> m_context;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/BMPWriter.cpp b/Userland/Libraries/LibGfx/BMPWriter.cpp
new file mode 100644
index 0000000000..8ae2b00a8b
--- /dev/null
+++ b/Userland/Libraries/LibGfx/BMPWriter.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2020, Ben Jilks <benjyjilks@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Vector.h>
+#include <LibGfx/BMPWriter.h>
+#include <LibGfx/Bitmap.h>
+#include <cstring>
+
+namespace Gfx {
+
+constexpr int bytes_per_pixel = 3;
+
+#define FILE_HEADER_SIZE 14
+#define IMAGE_INFORMATION_SIZE 40
+#define PIXEL_DATA_OFFSET FILE_HEADER_SIZE + IMAGE_INFORMATION_SIZE
+
+class Streamer {
+public:
+ Streamer(u8* data)
+ : m_data(data)
+ {
+ }
+
+ void write_u8(u8 i)
+ {
+ *(m_data++) = i;
+ }
+
+ void write_u16(u16 i)
+ {
+ *(m_data++) = i & 0xFF;
+ *(m_data++) = (i >> 8) & 0xFF;
+ }
+
+ void write_u32(u32 i)
+ {
+ write_u16(i & 0xFFFF);
+ write_u16((i >> 16) & 0xFFFF);
+ }
+
+ void write_i32(i32 i)
+ {
+ write_u32(static_cast<u32>(i));
+ }
+
+private:
+ u8* m_data;
+};
+
+static ByteBuffer write_pixel_data(const RefPtr<Bitmap> bitmap, int pixel_row_data_size)
+{
+ int image_size = pixel_row_data_size * bitmap->height();
+ auto buffer = ByteBuffer::create_uninitialized(image_size);
+
+ int current_row = 0;
+ for (int y = bitmap->height() - 1; y >= 0; --y) {
+ auto* row = buffer.data() + (pixel_row_data_size * current_row++);
+ for (int x = 0; x < bitmap->width(); x++) {
+ auto pixel = bitmap->get_pixel(x, y);
+ row[x * bytes_per_pixel + 0] = pixel.blue();
+ row[x * bytes_per_pixel + 1] = pixel.green();
+ row[x * bytes_per_pixel + 2] = pixel.red();
+ }
+ }
+
+ return buffer;
+}
+
+static ByteBuffer compress_pixel_data(const ByteBuffer& pixel_data, BMPWriter::Compression compression)
+{
+ switch (compression) {
+ case BMPWriter::Compression::RGB:
+ return pixel_data;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+ByteBuffer BMPWriter::dump(const RefPtr<Bitmap> bitmap)
+{
+ int pixel_row_data_size = (bytes_per_pixel * 8 * bitmap->width() + 31) / 32 * 4;
+ int image_size = pixel_row_data_size * bitmap->height();
+ auto buffer = ByteBuffer::create_uninitialized(PIXEL_DATA_OFFSET);
+
+ auto pixel_data = write_pixel_data(bitmap, pixel_row_data_size);
+ pixel_data = compress_pixel_data(pixel_data, m_compression);
+
+ int file_size = PIXEL_DATA_OFFSET + pixel_data.size();
+ Streamer streamer(buffer.data());
+ streamer.write_u8('B');
+ streamer.write_u8('M');
+ streamer.write_u32(file_size);
+ streamer.write_u32(0);
+ streamer.write_u32(PIXEL_DATA_OFFSET);
+
+ streamer.write_u32(IMAGE_INFORMATION_SIZE); // Header size
+ streamer.write_i32(bitmap->width()); // ImageWidth
+ streamer.write_i32(bitmap->height()); // ImageHeight
+ streamer.write_u16(1); // Planes
+ streamer.write_u16(bytes_per_pixel * 8); // BitsPerPixel
+ streamer.write_u32((u32)m_compression); // Compression
+ streamer.write_u32(image_size); // ImageSize
+ streamer.write_i32(0); // XpixelsPerMeter
+ streamer.write_i32(0); // YpixelsPerMeter
+ streamer.write_u32(0); // TotalColors
+ streamer.write_u32(0); // ImportantColors
+
+ buffer.append(pixel_data.data(), pixel_data.size());
+ return buffer;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/BMPWriter.h b/Userland/Libraries/LibGfx/BMPWriter.h
new file mode 100644
index 0000000000..d881ab5f9d
--- /dev/null
+++ b/Userland/Libraries/LibGfx/BMPWriter.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020, Ben Jilks <benjyjilks@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+
+namespace Gfx {
+
+class Bitmap;
+
+class BMPWriter {
+public:
+ BMPWriter() = default;
+
+ ByteBuffer dump(const RefPtr<Bitmap>);
+
+ enum class Compression : u32 {
+ RGB = 0,
+ };
+
+ inline void set_compression(Compression compression) { m_compression = compression; }
+
+private:
+ Compression m_compression { Compression::RGB };
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Bitmap.cpp b/Userland/Libraries/LibGfx/Bitmap.cpp
new file mode 100644
index 0000000000..7c031f59be
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Bitmap.cpp
@@ -0,0 +1,495 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Checked.h>
+#include <AK/Memory.h>
+#include <AK/MemoryStream.h>
+#include <AK/Optional.h>
+#include <AK/SharedBuffer.h>
+#include <AK/String.h>
+#include <LibGfx/BMPLoader.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/GIFLoader.h>
+#include <LibGfx/ICOLoader.h>
+#include <LibGfx/JPGLoader.h>
+#include <LibGfx/PBMLoader.h>
+#include <LibGfx/PGMLoader.h>
+#include <LibGfx/PNGLoader.h>
+#include <LibGfx/PPMLoader.h>
+#include <LibGfx/ShareableBitmap.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/mman.h>
+
+namespace Gfx {
+
+struct BackingStore {
+ void* data { nullptr };
+ size_t pitch { 0 };
+ size_t size_in_bytes { 0 };
+};
+
+size_t Bitmap::minimum_pitch(size_t width, BitmapFormat format)
+{
+ size_t element_size;
+ switch (determine_storage_format(format)) {
+ case StorageFormat::Indexed8:
+ element_size = 1;
+ break;
+ case StorageFormat::RGB32:
+ case StorageFormat::RGBA32:
+ element_size = 4;
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ return width * element_size;
+}
+
+static bool size_would_overflow(BitmapFormat format, const IntSize& size)
+{
+ if (size.width() < 0 || size.height() < 0)
+ return true;
+ // This check is a bit arbitrary, but should protect us from most shenanigans:
+ if (size.width() >= 32768 || size.height() >= 32768)
+ return true;
+ // In contrast, this check is absolutely necessary:
+ size_t pitch = Bitmap::minimum_pitch(size.width(), format);
+ return Checked<size_t>::multiplication_would_overflow(pitch, size.height());
+}
+
+RefPtr<Bitmap> Bitmap::create(BitmapFormat format, const IntSize& size)
+{
+ auto backing_store = Bitmap::allocate_backing_store(format, size, Purgeable::No);
+ if (!backing_store.has_value())
+ return nullptr;
+ return adopt(*new Bitmap(format, size, Purgeable::No, backing_store.value()));
+}
+
+RefPtr<Bitmap> Bitmap::create_purgeable(BitmapFormat format, const IntSize& size)
+{
+ auto backing_store = Bitmap::allocate_backing_store(format, size, Purgeable::Yes);
+ if (!backing_store.has_value())
+ return nullptr;
+ return adopt(*new Bitmap(format, size, Purgeable::Yes, backing_store.value()));
+}
+
+RefPtr<Bitmap> Bitmap::create_shareable(BitmapFormat format, const IntSize& size)
+{
+ if (size_would_overflow(format, size))
+ return nullptr;
+
+ const auto pitch = minimum_pitch(size.width(), format);
+ const auto data_size = size_in_bytes(pitch, size.height());
+ auto shared_buffer = SharedBuffer::create_with_size(data_size);
+ if (!shared_buffer)
+ return nullptr;
+ return adopt(*new Bitmap(format, shared_buffer.release_nonnull(), size, Vector<RGBA32>()));
+}
+
+Bitmap::Bitmap(BitmapFormat format, const IntSize& size, Purgeable purgeable, const BackingStore& backing_store)
+ : m_size(size)
+ , m_data(backing_store.data)
+ , m_pitch(backing_store.pitch)
+ , m_format(format)
+ , m_purgeable(purgeable == Purgeable::Yes)
+{
+ ASSERT(!m_size.is_empty());
+ ASSERT(!size_would_overflow(format, size));
+ ASSERT(m_data);
+ ASSERT(backing_store.size_in_bytes == size_in_bytes());
+ allocate_palette_from_format(format, {});
+ m_needs_munmap = true;
+}
+
+RefPtr<Bitmap> Bitmap::create_wrapper(BitmapFormat format, const IntSize& size, size_t pitch, void* data)
+{
+ if (size_would_overflow(format, size))
+ return nullptr;
+ return adopt(*new Bitmap(format, size, pitch, data));
+}
+
+RefPtr<Bitmap> Bitmap::load_from_file(const StringView& path)
+{
+#define __ENUMERATE_IMAGE_FORMAT(Name, Ext) \
+ if (path.ends_with(Ext, CaseSensitivity::CaseInsensitive)) \
+ return load_##Name(path);
+ ENUMERATE_IMAGE_FORMATS
+#undef __ENUMERATE_IMAGE_FORMAT
+
+ return nullptr;
+}
+
+Bitmap::Bitmap(BitmapFormat format, const IntSize& size, size_t pitch, void* data)
+ : m_size(size)
+ , m_data(data)
+ , m_pitch(pitch)
+ , m_format(format)
+{
+ ASSERT(pitch >= minimum_pitch(size.width(), format));
+ ASSERT(!size_would_overflow(format, size));
+ // FIXME: assert that `data` is actually long enough!
+
+ allocate_palette_from_format(format, {});
+}
+
+RefPtr<Bitmap> Bitmap::create_with_shared_buffer(BitmapFormat format, NonnullRefPtr<SharedBuffer>&& shared_buffer, const IntSize& size)
+{
+ return create_with_shared_buffer(format, move(shared_buffer), size, {});
+}
+
+static bool check_size(const IntSize& size, BitmapFormat format, unsigned actual_size)
+{
+
+ // FIXME: Code duplication of size_in_bytes() and m_pitch
+ unsigned expected_size_min = Bitmap::minimum_pitch(size.width(), format) * size.height();
+ unsigned expected_size_max = round_up_to_power_of_two(expected_size_min, PAGE_SIZE);
+ if (expected_size_min > actual_size || actual_size > expected_size_max) {
+ // Getting here is most likely an error.
+ dbgln("Constructing a shared bitmap for format {} and size {}, which demands {} bytes, which rounds up to at most {}.",
+ static_cast<int>(format),
+ size,
+ expected_size_min,
+ expected_size_max);
+
+ dbgln("However, we were given {} bytes, which is outside this range?! Refusing cowardly.", actual_size);
+ return false;
+ }
+ return true;
+}
+
+RefPtr<Bitmap> Bitmap::create_with_shared_buffer(BitmapFormat format, NonnullRefPtr<SharedBuffer>&& shared_buffer, const IntSize& size, const Vector<RGBA32>& palette)
+{
+ if (size_would_overflow(format, size))
+ return nullptr;
+
+ if (!check_size(size, format, shared_buffer->size()))
+ return {};
+
+ return adopt(*new Bitmap(format, move(shared_buffer), size, palette));
+}
+
+/// Read a bitmap as described by:
+/// - actual size
+/// - width
+/// - height
+/// - format
+/// - palette count
+/// - palette data (= palette count * RGBA32)
+/// - image data (= actual size * u8)
+RefPtr<Bitmap> Bitmap::create_from_serialized_byte_buffer(ByteBuffer&& buffer)
+{
+ InputMemoryStream stream { buffer };
+ unsigned actual_size;
+ unsigned width;
+ unsigned height;
+ BitmapFormat format;
+ unsigned palette_size;
+ Vector<RGBA32> palette;
+
+ auto read = [&]<typename T>(T& value) {
+ if (stream.read({ &value, sizeof(T) }) != sizeof(T))
+ return false;
+ return true;
+ };
+
+ if (!read(actual_size) || !read(width) || !read(height) || !read(format) || !read(palette_size))
+ return nullptr;
+
+ if (format > BitmapFormat::RGBA32 || format < BitmapFormat::Indexed1)
+ return nullptr;
+
+ if (!check_size({ width, height }, format, actual_size))
+ return {};
+
+ palette.ensure_capacity(palette_size);
+ for (size_t i = 0; i < palette_size; ++i) {
+ if (!read(palette[i]))
+ return {};
+ }
+
+ if (stream.remaining() < actual_size)
+ return {};
+
+ auto data = stream.bytes().slice(stream.offset(), actual_size);
+
+ auto bitmap = Bitmap::create(format, { width, height });
+ if (!bitmap)
+ return {};
+
+ bitmap->m_palette = new RGBA32[palette_size];
+ memcpy(bitmap->m_palette, palette.data(), palette_size * sizeof(RGBA32));
+
+ data.copy_to({ bitmap->scanline(0), bitmap->size_in_bytes() });
+
+ return bitmap;
+}
+
+ByteBuffer Bitmap::serialize_to_byte_buffer() const
+{
+ auto buffer = ByteBuffer::create_uninitialized(4 * sizeof(unsigned) + sizeof(BitmapFormat) + sizeof(RGBA32) * palette_size(m_format) + size_in_bytes());
+ OutputMemoryStream stream { buffer };
+
+ auto write = [&]<typename T>(T value) {
+ if (stream.write({ &value, sizeof(T) }) != sizeof(T))
+ return false;
+ return true;
+ };
+
+ auto palette = palette_to_vector();
+
+ if (!write(size_in_bytes()) || !write((unsigned)size().width()) || !write((unsigned)size().height()) || !write(m_format) || !write((unsigned)palette.size()))
+ return {};
+
+ for (auto& p : palette) {
+ if (!write(p))
+ return {};
+ }
+
+ auto size = size_in_bytes();
+ ASSERT(stream.remaining() == size);
+ if (stream.write({ scanline(0), size }) != size)
+ return {};
+
+ return buffer;
+}
+
+Bitmap::Bitmap(BitmapFormat format, NonnullRefPtr<SharedBuffer>&& shared_buffer, const IntSize& size, const Vector<RGBA32>& palette)
+ : m_size(size)
+ , m_data(shared_buffer->data<void>())
+ , m_pitch(minimum_pitch(size.width(), format))
+ , m_format(format)
+ , m_shared_buffer(move(shared_buffer))
+{
+ ASSERT(!is_indexed() || !palette.is_empty());
+ ASSERT(!size_would_overflow(format, size));
+ ASSERT(size_in_bytes() <= static_cast<size_t>(m_shared_buffer->size()));
+
+ if (is_indexed(m_format))
+ allocate_palette_from_format(m_format, palette);
+}
+
+RefPtr<Gfx::Bitmap> Bitmap::clone() const
+{
+ RefPtr<Gfx::Bitmap> new_bitmap {};
+ if (m_purgeable) {
+ new_bitmap = Bitmap::create_purgeable(format(), size());
+ } else {
+ new_bitmap = Bitmap::create(format(), size());
+ }
+
+ if (!new_bitmap) {
+ return nullptr;
+ }
+
+ ASSERT(size_in_bytes() == new_bitmap->size_in_bytes());
+ memcpy(new_bitmap->scanline(0), scanline(0), size_in_bytes());
+
+ return new_bitmap;
+}
+
+RefPtr<Gfx::Bitmap> Bitmap::rotated(Gfx::RotationDirection rotation_direction) const
+{
+ auto w = this->width();
+ auto h = this->height();
+
+ auto new_bitmap = Gfx::Bitmap::create(this->format(), { h, w });
+ if (!new_bitmap)
+ return nullptr;
+
+ for (int i = 0; i < w; i++) {
+ for (int j = 0; j < h; j++) {
+ Color color;
+ if (rotation_direction == Gfx::RotationDirection::Left)
+ color = this->get_pixel(w - i - 1, j);
+ else
+ color = this->get_pixel(i, h - j - 1);
+
+ new_bitmap->set_pixel(j, i, color);
+ }
+ }
+
+ return new_bitmap;
+}
+
+RefPtr<Gfx::Bitmap> Bitmap::flipped(Gfx::Orientation orientation) const
+{
+ auto w = this->width();
+ auto h = this->height();
+
+ auto new_bitmap = Gfx::Bitmap::create(this->format(), { w, h });
+ if (!new_bitmap)
+ return nullptr;
+
+ for (int i = 0; i < w; i++) {
+ for (int j = 0; j < h; j++) {
+ Color color = this->get_pixel(i, j);
+ if (orientation == Orientation::Vertical)
+ new_bitmap->set_pixel(i, h - j - 1, color);
+ else
+ new_bitmap->set_pixel(w - i - 1, j, color);
+ }
+ }
+
+ return new_bitmap;
+}
+
+RefPtr<Bitmap> Bitmap::to_bitmap_backed_by_shared_buffer() const
+{
+ if (m_shared_buffer)
+ return *this;
+ auto buffer = SharedBuffer::create_with_size(size_in_bytes());
+ if (!buffer)
+ return nullptr;
+ auto bitmap = Bitmap::create_with_shared_buffer(m_format, *buffer, m_size, palette_to_vector());
+ if (!bitmap)
+ return nullptr;
+ memcpy(buffer->data<void>(), scanline(0), size_in_bytes());
+ return bitmap;
+}
+
+Bitmap::~Bitmap()
+{
+ if (m_needs_munmap) {
+ int rc = munmap(m_data, size_in_bytes());
+ ASSERT(rc == 0);
+ }
+ m_data = nullptr;
+ delete[] m_palette;
+}
+
+void Bitmap::set_mmap_name([[maybe_unused]] const StringView& name)
+{
+ ASSERT(m_needs_munmap);
+#ifdef __serenity__
+ ::set_mmap_name(m_data, size_in_bytes(), name.to_string().characters());
+#endif
+}
+
+void Bitmap::fill(Color color)
+{
+ ASSERT(!is_indexed(m_format));
+ for (int y = 0; y < height(); ++y) {
+ auto* scanline = this->scanline(y);
+ fast_u32_fill(scanline, color.value(), width());
+ }
+}
+
+void Bitmap::set_volatile()
+{
+ ASSERT(m_purgeable);
+ if (m_volatile)
+ return;
+#ifdef __serenity__
+ int rc = madvise(m_data, size_in_bytes(), MADV_SET_VOLATILE);
+ if (rc < 0) {
+ perror("madvise(MADV_SET_VOLATILE)");
+ ASSERT_NOT_REACHED();
+ }
+#endif
+ m_volatile = true;
+}
+
+[[nodiscard]] bool Bitmap::set_nonvolatile()
+{
+ ASSERT(m_purgeable);
+ if (!m_volatile)
+ return true;
+#ifdef __serenity__
+ int rc = madvise(m_data, size_in_bytes(), MADV_SET_NONVOLATILE);
+ if (rc < 0) {
+ perror("madvise(MADV_SET_NONVOLATILE)");
+ ASSERT_NOT_REACHED();
+ }
+#else
+ int rc = 0;
+#endif
+ m_volatile = false;
+ return rc == 0;
+}
+
+int Bitmap::shbuf_id() const
+{
+ return m_shared_buffer ? m_shared_buffer->shbuf_id() : -1;
+}
+
+ShareableBitmap Bitmap::to_shareable_bitmap(pid_t peer_pid) const
+{
+ auto bitmap = to_bitmap_backed_by_shared_buffer();
+ if (!bitmap)
+ return {};
+ if (peer_pid > 0)
+ bitmap->shared_buffer()->share_with(peer_pid);
+ return ShareableBitmap(*bitmap);
+}
+
+Optional<BackingStore> Bitmap::allocate_backing_store(BitmapFormat format, const IntSize& size, [[maybe_unused]] Purgeable purgeable)
+{
+ if (size_would_overflow(format, size))
+ return {};
+
+ const auto pitch = minimum_pitch(size.width(), format);
+ const auto data_size_in_bytes = size_in_bytes(pitch, size.height());
+
+ int map_flags = MAP_ANONYMOUS | MAP_PRIVATE;
+ if (purgeable == Purgeable::Yes)
+ map_flags |= MAP_NORESERVE;
+#ifdef __serenity__
+ void* data = mmap_with_name(nullptr, data_size_in_bytes, PROT_READ | PROT_WRITE, map_flags, 0, 0, String::format("GraphicsBitmap [%dx%d]", size.width(), size.height()).characters());
+#else
+ void* data = mmap(nullptr, data_size_in_bytes, PROT_READ | PROT_WRITE, map_flags, 0, 0);
+#endif
+ if (data == MAP_FAILED) {
+ perror("mmap");
+ return {};
+ }
+ return { { data, pitch, data_size_in_bytes } };
+}
+
+void Bitmap::allocate_palette_from_format(BitmapFormat format, const Vector<RGBA32>& source_palette)
+{
+ size_t size = palette_size(format);
+ if (size == 0)
+ return;
+ m_palette = new RGBA32[size];
+ if (!source_palette.is_empty()) {
+ ASSERT(source_palette.size() == size);
+ memcpy(m_palette, source_palette.data(), size * sizeof(RGBA32));
+ }
+}
+
+Vector<RGBA32> Bitmap::palette_to_vector() const
+{
+ Vector<RGBA32> vector;
+ auto size = palette_size(m_format);
+ vector.ensure_capacity(size);
+ for (size_t i = 0; i < size; ++i)
+ vector.unchecked_append(palette_color(i).value());
+ return vector;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Bitmap.h b/Userland/Libraries/LibGfx/Bitmap.h
new file mode 100644
index 0000000000..4d0b236f57
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Bitmap.h
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <LibGfx/Color.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/Rect.h>
+
+#define ENUMERATE_IMAGE_FORMATS \
+ __ENUMERATE_IMAGE_FORMAT(pbm, ".pbm") \
+ __ENUMERATE_IMAGE_FORMAT(pgm, ".pgm") \
+ __ENUMERATE_IMAGE_FORMAT(png, ".png") \
+ __ENUMERATE_IMAGE_FORMAT(ppm, ".ppm") \
+ __ENUMERATE_IMAGE_FORMAT(gif, ".gif") \
+ __ENUMERATE_IMAGE_FORMAT(bmp, ".bmp") \
+ __ENUMERATE_IMAGE_FORMAT(ico, ".ico") \
+ __ENUMERATE_IMAGE_FORMAT(jpg, ".jpg") \
+ __ENUMERATE_IMAGE_FORMAT(jpg, ".jpeg")
+
+namespace Gfx {
+
+enum class BitmapFormat {
+ Invalid,
+ Indexed1,
+ Indexed2,
+ Indexed4,
+ Indexed8,
+ RGB32,
+ RGBA32,
+};
+
+enum class StorageFormat {
+ Indexed8,
+ RGB32,
+ RGBA32,
+};
+
+static StorageFormat determine_storage_format(BitmapFormat format)
+{
+ switch (format) {
+ case BitmapFormat::RGB32:
+ return StorageFormat::RGB32;
+ case BitmapFormat::RGBA32:
+ return StorageFormat::RGBA32;
+ case BitmapFormat::Indexed1:
+ case BitmapFormat::Indexed2:
+ case BitmapFormat::Indexed4:
+ case BitmapFormat::Indexed8:
+ return StorageFormat::Indexed8;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+struct BackingStore;
+
+enum RotationDirection {
+ Left,
+ Right
+};
+
+class Bitmap : public RefCounted<Bitmap> {
+public:
+ static RefPtr<Bitmap> create(BitmapFormat, const IntSize&);
+ static RefPtr<Bitmap> create_shareable(BitmapFormat, const IntSize&);
+ static RefPtr<Bitmap> create_purgeable(BitmapFormat, const IntSize&);
+ static RefPtr<Bitmap> create_wrapper(BitmapFormat, const IntSize&, size_t pitch, void*);
+ static RefPtr<Bitmap> load_from_file(const StringView& path);
+ static RefPtr<Bitmap> create_with_shared_buffer(BitmapFormat, NonnullRefPtr<SharedBuffer>&&, const IntSize&);
+ static RefPtr<Bitmap> create_with_shared_buffer(BitmapFormat, NonnullRefPtr<SharedBuffer>&&, const IntSize&, const Vector<RGBA32>& palette);
+ static RefPtr<Bitmap> create_from_serialized_byte_buffer(ByteBuffer&& buffer);
+ static bool is_path_a_supported_image_format(const StringView& path)
+ {
+#define __ENUMERATE_IMAGE_FORMAT(Name, Ext) \
+ if (path.ends_with(Ext, CaseSensitivity::CaseInsensitive)) \
+ return true;
+ ENUMERATE_IMAGE_FORMATS
+#undef __ENUMERATE_IMAGE_FORMAT
+
+ return false;
+ }
+
+ RefPtr<Gfx::Bitmap> clone() const;
+
+ RefPtr<Gfx::Bitmap> rotated(Gfx::RotationDirection) const;
+ RefPtr<Gfx::Bitmap> flipped(Gfx::Orientation) const;
+ RefPtr<Bitmap> to_bitmap_backed_by_shared_buffer() const;
+ ByteBuffer serialize_to_byte_buffer() const;
+
+ ShareableBitmap to_shareable_bitmap(pid_t peer_pid = -1) const;
+
+ ~Bitmap();
+
+ u8* scanline_u8(int y);
+ const u8* scanline_u8(int y) const;
+ RGBA32* scanline(int y);
+ const RGBA32* scanline(int y) const;
+
+ IntRect rect() const { return { {}, m_size }; }
+ IntSize size() const { return m_size; }
+ int width() const { return m_size.width(); }
+ int height() const { return m_size.height(); }
+ size_t pitch() const { return m_pitch; }
+ int shbuf_id() const;
+
+ SharedBuffer* shared_buffer() { return m_shared_buffer.ptr(); }
+ const SharedBuffer* shared_buffer() const { return m_shared_buffer.ptr(); }
+
+ ALWAYS_INLINE bool is_indexed() const
+ {
+ return is_indexed(m_format);
+ }
+
+ ALWAYS_INLINE static bool is_indexed(BitmapFormat format)
+ {
+ return format == BitmapFormat::Indexed8 || format == BitmapFormat::Indexed4
+ || format == BitmapFormat::Indexed2 || format == BitmapFormat::Indexed1;
+ }
+
+ static size_t palette_size(BitmapFormat format)
+ {
+ switch (format) {
+ case BitmapFormat::Indexed1:
+ return 2;
+ case BitmapFormat::Indexed2:
+ return 4;
+ case BitmapFormat::Indexed4:
+ return 16;
+ case BitmapFormat::Indexed8:
+ return 256;
+ default:
+ return 0;
+ }
+ }
+
+ Vector<RGBA32> palette_to_vector() const;
+
+ static unsigned bpp_for_format(BitmapFormat format)
+ {
+ switch (format) {
+ case BitmapFormat::Indexed1:
+ return 1;
+ case BitmapFormat::Indexed2:
+ return 2;
+ case BitmapFormat::Indexed4:
+ return 4;
+ case BitmapFormat::Indexed8:
+ return 8;
+ case BitmapFormat::RGB32:
+ case BitmapFormat::RGBA32:
+ return 32;
+ default:
+ ASSERT_NOT_REACHED();
+ case BitmapFormat::Invalid:
+ return 0;
+ }
+ }
+
+ static size_t minimum_pitch(size_t width, BitmapFormat);
+
+ unsigned bpp() const
+ {
+ return bpp_for_format(m_format);
+ }
+
+ void fill(Color);
+
+ bool has_alpha_channel() const { return m_format == BitmapFormat::RGBA32; }
+ BitmapFormat format() const { return m_format; }
+
+ void set_mmap_name(const StringView&);
+
+ static constexpr size_t size_in_bytes(size_t pitch, int height) { return pitch * height; }
+ size_t size_in_bytes() const { return size_in_bytes(m_pitch, height()); }
+
+ Color palette_color(u8 index) const { return Color::from_rgba(m_palette[index]); }
+ void set_palette_color(u8 index, Color color) { m_palette[index] = color.value(); }
+
+ template<StorageFormat>
+ Color get_pixel(int x, int y) const;
+ Color get_pixel(int x, int y) const;
+ Color get_pixel(const IntPoint& position) const
+ {
+ return get_pixel(position.x(), position.y());
+ }
+
+ template<StorageFormat>
+ void set_pixel(int x, int y, Color);
+ void set_pixel(int x, int y, Color);
+ void set_pixel(const IntPoint& position, Color color)
+ {
+ set_pixel(position.x(), position.y(), color);
+ }
+
+ bool is_purgeable() const { return m_purgeable; }
+ bool is_volatile() const { return m_volatile; }
+ void set_volatile();
+ [[nodiscard]] bool set_nonvolatile();
+
+private:
+ enum class Purgeable {
+ No,
+ Yes
+ };
+ Bitmap(BitmapFormat, const IntSize&, Purgeable, const BackingStore&);
+ Bitmap(BitmapFormat, const IntSize&, size_t pitch, void*);
+ Bitmap(BitmapFormat, NonnullRefPtr<SharedBuffer>&&, const IntSize&, const Vector<RGBA32>& palette);
+
+ static Optional<BackingStore> allocate_backing_store(BitmapFormat, const IntSize&, Purgeable);
+
+ void allocate_palette_from_format(BitmapFormat, const Vector<RGBA32>& source_palette);
+
+ IntSize m_size;
+ void* m_data { nullptr };
+ RGBA32* m_palette { nullptr };
+ size_t m_pitch { 0 };
+ BitmapFormat m_format { BitmapFormat::Invalid };
+ bool m_needs_munmap { false };
+ bool m_purgeable { false };
+ bool m_volatile { false };
+ RefPtr<SharedBuffer> m_shared_buffer;
+};
+
+inline u8* Bitmap::scanline_u8(int y)
+{
+ ASSERT(y >= 0 && y < height());
+ return reinterpret_cast<u8*>(m_data) + (y * m_pitch);
+}
+
+inline const u8* Bitmap::scanline_u8(int y) const
+{
+ ASSERT(y >= 0 && y < height());
+ return reinterpret_cast<const u8*>(m_data) + (y * m_pitch);
+}
+
+inline RGBA32* Bitmap::scanline(int y)
+{
+ return reinterpret_cast<RGBA32*>(scanline_u8(y));
+}
+
+inline const RGBA32* Bitmap::scanline(int y) const
+{
+ return reinterpret_cast<const RGBA32*>(scanline_u8(y));
+}
+
+template<>
+inline Color Bitmap::get_pixel<StorageFormat::RGB32>(int x, int y) const
+{
+ ASSERT(x >= 0 && x < width());
+ return Color::from_rgb(scanline(y)[x]);
+}
+
+template<>
+inline Color Bitmap::get_pixel<StorageFormat::RGBA32>(int x, int y) const
+{
+ ASSERT(x >= 0 && x < width());
+ return Color::from_rgba(scanline(y)[x]);
+}
+
+template<>
+inline Color Bitmap::get_pixel<StorageFormat::Indexed8>(int x, int y) const
+{
+ ASSERT(x >= 0 && x < width());
+ return Color::from_rgb(m_palette[scanline_u8(y)[x]]);
+}
+
+inline Color Bitmap::get_pixel(int x, int y) const
+{
+ switch (determine_storage_format(m_format)) {
+ case StorageFormat::RGB32:
+ return get_pixel<StorageFormat::RGB32>(x, y);
+ case StorageFormat::RGBA32:
+ return get_pixel<StorageFormat::RGBA32>(x, y);
+ case StorageFormat::Indexed8:
+ return get_pixel<StorageFormat::Indexed8>(x, y);
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+template<>
+inline void Bitmap::set_pixel<StorageFormat::RGB32>(int x, int y, Color color)
+{
+ ASSERT(x >= 0 && x < width());
+ scanline(y)[x] = color.value();
+}
+template<>
+inline void Bitmap::set_pixel<StorageFormat::RGBA32>(int x, int y, Color color)
+{
+ ASSERT(x >= 0 && x < width());
+ scanline(y)[x] = color.value(); // drop alpha
+}
+inline void Bitmap::set_pixel(int x, int y, Color color)
+{
+ switch (determine_storage_format(m_format)) {
+ case StorageFormat::RGB32:
+ set_pixel<StorageFormat::RGB32>(x, y, color);
+ break;
+ case StorageFormat::RGBA32:
+ set_pixel<StorageFormat::RGBA32>(x, y, color);
+ break;
+ case StorageFormat::Indexed8:
+ ASSERT_NOT_REACHED();
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/BitmapFont.cpp b/Userland/Libraries/LibGfx/BitmapFont.cpp
new file mode 100644
index 0000000000..e5e24cbde5
--- /dev/null
+++ b/Userland/Libraries/LibGfx/BitmapFont.cpp
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "BitmapFont.h"
+#include "Bitmap.h"
+#include "Emoji.h"
+#include <AK/StdLibExtras.h>
+#include <AK/StringBuilder.h>
+#include <AK/Utf32View.h>
+#include <AK/Utf8View.h>
+#include <AK/Vector.h>
+#include <AK/kmalloc.h>
+#include <LibCore/FileStream.h>
+#include <LibGfx/FontDatabase.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+namespace Gfx {
+
+struct [[gnu::packed]] FontFileHeader {
+ char magic[4];
+ u8 glyph_width;
+ u8 glyph_height;
+ u8 type;
+ u8 is_variable_width;
+ u8 glyph_spacing;
+ u8 baseline;
+ u8 mean_line;
+ u8 presentation_size;
+ u16 weight;
+ char name[32];
+ char family[32];
+};
+
+NonnullRefPtr<Font> BitmapFont::clone() const
+{
+ size_t bytes_per_glyph = sizeof(u32) * glyph_height();
+ auto* new_rows = static_cast<unsigned*>(malloc(bytes_per_glyph * m_glyph_count));
+ memcpy(new_rows, m_rows, bytes_per_glyph * m_glyph_count);
+ auto* new_widths = static_cast<u8*>(malloc(m_glyph_count));
+ if (m_glyph_widths)
+ memcpy(new_widths, m_glyph_widths, m_glyph_count);
+ else
+ memset(new_widths, m_glyph_width, m_glyph_count);
+ return adopt(*new BitmapFont(m_name, m_family, new_rows, new_widths, m_fixed_width, m_glyph_width, m_glyph_height, m_glyph_spacing, m_type, m_baseline, m_mean_line, m_presentation_size, m_weight, true));
+}
+
+NonnullRefPtr<BitmapFont> BitmapFont::create(u8 glyph_height, u8 glyph_width, bool fixed, FontTypes type)
+{
+ size_t bytes_per_glyph = sizeof(u32) * glyph_height;
+ size_t count = glyph_count_by_type(type);
+ auto* new_rows = static_cast<unsigned*>(malloc(bytes_per_glyph * count));
+ memset(new_rows, 0, bytes_per_glyph * count);
+ auto* new_widths = static_cast<u8*>(malloc(count));
+ memset(new_widths, glyph_width, count);
+ return adopt(*new BitmapFont("Untitled", "Untitled", new_rows, new_widths, fixed, glyph_width, glyph_height, 1, type, 0, 0, 0, 400, true));
+}
+
+BitmapFont::BitmapFont(String name, String family, unsigned* rows, u8* widths, bool is_fixed_width, u8 glyph_width, u8 glyph_height, u8 glyph_spacing, FontTypes type, u8 baseline, u8 mean_line, u8 presentation_size, u16 weight, bool owns_arrays)
+ : m_name(name)
+ , m_family(family)
+ , m_type(type)
+ , m_rows(rows)
+ , m_glyph_widths(widths)
+ , m_glyph_width(glyph_width)
+ , m_glyph_height(glyph_height)
+ , m_min_glyph_width(glyph_width)
+ , m_max_glyph_width(glyph_width)
+ , m_glyph_spacing(glyph_spacing)
+ , m_baseline(baseline)
+ , m_mean_line(mean_line)
+ , m_presentation_size(presentation_size)
+ , m_weight(weight)
+ , m_fixed_width(is_fixed_width)
+ , m_owns_arrays(owns_arrays)
+{
+ update_x_height();
+
+ m_glyph_count = glyph_count_by_type(m_type);
+
+ if (!m_fixed_width) {
+ u8 maximum = 0;
+ u8 minimum = 255;
+ for (size_t i = 0; i < m_glyph_count; ++i) {
+ minimum = min(minimum, m_glyph_widths[i]);
+ maximum = max(maximum, m_glyph_widths[i]);
+ }
+ m_min_glyph_width = minimum;
+ m_max_glyph_width = maximum;
+ }
+}
+
+BitmapFont::~BitmapFont()
+{
+ if (m_owns_arrays) {
+ free(m_glyph_widths);
+ free(m_rows);
+ }
+}
+
+RefPtr<BitmapFont> BitmapFont::load_from_memory(const u8* data)
+{
+ auto& header = *reinterpret_cast<const FontFileHeader*>(data);
+ if (memcmp(header.magic, "!Fnt", 4)) {
+ dbgln("header.magic != '!Fnt', instead it's '{:c}{:c}{:c}{:c}'", header.magic[0], header.magic[1], header.magic[2], header.magic[3]);
+ return nullptr;
+ }
+ if (header.name[sizeof(header.name) - 1] != '\0') {
+ dbgln("Font name not fully null-terminated");
+ return nullptr;
+ }
+
+ if (header.family[sizeof(header.family) - 1] != '\0') {
+ dbgln("Font family not fully null-terminated");
+ return nullptr;
+ }
+
+ FontTypes type;
+ if (header.type == 0)
+ type = FontTypes::Default;
+ else if (header.type == 1)
+ type = FontTypes::LatinExtendedA;
+ else
+ ASSERT_NOT_REACHED();
+
+ size_t count = glyph_count_by_type(type);
+ size_t bytes_per_glyph = sizeof(unsigned) * header.glyph_height;
+
+ auto* rows = const_cast<unsigned*>((const unsigned*)(data + sizeof(FontFileHeader)));
+ u8* widths = nullptr;
+ if (header.is_variable_width)
+ widths = (u8*)(rows) + count * bytes_per_glyph;
+ return adopt(*new BitmapFont(String(header.name), String(header.family), rows, widths, !header.is_variable_width, header.glyph_width, header.glyph_height, header.glyph_spacing, type, header.baseline, header.mean_line, header.presentation_size, header.weight));
+}
+
+size_t BitmapFont::glyph_count_by_type(FontTypes type)
+{
+ if (type == FontTypes::Default)
+ return 256;
+
+ if (type == FontTypes::LatinExtendedA)
+ return 384;
+
+ dbgln("Unknown font type: {}", (int)type);
+ ASSERT_NOT_REACHED();
+}
+
+RefPtr<BitmapFont> BitmapFont::load_from_file(const StringView& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return nullptr;
+
+ auto font = load_from_memory((const u8*)file_or_error.value()->data());
+ if (!font)
+ return nullptr;
+
+ font->m_mapped_file = file_or_error.release_value();
+ return font;
+}
+
+bool BitmapFont::write_to_file(const StringView& path)
+{
+ FontFileHeader header;
+ memset(&header, 0, sizeof(FontFileHeader));
+ memcpy(header.magic, "!Fnt", 4);
+ header.glyph_width = m_glyph_width;
+ header.glyph_height = m_glyph_height;
+ header.type = m_type;
+ header.baseline = m_baseline;
+ header.mean_line = m_mean_line;
+ header.is_variable_width = !m_fixed_width;
+ header.glyph_spacing = m_glyph_spacing;
+ header.presentation_size = m_presentation_size;
+ header.weight = m_weight;
+ memcpy(header.name, m_name.characters(), min(m_name.length(), sizeof(header.name) - 1));
+ memcpy(header.family, m_family.characters(), min(m_family.length(), sizeof(header.family) - 1));
+
+ size_t bytes_per_glyph = sizeof(unsigned) * m_glyph_height;
+ size_t count = glyph_count_by_type(m_type);
+
+ auto stream_result = Core::OutputFileStream::open_buffered(path);
+ if (stream_result.is_error())
+ return false;
+ auto& stream = stream_result.value();
+
+ stream << ReadonlyBytes { &header, sizeof(header) };
+ stream << ReadonlyBytes { m_rows, count * bytes_per_glyph };
+ stream << ReadonlyBytes { m_glyph_widths, count };
+
+ stream.flush();
+ if (stream.handle_any_error())
+ return false;
+
+ return true;
+}
+
+GlyphBitmap BitmapFont::glyph_bitmap(u32 code_point) const
+{
+ return GlyphBitmap(&m_rows[code_point * m_glyph_height], { glyph_width(code_point), m_glyph_height });
+}
+
+int BitmapFont::glyph_or_emoji_width(u32 code_point) const
+{
+ if (code_point < m_glyph_count)
+ return glyph_width(code_point);
+
+ if (m_fixed_width)
+ return m_glyph_width;
+
+ auto* emoji = Emoji::emoji_for_code_point(code_point);
+ if (emoji == nullptr)
+ return glyph_width('?');
+ return emoji->size().width();
+}
+
+int BitmapFont::width(const StringView& string) const
+{
+ Utf8View utf8 { string };
+ return width(utf8);
+}
+
+int BitmapFont::width(const Utf8View& utf8) const
+{
+ bool first = true;
+ int width = 0;
+
+ for (u32 code_point : utf8) {
+ if (!first)
+ width += glyph_spacing();
+ first = false;
+ width += glyph_or_emoji_width(code_point);
+ }
+
+ return width;
+}
+
+int BitmapFont::width(const Utf32View& view) const
+{
+ if (view.length() == 0)
+ return 0;
+ int width = (view.length() - 1) * glyph_spacing();
+ for (size_t i = 0; i < view.length(); ++i)
+ width += glyph_or_emoji_width(view.code_points()[i]);
+ return width;
+}
+
+void BitmapFont::set_type(FontTypes type)
+{
+ if (type == m_type)
+ return;
+
+ if (type == FontTypes::Default)
+ return;
+
+ size_t new_glyph_count = glyph_count_by_type(type);
+ if (new_glyph_count <= m_glyph_count) {
+ m_glyph_count = new_glyph_count;
+ return;
+ }
+
+ int item_count_to_copy = min(m_glyph_count, new_glyph_count);
+
+ size_t bytes_per_glyph = sizeof(u32) * glyph_height();
+
+ auto* new_rows = static_cast<unsigned*>(kmalloc(bytes_per_glyph * new_glyph_count));
+ memset(new_rows, (unsigned)0, bytes_per_glyph * new_glyph_count);
+ memcpy(new_rows, m_rows, bytes_per_glyph * item_count_to_copy);
+
+ auto* new_widths = static_cast<u8*>(kmalloc(new_glyph_count));
+ memset(new_widths, (u8)0, new_glyph_count);
+ memcpy(new_widths, m_glyph_widths, item_count_to_copy);
+
+ kfree(m_rows);
+ kfree(m_glyph_widths);
+
+ m_type = type;
+ m_glyph_count = new_glyph_count;
+ m_rows = new_rows;
+ m_glyph_widths = new_widths;
+}
+
+String BitmapFont::qualified_name() const
+{
+ return String::formatted("{} {} {}", family(), presentation_size(), weight());
+}
+
+const Font& BitmapFont::bold_variant() const
+{
+ if (m_bold_variant)
+ return *m_bold_variant;
+ m_bold_variant = Gfx::FontDatabase::the().get(m_family, m_presentation_size, 700);
+ if (!m_bold_variant)
+ m_bold_variant = this;
+ return *m_bold_variant;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/BitmapFont.h b/Userland/Libraries/LibGfx/BitmapFont.h
new file mode 100644
index 0000000000..c3ca9f4a1d
--- /dev/null
+++ b/Userland/Libraries/LibGfx/BitmapFont.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/MappedFile.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Size.h>
+
+namespace Gfx {
+
+enum FontTypes {
+ Default = 0,
+ LatinExtendedA = 1
+};
+
+class BitmapFont : public Font {
+public:
+ NonnullRefPtr<Font> clone() const;
+ static NonnullRefPtr<BitmapFont> create(u8 glyph_height, u8 glyph_width, bool fixed, FontTypes type);
+
+ static RefPtr<BitmapFont> load_from_file(const StringView& path);
+ bool write_to_file(const StringView& path);
+
+ ~BitmapFont();
+
+ u8 presentation_size() const { return m_presentation_size; }
+ void set_presentation_size(u8 size) { m_presentation_size = size; }
+
+ u16 weight() const { return m_weight; }
+ void set_weight(u16 weight) { m_weight = weight; }
+
+ GlyphBitmap glyph_bitmap(u32 code_point) const;
+
+ u8 glyph_width(size_t ch) const { return m_fixed_width ? m_glyph_width : m_glyph_widths[ch]; }
+ int glyph_or_emoji_width(u32 code_point) const;
+ u8 glyph_height() const { return m_glyph_height; }
+ int x_height() const { return m_x_height; }
+
+ u8 min_glyph_width() const { return m_min_glyph_width; }
+ u8 max_glyph_width() const { return m_max_glyph_width; }
+ u8 glyph_fixed_width() const { return m_glyph_width; }
+
+ u8 baseline() const { return m_baseline; }
+ void set_baseline(u8 baseline)
+ {
+ m_baseline = baseline;
+ update_x_height();
+ }
+
+ u8 mean_line() const { return m_mean_line; }
+ void set_mean_line(u8 mean_line)
+ {
+ m_mean_line = mean_line;
+ update_x_height();
+ }
+
+ int width(const StringView&) const;
+ int width(const Utf8View&) const;
+ int width(const Utf32View&) const;
+
+ const String& name() const { return m_name; }
+ void set_name(String name) { m_name = move(name); }
+
+ bool is_fixed_width() const { return m_fixed_width; }
+ void set_fixed_width(bool b) { m_fixed_width = b; }
+
+ u8 glyph_spacing() const { return m_glyph_spacing; }
+ void set_glyph_spacing(u8 spacing) { m_glyph_spacing = spacing; }
+
+ void set_glyph_width(size_t ch, u8 width)
+ {
+ ASSERT(m_glyph_widths);
+ m_glyph_widths[ch] = width;
+ }
+
+ int glyph_count() const { return m_glyph_count; }
+
+ FontTypes type() { return m_type; }
+ void set_type(FontTypes type);
+
+ const String& family() const { return m_family; }
+ void set_family(String family) { m_family = move(family); }
+
+ String qualified_name() const;
+
+ const Font& bold_variant() const;
+
+private:
+ BitmapFont(String name, String family, unsigned* rows, u8* widths, bool is_fixed_width, u8 glyph_width, u8 glyph_height, u8 glyph_spacing, FontTypes type, u8 baseline, u8 mean_line, u8 presentation_size, u16 weight, bool owns_arrays = false);
+
+ static RefPtr<BitmapFont> load_from_memory(const u8*);
+ static size_t glyph_count_by_type(FontTypes type);
+
+ void update_x_height() { m_x_height = m_baseline - m_mean_line; };
+
+ String m_name;
+ String m_family;
+ FontTypes m_type;
+ size_t m_glyph_count { 256 };
+
+ unsigned* m_rows { nullptr };
+ u8* m_glyph_widths { nullptr };
+ RefPtr<MappedFile> m_mapped_file;
+
+ u8 m_glyph_width { 0 };
+ u8 m_glyph_height { 0 };
+ u8 m_x_height { 0 };
+ u8 m_min_glyph_width { 0 };
+ u8 m_max_glyph_width { 0 };
+ u8 m_glyph_spacing { 0 };
+ u8 m_baseline { 0 };
+ u8 m_mean_line { 0 };
+ u8 m_presentation_size { 0 };
+ u16 m_weight { 0 };
+
+ bool m_fixed_width { false };
+ bool m_owns_arrays { false };
+
+ mutable RefPtr<Gfx::Font> m_bold_variant;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/CMakeLists.txt b/Userland/Libraries/LibGfx/CMakeLists.txt
new file mode 100644
index 0000000000..2a6d9750db
--- /dev/null
+++ b/Userland/Libraries/LibGfx/CMakeLists.txt
@@ -0,0 +1,37 @@
+set(SOURCES
+ AffineTransform.cpp
+ Bitmap.cpp
+ BitmapFont.cpp
+ BMPLoader.cpp
+ BMPWriter.cpp
+ CharacterBitmap.cpp
+ ClassicStylePainter.cpp
+ ClassicWindowTheme.cpp
+ Color.cpp
+ DisjointRectSet.cpp
+ Emoji.cpp
+ Font.cpp
+ FontDatabase.cpp
+ GIFLoader.cpp
+ ICOLoader.cpp
+ ImageDecoder.cpp
+ JPGLoader.cpp
+ Painter.cpp
+ Palette.cpp
+ Path.cpp
+ PBMLoader.cpp
+ PGMLoader.cpp
+ PNGLoader.cpp
+ PPMLoader.cpp
+ Point.cpp
+ Rect.cpp
+ ShareableBitmap.cpp
+ Size.cpp
+ StylePainter.cpp
+ SystemTheme.cpp
+ Triangle.cpp
+ WindowTheme.cpp
+)
+
+serenity_lib(LibGfx gfx)
+target_link_libraries(LibGfx LibM LibCore)
diff --git a/Userland/Libraries/LibGfx/CharacterBitmap.cpp b/Userland/Libraries/LibGfx/CharacterBitmap.cpp
new file mode 100644
index 0000000000..d51ff1d080
--- /dev/null
+++ b/Userland/Libraries/LibGfx/CharacterBitmap.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CharacterBitmap.h"
+
+namespace Gfx {
+
+CharacterBitmap::CharacterBitmap(const char* ascii_data, unsigned width, unsigned height)
+ : m_bits(ascii_data)
+ , m_size(width, height)
+{
+}
+
+CharacterBitmap::~CharacterBitmap()
+{
+}
+
+NonnullRefPtr<CharacterBitmap> CharacterBitmap::create_from_ascii(const char* asciiData, unsigned width, unsigned height)
+{
+ return adopt(*new CharacterBitmap(asciiData, width, height));
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/CharacterBitmap.h b/Userland/Libraries/LibGfx/CharacterBitmap.h
new file mode 100644
index 0000000000..9780383570
--- /dev/null
+++ b/Userland/Libraries/LibGfx/CharacterBitmap.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Size.h"
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+
+namespace Gfx {
+
+class CharacterBitmap : public RefCounted<CharacterBitmap> {
+public:
+ static NonnullRefPtr<CharacterBitmap> create_from_ascii(const char* asciiData, unsigned width, unsigned height);
+ ~CharacterBitmap();
+
+ bool bit_at(unsigned x, unsigned y) const { return m_bits[y * width() + x] == '#'; }
+ const char* bits() const { return m_bits; }
+
+ IntSize size() const { return m_size; }
+ unsigned width() const { return m_size.width(); }
+ unsigned height() const { return m_size.height(); }
+
+private:
+ CharacterBitmap(const char* b, unsigned w, unsigned h);
+
+ const char* m_bits { nullptr };
+ IntSize m_size;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/ClassicStylePainter.cpp b/Userland/Libraries/LibGfx/ClassicStylePainter.cpp
new file mode 100644
index 0000000000..95c9e9ffb6
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ClassicStylePainter.cpp
@@ -0,0 +1,405 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020 Sarah Taube <metalflakecobaltpaint@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringView.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/CharacterBitmap.h>
+#include <LibGfx/ClassicStylePainter.h>
+#include <LibGfx/Painter.h>
+#include <LibGfx/Palette.h>
+
+namespace Gfx {
+
+void ClassicStylePainter::paint_tab_button(Painter& painter, const IntRect& rect, const Palette& palette, bool active, bool hovered, bool enabled, bool top)
+{
+ Color base_color = palette.button();
+ Color highlight_color2 = palette.threed_highlight();
+ Color shadow_color1 = palette.threed_shadow1();
+ Color shadow_color2 = palette.threed_shadow2();
+
+ if (hovered && enabled && !active)
+ base_color = palette.hover_highlight();
+
+ PainterStateSaver saver(painter);
+ painter.translate(rect.location());
+
+ if (top) {
+ // Base
+ painter.fill_rect({ 1, 1, rect.width() - 2, rect.height() - 1 }, base_color);
+
+ // Top line
+ painter.draw_line({ 2, 0 }, { rect.width() - 3, 0 }, highlight_color2);
+
+ // Left side
+ painter.draw_line({ 0, 2 }, { 0, rect.height() - 1 }, highlight_color2);
+ painter.set_pixel({ 1, 1 }, highlight_color2);
+
+ // Right side
+
+ IntPoint top_right_outer { rect.width() - 1, 2 };
+ IntPoint bottom_right_outer { rect.width() - 1, rect.height() - 1 };
+ painter.draw_line(top_right_outer, bottom_right_outer, shadow_color2);
+
+ IntPoint top_right_inner { rect.width() - 2, 2 };
+ IntPoint bottom_right_inner { rect.width() - 2, rect.height() - 1 };
+ painter.draw_line(top_right_inner, bottom_right_inner, shadow_color1);
+
+ painter.set_pixel(rect.width() - 2, 1, shadow_color2);
+ } else {
+ // Base
+ painter.fill_rect({ 0, 0, rect.width() - 1, rect.height() }, base_color);
+
+ // Bottom line
+ painter.draw_line({ 2, rect.height() - 1 }, { rect.width() - 3, rect.height() - 1 }, shadow_color2);
+
+ // Left side
+ painter.draw_line({ 0, 0 }, { 0, rect.height() - 3 }, highlight_color2);
+ painter.set_pixel({ 1, rect.height() - 2 }, highlight_color2);
+
+ // Right side
+ IntPoint top_right_outer { rect.width() - 1, 0 };
+ IntPoint bottom_right_outer { rect.width() - 1, rect.height() - 3 };
+ painter.draw_line(top_right_outer, bottom_right_outer, shadow_color2);
+
+ IntPoint top_right_inner { rect.width() - 2, 0 };
+ IntPoint bottom_right_inner { rect.width() - 2, rect.height() - 3 };
+ painter.draw_line(top_right_inner, bottom_right_inner, shadow_color1);
+
+ painter.set_pixel(rect.width() - 2, rect.height() - 2, shadow_color2);
+ }
+}
+
+static void paint_button_new(Painter& painter, const IntRect& a_rect, const Palette& palette, bool pressed, bool checked, bool hovered, bool enabled, bool focused)
+{
+ Color button_color = palette.button();
+ Color highlight_color = palette.threed_highlight();
+ Color shadow_color1 = palette.threed_shadow1();
+ Color shadow_color2 = palette.threed_shadow2();
+
+ if (checked && enabled) {
+ if (hovered)
+ button_color = palette.hover_highlight();
+ else
+ button_color = palette.button();
+ } else if (hovered && enabled)
+ button_color = palette.hover_highlight();
+
+ PainterStateSaver saver(painter);
+
+ auto rect = a_rect;
+ if (focused) {
+ painter.draw_rect(a_rect, palette.threed_shadow2());
+ rect.shrink(2, 2);
+ }
+
+ painter.translate(rect.location());
+
+ if (pressed || checked) {
+ // Base
+ Gfx::IntRect base_rect { 1, 1, rect.width() - 2, rect.height() - 2 };
+
+ if (checked && !pressed)
+ painter.fill_rect_with_dither_pattern(base_rect, palette.button().lightened(1.3f), palette.button());
+ else
+ painter.fill_rect(base_rect, button_color);
+
+ // Top shadow
+ painter.draw_line({ 0, 0 }, { rect.width() - 2, 0 }, shadow_color2);
+ painter.draw_line({ 0, 0 }, { 0, rect.height() - 2 }, shadow_color2);
+
+ // Sunken shadow
+ painter.draw_line({ 1, 1 }, { rect.width() - 3, 1 }, shadow_color1);
+ painter.draw_line({ 1, 2 }, { 1, rect.height() - 3 }, shadow_color1);
+
+ // Outer highlight
+ painter.draw_line({ 0, rect.height() - 1 }, { rect.width() - 1, rect.height() - 1 }, highlight_color);
+ painter.draw_line({ rect.width() - 1, 0 }, { rect.width() - 1, rect.height() - 2 }, highlight_color);
+
+ // Inner highlight
+ painter.draw_line({ 1, rect.height() - 2 }, { rect.width() - 2, rect.height() - 2 }, palette.button());
+ painter.draw_line({ rect.width() - 2, 1 }, { rect.width() - 2, rect.height() - 3 }, palette.button());
+ } else {
+ // Base
+ painter.fill_rect({ 0, 0, rect.width(), rect.height() }, button_color);
+
+ // Top highlight
+ painter.draw_line({ 1, 1 }, { rect.width() - 3, 1 }, highlight_color);
+ painter.draw_line({ 1, 1 }, { 1, rect.height() - 3 }, highlight_color);
+
+ // Outer shadow
+ painter.draw_line({ 0, rect.height() - 1 }, { rect.width() - 1, rect.height() - 1 }, shadow_color2);
+ painter.draw_line({ rect.width() - 1, 0 }, { rect.width() - 1, rect.height() - 2 }, shadow_color2);
+
+ // Inner shadow
+ painter.draw_line({ 1, rect.height() - 2 }, { rect.width() - 2, rect.height() - 2 }, shadow_color1);
+ painter.draw_line({ rect.width() - 2, 1 }, { rect.width() - 2, rect.height() - 3 }, shadow_color1);
+ }
+}
+
+void ClassicStylePainter::paint_button(Painter& painter, const IntRect& rect, const Palette& palette, ButtonStyle button_style, bool pressed, bool hovered, bool checked, bool enabled, bool focused)
+{
+ if (button_style == ButtonStyle::Normal)
+ return paint_button_new(painter, rect, palette, pressed, checked, hovered, enabled, focused);
+
+ if (button_style == ButtonStyle::CoolBar && !enabled)
+ return;
+
+ Color button_color = palette.button();
+ Color highlight_color = palette.threed_highlight();
+ Color shadow_color = palette.threed_shadow1();
+
+ PainterStateSaver saver(painter);
+ painter.translate(rect.location());
+
+ if (pressed || checked) {
+ // Base
+ IntRect base_rect { 1, 1, rect.width() - 2, rect.height() - 2 };
+ if (checked && !pressed) {
+ painter.fill_rect_with_dither_pattern(base_rect, palette.button().lightened(1.3f), palette.button());
+ } else {
+ painter.fill_rect(base_rect, button_color);
+ }
+
+ // Sunken shadow
+ painter.draw_line({ 1, 1 }, { rect.width() - 2, 1 }, shadow_color);
+ painter.draw_line({ 1, 2 }, { 1, rect.height() - 2 }, shadow_color);
+
+ // Bottom highlight
+ painter.draw_line({ rect.width() - 2, 1 }, { rect.width() - 2, rect.height() - 3 }, highlight_color);
+ painter.draw_line({ 1, rect.height() - 2 }, { rect.width() - 2, rect.height() - 2 }, highlight_color);
+ } else if (button_style == ButtonStyle::CoolBar && hovered) {
+ // Base
+ painter.fill_rect({ 1, 1, rect.width() - 2, rect.height() - 2 }, button_color);
+
+ // Top highlight
+ painter.draw_line({ 1, 1 }, { rect.width() - 2, 1 }, highlight_color);
+ painter.draw_line({ 1, 2 }, { 1, rect.height() - 2 }, highlight_color);
+
+ // Bottom shadow
+ painter.draw_line({ rect.width() - 2, 1 }, { rect.width() - 2, rect.height() - 3 }, shadow_color);
+ painter.draw_line({ 1, rect.height() - 2 }, { rect.width() - 2, rect.height() - 2 }, shadow_color);
+ }
+}
+
+void ClassicStylePainter::paint_surface(Painter& painter, const IntRect& rect, const Palette& palette, bool paint_vertical_lines, bool paint_top_line)
+{
+ painter.fill_rect({ rect.x(), rect.y() + 1, rect.width(), rect.height() - 2 }, palette.button());
+ painter.draw_line(rect.top_left(), rect.top_right(), paint_top_line ? palette.threed_highlight() : palette.button());
+ painter.draw_line(rect.bottom_left(), rect.bottom_right(), palette.threed_shadow1());
+ if (paint_vertical_lines) {
+ painter.draw_line(rect.top_left().translated(0, 1), rect.bottom_left().translated(0, -1), palette.threed_highlight());
+ painter.draw_line(rect.top_right(), rect.bottom_right().translated(0, -1), palette.threed_shadow1());
+ }
+}
+
+void ClassicStylePainter::paint_frame(Painter& painter, const IntRect& rect, const Palette& palette, FrameShape shape, FrameShadow shadow, int thickness, bool skip_vertical_lines)
+{
+ Color top_left_color;
+ Color bottom_right_color;
+ Color dark_shade = palette.threed_shadow1();
+ Color light_shade = palette.threed_highlight();
+
+ if (shape == FrameShape::Container && thickness >= 2) {
+ if (shadow == FrameShadow::Raised) {
+ dark_shade = palette.threed_shadow2();
+ }
+ }
+
+ if (shadow == FrameShadow::Raised) {
+ top_left_color = light_shade;
+ bottom_right_color = dark_shade;
+ } else if (shadow == FrameShadow::Sunken) {
+ top_left_color = dark_shade;
+ bottom_right_color = light_shade;
+ } else if (shadow == FrameShadow::Plain) {
+ top_left_color = dark_shade;
+ bottom_right_color = dark_shade;
+ }
+
+ if (thickness >= 1) {
+ painter.draw_line(rect.top_left(), rect.top_right(), top_left_color);
+ painter.draw_line(rect.bottom_left(), rect.bottom_right(), bottom_right_color);
+
+ if (shape != FrameShape::Panel || !skip_vertical_lines) {
+ painter.draw_line(rect.top_left().translated(0, 1), rect.bottom_left().translated(0, -1), top_left_color);
+ painter.draw_line(rect.top_right(), rect.bottom_right().translated(0, -1), bottom_right_color);
+ }
+ }
+
+ if (shape == FrameShape::Container && thickness >= 2) {
+ Color top_left_color;
+ Color bottom_right_color;
+ Color dark_shade = palette.threed_shadow2();
+ Color light_shade = palette.button();
+ if (shadow == FrameShadow::Raised) {
+ dark_shade = palette.threed_shadow1();
+ top_left_color = light_shade;
+ bottom_right_color = dark_shade;
+ } else if (shadow == FrameShadow::Sunken) {
+ top_left_color = dark_shade;
+ bottom_right_color = light_shade;
+ } else if (shadow == FrameShadow::Plain) {
+ top_left_color = dark_shade;
+ bottom_right_color = dark_shade;
+ }
+ IntRect inner_container_frame_rect = rect.shrunken(2, 2);
+ painter.draw_line(inner_container_frame_rect.top_left(), inner_container_frame_rect.top_right(), top_left_color);
+ painter.draw_line(inner_container_frame_rect.bottom_left(), inner_container_frame_rect.bottom_right(), bottom_right_color);
+ painter.draw_line(inner_container_frame_rect.top_left().translated(0, 1), inner_container_frame_rect.bottom_left().translated(0, -1), top_left_color);
+ painter.draw_line(inner_container_frame_rect.top_right(), inner_container_frame_rect.bottom_right().translated(0, -1), bottom_right_color);
+ }
+
+ if (shape == FrameShape::Box && thickness >= 2) {
+ swap(top_left_color, bottom_right_color);
+ IntRect inner_rect = rect.shrunken(2, 2);
+ painter.draw_line(inner_rect.top_left(), inner_rect.top_right(), top_left_color);
+ painter.draw_line(inner_rect.bottom_left(), inner_rect.bottom_right(), bottom_right_color);
+ painter.draw_line(inner_rect.top_left().translated(0, 1), inner_rect.bottom_left().translated(0, -1), top_left_color);
+ painter.draw_line(inner_rect.top_right(), inner_rect.bottom_right().translated(0, -1), bottom_right_color);
+ }
+}
+
+void ClassicStylePainter::paint_window_frame(Painter& painter, const IntRect& rect, const Palette& palette)
+{
+ Color base_color = palette.button();
+ Color dark_shade = palette.threed_shadow2();
+ Color mid_shade = palette.threed_shadow1();
+ Color light_shade = palette.threed_highlight();
+
+ painter.draw_line(rect.top_left(), rect.top_right(), base_color);
+ painter.draw_line(rect.top_left().translated(0, 1), rect.bottom_left(), base_color);
+ painter.draw_line(rect.top_left().translated(1, 1), rect.top_right().translated(-1, 1), light_shade);
+ painter.draw_line(rect.top_left().translated(1, 1), rect.bottom_left().translated(1, -1), light_shade);
+ painter.draw_line(rect.top_left().translated(2, 2), rect.top_right().translated(-2, 2), base_color);
+ painter.draw_line(rect.top_left().translated(2, 2), rect.bottom_left().translated(2, -2), base_color);
+ painter.draw_line(rect.top_left().translated(3, 3), rect.top_right().translated(-3, 3), base_color);
+ painter.draw_line(rect.top_left().translated(3, 3), rect.bottom_left().translated(3, -3), base_color);
+
+ painter.draw_line(rect.top_right(), rect.bottom_right(), dark_shade);
+ painter.draw_line(rect.top_right().translated(-1, 1), rect.bottom_right().translated(-1, -1), mid_shade);
+ painter.draw_line(rect.top_right().translated(-2, 2), rect.bottom_right().translated(-2, -2), base_color);
+ painter.draw_line(rect.top_right().translated(-3, 3), rect.bottom_right().translated(-3, -3), base_color);
+ painter.draw_line(rect.bottom_left(), rect.bottom_right(), dark_shade);
+ painter.draw_line(rect.bottom_left().translated(1, -1), rect.bottom_right().translated(-1, -1), mid_shade);
+ painter.draw_line(rect.bottom_left().translated(2, -2), rect.bottom_right().translated(-2, -2), base_color);
+ painter.draw_line(rect.bottom_left().translated(3, -3), rect.bottom_right().translated(-3, -3), base_color);
+}
+
+void ClassicStylePainter::paint_progress_bar(Painter& painter, const IntRect& rect, const Palette& palette, int min, int max, int value, const StringView& text)
+{
+ // First we fill the entire widget with the gradient. This incurs a bit of
+ // overdraw but ensures a consistent look throughout the progression.
+ Color start_color = palette.active_window_border1();
+ Color end_color = palette.active_window_border2();
+ painter.fill_rect_with_gradient(rect, start_color, end_color);
+
+ if (!text.is_null()) {
+ painter.draw_text(rect.translated(1, 1), text, TextAlignment::Center, palette.base_text());
+ painter.draw_text(rect, text, TextAlignment::Center, palette.base_text().inverted());
+ }
+
+ float range_size = max - min;
+ float progress = (value - min) / range_size;
+
+ // Then we carve out a hole in the remaining part of the widget.
+ // We draw the text a third time, clipped and inverse, for sharp contrast.
+ float progress_width = progress * rect.width();
+ IntRect hole_rect { (int)progress_width, 0, (int)(rect.width() - progress_width), rect.height() };
+ hole_rect.move_by(rect.location());
+ hole_rect.set_right_without_resize(rect.right());
+ PainterStateSaver saver(painter);
+ painter.fill_rect(hole_rect, palette.base());
+
+ painter.add_clip_rect(hole_rect);
+ if (!text.is_null())
+ painter.draw_text(rect.translated(0, 0), text, TextAlignment::Center, palette.base_text());
+}
+
+static RefPtr<Gfx::Bitmap> s_unfilled_circle_bitmap;
+static RefPtr<Gfx::Bitmap> s_filled_circle_bitmap;
+static RefPtr<Gfx::Bitmap> s_changing_filled_circle_bitmap;
+static RefPtr<Gfx::Bitmap> s_changing_unfilled_circle_bitmap;
+
+static const Gfx::Bitmap& circle_bitmap(bool checked, bool changing)
+{
+ if (changing)
+ return checked ? *s_changing_filled_circle_bitmap : *s_changing_unfilled_circle_bitmap;
+ return checked ? *s_filled_circle_bitmap : *s_unfilled_circle_bitmap;
+}
+
+void ClassicStylePainter::paint_radio_button(Painter& painter, const IntRect& rect, const Palette&, bool is_checked, bool is_being_pressed)
+{
+ if (!s_unfilled_circle_bitmap) {
+ s_unfilled_circle_bitmap = Bitmap::load_from_file("/res/icons/serenity/unfilled-radio-circle.png");
+ s_filled_circle_bitmap = Bitmap::load_from_file("/res/icons/serenity/filled-radio-circle.png");
+ s_changing_filled_circle_bitmap = Bitmap::load_from_file("/res/icons/serenity/changing-filled-radio-circle.png");
+ s_changing_unfilled_circle_bitmap = Bitmap::load_from_file("/res/icons/serenity/changing-unfilled-radio-circle.png");
+ }
+
+ auto& bitmap = circle_bitmap(is_checked, is_being_pressed);
+ painter.blit(rect.location(), bitmap, bitmap.rect());
+}
+
+static const char* s_checked_bitmap_data = {
+ " "
+ " # "
+ " ## "
+ " ### "
+ " ## ### "
+ " ##### "
+ " ### "
+ " # "
+ " "
+};
+
+static Gfx::CharacterBitmap* s_checked_bitmap;
+static const int s_checked_bitmap_width = 9;
+static const int s_checked_bitmap_height = 9;
+
+void ClassicStylePainter::paint_check_box(Painter& painter, const IntRect& rect, const Palette& palette, bool is_enabled, bool is_checked, bool is_being_pressed)
+{
+ painter.fill_rect(rect, is_enabled ? palette.base() : palette.window());
+ paint_frame(painter, rect, palette, Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2);
+
+ if (is_being_pressed) {
+ // FIXME: This color should not be hard-coded.
+ painter.draw_rect(rect.shrunken(4, 4), Color::MidGray);
+ }
+
+ if (is_checked) {
+ if (!s_checked_bitmap)
+ s_checked_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_checked_bitmap_data, s_checked_bitmap_width, s_checked_bitmap_height).leak_ref();
+ painter.draw_bitmap(rect.shrunken(4, 4).location(), *s_checked_bitmap, is_enabled ? palette.base_text() : palette.threed_shadow1());
+ }
+}
+
+void ClassicStylePainter::paint_transparency_grid(Painter& painter, const IntRect& rect, const Palette& palette)
+{
+ painter.fill_rect_with_checkerboard(rect, { 8, 8 }, palette.base().darkened(0.9), palette.base());
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/ClassicStylePainter.h b/Userland/Libraries/LibGfx/ClassicStylePainter.h
new file mode 100644
index 0000000000..de5302ecb5
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ClassicStylePainter.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020 Sarah Taube <metalflakecobaltpaint@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/StylePainter.h>
+
+namespace Gfx {
+
+class ClassicStylePainter : public BaseStylePainter {
+public:
+ void paint_button(Painter&, const IntRect&, const Palette&, ButtonStyle, bool pressed, bool hovered = false, bool checked = false, bool enabled = true, bool focused = false) override;
+ void paint_tab_button(Painter&, const IntRect&, const Palette&, bool active, bool hovered, bool enabled, bool top) override;
+ void paint_surface(Painter&, const IntRect&, const Palette&, bool paint_vertical_lines = true, bool paint_top_line = true) override;
+ void paint_frame(Painter&, const IntRect&, const Palette&, FrameShape, FrameShadow, int thickness, bool skip_vertical_lines = false) override;
+ void paint_window_frame(Painter&, const IntRect&, const Palette&) override;
+ void paint_progress_bar(Painter&, const IntRect&, const Palette&, int min, int max, int value, const StringView& text) override;
+ void paint_radio_button(Painter&, const IntRect&, const Palette&, bool is_checked, bool is_being_pressed) override;
+ void paint_check_box(Painter&, const IntRect&, const Palette&, bool is_enabled, bool is_checked, bool is_being_pressed) override;
+ void paint_transparency_grid(Painter&, const IntRect&, const Palette&) override;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/ClassicWindowTheme.cpp b/Userland/Libraries/LibGfx/ClassicWindowTheme.cpp
new file mode 100644
index 0000000000..5476bec441
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ClassicWindowTheme.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ClassicWindowTheme.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibGfx/Painter.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+namespace Gfx {
+
+ClassicWindowTheme::ClassicWindowTheme()
+{
+}
+
+ClassicWindowTheme::~ClassicWindowTheme()
+{
+}
+
+Gfx::IntRect ClassicWindowTheme::title_bar_icon_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const
+{
+ auto titlebar_rect = title_bar_rect(window_type, window_rect, palette);
+ Gfx::IntRect icon_rect {
+ titlebar_rect.x() + 2,
+ titlebar_rect.y(),
+ 16,
+ 16,
+ };
+ icon_rect.center_vertically_within(titlebar_rect);
+ icon_rect.move_by(0, 1);
+ return icon_rect;
+}
+
+Gfx::IntRect ClassicWindowTheme::title_bar_text_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const
+{
+ auto titlebar_rect = title_bar_rect(window_type, window_rect, palette);
+ auto titlebar_icon_rect = title_bar_icon_rect(window_type, window_rect, palette);
+ return {
+ titlebar_rect.x() + 3 + titlebar_icon_rect.width() + 2,
+ titlebar_rect.y(),
+ titlebar_rect.width() - 5 - titlebar_icon_rect.width() - 2,
+ titlebar_rect.height()
+ };
+}
+
+void ClassicWindowTheme::paint_normal_frame(Painter& painter, WindowState window_state, const IntRect& window_rect, const StringView& title_text, const Bitmap& icon, const Palette& palette, const IntRect& leftmost_button_rect) const
+{
+ auto frame_rect = frame_rect_for_window(WindowType::Normal, window_rect, palette);
+ frame_rect.set_location({ 0, 0 });
+ Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
+
+ auto& title_font = FontDatabase::default_bold_font();
+
+ auto titlebar_rect = title_bar_rect(WindowType::Normal, window_rect, palette);
+ auto titlebar_icon_rect = title_bar_icon_rect(WindowType::Normal, window_rect, palette);
+ auto titlebar_inner_rect = title_bar_text_rect(WindowType::Normal, window_rect, palette);
+ auto titlebar_title_rect = titlebar_inner_rect;
+ titlebar_title_rect.set_width(FontDatabase::default_bold_font().width(title_text));
+
+ auto [title_color, border_color, border_color2, stripes_color, shadow_color] = compute_frame_colors(window_state, palette);
+
+ painter.draw_line(titlebar_rect.bottom_left().translated(0, 1), titlebar_rect.bottom_right().translated(0, 1), palette.button());
+ painter.draw_line(titlebar_rect.bottom_left().translated(0, 2), titlebar_rect.bottom_right().translated(0, 2), palette.button());
+
+ painter.fill_rect_with_gradient(titlebar_rect, border_color, border_color2);
+
+ int stripe_left = titlebar_title_rect.right() + 5;
+ int stripe_right = leftmost_button_rect.left() - 3;
+ if (stripe_left && stripe_right && stripe_left < stripe_right) {
+ for (int i = 2; i <= titlebar_inner_rect.height() - 2; i += 2) {
+ painter.draw_line({ stripe_left, titlebar_inner_rect.y() + i }, { stripe_right, titlebar_inner_rect.y() + i }, stripes_color);
+ }
+ }
+
+ auto clipped_title_rect = titlebar_title_rect;
+ clipped_title_rect.set_width(stripe_right - clipped_title_rect.x());
+ if (!clipped_title_rect.is_empty()) {
+ painter.draw_text(clipped_title_rect.translated(1, 2), title_text, title_font, Gfx::TextAlignment::CenterLeft, shadow_color, Gfx::TextElision::Right);
+ // FIXME: The translated(0, 1) wouldn't be necessary if we could center text based on its baseline.
+ painter.draw_text(clipped_title_rect.translated(0, 1), title_text, title_font, Gfx::TextAlignment::CenterLeft, title_color, Gfx::TextElision::Right);
+ }
+
+ painter.blit(titlebar_icon_rect.location(), icon, icon.rect());
+}
+
+IntRect ClassicWindowTheme::title_bar_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const
+{
+ auto& title_font = FontDatabase::default_bold_font();
+ auto window_titlebar_height = title_bar_height(palette);
+ // FIXME: The top of the titlebar doesn't get redrawn properly if this padding is different
+ int total_vertical_padding = title_font.glyph_height() - 1;
+
+ if (window_type == WindowType::Notification)
+ return { window_rect.width() + 3, total_vertical_padding / 2 - 1, window_titlebar_height, window_rect.height() };
+ return { 4, 4, window_rect.width(), window_titlebar_height };
+}
+
+ClassicWindowTheme::FrameColors ClassicWindowTheme::compute_frame_colors(WindowState state, const Palette& palette) const
+{
+ switch (state) {
+ case WindowState::Highlighted:
+ return { palette.highlight_window_title(), palette.highlight_window_border1(), palette.highlight_window_border2(), palette.highlight_window_title_stripes(), palette.highlight_window_title_shadow() };
+ case WindowState::Moving:
+ return { palette.moving_window_title(), palette.moving_window_border1(), palette.moving_window_border2(), palette.moving_window_title_stripes(), palette.moving_window_title_shadow() };
+ case WindowState::Active:
+ return { palette.active_window_title(), palette.active_window_border1(), palette.active_window_border2(), palette.active_window_title_stripes(), palette.active_window_title_shadow() };
+ case WindowState::Inactive:
+ return { palette.inactive_window_title(), palette.inactive_window_border1(), palette.inactive_window_border2(), palette.inactive_window_title_stripes(), palette.inactive_window_title_shadow() };
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+void ClassicWindowTheme::paint_notification_frame(Painter& painter, const IntRect& window_rect, const Palette& palette, const IntRect& close_button_rect) const
+{
+ auto frame_rect = frame_rect_for_window(WindowType::Notification, window_rect, palette);
+ frame_rect.set_location({ 0, 0 });
+ Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
+
+ auto titlebar_rect = title_bar_rect(WindowType::Notification, window_rect, palette);
+ painter.fill_rect_with_gradient(Gfx::Orientation::Vertical, titlebar_rect, palette.active_window_border1(), palette.active_window_border2());
+
+ int stripe_top = close_button_rect.bottom() + 4;
+ int stripe_bottom = window_rect.height() - 3;
+ if (stripe_top && stripe_bottom && stripe_top < stripe_bottom) {
+ for (int i = 2; i <= palette.window_title_height() - 2; i += 2) {
+ painter.draw_line({ titlebar_rect.x() + i, stripe_top }, { titlebar_rect.x() + i, stripe_bottom }, palette.active_window_title_stripes());
+ }
+ }
+}
+
+IntRect ClassicWindowTheme::frame_rect_for_window(WindowType window_type, const IntRect& window_rect, const Gfx::Palette& palette) const
+{
+ auto window_titlebar_height = title_bar_height(palette);
+
+ switch (window_type) {
+ case WindowType::Normal:
+ return {
+ window_rect.x() - 4,
+ window_rect.y() - window_titlebar_height - 6,
+ window_rect.width() + 8,
+ window_rect.height() + 10 + window_titlebar_height
+ };
+ case WindowType::Notification:
+ return {
+ window_rect.x() - 3,
+ window_rect.y() - 3,
+ window_rect.width() + 6 + window_titlebar_height,
+ window_rect.height() + 6
+ };
+ default:
+ return window_rect;
+ }
+}
+
+Vector<IntRect> ClassicWindowTheme::layout_buttons(WindowType window_type, const IntRect& window_rect, const Palette& palette, size_t buttons) const
+{
+ int window_button_width = palette.window_title_button_width();
+ int window_button_height = palette.window_title_button_height();
+ int pos;
+ Vector<IntRect> button_rects;
+ if (window_type == WindowType::Notification)
+ pos = title_bar_rect(window_type, window_rect, palette).top() + 2;
+ else
+ pos = title_bar_text_rect(window_type, window_rect, palette).right() + 1;
+
+ for (size_t i = 0; i < buttons; i++) {
+ if (window_type == WindowType::Notification) {
+ // The button height & width have to be equal or it leaks out of its area
+ Gfx::IntRect rect { 0, pos, window_button_height, window_button_height };
+ rect.center_horizontally_within(title_bar_rect(window_type, window_rect, palette));
+ button_rects.append(rect);
+ pos += window_button_height;
+ } else {
+ pos -= window_button_width;
+ Gfx::IntRect rect { pos, 0, window_button_width, window_button_height };
+ rect.center_vertically_within(title_bar_text_rect(window_type, window_rect, palette));
+ button_rects.append(rect);
+ }
+ }
+ return button_rects;
+}
+
+int ClassicWindowTheme::title_bar_height(const Palette& palette) const
+{
+ auto& title_font = FontDatabase::default_bold_font();
+ return max(palette.window_title_height(), title_font.glyph_height() + 8);
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/ClassicWindowTheme.h b/Userland/Libraries/LibGfx/ClassicWindowTheme.h
new file mode 100644
index 0000000000..b6ae32e891
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ClassicWindowTheme.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Color.h>
+#include <LibGfx/WindowTheme.h>
+
+namespace Gfx {
+
+class ClassicWindowTheme final : public WindowTheme {
+public:
+ ClassicWindowTheme();
+ virtual ~ClassicWindowTheme() override;
+
+ virtual void paint_normal_frame(Painter&, WindowState, const IntRect& window_rect, const StringView& title, const Bitmap& icon, const Palette&, const IntRect& leftmost_button_rect) const override;
+ virtual void paint_notification_frame(Painter&, const IntRect& window_rect, const Palette&, const IntRect& close_button_rect) const override;
+
+ virtual int title_bar_height(const Palette&) const override;
+ virtual IntRect title_bar_rect(WindowType, const IntRect& window_rect, const Palette&) const override;
+ virtual IntRect title_bar_icon_rect(WindowType, const IntRect& window_rect, const Palette&) const override;
+ virtual IntRect title_bar_text_rect(WindowType, const IntRect& window_rect, const Palette&) const override;
+
+ virtual IntRect frame_rect_for_window(WindowType, const IntRect& window_rect, const Palette&) const override;
+
+ virtual Vector<IntRect> layout_buttons(WindowType, const IntRect& window_rect, const Palette&, size_t buttons) const override;
+
+private:
+ struct FrameColors {
+ Color title_color;
+ Color border_color;
+ Color border_color2;
+ Color title_stripes_color;
+ Color title_shadow_color;
+ };
+
+ FrameColors compute_frame_colors(WindowState, const Palette&) const;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Color.cpp b/Userland/Libraries/LibGfx/Color.cpp
new file mode 100644
index 0000000000..f8a0f244f4
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Color.cpp
@@ -0,0 +1,440 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/Optional.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <LibGfx/Color.h>
+#include <LibGfx/SystemTheme.h>
+#include <LibIPC/Decoder.h>
+#include <LibIPC/Encoder.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace Gfx {
+
+Color::Color(NamedColor named)
+{
+ if (named == Transparent) {
+ m_value = 0;
+ return;
+ }
+
+ struct {
+ u8 r;
+ u8 g;
+ u8 b;
+ } rgb;
+
+ switch (named) {
+ case Black:
+ rgb = { 0, 0, 0 };
+ break;
+ case White:
+ rgb = { 255, 255, 255 };
+ break;
+ case Red:
+ rgb = { 255, 0, 0 };
+ break;
+ case Green:
+ rgb = { 0, 255, 0 };
+ break;
+ case Cyan:
+ rgb = { 0, 255, 255 };
+ break;
+ case DarkCyan:
+ rgb = { 0, 127, 127 };
+ break;
+ case MidCyan:
+ rgb = { 0, 192, 192 };
+ break;
+ case Blue:
+ rgb = { 0, 0, 255 };
+ break;
+ case Yellow:
+ rgb = { 255, 255, 0 };
+ break;
+ case Magenta:
+ rgb = { 255, 0, 255 };
+ break;
+ case DarkGray:
+ rgb = { 64, 64, 64 };
+ break;
+ case MidGray:
+ rgb = { 127, 127, 127 };
+ break;
+ case LightGray:
+ rgb = { 192, 192, 192 };
+ break;
+ case MidGreen:
+ rgb = { 0, 192, 0 };
+ break;
+ case MidBlue:
+ rgb = { 0, 0, 192 };
+ break;
+ case MidRed:
+ rgb = { 192, 0, 0 };
+ break;
+ case MidMagenta:
+ rgb = { 192, 0, 192 };
+ break;
+ case DarkGreen:
+ rgb = { 0, 128, 0 };
+ break;
+ case DarkBlue:
+ rgb = { 0, 0, 128 };
+ break;
+ case DarkRed:
+ rgb = { 128, 0, 0 };
+ break;
+ case WarmGray:
+ rgb = { 212, 208, 200 };
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+
+ m_value = 0xff000000 | (rgb.r << 16) | (rgb.g << 8) | rgb.b;
+}
+
+String Color::to_string() const
+{
+ return String::format("#%02x%02x%02x%02x", red(), green(), blue(), alpha());
+}
+
+String Color::to_string_without_alpha() const
+{
+ return String::format("#%02x%02x%02x", red(), green(), blue());
+}
+
+static Optional<Color> parse_rgb_color(const StringView& string)
+{
+ ASSERT(string.starts_with("rgb("));
+ ASSERT(string.ends_with(")"));
+
+ auto substring = string.substring_view(4, string.length() - 5);
+ auto parts = substring.split_view(',');
+
+ if (parts.size() != 3)
+ return {};
+
+ auto r = parts[0].to_uint().value_or(256);
+ auto g = parts[1].to_uint().value_or(256);
+ auto b = parts[2].to_uint().value_or(256);
+
+ if (r > 255 || g > 255 || b > 255)
+ return {};
+
+ return Color(r, g, b);
+}
+
+static Optional<Color> parse_rgba_color(const StringView& string)
+{
+ ASSERT(string.starts_with("rgba("));
+ ASSERT(string.ends_with(")"));
+
+ auto substring = string.substring_view(5, string.length() - 6);
+ auto parts = substring.split_view(',');
+
+ if (parts.size() != 4)
+ return {};
+
+ auto r = parts[0].to_int().value_or(256);
+ auto g = parts[1].to_int().value_or(256);
+ auto b = parts[2].to_int().value_or(256);
+
+ double alpha = strtod(parts[3].to_string().characters(), nullptr);
+ unsigned a = alpha * 255;
+
+ if (r > 255 || g > 255 || b > 255 || a > 255)
+ return {};
+
+ return Color(r, g, b, a);
+}
+
+Optional<Color> Color::from_string(const StringView& string)
+{
+ if (string.is_empty())
+ return {};
+
+ struct ColorAndWebName {
+ constexpr ColorAndWebName(RGBA32 c, const char* n)
+ : color(c)
+ , name(n)
+ {
+ }
+ RGBA32 color;
+ StringView name;
+ };
+
+ constexpr ColorAndWebName web_colors[] = {
+ // CSS Level 1
+ { 0x000000, "black" },
+ { 0xc0c0c0, "silver" },
+ { 0x808080, "gray" },
+ { 0xffffff, "white" },
+ { 0x800000, "maroon" },
+ { 0xff0000, "red" },
+ { 0x800080, "purple" },
+ { 0xff00ff, "fuchsia" },
+ { 0x008000, "green" },
+ { 0x00ff00, "lime" },
+ { 0x808000, "olive" },
+ { 0xffff00, "yellow" },
+ { 0x000080, "navy" },
+ { 0x0000ff, "blue" },
+ { 0x008080, "teal" },
+ { 0x00ffff, "aqua" },
+ // CSS Level 2 (Revision 1)
+ { 0xffa500, "orange" },
+ // CSS Color Module Level 3
+ { 0xf0f8ff, "aliceblue" },
+ { 0xfaebd7, "antiquewhite" },
+ { 0x7fffd4, "aquamarine" },
+ { 0xf0ffff, "azure" },
+ { 0xf5f5dc, "beige" },
+ { 0xffe4c4, "bisque" },
+ { 0xffebcd, "blanchedalmond" },
+ { 0x8a2be2, "blueviolet" },
+ { 0xa52a2a, "brown" },
+ { 0xdeb887, "burlywood" },
+ { 0x5f9ea0, "cadetblue" },
+ { 0x7fff00, "chartreuse" },
+ { 0xd2691e, "chocolate" },
+ { 0xff7f50, "coral" },
+ { 0x6495ed, "cornflowerblue" },
+ { 0xfff8dc, "cornsilk" },
+ { 0xdc143c, "crimson" },
+ { 0x00ffff, "cyan" },
+ { 0x00008b, "darkblue" },
+ { 0x008b8b, "darkcyan" },
+ { 0xb8860b, "darkgoldenrod" },
+ { 0xa9a9a9, "darkgray" },
+ { 0x006400, "darkgreen" },
+ { 0xa9a9a9, "darkgrey" },
+ { 0xbdb76b, "darkkhaki" },
+ { 0x8b008b, "darkmagenta" },
+ { 0x556b2f, "darkolivegreen" },
+ { 0xff8c00, "darkorange" },
+ { 0x9932cc, "darkorchid" },
+ { 0x8b0000, "darkred" },
+ { 0xe9967a, "darksalmon" },
+ { 0x8fbc8f, "darkseagreen" },
+ { 0x483d8b, "darkslateblue" },
+ { 0x2f4f4f, "darkslategray" },
+ { 0x2f4f4f, "darkslategrey" },
+ { 0x00ced1, "darkturquoise" },
+ { 0x9400d3, "darkviolet" },
+ { 0xff1493, "deeppink" },
+ { 0x00bfff, "deepskyblue" },
+ { 0x696969, "dimgray" },
+ { 0x696969, "dimgrey" },
+ { 0x1e90ff, "dodgerblue" },
+ { 0xb22222, "firebrick" },
+ { 0xfffaf0, "floralwhite" },
+ { 0x228b22, "forestgreen" },
+ { 0xdcdcdc, "gainsboro" },
+ { 0xf8f8ff, "ghostwhite" },
+ { 0xffd700, "gold" },
+ { 0xdaa520, "goldenrod" },
+ { 0xadff2f, "greenyellow" },
+ { 0x808080, "grey" },
+ { 0xf0fff0, "honeydew" },
+ { 0xff69b4, "hotpink" },
+ { 0xcd5c5c, "indianred" },
+ { 0x4b0082, "indigo" },
+ { 0xfffff0, "ivory" },
+ { 0xf0e68c, "khaki" },
+ { 0xe6e6fa, "lavender" },
+ { 0xfff0f5, "lavenderblush" },
+ { 0x7cfc00, "lawngreen" },
+ { 0xfffacd, "lemonchiffon" },
+ { 0xadd8e6, "lightblue" },
+ { 0xf08080, "lightcoral" },
+ { 0xe0ffff, "lightcyan" },
+ { 0xfafad2, "lightgoldenrodyellow" },
+ { 0xd3d3d3, "lightgray" },
+ { 0x90ee90, "lightgreen" },
+ { 0xd3d3d3, "lightgrey" },
+ { 0xffb6c1, "lightpink" },
+ { 0xffa07a, "lightsalmon" },
+ { 0x20b2aa, "lightseagreen" },
+ { 0x87cefa, "lightskyblue" },
+ { 0x778899, "lightslategray" },
+ { 0x778899, "lightslategrey" },
+ { 0xb0c4de, "lightsteelblue" },
+ { 0xffffe0, "lightyellow" },
+ { 0x32cd32, "limegreen" },
+ { 0xfaf0e6, "linen" },
+ { 0xff00ff, "magenta" },
+ { 0x66cdaa, "mediumaquamarine" },
+ { 0x0000cd, "mediumblue" },
+ { 0xba55d3, "mediumorchid" },
+ { 0x9370db, "mediumpurple" },
+ { 0x3cb371, "mediumseagreen" },
+ { 0x7b68ee, "mediumslateblue" },
+ { 0x00fa9a, "mediumspringgreen" },
+ { 0x48d1cc, "mediumturquoise" },
+ { 0xc71585, "mediumvioletred" },
+ { 0x191970, "midnightblue" },
+ { 0xf5fffa, "mintcream" },
+ { 0xffe4e1, "mistyrose" },
+ { 0xffe4b5, "moccasin" },
+ { 0xffdead, "navajowhite" },
+ { 0xfdf5e6, "oldlace" },
+ { 0x6b8e23, "olivedrab" },
+ { 0xff4500, "orangered" },
+ { 0xda70d6, "orchid" },
+ { 0xeee8aa, "palegoldenrod" },
+ { 0x98fb98, "palegreen" },
+ { 0xafeeee, "paleturquoise" },
+ { 0xdb7093, "palevioletred" },
+ { 0xffefd5, "papayawhip" },
+ { 0xffdab9, "peachpuff" },
+ { 0xcd853f, "peru" },
+ { 0xffc0cb, "pink" },
+ { 0xdda0dd, "plum" },
+ { 0xb0e0e6, "powderblue" },
+ { 0xbc8f8f, "rosybrown" },
+ { 0x4169e1, "royalblue" },
+ { 0x8b4513, "saddlebrown" },
+ { 0xfa8072, "salmon" },
+ { 0xf4a460, "sandybrown" },
+ { 0x2e8b57, "seagreen" },
+ { 0xfff5ee, "seashell" },
+ { 0xa0522d, "sienna" },
+ { 0x87ceeb, "skyblue" },
+ { 0x6a5acd, "slateblue" },
+ { 0x708090, "slategray" },
+ { 0x708090, "slategrey" },
+ { 0xfffafa, "snow" },
+ { 0x00ff7f, "springgreen" },
+ { 0x4682b4, "steelblue" },
+ { 0xd2b48c, "tan" },
+ { 0xd8bfd8, "thistle" },
+ { 0xff6347, "tomato" },
+ { 0x40e0d0, "turquoise" },
+ { 0xee82ee, "violet" },
+ { 0xf5deb3, "wheat" },
+ { 0xf5f5f5, "whitesmoke" },
+ { 0x9acd32, "yellowgreen" },
+ // CSS Color Module Level 4
+ { 0x663399, "rebeccapurple" },
+ // (Fallback)
+ { 0x000000, nullptr }
+ };
+
+ for (size_t i = 0; !web_colors[i].name.is_null(); ++i) {
+ if (string == web_colors[i].name)
+ return Color::from_rgb(web_colors[i].color);
+ }
+
+ if (string.starts_with("rgb(") && string.ends_with(")"))
+ return parse_rgb_color(string);
+
+ if (string.starts_with("rgba(") && string.ends_with(")"))
+ return parse_rgba_color(string);
+
+ if (string[0] != '#')
+ return {};
+
+ auto hex_nibble_to_u8 = [](char nibble) -> Optional<u8> {
+ if (!isxdigit(nibble))
+ return {};
+ if (nibble >= '0' && nibble <= '9')
+ return nibble - '0';
+ return 10 + (tolower(nibble) - 'a');
+ };
+
+ if (string.length() == 4) {
+ Optional<u8> r = hex_nibble_to_u8(string[1]);
+ Optional<u8> g = hex_nibble_to_u8(string[2]);
+ Optional<u8> b = hex_nibble_to_u8(string[3]);
+ if (!r.has_value() || !g.has_value() || !b.has_value())
+ return {};
+ return Color(r.value() * 17, g.value() * 17, b.value() * 17);
+ }
+
+ if (string.length() == 5) {
+ Optional<u8> r = hex_nibble_to_u8(string[1]);
+ Optional<u8> g = hex_nibble_to_u8(string[2]);
+ Optional<u8> b = hex_nibble_to_u8(string[3]);
+ Optional<u8> a = hex_nibble_to_u8(string[4]);
+ if (!r.has_value() || !g.has_value() || !b.has_value() || !a.has_value())
+ return {};
+ return Color(r.value() * 17, g.value() * 17, b.value() * 17, a.value() * 17);
+ }
+
+ if (string.length() != 7 && string.length() != 9)
+ return {};
+
+ auto to_hex = [&](char c1, char c2) -> Optional<u8> {
+ auto nib1 = hex_nibble_to_u8(c1);
+ auto nib2 = hex_nibble_to_u8(c2);
+ if (!nib1.has_value() || !nib2.has_value())
+ return {};
+ return nib1.value() << 4 | nib2.value();
+ };
+
+ Optional<u8> r = to_hex(string[1], string[2]);
+ Optional<u8> g = to_hex(string[3], string[4]);
+ Optional<u8> b = to_hex(string[5], string[6]);
+ Optional<u8> a = string.length() == 9 ? to_hex(string[7], string[8]) : Optional<u8>(255);
+
+ if (!r.has_value() || !g.has_value() || !b.has_value() || !a.has_value())
+ return {};
+
+ return Color(r.value(), g.value(), b.value(), a.value());
+}
+
+const LogStream& operator<<(const LogStream& stream, Color value)
+{
+ return stream << value.to_string();
+}
+}
+
+bool IPC::encode(IPC::Encoder& encoder, const Color& color)
+{
+ encoder << color.value();
+ return true;
+}
+
+bool IPC::decode(IPC::Decoder& decoder, Color& color)
+{
+ u32 rgba = 0;
+ if (!decoder.decode(rgba))
+ return false;
+ color = Color::from_rgba(rgba);
+ return true;
+}
+
+void AK::Formatter<Gfx::Color>::format(FormatBuilder& builder, const Gfx::Color& value)
+{
+ Formatter<StringView>::format(builder, value.to_string());
+}
diff --git a/Userland/Libraries/LibGfx/Color.h b/Userland/Libraries/LibGfx/Color.h
new file mode 100644
index 0000000000..e30cacba6c
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Color.h
@@ -0,0 +1,333 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Assertions.h>
+#include <AK/Format.h>
+#include <AK/Forward.h>
+#include <AK/SIMD.h>
+#include <AK/StdLibExtras.h>
+#include <LibIPC/Forward.h>
+
+namespace Gfx {
+
+enum class ColorRole;
+typedef u32 RGBA32;
+
+constexpr u32 make_rgb(u8 r, u8 g, u8 b)
+{
+ return ((r << 16) | (g << 8) | b);
+}
+
+struct HSV {
+ double hue { 0 };
+ double saturation { 0 };
+ double value { 0 };
+};
+
+class Color {
+public:
+ enum NamedColor {
+ Transparent,
+ Black,
+ White,
+ Red,
+ Green,
+ Cyan,
+ Blue,
+ Yellow,
+ Magenta,
+ DarkGray,
+ MidGray,
+ LightGray,
+ WarmGray,
+ DarkCyan,
+ DarkGreen,
+ DarkBlue,
+ DarkRed,
+ MidCyan,
+ MidGreen,
+ MidRed,
+ MidBlue,
+ MidMagenta,
+ };
+
+ constexpr Color() { }
+ Color(NamedColor);
+ constexpr Color(u8 r, u8 g, u8 b)
+ : m_value(0xff000000 | (r << 16) | (g << 8) | b)
+ {
+ }
+ constexpr Color(u8 r, u8 g, u8 b, u8 a)
+ : m_value((a << 24) | (r << 16) | (g << 8) | b)
+ {
+ }
+
+ static constexpr Color from_rgb(unsigned rgb) { return Color(rgb | 0xff000000); }
+ static constexpr Color from_rgba(unsigned rgba) { return Color(rgba); }
+
+ constexpr u8 red() const { return (m_value >> 16) & 0xff; }
+ constexpr u8 green() const { return (m_value >> 8) & 0xff; }
+ constexpr u8 blue() const { return m_value & 0xff; }
+ constexpr u8 alpha() const { return (m_value >> 24) & 0xff; }
+
+ void set_alpha(u8 value)
+ {
+ m_value &= 0x00ffffff;
+ m_value |= value << 24;
+ }
+
+ constexpr void set_red(u8 value)
+ {
+ m_value &= 0xff00ffff;
+ m_value |= value << 16;
+ }
+
+ constexpr void set_green(u8 value)
+ {
+ m_value &= 0xffff00ff;
+ m_value |= value << 8;
+ }
+
+ constexpr void set_blue(u8 value)
+ {
+ m_value &= 0xffffff00;
+ m_value |= value;
+ }
+
+ Color with_alpha(u8 alpha) const
+ {
+ return Color((m_value & 0x00ffffff) | alpha << 24);
+ }
+
+ Color blend(Color source) const
+ {
+ if (!alpha() || source.alpha() == 255)
+ return source;
+
+ if (!source.alpha())
+ return *this;
+
+#ifdef __SSE__
+ using AK::SIMD::i32x4;
+
+ const i32x4 color = {
+ red(),
+ green(),
+ blue()
+ };
+ const i32x4 source_color = {
+ source.red(),
+ source.green(),
+ source.blue()
+ };
+
+ const int d = 255 * (alpha() + source.alpha()) - alpha() * source.alpha();
+ const i32x4 out = (color * alpha() * (255 - source.alpha()) + 255 * source.alpha() * source_color) / d;
+ return Color(out[0], out[1], out[2], d / 255);
+#else
+ int d = 255 * (alpha() + source.alpha()) - alpha() * source.alpha();
+ u8 r = (red() * alpha() * (255 - source.alpha()) + 255 * source.alpha() * source.red()) / d;
+ u8 g = (green() * alpha() * (255 - source.alpha()) + 255 * source.alpha() * source.green()) / d;
+ u8 b = (blue() * alpha() * (255 - source.alpha()) + 255 * source.alpha() * source.blue()) / d;
+ u8 a = d / 255;
+ return Color(r, g, b, a);
+#endif
+ }
+
+ Color to_grayscale() const
+ {
+ int gray = (red() + green() + blue()) / 3;
+ return Color(gray, gray, gray, alpha());
+ }
+
+ Color darkened(float amount = 0.5f) const
+ {
+ return Color(red() * amount, green() * amount, blue() * amount, alpha());
+ }
+
+ Color lightened(float amount = 1.2f) const
+ {
+ return Color(min(255, (int)((float)red() * amount)), min(255, (int)((float)green() * amount)), min(255, (int)((float)blue() * amount)), alpha());
+ }
+
+ Color inverted() const
+ {
+ return Color(~red(), ~green(), ~blue(), alpha());
+ }
+
+ Color xored(const Color& other) const
+ {
+ return Color(((other.m_value ^ m_value) & 0x00ffffff) | (m_value & 0xff000000));
+ }
+
+ RGBA32 value() const { return m_value; }
+
+ bool operator==(const Color& other) const
+ {
+ return m_value == other.m_value;
+ }
+
+ bool operator!=(const Color& other) const
+ {
+ return m_value != other.m_value;
+ }
+
+ String to_string() const;
+ String to_string_without_alpha() const;
+ static Optional<Color> from_string(const StringView&);
+
+ HSV to_hsv() const
+ {
+ HSV hsv;
+ double r = static_cast<double>(red()) / 255.0;
+ double g = static_cast<double>(green()) / 255.0;
+ double b = static_cast<double>(blue()) / 255.0;
+ double max = AK::max(AK::max(r, g), b);
+ double min = AK::min(AK::min(r, g), b);
+ double chroma = max - min;
+
+ if (!chroma)
+ hsv.hue = 0.0;
+ else if (max == r)
+ hsv.hue = (60.0 * ((g - b) / chroma)) + 360.0;
+ else if (max == g)
+ hsv.hue = (60.0 * ((b - r) / chroma)) + 120.0;
+ else
+ hsv.hue = (60.0 * ((r - g) / chroma)) + 240.0;
+
+ if (hsv.hue >= 360.0)
+ hsv.hue -= 360.0;
+
+ if (!max)
+ hsv.saturation = 0;
+ else
+ hsv.saturation = chroma / max;
+
+ hsv.value = max;
+
+ ASSERT(hsv.hue >= 0.0 && hsv.hue < 360.0);
+ ASSERT(hsv.saturation >= 0.0 && hsv.saturation <= 1.0);
+ ASSERT(hsv.value >= 0.0 && hsv.value <= 1.0);
+
+ return hsv;
+ }
+
+ static Color from_hsv(double hue, double saturation, double value)
+ {
+ return from_hsv({ hue, saturation, value });
+ }
+
+ static Color from_hsv(const HSV& hsv)
+ {
+ ASSERT(hsv.hue >= 0.0 && hsv.hue < 360.0);
+ ASSERT(hsv.saturation >= 0.0 && hsv.saturation <= 1.0);
+ ASSERT(hsv.value >= 0.0 && hsv.value <= 1.0);
+
+ double hue = hsv.hue;
+ double saturation = hsv.saturation;
+ double value = hsv.value;
+
+ int high = static_cast<int>(hue / 60.0) % 6;
+ double f = (hue / 60.0) - high;
+ double c1 = value * (1.0 - saturation);
+ double c2 = value * (1.0 - saturation * f);
+ double c3 = value * (1.0 - saturation * (1.0 - f));
+
+ double r = 0;
+ double g = 0;
+ double b = 0;
+
+ switch (high) {
+ case 0:
+ r = value;
+ g = c3;
+ b = c1;
+ break;
+ case 1:
+ r = c2;
+ g = value;
+ b = c1;
+ break;
+ case 2:
+ r = c1;
+ g = value;
+ b = c3;
+ break;
+ case 3:
+ r = c1;
+ g = c2;
+ b = value;
+ break;
+ case 4:
+ r = c3;
+ g = c1;
+ b = value;
+ break;
+ case 5:
+ r = value;
+ g = c1;
+ b = c2;
+ break;
+ }
+
+ u8 out_r = (u8)(r * 255);
+ u8 out_g = (u8)(g * 255);
+ u8 out_b = (u8)(b * 255);
+ return Color(out_r, out_g, out_b);
+ }
+
+private:
+ constexpr explicit Color(RGBA32 rgba)
+ : m_value(rgba)
+ {
+ }
+
+ RGBA32 m_value { 0 };
+};
+
+const LogStream& operator<<(const LogStream&, Color);
+
+}
+
+using Gfx::Color;
+
+namespace AK {
+
+template<>
+struct Formatter<Gfx::Color> : public Formatter<StringView> {
+ void format(FormatBuilder& builder, const Gfx::Color& value);
+};
+
+}
+
+namespace IPC {
+
+bool encode(Encoder&, const Gfx::Color&);
+bool decode(Decoder&, Gfx::Color&);
+
+}
diff --git a/Userland/Libraries/LibGfx/DisjointRectSet.cpp b/Userland/Libraries/LibGfx/DisjointRectSet.cpp
new file mode 100644
index 0000000000..1ec3d95d90
--- /dev/null
+++ b/Userland/Libraries/LibGfx/DisjointRectSet.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/DisjointRectSet.h>
+
+namespace Gfx {
+
+bool DisjointRectSet::add_no_shatter(const IntRect& new_rect)
+{
+ if (new_rect.is_empty())
+ return false;
+ for (auto& rect : m_rects) {
+ if (rect.contains(new_rect))
+ return false;
+ }
+
+ m_rects.append(new_rect);
+ return true;
+}
+
+void DisjointRectSet::shatter()
+{
+ Vector<IntRect, 32> output;
+ output.ensure_capacity(m_rects.size());
+ bool pass_had_intersections = false;
+ do {
+ pass_had_intersections = false;
+ output.clear_with_capacity();
+ for (size_t i = 0; i < m_rects.size(); ++i) {
+ auto& r1 = m_rects[i];
+ for (size_t j = 0; j < m_rects.size(); ++j) {
+ if (i == j)
+ continue;
+ auto& r2 = m_rects[j];
+ if (!r1.intersects(r2))
+ continue;
+ pass_had_intersections = true;
+ auto pieces = r1.shatter(r2);
+ for (auto& piece : pieces)
+ output.append(piece);
+ m_rects.remove(i);
+ for (; i < m_rects.size(); ++i)
+ output.append(m_rects[i]);
+ goto next_pass;
+ }
+ output.append(r1);
+ }
+ next_pass:
+ swap(output, m_rects);
+ } while (pass_had_intersections);
+}
+
+void DisjointRectSet::move_by(int dx, int dy)
+{
+ for (auto& r : m_rects)
+ r.move_by(dx, dy);
+}
+
+bool DisjointRectSet::contains(const IntRect& rect) const
+{
+ if (is_empty() || rect.is_empty())
+ return false;
+
+ // TODO: This could use some optimization
+ DisjointRectSet remainder(rect);
+ for (auto& r : m_rects) {
+ auto shards = remainder.shatter(r);
+ if (shards.is_empty())
+ return true;
+ remainder = move(shards);
+ }
+ return false;
+}
+
+bool DisjointRectSet::intersects(const IntRect& rect) const
+{
+ for (auto& r : m_rects) {
+ if (r.intersects(rect))
+ return true;
+ }
+ return false;
+}
+
+bool DisjointRectSet::intersects(const DisjointRectSet& rects) const
+{
+ if (this == &rects)
+ return true;
+
+ for (auto& r : m_rects) {
+ for (auto& r2 : rects.m_rects) {
+ if (r.intersects(r2))
+ return true;
+ }
+ }
+ return false;
+}
+
+DisjointRectSet DisjointRectSet::intersected(const IntRect& rect) const
+{
+ DisjointRectSet intersected_rects;
+ intersected_rects.m_rects.ensure_capacity(m_rects.capacity());
+ for (auto& r : m_rects) {
+ auto intersected_rect = r.intersected(rect);
+ if (!intersected_rect.is_empty())
+ intersected_rects.m_rects.append(intersected_rect);
+ }
+ // Since there should be no overlaps, we don't need to call shatter()
+ return intersected_rects;
+}
+
+DisjointRectSet DisjointRectSet::intersected(const DisjointRectSet& rects) const
+{
+ if (&rects == this)
+ return clone();
+ if (is_empty() || rects.is_empty())
+ return {};
+
+ DisjointRectSet intersected_rects;
+ intersected_rects.m_rects.ensure_capacity(m_rects.capacity());
+ for (auto& r : m_rects) {
+ for (auto& r2 : rects.m_rects) {
+ auto intersected_rect = r.intersected(r2);
+ if (!intersected_rect.is_empty())
+ intersected_rects.m_rects.append(intersected_rect);
+ }
+ }
+ // Since there should be no overlaps, we don't need to call shatter()
+ return intersected_rects;
+}
+
+DisjointRectSet DisjointRectSet::shatter(const IntRect& hammer) const
+{
+ if (hammer.is_empty())
+ return clone();
+
+ DisjointRectSet shards;
+ for (auto& rect : m_rects) {
+ for (auto& shard : rect.shatter(hammer))
+ shards.add_no_shatter(shard);
+ }
+ // Since there should be no overlaps, we don't need to call shatter()
+ return shards;
+}
+
+DisjointRectSet DisjointRectSet::shatter(const DisjointRectSet& hammer) const
+{
+ if (this == &hammer)
+ return {};
+ if (hammer.is_empty() || !intersects(hammer))
+ return clone();
+
+ // TODO: This could use some optimization
+ DisjointRectSet shards = shatter(hammer.m_rects[0]);
+ auto rects_count = hammer.m_rects.size();
+ for (size_t i = 1; i < rects_count && !shards.is_empty(); i++) {
+ if (hammer.m_rects[i].intersects(shards.m_rects)) {
+ auto shattered = shards.shatter(hammer.m_rects[i]);
+ shards = move(shattered);
+ }
+ }
+ // Since there should be no overlaps, we don't need to call shatter()
+ return shards;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/DisjointRectSet.h b/Userland/Libraries/LibGfx/DisjointRectSet.h
new file mode 100644
index 0000000000..a54ce44ce3
--- /dev/null
+++ b/Userland/Libraries/LibGfx/DisjointRectSet.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Vector.h>
+#include <LibGfx/Point.h>
+#include <LibGfx/Rect.h>
+
+namespace Gfx {
+
+class DisjointRectSet {
+public:
+ DisjointRectSet(const DisjointRectSet&) = delete;
+ DisjointRectSet& operator=(const DisjointRectSet&) = delete;
+
+ DisjointRectSet() { }
+ ~DisjointRectSet() { }
+
+ DisjointRectSet(const IntRect& rect)
+ {
+ m_rects.append(rect);
+ }
+
+ DisjointRectSet(DisjointRectSet&&) = default;
+ DisjointRectSet& operator=(DisjointRectSet&&) = default;
+
+ DisjointRectSet clone() const
+ {
+ DisjointRectSet rects;
+ rects.m_rects = m_rects;
+ return rects;
+ }
+
+ void move_by(int dx, int dy);
+ void move_by(const IntPoint& delta)
+ {
+ move_by(delta.x(), delta.y());
+ }
+
+ void add(const IntRect& rect)
+ {
+ if (add_no_shatter(rect) && m_rects.size() > 1)
+ shatter();
+ }
+
+ template<typename Container>
+ void add_many(const Container& rects)
+ {
+ bool added = false;
+ for (const auto& rect : rects) {
+ if (add_no_shatter(rect))
+ added = true;
+ }
+ if (added && m_rects.size() > 1)
+ shatter();
+ }
+
+ void add(const DisjointRectSet& rect_set)
+ {
+ if (this == &rect_set)
+ return;
+ if (m_rects.is_empty()) {
+ m_rects = rect_set.m_rects;
+ } else {
+ add_many(rect_set.rects());
+ }
+ }
+
+ DisjointRectSet shatter(const IntRect&) const;
+ DisjointRectSet shatter(const DisjointRectSet& hammer) const;
+
+ bool contains(const IntRect&) const;
+ bool intersects(const IntRect&) const;
+ bool intersects(const DisjointRectSet&) const;
+ DisjointRectSet intersected(const IntRect&) const;
+ DisjointRectSet intersected(const DisjointRectSet&) const;
+
+ template<typename Function>
+ IterationDecision for_each_intersected(const IntRect& rect, Function f) const
+ {
+ if (is_empty() || rect.is_empty())
+ return IterationDecision::Continue;
+ for (auto& r : m_rects) {
+ auto intersected_rect = r.intersected(rect);
+ if (intersected_rect.is_empty())
+ continue;
+ IterationDecision decision = f(intersected_rect);
+ if (decision != IterationDecision::Continue)
+ return decision;
+ }
+ return IterationDecision::Continue;
+ }
+
+ template<typename Function>
+ IterationDecision for_each_intersected(const DisjointRectSet& rects, Function f) const
+ {
+ if (is_empty() || rects.is_empty())
+ return IterationDecision::Continue;
+ if (this == &rects) {
+ for (auto& r : m_rects) {
+ IterationDecision decision = f(r);
+ if (decision != IterationDecision::Continue)
+ return decision;
+ }
+ } else {
+ for (auto& r : m_rects) {
+ for (auto& r2 : rects.m_rects) {
+ auto intersected_rect = r.intersected(r2);
+ if (intersected_rect.is_empty())
+ continue;
+ IterationDecision decision = f(intersected_rect);
+ if (decision != IterationDecision::Continue)
+ return decision;
+ }
+ }
+ }
+ return IterationDecision::Continue;
+ }
+
+ bool is_empty() const { return m_rects.is_empty(); }
+ size_t size() const { return m_rects.size(); }
+
+ void clear() { m_rects.clear(); }
+ void clear_with_capacity() { m_rects.clear_with_capacity(); }
+ const Vector<IntRect, 32>& rects() const { return m_rects; }
+ Vector<IntRect, 32> take_rects() { return move(m_rects); }
+
+private:
+ bool add_no_shatter(const IntRect&);
+ void shatter();
+
+ Vector<IntRect, 32> m_rects;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Emoji.cpp b/Userland/Libraries/LibGfx/Emoji.cpp
new file mode 100644
index 0000000000..5965f452fb
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Emoji.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/HashMap.h>
+#include <AK/String.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Emoji.h>
+
+namespace Gfx {
+
+static HashMap<u32, RefPtr<Gfx::Bitmap>> s_emojis;
+
+const Bitmap* Emoji::emoji_for_code_point(u32 code_point)
+{
+ auto it = s_emojis.find(code_point);
+ if (it != s_emojis.end())
+ return (*it).value.ptr();
+
+ String path = String::format("/res/emoji/U+%X.png", code_point);
+
+ auto bitmap = Bitmap::load_from_file(path);
+ if (!bitmap) {
+ s_emojis.set(code_point, nullptr);
+ return nullptr;
+ }
+
+ s_emojis.set(code_point, bitmap);
+ return bitmap.ptr();
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Emoji.h b/Userland/Libraries/LibGfx/Emoji.h
new file mode 100644
index 0000000000..232927732e
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Emoji.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+
+namespace Gfx {
+
+class Bitmap;
+
+class Emoji {
+public:
+ static const Gfx::Bitmap* emoji_for_code_point(u32 code_point);
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Filters/BoxBlurFilter.h b/Userland/Libraries/LibGfx/Filters/BoxBlurFilter.h
new file mode 100644
index 0000000000..8be43befe1
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Filters/BoxBlurFilter.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "GenericConvolutionFilter.h"
+
+namespace Gfx {
+
+template<size_t N>
+class BoxBlurFilter : public GenericConvolutionFilter<N> {
+public:
+ BoxBlurFilter() { }
+ virtual ~BoxBlurFilter() { }
+
+ virtual const char* class_name() const override { return "BoxBlurFilter"; }
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Filters/Filter.h b/Userland/Libraries/LibGfx/Filters/Filter.h
new file mode 100644
index 0000000000..e0ea811d4a
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Filters/Filter.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Rect.h>
+
+namespace Gfx {
+
+class Filter {
+public:
+ class Parameters {
+ public:
+ virtual bool is_generic_convolution_filter() const { return false; }
+
+ virtual ~Parameters() { }
+ };
+ virtual ~Filter() { }
+
+ virtual const char* class_name() const = 0;
+
+ virtual void apply(Bitmap&, const IntRect&, const Bitmap&, const IntRect&, const Parameters&) = 0;
+
+protected:
+ Filter() { }
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Filters/GenericConvolutionFilter.h b/Userland/Libraries/LibGfx/Filters/GenericConvolutionFilter.h
new file mode 100644
index 0000000000..416f84321a
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Filters/GenericConvolutionFilter.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Filter.h"
+#include <LibGfx/Matrix.h>
+#include <LibGfx/Matrix4x4.h>
+
+namespace Gfx {
+
+template<size_t N, typename T>
+inline static constexpr void normalize(Matrix<N, T>& matrix)
+{
+ auto sum = 0.0f;
+ for (size_t i = 0; i < matrix.Size; ++i) {
+ for (size_t j = 0; j < matrix.Size; ++j) {
+ sum += matrix.elements()[i][j];
+ }
+ }
+ for (size_t i = 0; i < matrix.Size; ++i) {
+ for (size_t j = 0; j < matrix.Size; ++j) {
+ matrix.elements()[i][j] /= sum;
+ }
+ }
+}
+
+template<size_t N>
+class GenericConvolutionFilter : public Filter {
+public:
+ class Parameters : public Filter::Parameters {
+ public:
+ Parameters(const Gfx::Matrix<N, float>& kernel, bool should_wrap = false)
+ : m_kernel(kernel)
+ , m_should_wrap(should_wrap)
+
+ {
+ }
+
+ const Gfx::Matrix<N, float>& kernel() const { return m_kernel; }
+ Gfx::Matrix<N, float>& kernel() { return m_kernel; }
+ bool should_wrap() const { return m_should_wrap; }
+
+ private:
+ virtual bool is_generic_convolution_filter() const override { return true; }
+ Gfx::Matrix<N, float> m_kernel;
+ bool m_should_wrap { false };
+ };
+
+ class ApplyCache {
+ template<size_t>
+ friend class GenericConvolutionFilter;
+
+ private:
+ RefPtr<Gfx::Bitmap> m_target;
+ };
+
+ GenericConvolutionFilter() { }
+ virtual ~GenericConvolutionFilter() { }
+
+ virtual const char* class_name() const override { return "GenericConvolutionFilter"; }
+
+ virtual void apply(Bitmap& target_bitmap, const IntRect& target_rect, const Bitmap& source_bitmap, const IntRect& source_rect, const Filter::Parameters& parameters) override
+ {
+ ASSERT(parameters.is_generic_convolution_filter());
+ auto& gcf_params = static_cast<const GenericConvolutionFilter::Parameters&>(parameters);
+
+ ApplyCache apply_cache;
+ apply(target_bitmap, target_rect, source_bitmap, source_rect, gcf_params, apply_cache);
+ }
+
+ void apply(Bitmap& target, IntRect target_rect, const Bitmap& source, const IntRect& source_rect, const GenericConvolutionFilter::Parameters& parameters, ApplyCache& apply_cache)
+ {
+ // The target area (where the filter is applied) must be entirely
+ // contained by the source area. source_rect should be describing
+ // the pixels that can be accessed to apply this filter, while
+ // target_rect should describe the area where to apply the filter on.
+ ASSERT(source_rect.contains(target_rect));
+ ASSERT(source.size().contains(target.size()));
+ ASSERT(target.rect().contains(target_rect));
+ ASSERT(source.rect().contains(source_rect));
+
+ // If source is different from target, it should still be describing
+ // essentially the same bitmap. But it allows us to modify target
+ // without a temporary bitmap. This is important if this filter
+ // is applied on multiple areas of the same bitmap, at which point
+ // we would need to be able to access unmodified pixels if the
+ // areas are (almost) adjacent.
+ int source_delta_x = target_rect.x() - source_rect.x();
+ int source_delta_y = target_rect.y() - source_rect.y();
+ if (&target == &source && (!apply_cache.m_target || !apply_cache.m_target->size().contains(source_rect.size()))) {
+ // TODO: We probably don't need the entire source_rect, we could inflate
+ // the target_rect appropriately
+ apply_cache.m_target = Gfx::Bitmap::create(source.format(), source_rect.size());
+ target_rect.move_by(-target_rect.location());
+ }
+
+ Bitmap* render_target_bitmap = (&target != &source) ? &target : apply_cache.m_target.ptr();
+
+ // FIXME: Help! I am naive!
+ constexpr static ssize_t offset = N / 2;
+ for (auto i_ = 0; i_ < target_rect.width(); ++i_) {
+ ssize_t i = i_ + target_rect.x();
+ for (auto j_ = 0; j_ < target_rect.height(); ++j_) {
+ ssize_t j = j_ + target_rect.y();
+ FloatVector3 value(0, 0, 0);
+ for (auto k = 0l; k < (ssize_t)N; ++k) {
+ auto ki = i + k - offset;
+ if (ki < source_rect.x() || ki > source_rect.right()) {
+ if (parameters.should_wrap())
+ ki = (ki + source.size().width()) % source.size().width(); // TODO: fix up using source_rect
+ else
+ continue;
+ }
+
+ for (auto l = 0l; l < (ssize_t)N; ++l) {
+ auto lj = j + l - offset;
+ if (lj < source_rect.y() || lj > source_rect.bottom()) {
+ if (parameters.should_wrap())
+ lj = (lj + source.size().height()) % source.size().height(); // TODO: fix up using source_rect
+ else
+ continue;
+ }
+
+ auto pixel = source.get_pixel(ki, lj);
+ FloatVector3 pixel_value(pixel.red(), pixel.green(), pixel.blue());
+
+ value = value + pixel_value * parameters.kernel().elements()[k][l];
+ }
+ }
+
+ value.clamp(0, 255);
+ render_target_bitmap->set_pixel(i, j, Color(value.x(), value.y(), value.z(), source.get_pixel(i + source_delta_x, j + source_delta_y).alpha()));
+ }
+ }
+
+ if (render_target_bitmap != &target) {
+ // FIXME: Substitute for some sort of faster "blit" method.
+ for (auto i_ = 0; i_ < target_rect.width(); ++i_) {
+ auto i = i_ + target_rect.x();
+ for (auto j_ = 0; j_ < target_rect.height(); ++j_) {
+ auto j = j_ + target_rect.y();
+ target.set_pixel(i, j, render_target_bitmap->get_pixel(i_, j_));
+ }
+ }
+ }
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Filters/LaplacianFilter.h b/Userland/Libraries/LibGfx/Filters/LaplacianFilter.h
new file mode 100644
index 0000000000..fe1c247582
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Filters/LaplacianFilter.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "GenericConvolutionFilter.h"
+
+namespace Gfx {
+
+class LaplacianFilter : public GenericConvolutionFilter<3> {
+public:
+ LaplacianFilter() { }
+ virtual ~LaplacianFilter() { }
+
+ virtual const char* class_name() const override { return "LaplacianFilter"; }
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Filters/SharpenFilter.h b/Userland/Libraries/LibGfx/Filters/SharpenFilter.h
new file mode 100644
index 0000000000..25456aa691
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Filters/SharpenFilter.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "GenericConvolutionFilter.h"
+
+namespace Gfx {
+
+class SharpenFilter : public GenericConvolutionFilter<3> {
+public:
+ SharpenFilter() { }
+ virtual ~SharpenFilter() { }
+
+ virtual const char* class_name() const override { return "SharpenFilter"; }
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Filters/SpatialGaussianBlurFilter.h b/Userland/Libraries/LibGfx/Filters/SpatialGaussianBlurFilter.h
new file mode 100644
index 0000000000..8bf8d414ca
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Filters/SpatialGaussianBlurFilter.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "GenericConvolutionFilter.h"
+#include <AK/StdLibExtras.h>
+
+namespace Gfx {
+
+template<size_t N, typename = typename AK::EnableIf<N % 2 == 1>::Type>
+class SpatialGaussianBlurFilter : public GenericConvolutionFilter<N> {
+public:
+ SpatialGaussianBlurFilter() { }
+ virtual ~SpatialGaussianBlurFilter() { }
+
+ virtual const char* class_name() const override { return "SpatialGaussianBlurFilter"; }
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Font.cpp b/Userland/Libraries/LibGfx/Font.cpp
new file mode 100644
index 0000000000..ba0849b21b
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Font.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/BitmapFont.h>
+#include <LibGfx/Font.h>
+
+namespace Gfx {
+
+RefPtr<Font> Font::load_from_file(const StringView& path)
+{
+ if (path.ends_with(".font")) {
+ return BitmapFont::load_from_file(path);
+ }
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Font.h b/Userland/Libraries/LibGfx/Font.h
new file mode 100644
index 0000000000..2e74798725
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Font.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/MappedFile.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <LibGfx/Size.h>
+
+namespace Gfx {
+
+// FIXME: Make a MutableGlyphBitmap buddy class for FontEditor instead?
+class GlyphBitmap {
+ friend class BitmapFont;
+
+public:
+ const unsigned* rows() const { return m_rows; }
+ unsigned row(unsigned index) const { return m_rows[index]; }
+
+ bool bit_at(int x, int y) const { return row(y) & (1 << x); }
+ void set_bit_at(int x, int y, bool b)
+ {
+ auto& mutable_row = const_cast<unsigned*>(m_rows)[y];
+ if (b)
+ mutable_row |= 1 << x;
+ else
+ mutable_row &= ~(1 << x);
+ }
+
+ IntSize size() const { return m_size; }
+ int width() const { return m_size.width(); }
+ int height() const { return m_size.height(); }
+
+private:
+ GlyphBitmap(const unsigned* rows, IntSize size)
+ : m_rows(rows)
+ , m_size(size)
+ {
+ }
+
+ const unsigned* m_rows { nullptr };
+ IntSize m_size;
+};
+
+class Font : public RefCounted<Font> {
+public:
+ static RefPtr<Font> load_from_file(const StringView& path);
+
+ virtual NonnullRefPtr<Font> clone() const = 0;
+ virtual ~Font() {};
+
+ virtual u8 presentation_size() const = 0;
+
+ virtual u16 weight() const = 0;
+ virtual GlyphBitmap glyph_bitmap(u32 code_point) const = 0;
+
+ virtual u8 glyph_width(size_t ch) const = 0;
+ virtual int glyph_or_emoji_width(u32 code_point) const = 0;
+ virtual u8 glyph_height() const = 0;
+ virtual int x_height() const = 0;
+
+ virtual u8 min_glyph_width() const = 0;
+ virtual u8 max_glyph_width() const = 0;
+ virtual u8 glyph_fixed_width() const = 0;
+
+ virtual u8 baseline() const = 0;
+ virtual u8 mean_line() const = 0;
+
+ virtual int width(const StringView&) const = 0;
+ virtual int width(const Utf8View&) const = 0;
+ virtual int width(const Utf32View&) const = 0;
+
+ virtual const String& name() const = 0;
+
+ virtual bool is_fixed_width() const = 0;
+
+ virtual u8 glyph_spacing() const = 0;
+
+ virtual int glyph_count() const = 0;
+
+ virtual const String& family() const = 0;
+
+ virtual String qualified_name() const = 0;
+
+ virtual const Font& bold_variant() const = 0;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/FontDatabase.cpp b/Userland/Libraries/LibGfx/FontDatabase.cpp
new file mode 100644
index 0000000000..4fe466ece9
--- /dev/null
+++ b/Userland/Libraries/LibGfx/FontDatabase.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/NonnullRefPtrVector.h>
+#include <AK/QuickSort.h>
+#include <LibCore/DirIterator.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/FontDatabase.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace Gfx {
+
+static FontDatabase* s_the;
+
+FontDatabase& FontDatabase::the()
+{
+ if (!s_the)
+ s_the = new FontDatabase;
+ return *s_the;
+}
+
+Font& FontDatabase::default_font()
+{
+ static Font* font;
+ if (!font) {
+ font = FontDatabase::the().get_by_name("Katica 10 400");
+ ASSERT(font);
+ }
+ return *font;
+}
+
+Font& FontDatabase::default_fixed_width_font()
+{
+ static Font* font;
+ if (!font) {
+ font = FontDatabase::the().get_by_name("Csilla 10 400");
+ ASSERT(font);
+ }
+ return *font;
+}
+
+Font& FontDatabase::default_bold_fixed_width_font()
+{
+ static Font* font;
+ if (!font) {
+ font = FontDatabase::the().get_by_name("Csilla 10 700");
+ ASSERT(font);
+ }
+ return *font;
+}
+
+Font& FontDatabase::default_bold_font()
+{
+ static Font* font;
+ if (!font) {
+ font = FontDatabase::the().get_by_name("Katica 10 700");
+ ASSERT(font);
+ }
+ return *font;
+}
+
+struct FontDatabase::Private {
+ HashMap<String, RefPtr<Gfx::Font>> full_name_to_font_map;
+};
+
+FontDatabase::FontDatabase()
+ : m_private(make<Private>())
+{
+ Core::DirIterator di("/res/fonts", Core::DirIterator::SkipDots);
+ if (di.has_error()) {
+ fprintf(stderr, "DirIterator: %s\n", di.error_string());
+ exit(1);
+ }
+ while (di.has_next()) {
+ String name = di.next_path();
+ if (!name.ends_with(".font"))
+ continue;
+
+ auto path = String::format("/res/fonts/%s", name.characters());
+ if (auto font = Gfx::Font::load_from_file(path)) {
+ m_private->full_name_to_font_map.set(font->qualified_name(), font);
+ }
+ }
+}
+
+FontDatabase::~FontDatabase()
+{
+}
+
+void FontDatabase::for_each_font(Function<void(const Gfx::Font&)> callback)
+{
+ Vector<RefPtr<Gfx::Font>> fonts;
+ fonts.ensure_capacity(m_private->full_name_to_font_map.size());
+ for (auto& it : m_private->full_name_to_font_map)
+ fonts.append(it.value);
+ quick_sort(fonts, [](auto& a, auto& b) { return a->qualified_name() < b->qualified_name(); });
+ for (auto& font : fonts)
+ callback(*font);
+}
+
+void FontDatabase::for_each_fixed_width_font(Function<void(const Gfx::Font&)> callback)
+{
+ Vector<RefPtr<Gfx::Font>> fonts;
+ fonts.ensure_capacity(m_private->full_name_to_font_map.size());
+ for (auto& it : m_private->full_name_to_font_map) {
+ if (it.value->is_fixed_width())
+ fonts.append(it.value);
+ }
+ quick_sort(fonts, [](auto& a, auto& b) { return a->qualified_name() < b->qualified_name(); });
+ for (auto& font : fonts)
+ callback(*font);
+}
+
+RefPtr<Gfx::Font> FontDatabase::get_by_name(const StringView& name)
+{
+ auto it = m_private->full_name_to_font_map.find(name);
+ if (it == m_private->full_name_to_font_map.end()) {
+ dbgln("Font lookup failed: '{}'", name);
+ return nullptr;
+ }
+ return it->value;
+}
+
+RefPtr<Gfx::Font> FontDatabase::get(const String& family, unsigned size, unsigned weight)
+{
+ for (auto& it : m_private->full_name_to_font_map) {
+ auto& font = *it.value;
+ if (font.family() == family && font.presentation_size() == size && font.weight() == weight)
+ return font;
+ }
+ return nullptr;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/FontDatabase.h b/Userland/Libraries/LibGfx/FontDatabase.h
new file mode 100644
index 0000000000..e1c21a20a3
--- /dev/null
+++ b/Userland/Libraries/LibGfx/FontDatabase.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/HashMap.h>
+#include <AK/String.h>
+#include <LibGfx/Forward.h>
+
+namespace Gfx {
+
+class FontDatabase {
+public:
+ static FontDatabase& the();
+
+ static Font& default_font();
+ static Font& default_bold_font();
+
+ static Font& default_fixed_width_font();
+ static Font& default_bold_fixed_width_font();
+
+ RefPtr<Gfx::Font> get(const String& family, unsigned size, unsigned weight);
+ RefPtr<Gfx::Font> get_by_name(const StringView&);
+ void for_each_font(Function<void(const Gfx::Font&)>);
+ void for_each_fixed_width_font(Function<void(const Gfx::Font&)>);
+
+private:
+ FontDatabase();
+ ~FontDatabase();
+
+ struct Private;
+ OwnPtr<Private> m_private;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Forward.h b/Userland/Libraries/LibGfx/Forward.h
new file mode 100644
index 0000000000..7d7fc2f482
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Forward.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace Gfx {
+
+class Bitmap;
+class CharacterBitmap;
+class Color;
+class DisjointRectSet;
+class Emoji;
+class Font;
+class GlyphBitmap;
+class ImageDecoder;
+class Painter;
+class Palette;
+class PaletteImpl;
+class Path;
+class ShareableBitmap;
+class StylePainter;
+struct SystemTheme;
+class Triangle;
+
+template<typename T>
+class Point;
+
+template<typename T>
+class Size;
+
+template<typename T>
+class Rect;
+
+using IntRect = Rect<int>;
+using FloatRect = Rect<float>;
+
+using IntPoint = Point<int>;
+using FloatPoint = Point<float>;
+
+using IntSize = Size<int>;
+using FloatSize = Size<float>;
+
+enum class BitmapFormat;
+enum class ColorRole;
+enum class TextAlignment;
+
+}
+
+using Gfx::Color;
diff --git a/Userland/Libraries/LibGfx/GIFLoader.cpp b/Userland/Libraries/LibGfx/GIFLoader.cpp
new file mode 100644
index 0000000000..d29c5969e3
--- /dev/null
+++ b/Userland/Libraries/LibGfx/GIFLoader.cpp
@@ -0,0 +1,765 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Array.h>
+#include <AK/ByteBuffer.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/MemoryStream.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <LibGfx/GIFLoader.h>
+#include <LibGfx/Painter.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+//#define GIF_DEBUG
+
+namespace Gfx {
+
+// Row strides and offsets for each interlace pass.
+static const int INTERLACE_ROW_STRIDES[] = { 8, 8, 4, 2 };
+static const int INTERLACE_ROW_OFFSETS[] = { 0, 4, 2, 1 };
+
+struct ImageDescriptor {
+ u16 x { 0 };
+ u16 y { 0 };
+ u16 width { 0 };
+ u16 height { 0 };
+ bool use_global_color_map { true };
+ bool interlaced { false };
+ Color color_map[256];
+ u8 lzw_min_code_size { 0 };
+ Vector<u8> lzw_encoded_bytes;
+
+ // Fields from optional graphic control extension block
+ enum DisposalMethod : u8 {
+ None = 0,
+ InPlace = 1,
+ RestoreBackground = 2,
+ RestorePrevious = 3,
+ };
+ DisposalMethod disposal_method { None };
+ u8 transparency_index { 0 };
+ u16 duration { 0 };
+ bool transparent { false };
+ bool user_input { false };
+
+ const IntRect rect() const
+ {
+ return { this->x, this->y, this->width, this->height };
+ }
+};
+
+struct LogicalScreen {
+ u16 width;
+ u16 height;
+ Color color_map[256];
+};
+
+struct GIFLoadingContext {
+ enum State {
+ NotDecoded = 0,
+ FrameDescriptorsLoaded,
+ FrameComplete,
+ };
+ State state { NotDecoded };
+ enum ErrorState {
+ NoError = 0,
+ FailedToDecodeAllFrames,
+ FailedToDecodeAnyFrame,
+ FailedToLoadFrameDescriptors,
+ };
+ ErrorState error_state { NoError };
+ const u8* data { nullptr };
+ size_t data_size { 0 };
+ LogicalScreen logical_screen {};
+ u8 background_color_index { 0 };
+ NonnullOwnPtrVector<ImageDescriptor> images {};
+ size_t loops { 1 };
+ RefPtr<Gfx::Bitmap> frame_buffer;
+ size_t current_frame { 0 };
+ RefPtr<Gfx::Bitmap> prev_frame_buffer;
+};
+
+RefPtr<Gfx::Bitmap> load_gif(const StringView& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return nullptr;
+ GIFImageDecoderPlugin gif_decoder((const u8*)file_or_error.value()->data(), file_or_error.value()->size());
+ auto bitmap = gif_decoder.bitmap();
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded GIF: {}", bitmap->size(), LexicalPath::canonicalized_path(path)));
+ return bitmap;
+}
+
+RefPtr<Gfx::Bitmap> load_gif_from_memory(const u8* data, size_t length)
+{
+ GIFImageDecoderPlugin gif_decoder(data, length);
+ auto bitmap = gif_decoder.bitmap();
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded GIF: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+enum class GIFFormat {
+ GIF87a,
+ GIF89a,
+};
+
+static Optional<GIFFormat> decode_gif_header(InputMemoryStream& stream)
+{
+ static const char valid_header_87[] = "GIF87a";
+ static const char valid_header_89[] = "GIF89a";
+
+ Array<u8, 6> header;
+ stream >> header;
+
+ if (stream.handle_any_error())
+ return {};
+
+ if (header.span() == ReadonlyBytes { valid_header_87, 6 })
+ return GIFFormat::GIF87a;
+ if (header.span() == ReadonlyBytes { valid_header_89, 6 })
+ return GIFFormat::GIF89a;
+
+ return {};
+}
+
+class LZWDecoder {
+private:
+ static constexpr int max_code_size = 12;
+
+public:
+ explicit LZWDecoder(const Vector<u8>& lzw_bytes, u8 min_code_size)
+ : m_lzw_bytes(lzw_bytes)
+ , m_code_size(min_code_size)
+ , m_original_code_size(min_code_size)
+ , m_table_capacity(pow(2, min_code_size))
+ {
+ init_code_table();
+ }
+
+ u16 add_control_code()
+ {
+ const u16 control_code = m_code_table.size();
+ m_code_table.append(Vector<u8> {});
+ m_original_code_table.append(Vector<u8> {});
+ if (m_code_table.size() >= m_table_capacity && m_code_size < max_code_size) {
+
+ ++m_code_size;
+ ++m_original_code_size;
+ m_table_capacity *= 2;
+ }
+ return control_code;
+ }
+
+ void reset()
+ {
+ m_code_table.clear();
+ m_code_table.append(m_original_code_table);
+ m_code_size = m_original_code_size;
+ m_table_capacity = pow(2, m_code_size);
+ m_output.clear();
+ }
+
+ Optional<u16> next_code()
+ {
+ size_t current_byte_index = m_current_bit_index / 8;
+ if (current_byte_index >= m_lzw_bytes.size()) {
+ return {};
+ }
+
+ // Extract the code bits using a 32-bit mask to cover the possibility that if
+ // the current code size > 9 bits then the code can span 3 bytes.
+ u8 current_bit_offset = m_current_bit_index % 8;
+ u32 mask = (u32)(m_table_capacity - 1) << current_bit_offset;
+
+ // Make a padded copy of the final bytes in the data to ensure we don't read past the end.
+ if (current_byte_index + sizeof(mask) > m_lzw_bytes.size()) {
+ u8 padded_last_bytes[sizeof(mask)] = { 0 };
+ for (int i = 0; current_byte_index + i < m_lzw_bytes.size(); ++i) {
+ padded_last_bytes[i] = m_lzw_bytes[current_byte_index + i];
+ }
+ const u32* addr = (const u32*)&padded_last_bytes;
+ m_current_code = (*addr & mask) >> current_bit_offset;
+ } else {
+ const u32* addr = (const u32*)&m_lzw_bytes.at(current_byte_index);
+ m_current_code = (*addr & mask) >> current_bit_offset;
+ }
+
+ if (m_current_code > m_code_table.size()) {
+#ifdef GIF_DEBUG
+ dbg() << "Corrupted LZW stream, invalid code: " << m_current_code << " at bit index: "
+ << m_current_bit_index << ", code table size: " << m_code_table.size();
+#endif
+ return {};
+ } else if (m_current_code == m_code_table.size() && m_output.is_empty()) {
+#ifdef GIF_DEBUG
+ dbg() << "Corrupted LZW stream, valid new code but output buffer is empty: " << m_current_code
+ << " at bit index: " << m_current_bit_index << ", code table size: " << m_code_table.size();
+#endif
+ return {};
+ }
+
+ m_current_bit_index += m_code_size;
+
+ return m_current_code;
+ }
+
+ Vector<u8>& get_output()
+ {
+ ASSERT(m_current_code <= m_code_table.size());
+ if (m_current_code < m_code_table.size()) {
+ Vector<u8> new_entry = m_output;
+ m_output = m_code_table.at(m_current_code);
+ new_entry.append(m_output[0]);
+ extend_code_table(new_entry);
+ } else if (m_current_code == m_code_table.size()) {
+ ASSERT(!m_output.is_empty());
+ m_output.append(m_output[0]);
+ extend_code_table(m_output);
+ }
+ return m_output;
+ }
+
+private:
+ void init_code_table()
+ {
+ m_code_table.clear();
+ for (u16 i = 0; i < m_table_capacity; ++i) {
+ m_code_table.append({ (u8)i });
+ }
+ m_original_code_table = m_code_table;
+ }
+
+ void extend_code_table(const Vector<u8>& entry)
+ {
+ if (entry.size() > 1 && m_code_table.size() < 4096) {
+ m_code_table.append(entry);
+ if (m_code_table.size() >= m_table_capacity && m_code_size < max_code_size) {
+ ++m_code_size;
+ m_table_capacity *= 2;
+ }
+ }
+ }
+
+ const Vector<u8>& m_lzw_bytes;
+
+ int m_current_bit_index { 0 };
+
+ Vector<Vector<u8>> m_code_table {};
+ Vector<Vector<u8>> m_original_code_table {};
+
+ u8 m_code_size { 0 };
+ u8 m_original_code_size { 0 };
+
+ u32 m_table_capacity { 0 };
+
+ u16 m_current_code { 0 };
+ Vector<u8> m_output {};
+};
+
+static void copy_frame_buffer(Bitmap& dest, const Bitmap& src)
+{
+ ASSERT(dest.size_in_bytes() == src.size_in_bytes());
+ memcpy(dest.scanline(0), src.scanline(0), dest.size_in_bytes());
+}
+
+static bool decode_frame(GIFLoadingContext& context, size_t frame_index)
+{
+ if (frame_index >= context.images.size()) {
+ return false;
+ }
+
+ if (context.state >= GIFLoadingContext::State::FrameComplete && frame_index == context.current_frame) {
+ return true;
+ }
+
+ size_t start_frame = context.current_frame + 1;
+ if (context.state < GIFLoadingContext::State::FrameComplete) {
+ start_frame = 0;
+ context.frame_buffer = Bitmap::create_purgeable(BitmapFormat::RGBA32, { context.logical_screen.width, context.logical_screen.height });
+ if (!context.frame_buffer)
+ return false;
+ context.prev_frame_buffer = Bitmap::create_purgeable(BitmapFormat::RGBA32, { context.logical_screen.width, context.logical_screen.height });
+ if (!context.prev_frame_buffer)
+ return false;
+ } else if (frame_index < context.current_frame) {
+ start_frame = 0;
+ }
+
+ for (size_t i = start_frame; i <= frame_index; ++i) {
+ auto& image = context.images.at(i);
+
+ const auto previous_image_disposal_method = i > 0 ? context.images.at(i - 1).disposal_method : ImageDescriptor::DisposalMethod::None;
+
+ if (i == 0) {
+ context.frame_buffer->fill(Color::Transparent);
+ } else if (i > 0 && image.disposal_method == ImageDescriptor::DisposalMethod::RestorePrevious
+ && previous_image_disposal_method != ImageDescriptor::DisposalMethod::RestorePrevious) {
+ // This marks the start of a run of frames that once disposed should be restored to the
+ // previous underlying image contents. Therefore we make a copy of the current frame
+ // buffer so that it can be restored later.
+ copy_frame_buffer(*context.prev_frame_buffer, *context.frame_buffer);
+ }
+
+ if (previous_image_disposal_method == ImageDescriptor::DisposalMethod::RestoreBackground) {
+ // Note: RestoreBackground could be interpreted either as restoring the underlying
+ // background of the entire image (e.g. container element's background-color), or the
+ // background color of the GIF itself. It appears that all major browsers and most other
+ // GIF decoders adhere to the former interpretation, therefore we will do the same by
+ // clearing the entire frame buffer to transparent.
+ Painter painter(*context.frame_buffer);
+ painter.clear_rect(context.images.at(i - 1).rect(), Color::Transparent);
+ } else if (i > 0 && previous_image_disposal_method == ImageDescriptor::DisposalMethod::RestorePrevious) {
+ // Previous frame indicated that once disposed, it should be restored to *its* previous
+ // underlying image contents, therefore we restore the saved previous frame buffer.
+ copy_frame_buffer(*context.frame_buffer, *context.prev_frame_buffer);
+ }
+
+ LZWDecoder decoder(image.lzw_encoded_bytes, image.lzw_min_code_size);
+
+ // Add GIF-specific control codes
+ const int clear_code = decoder.add_control_code();
+ const int end_of_information_code = decoder.add_control_code();
+
+ const auto& color_map = image.use_global_color_map ? context.logical_screen.color_map : image.color_map;
+
+ int pixel_index = 0;
+ int row = 0;
+ int interlace_pass = 0;
+ while (true) {
+ Optional<u16> code = decoder.next_code();
+ if (!code.has_value()) {
+#ifdef GIF_DEBUG
+ dbgln("Unexpectedly reached end of gif frame data");
+#endif
+ return false;
+ }
+
+ if (code.value() == clear_code) {
+ decoder.reset();
+ continue;
+ }
+ if (code.value() == end_of_information_code)
+ break;
+ if (!image.width)
+ continue;
+
+ auto colors = decoder.get_output();
+ for (const auto& color : colors) {
+ auto c = color_map[color];
+
+ int x = pixel_index % image.width + image.x;
+ int y = row + image.y;
+
+ if (context.frame_buffer->rect().contains(x, y) && (!image.transparent || color != image.transparency_index)) {
+ context.frame_buffer->set_pixel(x, y, c);
+ }
+
+ ++pixel_index;
+ if (pixel_index % image.width == 0) {
+ if (image.interlaced) {
+ if (row + INTERLACE_ROW_STRIDES[interlace_pass] >= image.height) {
+ ++interlace_pass;
+ // FIXME: We could probably figure this out earlier and fail before doing a bunch of work.
+ if (interlace_pass >= 4)
+ return false;
+ row = INTERLACE_ROW_OFFSETS[interlace_pass];
+ } else {
+ row += INTERLACE_ROW_STRIDES[interlace_pass];
+ }
+ } else {
+ ++row;
+ }
+ }
+ }
+ }
+
+ context.current_frame = i;
+ context.state = GIFLoadingContext::State::FrameComplete;
+ }
+
+ return true;
+}
+
+static bool load_gif_frame_descriptors(GIFLoadingContext& context)
+{
+ if (context.data_size < 32)
+ return false;
+
+ InputMemoryStream stream { { context.data, context.data_size } };
+
+ Optional<GIFFormat> format = decode_gif_header(stream);
+ if (!format.has_value()) {
+ return false;
+ }
+
+ LittleEndian<u16> value;
+
+ stream >> value;
+ context.logical_screen.width = value;
+
+ stream >> value;
+ context.logical_screen.height = value;
+
+ if (stream.handle_any_error())
+ return false;
+
+ if (context.logical_screen.width > maximum_width_for_decoded_images || context.logical_screen.height > maximum_height_for_decoded_images) {
+ dbgln("This GIF is too large for comfort: {}x{}", context.logical_screen.width, context.logical_screen.height);
+ return false;
+ }
+
+ u8 gcm_info = 0;
+ stream >> gcm_info;
+
+ if (stream.handle_any_error())
+ return false;
+
+ stream >> context.background_color_index;
+ if (stream.handle_any_error())
+ return false;
+
+ u8 pixel_aspect_ratio = 0;
+ stream >> pixel_aspect_ratio;
+ if (stream.handle_any_error())
+ return false;
+
+ u8 bits_per_pixel = (gcm_info & 7) + 1;
+ int color_map_entry_count = 1;
+ for (int i = 0; i < bits_per_pixel; ++i)
+ color_map_entry_count *= 2;
+
+ for (int i = 0; i < color_map_entry_count; ++i) {
+ u8 r = 0;
+ u8 g = 0;
+ u8 b = 0;
+ stream >> r >> g >> b;
+ context.logical_screen.color_map[i] = { r, g, b };
+ }
+
+ if (stream.handle_any_error())
+ return false;
+
+ NonnullOwnPtr<ImageDescriptor> current_image = make<ImageDescriptor>();
+ for (;;) {
+ u8 sentinel = 0;
+ stream >> sentinel;
+
+ if (stream.handle_any_error())
+ return false;
+
+ if (sentinel == '!') {
+ u8 extension_type = 0;
+ stream >> extension_type;
+ if (stream.handle_any_error())
+ return false;
+
+ u8 sub_block_length = 0;
+
+ Vector<u8> sub_block {};
+ for (;;) {
+ stream >> sub_block_length;
+
+ if (stream.handle_any_error())
+ return false;
+
+ if (sub_block_length == 0)
+ break;
+
+ u8 dummy = 0;
+ for (u16 i = 0; i < sub_block_length; ++i) {
+ stream >> dummy;
+ sub_block.append(dummy);
+ }
+
+ if (stream.handle_any_error())
+ return false;
+ }
+
+ if (extension_type == 0xF9) {
+ if (sub_block.size() != 4) {
+#ifdef GIF_DEBUG
+ dbgln("Unexpected graphic control size");
+#endif
+ continue;
+ }
+
+ u8 disposal_method = (sub_block[0] & 0x1C) >> 2;
+ current_image->disposal_method = (ImageDescriptor::DisposalMethod)disposal_method;
+
+ u8 user_input = (sub_block[0] & 0x2) >> 1;
+ current_image->user_input = user_input == 1;
+
+ u8 transparent = sub_block[0] & 1;
+ current_image->transparent = transparent == 1;
+
+ u16 duration = sub_block[1] + ((u16)sub_block[2] >> 8);
+ current_image->duration = duration;
+
+ current_image->transparency_index = sub_block[3];
+ }
+
+ if (extension_type == 0xFF) {
+ if (sub_block.size() != 14) {
+#ifdef GIF_DEBUG
+ dbg() << "Unexpected application extension size: " << sub_block.size();
+#endif
+ continue;
+ }
+
+ if (sub_block[11] != 1) {
+#ifdef GIF_DEBUG
+ dbgln("Unexpected application extension format");
+#endif
+ continue;
+ }
+
+ u16 loops = sub_block[12] + (sub_block[13] << 8);
+ context.loops = loops;
+ }
+
+ continue;
+ }
+
+ if (sentinel == ',') {
+ context.images.append(move(current_image));
+ auto& image = context.images.last();
+
+ LittleEndian<u16> tmp;
+
+ u8 packed_fields { 0 };
+
+ stream >> tmp;
+ image.x = tmp;
+
+ stream >> tmp;
+ image.y = tmp;
+
+ stream >> tmp;
+ image.width = tmp;
+
+ stream >> tmp;
+ image.height = tmp;
+
+ stream >> packed_fields;
+ if (stream.handle_any_error())
+ return false;
+
+ image.use_global_color_map = !(packed_fields & 0x80);
+ image.interlaced = (packed_fields & 0x40) != 0;
+
+ if (!image.use_global_color_map) {
+ size_t local_color_table_size = pow(2, (packed_fields & 7) + 1);
+
+ for (size_t i = 0; i < local_color_table_size; ++i) {
+ u8 r = 0;
+ u8 g = 0;
+ u8 b = 0;
+ stream >> r >> g >> b;
+ image.color_map[i] = { r, g, b };
+ }
+ }
+
+ stream >> image.lzw_min_code_size;
+ if (stream.handle_any_error())
+ return false;
+
+ u8 lzw_encoded_bytes_expected = 0;
+
+ for (;;) {
+ stream >> lzw_encoded_bytes_expected;
+
+ if (stream.handle_any_error())
+ return false;
+
+ if (lzw_encoded_bytes_expected == 0)
+ break;
+
+ Array<u8, 256> buffer;
+ stream >> buffer.span().trim(lzw_encoded_bytes_expected);
+
+ if (stream.handle_any_error())
+ return false;
+
+ for (int i = 0; i < lzw_encoded_bytes_expected; ++i) {
+ image.lzw_encoded_bytes.append(buffer[i]);
+ }
+ }
+
+ current_image = make<ImageDescriptor>();
+ continue;
+ }
+
+ if (sentinel == ';') {
+ break;
+ }
+
+ return false;
+ }
+
+ context.state = GIFLoadingContext::State::FrameDescriptorsLoaded;
+ return true;
+}
+
+GIFImageDecoderPlugin::GIFImageDecoderPlugin(const u8* data, size_t size)
+{
+ m_context = make<GIFLoadingContext>();
+ m_context->data = data;
+ m_context->data_size = size;
+}
+
+GIFImageDecoderPlugin::~GIFImageDecoderPlugin() { }
+
+IntSize GIFImageDecoderPlugin::size()
+{
+ if (m_context->error_state == GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors) {
+ return {};
+ }
+
+ if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) {
+ if (!load_gif_frame_descriptors(*m_context)) {
+ m_context->error_state = GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors;
+ return {};
+ }
+ }
+
+ return { m_context->logical_screen.width, m_context->logical_screen.height };
+}
+
+RefPtr<Gfx::Bitmap> GIFImageDecoderPlugin::bitmap()
+{
+ if (m_context->state < GIFLoadingContext::State::FrameComplete) {
+ return frame(0).image;
+ }
+ return m_context->frame_buffer;
+}
+
+void GIFImageDecoderPlugin::set_volatile()
+{
+ if (m_context->frame_buffer) {
+ m_context->frame_buffer->set_volatile();
+ }
+}
+
+bool GIFImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->frame_buffer) {
+ return true;
+ }
+ return m_context->frame_buffer->set_nonvolatile();
+}
+
+bool GIFImageDecoderPlugin::sniff()
+{
+ InputMemoryStream stream { { m_context->data, m_context->data_size } };
+ return decode_gif_header(stream).has_value();
+}
+
+bool GIFImageDecoderPlugin::is_animated()
+{
+ if (m_context->error_state != GIFLoadingContext::ErrorState::NoError) {
+ return false;
+ }
+
+ if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) {
+ if (!load_gif_frame_descriptors(*m_context)) {
+ m_context->error_state = GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors;
+ return false;
+ }
+ }
+
+ return m_context->images.size() > 1;
+}
+
+size_t GIFImageDecoderPlugin::loop_count()
+{
+ if (m_context->error_state != GIFLoadingContext::ErrorState::NoError) {
+ return 0;
+ }
+
+ if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) {
+ if (!load_gif_frame_descriptors(*m_context)) {
+ m_context->error_state = GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors;
+ return 0;
+ }
+ }
+
+ return m_context->loops;
+}
+
+size_t GIFImageDecoderPlugin::frame_count()
+{
+ if (m_context->error_state != GIFLoadingContext::ErrorState::NoError) {
+ return 1;
+ }
+
+ if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) {
+ if (!load_gif_frame_descriptors(*m_context)) {
+ m_context->error_state = GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors;
+ return 1;
+ }
+ }
+
+ return m_context->images.size();
+}
+
+ImageFrameDescriptor GIFImageDecoderPlugin::frame(size_t i)
+{
+ if (m_context->error_state >= GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame) {
+ return {};
+ }
+
+ if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) {
+ if (!load_gif_frame_descriptors(*m_context)) {
+ m_context->error_state = GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors;
+ return {};
+ }
+ }
+
+ if (m_context->error_state == GIFLoadingContext::ErrorState::NoError && !decode_frame(*m_context, i)) {
+ if (m_context->state < GIFLoadingContext::State::FrameComplete || !decode_frame(*m_context, 0)) {
+ m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame;
+ return {};
+ }
+ m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAllFrames;
+ }
+
+ ImageFrameDescriptor frame {};
+ frame.image = m_context->frame_buffer->clone();
+ frame.duration = m_context->images.at(i).duration * 10;
+
+ if (frame.duration <= 10) {
+ frame.duration = 100;
+ }
+
+ return frame;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/GIFLoader.h b/Userland/Libraries/LibGfx/GIFLoader.h
new file mode 100644
index 0000000000..24be735381
--- /dev/null
+++ b/Userland/Libraries/LibGfx/GIFLoader.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_gif(const StringView& path);
+RefPtr<Gfx::Bitmap> load_gif_from_memory(const u8*, size_t);
+
+struct GIFLoadingContext;
+
+class GIFImageDecoderPlugin final : public ImageDecoderPlugin {
+public:
+ virtual ~GIFImageDecoderPlugin() override;
+ GIFImageDecoderPlugin(const u8*, size_t);
+
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+ virtual bool sniff() override;
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<GIFLoadingContext> m_context;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Gamma.h b/Userland/Libraries/LibGfx/Gamma.h
new file mode 100644
index 0000000000..f6be93b8fe
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Gamma.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Color.h"
+#include <math.h>
+#include <xmmintrin.h>
+
+#include <AK/SIMD.h>
+
+#define GAMMA 2.2
+
+// Most computer graphics are stored in the sRGB color space, which stores something close to
+// the square root of the display intensity of each color channel. This is problematic for most
+// operations that we want to perform on colors, since they typically assume that color scales
+// linearly (e.g. rgb(127, 0, 0) is half as bright as rgb(255, 0, 0)). This causes incorrect
+// results that look more gray than they should, to fix this we have to convert colors to the linear
+// color space before performing these operations, then convert back before displaying.
+//
+// Conversion between linear and sRGB spaces are somewhat expensive to do on the CPU, so we instead
+// interpret sRGB colors as gamma2.2 colors, which are close enough in most cases to be indistinguishable.
+// Gamma 2.2 colors follow the simple rule of `display_intensity = pow(stored_intensity, 2.2)`.
+// This module implements some fast color space transforms between the gamma2.2 and linear color spaces, plus
+// some common primitive operations like blending.
+//
+// For a more in-depth overview of how gamma-adjustment works, check out:
+// https://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/
+
+namespace Gfx {
+
+using AK::SIMD::f32x4;
+
+#ifndef NO_FPU
+
+# ifdef __SSE__
+
+// Transform f32x4 from gamma2.2 space to linear space
+// Assumes x is in range [0, 1]
+// FIXME: Remove this hack once clang-11 is available as the default in Github Actions.
+// This is apparently sometime mid-December. https://github.com/actions/virtual-environments/issues/2130
+# if !defined(__clang__) || __clang_major__ >= 11
+constexpr f32x4 gamma_to_linear4(f32x4 x)
+# else
+inline f32x4 gamma_to_linear4(f32x4 x)
+# endif
+{
+ return (0.8f + 0.2f * x) * x * x;
+}
+
+// Transform f32x4 from linear space to gamma2.2 space
+// Assumes x is in range [0, 1]
+inline f32x4 linear_to_gamma4(f32x4 x)
+{
+ // Source for approximation: https://mimosa-pudica.net/fast-gamma/
+ constexpr float a = 0.00279491f;
+ constexpr float b = 1.15907984f;
+ float c = (b / sqrt(1 + a)) - 1;
+ return ((b * __builtin_ia32_rsqrtps(x + a)) - c) * x;
+}
+
+// Linearize v1 and v2, lerp them by mix factor, then convert back.
+// The output is entirely v1 when mix = 0 and entirely v2 when mix = 1
+inline f32x4 gamma_accurate_lerp4(f32x4 v1, f32x4 v2, float mix)
+{
+ return linear_to_gamma4(gamma_to_linear4(v1) * (1 - mix) + gamma_to_linear4(v2) * mix);
+}
+
+# endif
+
+// Transform scalar from gamma2.2 space to linear space
+// Assumes x is in range [0, 1]
+constexpr float gamma_to_linear(float x)
+{
+ return (0.8 + 0.2 * x) * x * x;
+}
+
+// Transform scalar from linear space to gamma2.2 space
+// Assumes x is in range [0, 1]
+inline float linear_to_gamma(float x)
+{
+ // Source for approximation: https://mimosa-pudica.net/fast-gamma/
+ constexpr float a = 0.00279491;
+ constexpr float b = 1.15907984;
+ float c = (b / sqrt(1 + a)) - 1;
+ return ((b / __builtin_sqrt(x + a)) - c) * x;
+}
+
+// Linearize v1 and v2, lerp them by mix factor, then convert back.
+// The output is entirely v1 when mix = 0 and entirely v2 when mix = 1
+inline float gamma_accurate_lerp(float v1, float v2, float mix)
+{
+ return linear_to_gamma(gamma_to_linear(v1) * (1 - mix) + gamma_to_linear(v2) * mix);
+}
+
+// Convert a and b to linear space, blend them by mix factor, then convert back.
+// The output is entirely a when mix = 0 and entirely b when mix = 1
+inline Color gamma_accurate_blend(Color a, Color b, float mix)
+{
+# ifdef __SSE__
+ f32x4 ac = {
+ (float)a.red(),
+ (float)a.green(),
+ (float)a.blue(),
+ };
+ f32x4 bc = {
+ (float)b.red(),
+ (float)b.green(),
+ (float)b.blue(),
+ };
+ f32x4 out = 255.f * gamma_accurate_lerp4(ac * (1.f / 255.f), bc * (1.f / 255.f), mix);
+ return Color(out[0], out[1], out[2]);
+# else
+ return {
+ static_cast<u8>(255. * gamma_accurate_lerp(a.red() / 255., b.red() / 255., mix)),
+ static_cast<u8>(255. * gamma_accurate_lerp(a.green() / 255., b.green() / 255., mix)),
+ static_cast<u8>(255. * gamma_accurate_lerp(a.blue() / 255., b.blue() / 255., mix)),
+ };
+# endif
+}
+
+#endif
+
+}
diff --git a/Userland/Libraries/LibGfx/ICOLoader.cpp b/Userland/Libraries/LibGfx/ICOLoader.cpp
new file mode 100644
index 0000000000..031f4e657e
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ICOLoader.cpp
@@ -0,0 +1,438 @@
+/*
+ * Copyright (c) 2020, Paul Roukema <roukemap@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ByteBuffer.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/MemoryStream.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <AK/Types.h>
+#include <LibGfx/ICOLoader.h>
+#include <LibGfx/PNGLoader.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+//#define ICO_DEBUG
+
+namespace Gfx {
+
+// FIXME: This is in little-endian order. Maybe need a NetworkOrdered<T> equivalent eventually.
+struct ICONDIR {
+ u16 must_be_0 = 0;
+ u16 must_be_1 = 0;
+ u16 image_count = 0;
+};
+static_assert(sizeof(ICONDIR) == 6);
+
+struct ICONDIRENTRY {
+ u8 width;
+ u8 height;
+ u8 color_count;
+ u8 reserved_0;
+ u16 planes;
+ u16 bits_per_pixel;
+ u32 size;
+ u32 offset;
+};
+static_assert(sizeof(ICONDIRENTRY) == 16);
+
+struct [[gnu::packed]] BMPFILEHEADER {
+ u8 signature[2];
+ u32 size;
+ u16 reserved1;
+ u16 reserved2;
+ u32 offset;
+};
+static_assert(sizeof(BMPFILEHEADER) == 14);
+
+struct BITMAPINFOHEADER {
+ u32 size;
+ i32 width;
+ i32 height;
+ u16 planes;
+ u16 bpp;
+ u32 compression;
+ u32 size_image;
+ u32 vres;
+ u32 hres;
+ u32 palette_size;
+ u32 important_colors;
+};
+static_assert(sizeof(BITMAPINFOHEADER) == 40);
+
+struct [[gnu::packed]] BMP_ARGB {
+ u8 b;
+ u8 g;
+ u8 r;
+ u8 a;
+};
+static_assert(sizeof(BMP_ARGB) == 4);
+
+struct ImageDescriptor {
+ u16 width;
+ u16 height;
+ size_t offset;
+ size_t size;
+ RefPtr<Gfx::Bitmap> bitmap;
+};
+
+struct ICOLoadingContext {
+ enum State {
+ NotDecoded = 0,
+ Error,
+ DirectoryDecoded,
+ BitmapDecoded
+ };
+ State state { NotDecoded };
+ const u8* data { nullptr };
+ size_t data_size { 0 };
+ Vector<ImageDescriptor> images;
+ size_t largest_index;
+};
+
+RefPtr<Gfx::Bitmap> load_ico(const StringView& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return nullptr;
+ ICOImageDecoderPlugin decoder((const u8*)file_or_error.value()->data(), file_or_error.value()->size());
+ auto bitmap = decoder.bitmap();
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded ICO: {}", bitmap->size(), LexicalPath::canonicalized_path(path)));
+ return bitmap;
+}
+
+RefPtr<Gfx::Bitmap> load_ico_from_memory(const u8* data, size_t length)
+{
+ ICOImageDecoderPlugin decoder(data, length);
+ auto bitmap = decoder.bitmap();
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded ICO: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+static Optional<size_t> decode_ico_header(InputMemoryStream& stream)
+{
+ ICONDIR header;
+ stream >> Bytes { &header, sizeof(header) };
+ if (stream.handle_any_error())
+ return {};
+
+ if (header.must_be_0 != 0 || header.must_be_1 != 1)
+ return {};
+ return { header.image_count };
+}
+
+static Optional<ImageDescriptor> decode_ico_direntry(InputMemoryStream& stream)
+{
+ ICONDIRENTRY entry;
+ stream >> Bytes { &entry, sizeof(entry) };
+ if (stream.handle_any_error())
+ return {};
+
+ ImageDescriptor desc = { entry.width, entry.height, entry.offset, entry.size, nullptr };
+ if (desc.width == 0)
+ desc.width = 256;
+ if (desc.height == 0)
+ desc.height = 256;
+
+ return { desc };
+}
+
+static size_t find_largest_image(const ICOLoadingContext& context)
+{
+ size_t max_area = 0;
+ size_t index = 0;
+ size_t largest_index = 0;
+ for (const auto& desc : context.images) {
+ if (desc.width * desc.height > max_area) {
+ max_area = desc.width * desc.height;
+ largest_index = index;
+ }
+ ++index;
+ }
+ return largest_index;
+}
+
+static bool load_ico_directory(ICOLoadingContext& context)
+{
+ InputMemoryStream stream { { context.data, context.data_size } };
+
+ auto image_count = decode_ico_header(stream);
+ if (!image_count.has_value() || image_count.value() == 0) {
+ return false;
+ }
+
+ for (size_t i = 0; i < image_count.value(); ++i) {
+ auto maybe_desc = decode_ico_direntry(stream);
+ if (!maybe_desc.has_value()) {
+#ifdef ICO_DEBUG
+ printf("load_ico_directory: error loading entry: %lu\n", i);
+#endif
+ return false;
+ }
+
+ auto& desc = maybe_desc.value();
+ if (desc.offset + desc.size < desc.offset // detect integer overflow
+ || (desc.offset + desc.size) > context.data_size) {
+#ifdef ICO_DEBUG
+ printf("load_ico_directory: offset: %lu size: %lu doesn't fit in ICO size: %lu\n",
+ desc.offset, desc.size, context.data_size);
+#endif
+ return false;
+ }
+#ifdef ICO_DEBUG
+ printf("load_ico_directory: index %zu width: %u height: %u offset: %lu size: %lu\n",
+ i, desc.width, desc.height, desc.offset, desc.size);
+#endif
+ context.images.append(desc);
+ }
+ context.largest_index = find_largest_image(context);
+ context.state = ICOLoadingContext::State::DirectoryDecoded;
+ return true;
+}
+
+static bool load_ico_bmp(ICOLoadingContext& context, ImageDescriptor& desc)
+{
+ BITMAPINFOHEADER info;
+ if (desc.size < sizeof(info))
+ return false;
+
+ memcpy(&info, context.data + desc.offset, sizeof(info));
+ if (info.size != sizeof(info)) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: info size: %u, expected: %lu\n", info.size, sizeof(info));
+#endif
+ return false;
+ }
+
+ if (info.width < 0) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: width %d < 0\n", info.width);
+#endif
+ return false;
+ }
+ bool topdown = false;
+ if (info.height < 0) {
+ topdown = true;
+ info.height = -info.height;
+ }
+
+ if (info.planes != 1) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: planes: %d != 1", info.planes);
+#endif
+ return false;
+ }
+
+ if (info.bpp != 32) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: unsupported bpp: %u\n", info.bpp);
+#endif
+ return false;
+ }
+
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: width: %d height: %d direction: %s bpp: %d size_image: %u\n",
+ info.width, info.height, topdown ? "TopDown" : "BottomUp", info.bpp, info.size_image);
+#endif
+
+ if (info.compression != 0 || info.palette_size != 0 || info.important_colors != 0) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: following fields must be 0: compression: %u palette_size: %u important_colors: %u\n",
+ info.compression, info.palette_size, info.important_colors);
+#endif
+ return false;
+ }
+
+ if (info.width != desc.width || info.height != 2 * desc.height) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: size mismatch: ico %dx%d, bmp %dx%d\n",
+ desc.width, desc.height, info.width, info.height);
+#endif
+ return false;
+ }
+
+ // Mask is 1bpp, and each row must be 4-byte aligned
+ size_t mask_row_len = align_up_to(align_up_to(desc.width, 8) / 8, 4);
+ size_t required_len = desc.height * (desc.width * sizeof(BMP_ARGB) + mask_row_len);
+ size_t available_len = desc.size - sizeof(info);
+ if (required_len > available_len) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: required_len: %lu > available_len: %lu\n",
+ required_len, available_len);
+#endif
+ return false;
+ }
+
+ desc.bitmap = Bitmap::create_purgeable(BitmapFormat::RGBA32, { desc.width, desc.height });
+ if (!desc.bitmap)
+ return false;
+ Bitmap& bitmap = *desc.bitmap;
+ const u8* image_base = context.data + desc.offset + sizeof(info);
+ const BMP_ARGB* data_base = (const BMP_ARGB*)image_base;
+ const u8* mask_base = image_base + desc.width * desc.height * sizeof(BMP_ARGB);
+ for (int y = 0; y < desc.height; y++) {
+ const u8* row_mask = mask_base + mask_row_len * y;
+ const BMP_ARGB* row_data = data_base + desc.width * y;
+ for (int x = 0; x < desc.width; x++) {
+ u8 mask = !!(row_mask[x / 8] & (0x80 >> (x % 8)));
+ BMP_ARGB data = row_data[x];
+ bitmap.set_pixel(x, topdown ? y : desc.height - y - 1,
+ Color(data.r, data.g, data.b, mask ? 0 : data.a));
+ }
+ }
+ return true;
+}
+
+static bool load_ico_bitmap(ICOLoadingContext& context, Optional<size_t> index)
+{
+ if (context.state < ICOLoadingContext::State::DirectoryDecoded) {
+ if (!load_ico_directory(context)) {
+ context.state = ICOLoadingContext::State::Error;
+ return false;
+ }
+ context.state = ICOLoadingContext::State::DirectoryDecoded;
+ }
+ size_t real_index = context.largest_index;
+ if (index.has_value())
+ real_index = index.value();
+ if (real_index >= context.images.size()) {
+ return false;
+ }
+
+ ImageDescriptor& desc = context.images[real_index];
+
+ PNGImageDecoderPlugin png_decoder(context.data + desc.offset, desc.size);
+ if (png_decoder.sniff()) {
+ desc.bitmap = png_decoder.bitmap();
+ if (!desc.bitmap) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bitmap: failed to load PNG encoded image index: %lu\n", real_index);
+#endif
+ return false;
+ }
+ return true;
+ } else {
+ if (!load_ico_bmp(context, desc)) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bitmap: failed to load BMP encoded image index: %lu\n", real_index);
+#endif
+ return false;
+ }
+ return true;
+ }
+}
+
+ICOImageDecoderPlugin::ICOImageDecoderPlugin(const u8* data, size_t size)
+{
+ m_context = make<ICOLoadingContext>();
+ m_context->data = data;
+ m_context->data_size = size;
+}
+
+ICOImageDecoderPlugin::~ICOImageDecoderPlugin() { }
+
+IntSize ICOImageDecoderPlugin::size()
+{
+ if (m_context->state == ICOLoadingContext::State::Error) {
+ return {};
+ }
+
+ if (m_context->state < ICOLoadingContext::State::DirectoryDecoded) {
+ if (!load_ico_directory(*m_context)) {
+ m_context->state = ICOLoadingContext::State::Error;
+ return {};
+ }
+ m_context->state = ICOLoadingContext::State::DirectoryDecoded;
+ }
+
+ return { m_context->images[m_context->largest_index].width, m_context->images[m_context->largest_index].height };
+}
+
+RefPtr<Gfx::Bitmap> ICOImageDecoderPlugin::bitmap()
+{
+ if (m_context->state == ICOLoadingContext::State::Error)
+ return nullptr;
+
+ if (m_context->state < ICOLoadingContext::State::BitmapDecoded) {
+ // NOTE: This forces the chunk decoding to happen.
+ bool success = load_ico_bitmap(*m_context, {});
+ if (!success) {
+ m_context->state = ICOLoadingContext::State::Error;
+ return nullptr;
+ }
+ m_context->state = ICOLoadingContext::State::BitmapDecoded;
+ }
+
+ ASSERT(m_context->images[m_context->largest_index].bitmap);
+ return m_context->images[m_context->largest_index].bitmap;
+}
+
+void ICOImageDecoderPlugin::set_volatile()
+{
+ if (m_context->images[0].bitmap)
+ m_context->images[0].bitmap->set_volatile();
+}
+
+bool ICOImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->images[0].bitmap)
+ return false;
+ return m_context->images[0].bitmap->set_nonvolatile();
+}
+
+bool ICOImageDecoderPlugin::sniff()
+{
+ InputMemoryStream stream { { m_context->data, m_context->data_size } };
+ return decode_ico_header(stream).has_value();
+}
+
+bool ICOImageDecoderPlugin::is_animated()
+{
+ return false;
+}
+
+size_t ICOImageDecoderPlugin::loop_count()
+{
+ return 0;
+}
+
+size_t ICOImageDecoderPlugin::frame_count()
+{
+ return 1;
+}
+
+ImageFrameDescriptor ICOImageDecoderPlugin::frame(size_t i)
+{
+ if (i > 0) {
+ return { bitmap(), 0 };
+ }
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/ICOLoader.h b/Userland/Libraries/LibGfx/ICOLoader.h
new file mode 100644
index 0000000000..12c40b3398
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ICOLoader.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, Paul Roukema <roukemap@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_ico(const StringView& path);
+RefPtr<Gfx::Bitmap> load_ico_from_memory(const u8*, size_t);
+
+struct ICOLoadingContext;
+
+class ICOImageDecoderPlugin final : public ImageDecoderPlugin {
+public:
+ virtual ~ICOImageDecoderPlugin() override;
+ ICOImageDecoderPlugin(const u8*, size_t);
+
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+ virtual bool sniff() override;
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<ICOLoadingContext> m_context;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/ImageDecoder.cpp b/Userland/Libraries/LibGfx/ImageDecoder.cpp
new file mode 100644
index 0000000000..21c4a72a59
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ImageDecoder.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/BMPLoader.h>
+#include <LibGfx/GIFLoader.h>
+#include <LibGfx/ICOLoader.h>
+#include <LibGfx/ImageDecoder.h>
+#include <LibGfx/JPGLoader.h>
+#include <LibGfx/PBMLoader.h>
+#include <LibGfx/PGMLoader.h>
+#include <LibGfx/PNGLoader.h>
+#include <LibGfx/PPMLoader.h>
+
+namespace Gfx {
+
+ImageDecoder::ImageDecoder(const u8* data, size_t size)
+{
+ m_plugin = make<PNGImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = make<GIFImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = make<BMPImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = make<PBMImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = make<PGMImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = make<PPMImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = make<ICOImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = make<JPGImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = nullptr;
+}
+
+ImageDecoder::~ImageDecoder()
+{
+}
+
+RefPtr<Gfx::Bitmap> ImageDecoder::bitmap() const
+{
+ if (!m_plugin)
+ return nullptr;
+ return m_plugin->bitmap();
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/ImageDecoder.h b/Userland/Libraries/LibGfx/ImageDecoder.h
new file mode 100644
index 0000000000..a054f524a9
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ImageDecoder.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/OwnPtr.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <LibGfx/Size.h>
+
+namespace Gfx {
+
+class Bitmap;
+
+static constexpr size_t maximum_width_for_decoded_images = 16384;
+static constexpr size_t maximum_height_for_decoded_images = 16384;
+
+struct ImageFrameDescriptor {
+ RefPtr<Bitmap> image;
+ int duration { 0 };
+};
+
+class ImageDecoderPlugin {
+public:
+ virtual ~ImageDecoderPlugin() { }
+
+ virtual IntSize size() = 0;
+ virtual RefPtr<Gfx::Bitmap> bitmap() = 0;
+
+ virtual void set_volatile() = 0;
+ [[nodiscard]] virtual bool set_nonvolatile() = 0;
+
+ virtual bool sniff() = 0;
+
+ virtual bool is_animated() = 0;
+ virtual size_t loop_count() = 0;
+ virtual size_t frame_count() = 0;
+ virtual ImageFrameDescriptor frame(size_t i) = 0;
+
+protected:
+ ImageDecoderPlugin() { }
+};
+
+class ImageDecoder : public RefCounted<ImageDecoder> {
+public:
+ static NonnullRefPtr<ImageDecoder> create(const u8* data, size_t size) { return adopt(*new ImageDecoder(data, size)); }
+ static NonnullRefPtr<ImageDecoder> create(const ByteBuffer& data) { return adopt(*new ImageDecoder(data.data(), data.size())); }
+ ~ImageDecoder();
+
+ bool is_valid() const { return m_plugin; }
+
+ IntSize size() const { return m_plugin ? m_plugin->size() : IntSize(); }
+ int width() const { return size().width(); }
+ int height() const { return size().height(); }
+ RefPtr<Gfx::Bitmap> bitmap() const;
+ void set_volatile()
+ {
+ if (m_plugin)
+ m_plugin->set_volatile();
+ }
+ [[nodiscard]] bool set_nonvolatile() { return m_plugin ? m_plugin->set_nonvolatile() : false; }
+ bool sniff() const { return m_plugin ? m_plugin->sniff() : false; }
+ bool is_animated() const { return m_plugin ? m_plugin->is_animated() : false; }
+ size_t loop_count() const { return m_plugin ? m_plugin->loop_count() : 0; }
+ size_t frame_count() const { return m_plugin ? m_plugin->frame_count() : 0; }
+ ImageFrameDescriptor frame(size_t i) const { return m_plugin ? m_plugin->frame(i) : ImageFrameDescriptor(); }
+
+private:
+ ImageDecoder(const u8*, size_t);
+
+ mutable OwnPtr<ImageDecoderPlugin> m_plugin;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/JPGLoader.cpp b/Userland/Libraries/LibGfx/JPGLoader.cpp
new file mode 100644
index 0000000000..5b521804f0
--- /dev/null
+++ b/Userland/Libraries/LibGfx/JPGLoader.cpp
@@ -0,0 +1,1420 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Bitmap.h>
+#include <AK/ByteBuffer.h>
+#include <AK/HashMap.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/MemoryStream.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/JPGLoader.h>
+#include <math.h>
+
+//#define JPG_DEBUG
+
+#define JPG_INVALID 0X0000
+
+#define JPG_APPN0 0XFFE0
+#define JPG_APPN1 0XFFE1
+#define JPG_APPN2 0XFFE2
+#define JPG_APPN3 0XFFE3
+#define JPG_APPN4 0XFFE4
+#define JPG_APPN5 0XFFE5
+#define JPG_APPN6 0XFFE6
+#define JPG_APPN7 0XFFE7
+#define JPG_APPN8 0XFFE8
+#define JPG_APPN9 0XFFE9
+#define JPG_APPNA 0XFFEA
+#define JPG_APPNB 0XFFEB
+#define JPG_APPNC 0XFFEC
+#define JPG_APPND 0XFFED
+#define JPG_APPNE 0xFFEE
+#define JPG_APPNF 0xFFEF
+
+#define JPG_RESERVED1 0xFFF1
+#define JPG_RESERVED2 0xFFF2
+#define JPG_RESERVED3 0xFFF3
+#define JPG_RESERVED4 0xFFF4
+#define JPG_RESERVED5 0xFFF5
+#define JPG_RESERVED6 0xFFF6
+#define JPG_RESERVED7 0xFFF7
+#define JPG_RESERVED8 0xFFF8
+#define JPG_RESERVED9 0xFFF9
+#define JPG_RESERVEDA 0xFFFA
+#define JPG_RESERVEDB 0xFFFB
+#define JPG_RESERVEDC 0xFFFC
+#define JPG_RESERVEDD 0xFFFD
+
+#define JPG_RST0 0xFFD0
+#define JPG_RST1 0xFFD1
+#define JPG_RST2 0xFFD2
+#define JPG_RST3 0xFFD3
+#define JPG_RST4 0xFFD4
+#define JPG_RST5 0xFFD5
+#define JPG_RST6 0xFFD6
+#define JPG_RST7 0xFFD7
+
+#define JPG_DHP 0xFFDE
+#define JPG_EXP 0xFFDF
+
+#define JPG_DHT 0XFFC4
+#define JPG_DQT 0XFFDB
+#define JPG_EOI 0xFFD9
+#define JPG_RST 0XFFDD
+#define JPG_SOF0 0XFFC0
+#define JPG_SOF2 0xFFC2
+#define JPG_SOI 0XFFD8
+#define JPG_SOS 0XFFDA
+#define JPG_COM 0xFFFE
+
+namespace Gfx {
+
+constexpr static u8 zigzag_map[64] {
+ 0, 1, 8, 16, 9, 2, 3, 10,
+ 17, 24, 32, 25, 18, 11, 4, 5,
+ 12, 19, 26, 33, 40, 48, 41, 34,
+ 27, 20, 13, 6, 7, 14, 21, 28,
+ 35, 42, 49, 56, 57, 50, 43, 36,
+ 29, 22, 15, 23, 30, 37, 44, 51,
+ 58, 59, 52, 45, 38, 31, 39, 46,
+ 53, 60, 61, 54, 47, 55, 62, 63
+};
+
+using Marker = u16;
+
+/**
+ * MCU means group of data units that are coded together. A data unit is an 8x8
+ * block of component data. In interleaved scans, number of non-interleaved data
+ * units of a component C is Ch * Cv, where Ch and Cv represent the horizontal &
+ * vertical subsampling factors of the component, respectively. A MacroBlock is
+ * an 8x8 block of RGB values before encoding, and 8x8 block of YCbCr values when
+ * we're done decoding the huffman stream.
+ */
+struct Macroblock {
+ union {
+ i32 y[64] = { 0 };
+ i32 r[64];
+ };
+
+ union {
+ i32 cb[64] = { 0 };
+ i32 g[64];
+ };
+
+ union {
+ i32 cr[64] = { 0 };
+ i32 b[64];
+ };
+};
+
+struct MacroblockMeta {
+ u32 total { 0 };
+ u32 padded_total { 0 };
+ u32 hcount { 0 };
+ u32 vcount { 0 };
+ u32 hpadded_count { 0 };
+ u32 vpadded_count { 0 };
+};
+
+struct ComponentSpec {
+ u8 serial_id { 255 }; // In the interval [0, 3).
+ u8 id { 0 };
+ u8 hsample_factor { 1 }; // Horizontal sampling factor.
+ u8 vsample_factor { 1 }; // Vertical sampling factor.
+ u8 ac_destination_id { 0 };
+ u8 dc_destination_id { 0 };
+ u8 qtable_id { 0 }; // Quantization table id.
+};
+
+struct StartOfFrame {
+
+ // Of these, only the first 3 are in mainstream use, and refers to SOF0-2.
+ enum class FrameType {
+ Baseline_DCT = 0,
+ Extended_Sequential_DCT = 1,
+ Progressive_DCT = 2,
+ Sequential_Lossless = 3,
+ Differential_Sequential_DCT = 5,
+ Differential_Progressive_DCT = 6,
+ Differential_Sequential_Lossless = 7,
+ Extended_Sequential_DCT_Arithmetic = 9,
+ Progressive_DCT_Arithmetic = 10,
+ Sequential_Lossless_Arithmetic = 11,
+ Differential_Sequential_DCT_Arithmetic = 13,
+ Differential_Progressive_DCT_Arithmetic = 14,
+ Differential_Sequential_Lossless_Arithmetic = 15,
+ };
+
+ FrameType type { FrameType::Baseline_DCT };
+ u8 precision { 0 };
+ u16 height { 0 };
+ u16 width { 0 };
+};
+
+struct HuffmanTableSpec {
+ u8 type { 0 };
+ u8 destination_id { 0 };
+ u8 code_counts[16] = { 0 };
+ Vector<u8> symbols;
+ Vector<u16> codes;
+};
+
+struct HuffmanStreamState {
+ Vector<u8> stream;
+ u8 bit_offset { 0 };
+ size_t byte_offset { 0 };
+};
+
+struct JPGLoadingContext {
+ enum State {
+ NotDecoded = 0,
+ Error,
+ FrameDecoded,
+ BitmapDecoded
+ };
+
+ State state { State::NotDecoded };
+ const u8* data { nullptr };
+ size_t data_size { 0 };
+ u32 luma_table[64] = { 0 };
+ u32 chroma_table[64] = { 0 };
+ StartOfFrame frame;
+ u8 hsample_factor { 0 };
+ u8 vsample_factor { 0 };
+ u8 component_count { 0 };
+ HashMap<u8, ComponentSpec> components;
+ RefPtr<Gfx::Bitmap> bitmap;
+ u16 dc_reset_interval { 0 };
+ HashMap<u8, HuffmanTableSpec> dc_tables;
+ HashMap<u8, HuffmanTableSpec> ac_tables;
+ HuffmanStreamState huffman_stream;
+ i32 previous_dc_values[3] = { 0 };
+ MacroblockMeta mblock_meta;
+};
+
+static void generate_huffman_codes(HuffmanTableSpec& table)
+{
+ unsigned code = 0;
+ for (auto number_of_codes : table.code_counts) {
+ for (int i = 0; i < number_of_codes; i++)
+ table.codes.append(code++);
+ code <<= 1;
+ }
+}
+
+static Optional<size_t> read_huffman_bits(HuffmanStreamState& hstream, size_t count = 1)
+{
+ if (count > (8 * sizeof(size_t))) {
+#ifdef JPG_DEBUG
+ dbg() << String::format("Can't read %zu bits at once!", count);
+#endif
+ return {};
+ }
+ size_t value = 0;
+ while (count--) {
+ if (hstream.byte_offset >= hstream.stream.size()) {
+#ifdef JPG_DEBUG
+ dbg() << String::format("Huffman stream exhausted. This could be an error!");
+#endif
+ return {};
+ }
+ u8 current_byte = hstream.stream[hstream.byte_offset];
+ u8 current_bit = 1u & (u32)(current_byte >> (7 - hstream.bit_offset)); // MSB first.
+ hstream.bit_offset++;
+ value = (value << 1) | (size_t)current_bit;
+ if (hstream.bit_offset == 8) {
+ hstream.byte_offset++;
+ hstream.bit_offset = 0;
+ }
+ }
+ return value;
+}
+
+static Optional<u8> get_next_symbol(HuffmanStreamState& hstream, const HuffmanTableSpec& table)
+{
+ unsigned code = 0;
+ size_t code_cursor = 0;
+ for (int i = 0; i < 16; i++) { // Codes can't be longer than 16 bits.
+ auto result = read_huffman_bits(hstream);
+ if (!result.has_value())
+ return {};
+ code = (code << 1) | (i32)result.release_value();
+ for (int j = 0; j < table.code_counts[i]; j++) {
+ if (code == table.codes[code_cursor])
+ return table.symbols[code_cursor];
+ code_cursor++;
+ }
+ }
+
+#ifdef JPG_DEBUG
+ dbgln("If you're seeing this...the jpeg decoder needs to support more kinds of JPEGs!");
+#endif
+ return {};
+}
+
+/**
+ * Build the macroblocks possible by reading single (MCU) subsampled pair of CbCr.
+ * Depending on the sampling factors, we may not see triples of y, cb, cr in that
+ * order. If sample factors differ from one, we'll read more than one block of y-
+ * coefficients before we get to read a cb-cr block.
+
+ * In the function below, `hcursor` and `vcursor` denote the location of the block
+ * we're building in the macroblock matrix. `vfactor_i` and `hfactor_i` are cursors
+ * that iterate over the vertical and horizontal subsampling factors, respectively.
+ * When we finish one iteration of the innermost loop, we'll have the coefficients
+ * of one of the components of block at position `mb_index`. When the outermost loop
+ * finishes first iteration, we'll have all the luminance coefficients for all the
+ * macroblocks that share the chrominance data. Next two iterations (assuming that
+ * we are dealing with three components) will fill up the blocks with chroma data.
+ */
+static bool build_macroblocks(JPGLoadingContext& context, Vector<Macroblock>& macroblocks, u8 hcursor, u8 vcursor)
+{
+ for (auto it = context.components.begin(); it != context.components.end(); ++it) {
+ ComponentSpec& component = it->value;
+
+ if (component.dc_destination_id >= context.dc_tables.size())
+ return false;
+ if (component.ac_destination_id >= context.ac_tables.size())
+ return false;
+
+ for (u8 vfactor_i = 0; vfactor_i < component.vsample_factor; vfactor_i++) {
+ for (u8 hfactor_i = 0; hfactor_i < component.hsample_factor; hfactor_i++) {
+ u32 mb_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hfactor_i + hcursor);
+ Macroblock& block = macroblocks[mb_index];
+
+ auto& dc_table = context.dc_tables.find(component.dc_destination_id)->value;
+ auto& ac_table = context.ac_tables.find(component.ac_destination_id)->value;
+
+ auto symbol_or_error = get_next_symbol(context.huffman_stream, dc_table);
+ if (!symbol_or_error.has_value())
+ return false;
+
+ // For DC coefficients, symbol encodes the length of the coefficient.
+ auto dc_length = symbol_or_error.release_value();
+ if (dc_length > 11) {
+#ifdef JPG_DEBUG
+ dbg() << String::format("DC coefficient too long: %i!", dc_length);
+#endif
+ return false;
+ }
+
+ auto coeff_or_error = read_huffman_bits(context.huffman_stream, dc_length);
+ if (!coeff_or_error.has_value())
+ return false;
+
+ // DC coefficients are encoded as the difference between previous and current DC values.
+ i32 dc_diff = coeff_or_error.release_value();
+
+ // If MSB in diff is 0, the difference is -ve. Otherwise +ve.
+ if (dc_length != 0 && dc_diff < (1 << (dc_length - 1)))
+ dc_diff -= (1 << dc_length) - 1;
+
+ i32* select_component = component.serial_id == 0 ? block.y : (component.serial_id == 1 ? block.cb : block.cr);
+ auto& previous_dc = context.previous_dc_values[component.serial_id];
+ select_component[0] = previous_dc += dc_diff;
+
+ // Compute the AC coefficients.
+ for (int j = 1; j < 64;) {
+ symbol_or_error = get_next_symbol(context.huffman_stream, ac_table);
+ if (!symbol_or_error.has_value())
+ return false;
+
+ // AC symbols encode 2 pieces of information, the high 4 bits represent
+ // number of zeroes to be stuffed before reading the coefficient. Low 4
+ // bits represent the magnitude of the coefficient.
+ auto ac_symbol = symbol_or_error.release_value();
+ if (ac_symbol == 0)
+ break;
+
+ // ac_symbol = 0xF0 means we need to skip 16 zeroes.
+ u8 run_length = ac_symbol == 0xF0 ? 16 : ac_symbol >> 4;
+ j += run_length;
+
+ if (j >= 64) {
+#ifdef JPG_DEBUG
+ dbg() << String::format("Run-length exceeded boundaries. Cursor: %i, Skipping: %i!", j, run_length);
+#endif
+ return false;
+ }
+
+ u8 coeff_length = ac_symbol & 0x0F;
+ if (coeff_length > 10) {
+#ifdef JPG_DEBUG
+ dbg() << String::format("AC coefficient too long: %i!", coeff_length);
+#endif
+ return false;
+ }
+
+ if (coeff_length != 0) {
+ coeff_or_error = read_huffman_bits(context.huffman_stream, coeff_length);
+ if (!coeff_or_error.has_value())
+ return false;
+ i32 ac_coefficient = coeff_or_error.release_value();
+ if (ac_coefficient < (1 << (coeff_length - 1)))
+ ac_coefficient -= (1 << coeff_length) - 1;
+
+ select_component[zigzag_map[j++]] = ac_coefficient;
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+static Optional<Vector<Macroblock>> decode_huffman_stream(JPGLoadingContext& context)
+{
+ Vector<Macroblock> macroblocks;
+ macroblocks.resize(context.mblock_meta.padded_total);
+
+#ifdef JPG_DEBUG
+ dbg() << "Image width: " << context.frame.width;
+ dbg() << "Image height: " << context.frame.height;
+ dbg() << "Macroblocks in a row: " << context.mblock_meta.hpadded_count;
+ dbg() << "Macroblocks in a column: " << context.mblock_meta.vpadded_count;
+ dbg() << "Macroblock meta padded total: " << context.mblock_meta.padded_total;
+#endif
+
+ // Compute huffman codes for DC and AC tables.
+ for (auto it = context.dc_tables.begin(); it != context.dc_tables.end(); ++it)
+ generate_huffman_codes(it->value);
+
+ for (auto it = context.ac_tables.begin(); it != context.ac_tables.end(); ++it)
+ generate_huffman_codes(it->value);
+
+ for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) {
+ for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) {
+ u32 i = vcursor * context.mblock_meta.hpadded_count + hcursor;
+ if (context.dc_reset_interval > 0) {
+ if (i % context.dc_reset_interval == 0) {
+ context.previous_dc_values[0] = 0;
+ context.previous_dc_values[1] = 0;
+ context.previous_dc_values[2] = 0;
+
+ // Restart markers are stored in byte boundaries. Advance the huffman stream cursor to
+ // the 0th bit of the next byte.
+ if (context.huffman_stream.byte_offset < context.huffman_stream.stream.size()) {
+ if (context.huffman_stream.bit_offset > 0) {
+ context.huffman_stream.bit_offset = 0;
+ context.huffman_stream.byte_offset++;
+ }
+
+ // Skip the restart marker (RSTn).
+ context.huffman_stream.byte_offset++;
+ }
+ }
+ }
+
+ if (!build_macroblocks(context, macroblocks, hcursor, vcursor)) {
+#ifdef JPG_DEBUG
+ dbg() << "Failed to build Macroblock " << i;
+ dbg() << "Huffman stream byte offset " << context.huffman_stream.byte_offset;
+ dbg() << "Huffman stream bit offset " << context.huffman_stream.bit_offset;
+#endif
+ return {};
+ }
+ }
+ }
+
+ return macroblocks;
+}
+
+static inline bool bounds_okay(const size_t cursor, const size_t delta, const size_t bound)
+{
+ return (delta + cursor) < bound;
+}
+
+static inline bool is_valid_marker(const Marker marker)
+{
+ if (marker >= JPG_APPN0 && marker <= JPG_APPNF) {
+#ifdef JPG_DEBUG
+ if (marker != JPG_APPN0)
+ dbg() << String::format("%04x not supported yet. The decoder may fail!", marker);
+#endif
+ return true;
+ }
+ if (marker >= JPG_RESERVED1 && marker <= JPG_RESERVEDD)
+ return true;
+ if (marker >= JPG_RST0 && marker <= JPG_RST7)
+ return true;
+ switch (marker) {
+ case JPG_COM:
+ case JPG_DHP:
+ case JPG_EXP:
+ case JPG_DHT:
+ case JPG_DQT:
+ case JPG_RST:
+ case JPG_SOF0:
+ case JPG_SOI:
+ case JPG_SOS:
+ return true;
+ }
+
+ if (marker >= 0xFFC0 && marker <= 0xFFCF) {
+ if (marker != 0xFFC4 && marker != 0xFFC8 && marker != 0xFFCC) {
+#ifdef JPG_DEBUG
+ dbg() << "Decoding this frame-type (SOF" << (marker & 0xf) << ") is not currently supported. Decoder will fail!";
+#endif
+ return false;
+ }
+ }
+
+ return false;
+}
+
+static inline u16 read_be_word(InputMemoryStream& stream)
+{
+ BigEndian<u16> tmp;
+ stream >> tmp;
+ return tmp;
+}
+
+static inline Marker read_marker_at_cursor(InputMemoryStream& stream)
+{
+ u16 marker = read_be_word(stream);
+ if (stream.handle_any_error())
+ return JPG_INVALID;
+ if (is_valid_marker(marker))
+ return marker;
+ if (marker != 0xFFFF)
+ return JPG_INVALID;
+ u8 next;
+ do {
+ stream >> next;
+ if (stream.handle_any_error() || next == 0x00)
+ return JPG_INVALID;
+ } while (next == 0xFF);
+ marker = 0xFF00 | (u16)next;
+ return is_valid_marker(marker) ? marker : JPG_INVALID;
+}
+
+static bool read_start_of_scan(InputMemoryStream& stream, JPGLoadingContext& context)
+{
+ if (context.state < JPGLoadingContext::State::FrameDecoded) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": SOS found before reading a SOF!";
+#endif
+ return false;
+ }
+
+ u16 bytes_to_read = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ bytes_to_read -= 2;
+ if (!bounds_okay(stream.offset(), bytes_to_read, context.data_size))
+ return false;
+ u8 component_count = 0;
+ stream >> component_count;
+ if (stream.handle_any_error())
+ return false;
+ if (component_count != context.component_count) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset()
+ << String::format(": Unsupported number of components: %i!", component_count);
+#endif
+ return false;
+ }
+
+ for (int i = 0; i < component_count; i++) {
+ ComponentSpec* component = nullptr;
+ u8 component_id = 0;
+ stream >> component_id;
+ if (stream.handle_any_error())
+ return false;
+
+ auto it = context.components.find(component_id);
+ if (it != context.components.end()) {
+ component = &it->value;
+ if (i != component->serial_id) {
+ dbgln("JPEG decode failed (i != component->serial_id)");
+ return false;
+ }
+ } else {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << String::format(": Unsupported component id: %i!", component_id);
+#endif
+ return false;
+ }
+
+ u8 table_ids = 0;
+ stream >> table_ids;
+ if (stream.handle_any_error())
+ return false;
+
+ component->dc_destination_id = table_ids >> 4;
+ component->ac_destination_id = table_ids & 0x0F;
+
+ if (context.dc_tables.size() != context.ac_tables.size()) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": DC & AC table count mismatch!";
+#endif
+ return false;
+ }
+
+ if (!context.dc_tables.contains(component->dc_destination_id)) {
+#ifdef JPG_DEBUG
+ dbgln("DC table (id: {}) does not exist!", component->dc_destination_id);
+#endif
+ return false;
+ }
+
+ if (!context.ac_tables.contains(component->ac_destination_id)) {
+#ifdef JPG_DEBUG
+ dbgln("AC table (id: {}) does not exist!", component->ac_destination_id);
+#endif
+ return false;
+ }
+ }
+
+ u8 spectral_selection_start = 0;
+ stream >> spectral_selection_start;
+ if (stream.handle_any_error())
+ return false;
+ u8 spectral_selection_end = 0;
+ stream >> spectral_selection_end;
+ if (stream.handle_any_error())
+ return false;
+ u8 successive_approximation = 0;
+ stream >> successive_approximation;
+ if (stream.handle_any_error())
+ return false;
+ // The three values should be fixed for baseline JPEGs utilizing sequential DCT.
+ if (spectral_selection_start != 0 || spectral_selection_end != 63 || successive_approximation != 0) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": ERROR! Start of Selection: " << spectral_selection_start
+ << ", End of Selection: " << spectral_selection_end
+ << ", Successive Approximation: " << successive_approximation << "!";
+#endif
+ return false;
+ }
+ return true;
+}
+
+static bool read_reset_marker(InputMemoryStream& stream, JPGLoadingContext& context)
+{
+ u16 bytes_to_read = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ bytes_to_read -= 2;
+ if (bytes_to_read != 2) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Malformed reset marker found!";
+#endif
+ return false;
+ }
+ context.dc_reset_interval = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ return true;
+}
+
+static bool read_huffman_table(InputMemoryStream& stream, JPGLoadingContext& context)
+{
+ i32 bytes_to_read = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ if (!bounds_okay(stream.offset(), bytes_to_read, context.data_size))
+ return false;
+ bytes_to_read -= 2;
+ while (bytes_to_read > 0) {
+ HuffmanTableSpec table;
+ u8 table_info = 0;
+ stream >> table_info;
+ if (stream.handle_any_error())
+ return false;
+ u8 table_type = table_info >> 4;
+ u8 table_destination_id = table_info & 0x0F;
+ if (table_type > 1) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << String::format(": Unrecognized huffman table: %i!", table_type);
+#endif
+ return false;
+ }
+ if (table_destination_id > 1) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset()
+ << String::format(": Invalid huffman table destination id: %i!", table_destination_id);
+#endif
+ return false;
+ }
+
+ table.type = table_type;
+ table.destination_id = table_destination_id;
+ u32 total_codes = 0;
+
+ // Read code counts. At each index K, the value represents the number of K+1 bit codes in this header.
+ for (int i = 0; i < 16; i++) {
+ u8 count = 0;
+ stream >> count;
+ if (stream.handle_any_error())
+ return false;
+ total_codes += count;
+ table.code_counts[i] = count;
+ }
+
+ table.codes.ensure_capacity(total_codes);
+
+ // Read symbols. Read X bytes, where X is the sum of the counts of codes read in the previous step.
+ for (u32 i = 0; i < total_codes; i++) {
+ u8 symbol = 0;
+ stream >> symbol;
+ if (stream.handle_any_error())
+ return false;
+ table.symbols.append(symbol);
+ }
+
+ if (stream.handle_any_error())
+ return false;
+
+ auto& huffman_table = table.type == 0 ? context.dc_tables : context.ac_tables;
+ huffman_table.set(table.destination_id, table);
+ ASSERT(huffman_table.size() <= 2);
+
+ bytes_to_read -= 1 + 16 + total_codes;
+ }
+
+ if (bytes_to_read != 0) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Extra bytes detected in huffman header!";
+#endif
+ return false;
+ }
+ return true;
+}
+
+static inline bool validate_luma_and_modify_context(const ComponentSpec& luma, JPGLoadingContext& context)
+{
+ if ((luma.hsample_factor == 1 || luma.hsample_factor == 2) && (luma.vsample_factor == 1 || luma.vsample_factor == 2)) {
+ context.mblock_meta.hpadded_count += luma.hsample_factor == 1 ? 0 : context.mblock_meta.hcount % 2;
+ context.mblock_meta.vpadded_count += luma.vsample_factor == 1 ? 0 : context.mblock_meta.vcount % 2;
+ context.mblock_meta.padded_total = context.mblock_meta.hpadded_count * context.mblock_meta.vpadded_count;
+ // For easy reference to relevant sample factors.
+ context.hsample_factor = luma.hsample_factor;
+ context.vsample_factor = luma.vsample_factor;
+#ifdef JPG_DEBUG
+ dbg() << String::format("Horizontal Subsampling Factor: %i", luma.hsample_factor);
+ dbg() << String::format("Vertical Subsampling Factor: %i", luma.vsample_factor);
+#endif
+ return true;
+ }
+ return false;
+}
+
+static inline void set_macroblock_metadata(JPGLoadingContext& context)
+{
+ context.mblock_meta.hcount = (context.frame.width + 7) / 8;
+ context.mblock_meta.vcount = (context.frame.height + 7) / 8;
+ context.mblock_meta.hpadded_count = context.mblock_meta.hcount;
+ context.mblock_meta.vpadded_count = context.mblock_meta.vcount;
+ context.mblock_meta.total = context.mblock_meta.hcount * context.mblock_meta.vcount;
+}
+
+static bool read_start_of_frame(InputMemoryStream& stream, JPGLoadingContext& context)
+{
+ if (context.state == JPGLoadingContext::FrameDecoded) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": SOF repeated!";
+#endif
+ return false;
+ }
+
+ i32 bytes_to_read = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+
+ bytes_to_read -= 2;
+ if (!bounds_okay(stream.offset(), bytes_to_read, context.data_size))
+ return false;
+
+ stream >> context.frame.precision;
+ if (stream.handle_any_error())
+ return false;
+ if (context.frame.precision != 8) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": SOF precision != 8!";
+#endif
+ return false;
+ }
+
+ context.frame.height = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ context.frame.width = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ if (!context.frame.width || !context.frame.height) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": ERROR! Image height: " << context.frame.height << ", Image width: "
+ << context.frame.width << "!";
+#endif
+ return false;
+ }
+
+ if (context.frame.width > maximum_width_for_decoded_images || context.frame.height > maximum_height_for_decoded_images) {
+ dbgln("This JPEG is too large for comfort: {}x{}", context.frame.width, context.frame.height);
+ return false;
+ }
+
+ set_macroblock_metadata(context);
+
+ stream >> context.component_count;
+ if (stream.handle_any_error())
+ return false;
+ if (context.component_count != 1 && context.component_count != 3) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Unsupported number of components in SOF: "
+ << context.component_count << "!";
+#endif
+ return false;
+ }
+
+ for (u8 i = 0; i < context.component_count; i++) {
+ ComponentSpec component;
+ component.serial_id = i;
+
+ stream >> component.id;
+ if (stream.handle_any_error())
+ return false;
+
+ u8 subsample_factors = 0;
+ stream >> subsample_factors;
+ if (stream.handle_any_error())
+ return false;
+ component.hsample_factor = subsample_factors >> 4;
+ component.vsample_factor = subsample_factors & 0x0F;
+
+ if (component.serial_id == 0) {
+ // By convention, downsampling is applied only on chroma components. So we should
+ // hope to see the maximum sampling factor in the luma component.
+ if (!validate_luma_and_modify_context(component, context)) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Unsupported luma subsampling factors: "
+ << "horizontal: " << component.hsample_factor << ", vertical: " << component.vsample_factor;
+#endif
+ return false;
+ }
+ } else {
+ if (component.hsample_factor != 1 || component.vsample_factor != 1) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Unsupported chroma subsampling factors: "
+ << "horizontal: " << component.hsample_factor << ", vertical: " << component.vsample_factor;
+#endif
+ return false;
+ }
+ }
+
+ stream >> component.qtable_id;
+ if (stream.handle_any_error())
+ return false;
+ if (component.qtable_id > 1) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Unsupported quantization table id: "
+ << component.qtable_id << "!";
+#endif
+ return false;
+ }
+
+ context.components.set(component.id, component);
+ }
+
+ return true;
+}
+
+static bool read_quantization_table(InputMemoryStream& stream, JPGLoadingContext& context)
+{
+ i32 bytes_to_read = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ bytes_to_read -= 2;
+ if (!bounds_okay(stream.offset(), bytes_to_read, context.data_size))
+ return false;
+ while (bytes_to_read > 0) {
+ u8 info_byte = 0;
+ stream >> info_byte;
+ if (stream.handle_any_error())
+ return false;
+ u8 element_unit_hint = info_byte >> 4;
+ if (element_unit_hint > 1) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset()
+ << String::format(": Unsupported unit hint in quantization table: %i!", element_unit_hint);
+#endif
+ return false;
+ }
+ u8 table_id = info_byte & 0x0F;
+ if (table_id > 1) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << String::format(": Unsupported quantization table id: %i!", table_id);
+#endif
+ return false;
+ }
+ u32* table = table_id == 0 ? context.luma_table : context.chroma_table;
+ for (int i = 0; i < 64; i++) {
+ if (element_unit_hint == 0) {
+ u8 tmp = 0;
+ stream >> tmp;
+ if (stream.handle_any_error())
+ return false;
+ table[zigzag_map[i]] = tmp;
+ } else {
+ table[zigzag_map[i]] = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ }
+ }
+ if (stream.handle_any_error())
+ return false;
+
+ bytes_to_read -= 1 + (element_unit_hint == 0 ? 64 : 128);
+ }
+ if (bytes_to_read != 0) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Invalid length for one or more quantization tables!";
+#endif
+ return false;
+ }
+
+ return true;
+}
+
+static bool skip_marker_with_length(InputMemoryStream& stream)
+{
+ u16 bytes_to_skip = read_be_word(stream);
+ bytes_to_skip -= 2;
+ if (stream.handle_any_error())
+ return false;
+ stream.discard_or_error(bytes_to_skip);
+ return !stream.handle_any_error();
+}
+
+static void dequantize(JPGLoadingContext& context, Vector<Macroblock>& macroblocks)
+{
+ for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) {
+ for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) {
+ for (auto it = context.components.begin(); it != context.components.end(); ++it) {
+ auto& component = it->value;
+ const u32* table = component.qtable_id == 0 ? context.luma_table : context.chroma_table;
+ for (u32 vfactor_i = 0; vfactor_i < component.vsample_factor; vfactor_i++) {
+ for (u32 hfactor_i = 0; hfactor_i < component.hsample_factor; hfactor_i++) {
+ u32 mb_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hfactor_i + hcursor);
+ Macroblock& block = macroblocks[mb_index];
+ int* block_component = component.serial_id == 0 ? block.y : (component.serial_id == 1 ? block.cb : block.cr);
+ for (u32 k = 0; k < 64; k++)
+ block_component[k] *= table[k];
+ }
+ }
+ }
+ }
+ }
+}
+
+static void inverse_dct(const JPGLoadingContext& context, Vector<Macroblock>& macroblocks)
+{
+ static const float m0 = 2.0 * cos(1.0 / 16.0 * 2.0 * M_PI);
+ static const float m1 = 2.0 * cos(2.0 / 16.0 * 2.0 * M_PI);
+ static const float m3 = 2.0 * cos(2.0 / 16.0 * 2.0 * M_PI);
+ static const float m5 = 2.0 * cos(3.0 / 16.0 * 2.0 * M_PI);
+ static const float m2 = m0 - m5;
+ static const float m4 = m0 + m5;
+ static const float s0 = cos(0.0 / 16.0 * M_PI) / sqrt(8);
+ static const float s1 = cos(1.0 / 16.0 * M_PI) / 2.0;
+ static const float s2 = cos(2.0 / 16.0 * M_PI) / 2.0;
+ static const float s3 = cos(3.0 / 16.0 * M_PI) / 2.0;
+ static const float s4 = cos(4.0 / 16.0 * M_PI) / 2.0;
+ static const float s5 = cos(5.0 / 16.0 * M_PI) / 2.0;
+ static const float s6 = cos(6.0 / 16.0 * M_PI) / 2.0;
+ static const float s7 = cos(7.0 / 16.0 * M_PI) / 2.0;
+
+ for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) {
+ for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) {
+ for (auto it = context.components.begin(); it != context.components.end(); ++it) {
+ auto& component = it->value;
+ for (u8 vfactor_i = 0; vfactor_i < component.vsample_factor; vfactor_i++) {
+ for (u8 hfactor_i = 0; hfactor_i < component.hsample_factor; hfactor_i++) {
+ u32 mb_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hfactor_i + hcursor);
+ Macroblock& block = macroblocks[mb_index];
+ i32* block_component = component.serial_id == 0 ? block.y : (component.serial_id == 1 ? block.cb : block.cr);
+ for (u32 k = 0; k < 8; ++k) {
+ const float g0 = block_component[0 * 8 + k] * s0;
+ const float g1 = block_component[4 * 8 + k] * s4;
+ const float g2 = block_component[2 * 8 + k] * s2;
+ const float g3 = block_component[6 * 8 + k] * s6;
+ const float g4 = block_component[5 * 8 + k] * s5;
+ const float g5 = block_component[1 * 8 + k] * s1;
+ const float g6 = block_component[7 * 8 + k] * s7;
+ const float g7 = block_component[3 * 8 + k] * s3;
+
+ const float f0 = g0;
+ const float f1 = g1;
+ const float f2 = g2;
+ const float f3 = g3;
+ const float f4 = g4 - g7;
+ const float f5 = g5 + g6;
+ const float f6 = g5 - g6;
+ const float f7 = g4 + g7;
+
+ const float e0 = f0;
+ const float e1 = f1;
+ const float e2 = f2 - f3;
+ const float e3 = f2 + f3;
+ const float e4 = f4;
+ const float e5 = f5 - f7;
+ const float e6 = f6;
+ const float e7 = f5 + f7;
+ const float e8 = f4 + f6;
+
+ const float d0 = e0;
+ const float d1 = e1;
+ const float d2 = e2 * m1;
+ const float d3 = e3;
+ const float d4 = e4 * m2;
+ const float d5 = e5 * m3;
+ const float d6 = e6 * m4;
+ const float d7 = e7;
+ const float d8 = e8 * m5;
+
+ const float c0 = d0 + d1;
+ const float c1 = d0 - d1;
+ const float c2 = d2 - d3;
+ const float c3 = d3;
+ const float c4 = d4 + d8;
+ const float c5 = d5 + d7;
+ const float c6 = d6 - d8;
+ const float c7 = d7;
+ const float c8 = c5 - c6;
+
+ const float b0 = c0 + c3;
+ const float b1 = c1 + c2;
+ const float b2 = c1 - c2;
+ const float b3 = c0 - c3;
+ const float b4 = c4 - c8;
+ const float b5 = c8;
+ const float b6 = c6 - c7;
+ const float b7 = c7;
+
+ block_component[0 * 8 + k] = b0 + b7;
+ block_component[1 * 8 + k] = b1 + b6;
+ block_component[2 * 8 + k] = b2 + b5;
+ block_component[3 * 8 + k] = b3 + b4;
+ block_component[4 * 8 + k] = b3 - b4;
+ block_component[5 * 8 + k] = b2 - b5;
+ block_component[6 * 8 + k] = b1 - b6;
+ block_component[7 * 8 + k] = b0 - b7;
+ }
+ for (u32 l = 0; l < 8; ++l) {
+ const float g0 = block_component[l * 8 + 0] * s0;
+ const float g1 = block_component[l * 8 + 4] * s4;
+ const float g2 = block_component[l * 8 + 2] * s2;
+ const float g3 = block_component[l * 8 + 6] * s6;
+ const float g4 = block_component[l * 8 + 5] * s5;
+ const float g5 = block_component[l * 8 + 1] * s1;
+ const float g6 = block_component[l * 8 + 7] * s7;
+ const float g7 = block_component[l * 8 + 3] * s3;
+
+ const float f0 = g0;
+ const float f1 = g1;
+ const float f2 = g2;
+ const float f3 = g3;
+ const float f4 = g4 - g7;
+ const float f5 = g5 + g6;
+ const float f6 = g5 - g6;
+ const float f7 = g4 + g7;
+
+ const float e0 = f0;
+ const float e1 = f1;
+ const float e2 = f2 - f3;
+ const float e3 = f2 + f3;
+ const float e4 = f4;
+ const float e5 = f5 - f7;
+ const float e6 = f6;
+ const float e7 = f5 + f7;
+ const float e8 = f4 + f6;
+
+ const float d0 = e0;
+ const float d1 = e1;
+ const float d2 = e2 * m1;
+ const float d3 = e3;
+ const float d4 = e4 * m2;
+ const float d5 = e5 * m3;
+ const float d6 = e6 * m4;
+ const float d7 = e7;
+ const float d8 = e8 * m5;
+
+ const float c0 = d0 + d1;
+ const float c1 = d0 - d1;
+ const float c2 = d2 - d3;
+ const float c3 = d3;
+ const float c4 = d4 + d8;
+ const float c5 = d5 + d7;
+ const float c6 = d6 - d8;
+ const float c7 = d7;
+ const float c8 = c5 - c6;
+
+ const float b0 = c0 + c3;
+ const float b1 = c1 + c2;
+ const float b2 = c1 - c2;
+ const float b3 = c0 - c3;
+ const float b4 = c4 - c8;
+ const float b5 = c8;
+ const float b6 = c6 - c7;
+ const float b7 = c7;
+
+ block_component[l * 8 + 0] = b0 + b7;
+ block_component[l * 8 + 1] = b1 + b6;
+ block_component[l * 8 + 2] = b2 + b5;
+ block_component[l * 8 + 3] = b3 + b4;
+ block_component[l * 8 + 4] = b3 - b4;
+ block_component[l * 8 + 5] = b2 - b5;
+ block_component[l * 8 + 6] = b1 - b6;
+ block_component[l * 8 + 7] = b0 - b7;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+static void ycbcr_to_rgb(const JPGLoadingContext& context, Vector<Macroblock>& macroblocks)
+{
+ for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) {
+ for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) {
+ const u32 chroma_block_index = vcursor * context.mblock_meta.hpadded_count + hcursor;
+ const Macroblock& chroma = macroblocks[chroma_block_index];
+ // Overflows are intentional.
+ for (u8 vfactor_i = context.vsample_factor - 1; vfactor_i < context.vsample_factor; --vfactor_i) {
+ for (u8 hfactor_i = context.hsample_factor - 1; hfactor_i < context.hsample_factor; --hfactor_i) {
+ u32 mb_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hcursor + hfactor_i);
+ i32* y = macroblocks[mb_index].y;
+ i32* cb = macroblocks[mb_index].cb;
+ i32* cr = macroblocks[mb_index].cr;
+ for (u8 i = 7; i < 8; --i) {
+ for (u8 j = 7; j < 8; --j) {
+ const u8 pixel = i * 8 + j;
+ const u32 chroma_pxrow = (i / context.vsample_factor) + 4 * vfactor_i;
+ const u32 chroma_pxcol = (j / context.hsample_factor) + 4 * hfactor_i;
+ const u32 chroma_pixel = chroma_pxrow * 8 + chroma_pxcol;
+ int r = y[pixel] + 1.402f * chroma.cr[chroma_pixel] + 128;
+ int g = y[pixel] - 0.344f * chroma.cb[chroma_pixel] - 0.714f * chroma.cr[chroma_pixel] + 128;
+ int b = y[pixel] + 1.772f * chroma.cb[chroma_pixel] + 128;
+ y[pixel] = r < 0 ? 0 : (r > 255 ? 255 : r);
+ cb[pixel] = g < 0 ? 0 : (g > 255 ? 255 : g);
+ cr[pixel] = b < 0 ? 0 : (b > 255 ? 255 : b);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+static bool compose_bitmap(JPGLoadingContext& context, const Vector<Macroblock>& macroblocks)
+{
+ context.bitmap = Bitmap::create_purgeable(BitmapFormat::RGB32, { context.frame.width, context.frame.height });
+ if (!context.bitmap)
+ return false;
+
+ for (u32 y = context.frame.height - 1; y < context.frame.height; y--) {
+ const u32 block_row = y / 8;
+ const u32 pixel_row = y % 8;
+ for (u32 x = 0; x < context.frame.width; x++) {
+ const u32 block_column = x / 8;
+ auto& block = macroblocks[block_row * context.mblock_meta.hpadded_count + block_column];
+ const u32 pixel_column = x % 8;
+ const u32 pixel_index = pixel_row * 8 + pixel_column;
+ const Color color { (u8)block.y[pixel_index], (u8)block.cb[pixel_index], (u8)block.cr[pixel_index] };
+ context.bitmap->set_pixel(x, y, color);
+ }
+ }
+
+ return true;
+}
+
+static bool parse_header(InputMemoryStream& stream, JPGLoadingContext& context)
+{
+ auto marker = read_marker_at_cursor(stream);
+ if (stream.handle_any_error())
+ return false;
+ if (marker != JPG_SOI) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << String::format(": SOI not found: %x!", marker);
+#endif
+ return false;
+ }
+ for (;;) {
+ marker = read_marker_at_cursor(stream);
+ if (stream.handle_any_error())
+ return false;
+
+ // Set frame type if the marker marks a new frame.
+ if (marker >= 0xFFC0 && marker <= 0xFFCF) {
+ // Ignore interleaved markers.
+ if (marker != 0xFFC4 && marker != 0xFFC8 && marker != 0xFFCC) {
+ context.frame.type = static_cast<StartOfFrame::FrameType>(marker & 0xF);
+ }
+ }
+
+ switch (marker) {
+ case JPG_INVALID:
+ case JPG_RST0:
+ case JPG_RST1:
+ case JPG_RST2:
+ case JPG_RST3:
+ case JPG_RST4:
+ case JPG_RST5:
+ case JPG_RST6:
+ case JPG_RST7:
+ case JPG_SOI:
+ case JPG_EOI:
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << String::format(": Unexpected marker %x!", marker);
+#endif
+ return false;
+ case JPG_SOF0:
+ if (!read_start_of_frame(stream, context))
+ return false;
+ context.state = JPGLoadingContext::FrameDecoded;
+ break;
+ case JPG_DQT:
+ if (!read_quantization_table(stream, context))
+ return false;
+ break;
+ case JPG_RST:
+ if (!read_reset_marker(stream, context))
+ return false;
+ break;
+ case JPG_DHT:
+ if (!read_huffman_table(stream, context))
+ return false;
+ break;
+ case JPG_SOS:
+ return read_start_of_scan(stream, context);
+ default:
+ if (!skip_marker_with_length(stream)) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << String::format(": Error skipping marker: %x!", marker);
+#endif
+ return false;
+ }
+ break;
+ }
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+static bool scan_huffman_stream(InputMemoryStream& stream, JPGLoadingContext& context)
+{
+ u8 last_byte;
+ u8 current_byte = 0;
+ stream >> current_byte;
+ if (stream.handle_any_error())
+ return false;
+
+ for (;;) {
+ last_byte = current_byte;
+ stream >> current_byte;
+ if (stream.handle_any_error()) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": EOI not found!";
+#endif
+ return false;
+ }
+
+ if (last_byte == 0xFF) {
+ if (current_byte == 0xFF)
+ continue;
+ if (current_byte == 0x00) {
+ stream >> current_byte;
+ if (stream.handle_any_error())
+ return false;
+ context.huffman_stream.stream.append(last_byte);
+ continue;
+ }
+ Marker marker = 0xFF00 | current_byte;
+ if (marker == JPG_EOI)
+ return true;
+ if (marker >= JPG_RST0 && marker <= JPG_RST7) {
+ context.huffman_stream.stream.append(marker);
+ stream >> current_byte;
+ if (stream.handle_any_error())
+ return false;
+ continue;
+ }
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << String::format(": Invalid marker: %x!", marker);
+#endif
+ return false;
+ } else {
+ context.huffman_stream.stream.append(last_byte);
+ }
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+static bool decode_jpg(JPGLoadingContext& context)
+{
+ InputMemoryStream stream { { context.data, context.data_size } };
+
+ if (!parse_header(stream, context))
+ return false;
+ if (!scan_huffman_stream(stream, context))
+ return false;
+
+ auto result = decode_huffman_stream(context);
+ if (!result.has_value()) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Failed to decode Macroblocks!";
+#endif
+ return false;
+ }
+
+ auto macroblocks = result.release_value();
+ dequantize(context, macroblocks);
+ inverse_dct(context, macroblocks);
+ ycbcr_to_rgb(context, macroblocks);
+ if (!compose_bitmap(context, macroblocks))
+ return false;
+ return true;
+}
+
+static RefPtr<Gfx::Bitmap> load_jpg_impl(const u8* data, size_t data_size)
+{
+ JPGLoadingContext context;
+ context.data = data;
+ context.data_size = data_size;
+
+ if (!decode_jpg(context))
+ return nullptr;
+
+ return context.bitmap;
+}
+
+RefPtr<Gfx::Bitmap> load_jpg(const StringView& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return nullptr;
+ auto bitmap = load_jpg_impl((const u8*)file_or_error.value()->data(), file_or_error.value()->size());
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded JPG: {}", bitmap->size(), LexicalPath::canonicalized_path(path)));
+ return bitmap;
+}
+
+RefPtr<Gfx::Bitmap> load_jpg_from_memory(const u8* data, size_t length)
+{
+ auto bitmap = load_jpg_impl(data, length);
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded jpg: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+JPGImageDecoderPlugin::JPGImageDecoderPlugin(const u8* data, size_t size)
+{
+ m_context = make<JPGLoadingContext>();
+ m_context->data = data;
+ m_context->data_size = size;
+ m_context->huffman_stream.stream.ensure_capacity(50 * KiB);
+}
+
+JPGImageDecoderPlugin::~JPGImageDecoderPlugin()
+{
+}
+
+IntSize JPGImageDecoderPlugin::size()
+{
+ if (m_context->state == JPGLoadingContext::State::Error)
+ return {};
+ if (m_context->state >= JPGLoadingContext::State::FrameDecoded)
+ return { m_context->frame.width, m_context->frame.height };
+
+ return {};
+}
+
+RefPtr<Gfx::Bitmap> JPGImageDecoderPlugin::bitmap()
+{
+ if (m_context->state == JPGLoadingContext::State::Error)
+ return nullptr;
+ if (m_context->state < JPGLoadingContext::State::BitmapDecoded) {
+ if (!decode_jpg(*m_context)) {
+ m_context->state = JPGLoadingContext::State::Error;
+ return nullptr;
+ }
+ m_context->state = JPGLoadingContext::State::BitmapDecoded;
+ }
+
+ return m_context->bitmap;
+}
+
+void JPGImageDecoderPlugin::set_volatile()
+{
+ if (m_context->bitmap)
+ m_context->bitmap->set_volatile();
+}
+
+bool JPGImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->bitmap)
+ return false;
+ return m_context->bitmap->set_nonvolatile();
+}
+
+bool JPGImageDecoderPlugin::sniff()
+{
+ return m_context->data_size > 3
+ && m_context->data[0] == 0xFF
+ && m_context->data[1] == 0xD8
+ && m_context->data[2] == 0xFF;
+}
+
+bool JPGImageDecoderPlugin::is_animated()
+{
+ return false;
+}
+
+size_t JPGImageDecoderPlugin::loop_count()
+{
+ return 0;
+}
+
+size_t JPGImageDecoderPlugin::frame_count()
+{
+ return 1;
+}
+
+ImageFrameDescriptor JPGImageDecoderPlugin::frame(size_t i)
+{
+ if (i > 0) {
+ return { bitmap(), 0 };
+ }
+ return {};
+}
+}
diff --git a/Userland/Libraries/LibGfx/JPGLoader.h b/Userland/Libraries/LibGfx/JPGLoader.h
new file mode 100644
index 0000000000..67af000882
--- /dev/null
+++ b/Userland/Libraries/LibGfx/JPGLoader.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Vector.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+#include <LibGfx/Size.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_jpg(const StringView& path);
+RefPtr<Gfx::Bitmap> load_jpg_from_memory(const u8* data, size_t length);
+
+struct JPGLoadingContext;
+
+class JPGImageDecoderPlugin : public ImageDecoderPlugin {
+public:
+ virtual ~JPGImageDecoderPlugin() override;
+ JPGImageDecoderPlugin(const u8*, size_t);
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+ virtual bool sniff() override;
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<JPGLoadingContext> m_context;
+};
+}
diff --git a/Userland/Libraries/LibGfx/Matrix.h b/Userland/Libraries/LibGfx/Matrix.h
new file mode 100644
index 0000000000..aa9cf5e64f
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Matrix.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+#include <initializer_list>
+
+namespace Gfx {
+
+template<size_t N, typename T>
+class Matrix {
+public:
+ static constexpr size_t Size = N;
+
+ Matrix() = default;
+ Matrix(std::initializer_list<T> elements)
+ {
+ ASSERT(elements.size() == N * N);
+ size_t i = 0;
+ for (auto& element : elements) {
+ m_elements[i / N][i % N] = element;
+ ++i;
+ }
+ }
+
+ template<typename... Args>
+ Matrix(Args... args)
+ : Matrix({ (T)args... })
+ {
+ }
+
+ Matrix(const Matrix& other)
+ {
+ __builtin_memcpy(m_elements, other.elements(), sizeof(T) * N * N);
+ }
+
+ auto elements() const { return m_elements; }
+ auto elements() { return m_elements; }
+
+ Matrix operator*(const Matrix& other) const
+ {
+ Matrix product;
+ for (int i = 0; i < N; ++i) {
+ for (int j = 0; j < N; ++j) {
+ auto& element = product.m_elements[i][j];
+
+ if constexpr (N == 4) {
+ element = m_elements[0][j] * other.m_elements[i][0]
+ + m_elements[1][j] * other.m_elements[i][1]
+ + m_elements[2][j] * other.m_elements[i][2]
+ + m_elements[3][j] * other.m_elements[i][3];
+ } else if constexpr (N == 3) {
+ element = m_elements[0][j] * other.m_elements[i][0]
+ + m_elements[1][j] * other.m_elements[i][1]
+ + m_elements[2][j] * other.m_elements[i][2];
+ } else if constexpr (N == 2) {
+ element = m_elements[0][j] * other.m_elements[i][0]
+ + m_elements[1][j] * other.m_elements[i][1];
+ } else if constexpr (N == 1) {
+ element = m_elements[0][j] * other.m_elements[i][0];
+ } else {
+ T value {};
+ for (size_t k = 0; k < N; ++k)
+ value += m_elements[k][j] * other.m_elements[i][k];
+
+ element = value;
+ }
+ }
+ }
+
+ return product;
+ }
+
+private:
+ T m_elements[N][N];
+};
+
+}
+
+using Gfx::Matrix;
diff --git a/Userland/Libraries/LibGfx/Matrix4x4.h b/Userland/Libraries/LibGfx/Matrix4x4.h
new file mode 100644
index 0000000000..643ab3ea62
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Matrix4x4.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Matrix.h>
+#include <LibGfx/Vector3.h>
+#include <math.h>
+
+namespace Gfx {
+
+template<typename T>
+class Matrix4x4 final : public Matrix<4, T> {
+public:
+ Matrix4x4() = default;
+ Matrix4x4(T _11, T _12, T _13, T _14,
+ T _21, T _22, T _23, T _24,
+ T _31, T _32, T _33, T _34,
+ T _41, T _42, T _43, T _44)
+ : m_elements {
+ _11, _12, _13, _14,
+ _21, _22, _23, _24,
+ _31, _32, _33, _34,
+ _41, _42, _43, _44
+ }
+ {
+ }
+
+ auto elements() const { return m_elements; }
+ auto elements() { return m_elements; }
+
+ Matrix4x4 operator*(const Matrix4x4& other) const
+ {
+ Matrix4x4 product;
+ for (int i = 0; i < 4; ++i) {
+ for (int j = 0; j < 4; ++j) {
+ product.m_elements[i][j] = m_elements[0][j] * other.m_elements[i][0]
+ + m_elements[1][j] * other.m_elements[i][1]
+ + m_elements[2][j] * other.m_elements[i][2]
+ + m_elements[3][j] * other.m_elements[i][3];
+ }
+ }
+ return product;
+ }
+
+ Vector3<T> transform_point(const Vector3<T>& p) const
+ {
+ return Vector3<T>(
+ p.x() * m_elements[0][0] + p.y() * m_elements[1][0] + p.z() * m_elements[2][0] + m_elements[3][0],
+ p.x() * m_elements[0][1] + p.y() * m_elements[1][1] + p.z() * m_elements[2][1] + m_elements[3][1],
+ p.x() * m_elements[0][2] + p.y() * m_elements[1][2] + p.z() * m_elements[2][2] + m_elements[3][2]);
+ }
+
+ static Matrix4x4 translate(const Vector3<T>& p)
+ {
+ return Matrix4x4(
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ p.x(), p.y(), p.z(), 1);
+ }
+
+ static Matrix4x4 scale(const Vector3<T>& s)
+ {
+ return Matrix4x4(
+ s.x(), 0, 0, 0,
+ 0, s.y(), 0, 0,
+ 0, 0, s.z(), 0,
+ 0, 0, 0, 1);
+ }
+
+ static Matrix4x4 rotate(const Vector3<T>& axis, T angle)
+ {
+ T c = cos(angle);
+ T s = sin(angle);
+ T t = 1 - c;
+ T x = axis.x();
+ T y = axis.y();
+ T z = axis.z();
+
+ return Matrix4x4(
+ t * x * x + c, t * x * y - z * s, t * x * z + y * s, 0,
+ t * x * y + z * s, t * y * y + c, t * y * z - x * s, 0,
+ t * x * z - y * s, t * y * z + x * s, t * z * z + c, 0,
+ 0, 0, 0, 1);
+ }
+
+private:
+ T m_elements[4][4];
+};
+
+typedef Matrix4x4<float> FloatMatrix4x4;
+typedef Matrix4x4<double> DoubleMatrix4x4;
+
+}
+
+using Gfx::DoubleMatrix4x4;
+using Gfx::FloatMatrix4x4;
+using Gfx::Matrix4x4;
diff --git a/Userland/Libraries/LibGfx/Orientation.h b/Userland/Libraries/LibGfx/Orientation.h
new file mode 100644
index 0000000000..e83af3f530
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Orientation.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace Gfx {
+
+enum class Orientation {
+ Horizontal,
+ Vertical
+};
+
+}
+
+using Gfx::Orientation;
diff --git a/Userland/Libraries/LibGfx/PBMLoader.cpp b/Userland/Libraries/LibGfx/PBMLoader.cpp
new file mode 100644
index 0000000000..4040b09c97
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PBMLoader.cpp
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2020, Hüseyin ASLITÜRK <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "PBMLoader.h"
+#include "PortableImageLoaderCommon.h"
+#include "Streamer.h"
+#include <AK/Endian.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/StringBuilder.h>
+#include <string.h>
+
+namespace Gfx {
+
+struct PBMLoadingContext {
+ enum Type {
+ Unknown,
+ ASCII,
+ RAWBITS
+ };
+
+ enum State {
+ NotDecoded = 0,
+ Error,
+ MagicNumber,
+ Width,
+ Height,
+ Bitmap,
+ Decoded
+ };
+
+ static constexpr auto ascii_magic_number = '1';
+ static constexpr auto binary_magic_number = '4';
+ static constexpr auto image_type = "PBM";
+
+ Type type { Type::Unknown };
+ State state { State::NotDecoded };
+ const u8* data { nullptr };
+ size_t data_size { 0 };
+ size_t width { 0 };
+ size_t height { 0 };
+ RefPtr<Gfx::Bitmap> bitmap;
+};
+
+static bool read_image_data(PBMLoadingContext& context, Streamer& streamer)
+{
+ u8 byte;
+ Vector<Gfx::Color> color_data;
+
+ if (context.type == PBMLoadingContext::ASCII) {
+ while (streamer.read(byte)) {
+ if (byte == '0') {
+ color_data.append(Color::White);
+ } else if (byte == '1') {
+ color_data.append(Color::Black);
+ }
+ }
+ } else if (context.type == PBMLoadingContext::RAWBITS) {
+ size_t color_index = 0;
+
+ while (streamer.read(byte)) {
+ for (int i = 0; i < 8; i++) {
+ int val = byte & 0x80;
+
+ if (val == 0) {
+ color_data.append(Color::White);
+ } else {
+ color_data.append(Color::Black);
+ }
+
+ byte = byte << 1;
+ color_index++;
+
+ if (color_index % context.width == 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ size_t context_size = (u32)context.width * (u32)context.height;
+ if (context_size != color_data.size()) {
+ dbgln("Not enough color data in image.");
+ return false;
+ }
+
+ if (!create_bitmap(context)) {
+ return false;
+ }
+
+ set_pixels(context, color_data);
+
+ context.state = PBMLoadingContext::State::Bitmap;
+ return true;
+}
+
+RefPtr<Gfx::Bitmap> load_pbm(const StringView& path)
+{
+ return load<PBMLoadingContext>(path);
+}
+
+RefPtr<Gfx::Bitmap> load_pbm_from_memory(const u8* data, size_t length)
+{
+ auto bitmap = load_impl<PBMLoadingContext>(data, length);
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded PBM: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+PBMImageDecoderPlugin::PBMImageDecoderPlugin(const u8* data, size_t size)
+{
+ m_context = make<PBMLoadingContext>();
+ m_context->data = data;
+ m_context->data_size = size;
+}
+
+PBMImageDecoderPlugin::~PBMImageDecoderPlugin()
+{
+}
+
+IntSize PBMImageDecoderPlugin::size()
+{
+ if (m_context->state == PBMLoadingContext::State::Error)
+ return {};
+
+ if (m_context->state < PBMLoadingContext::State::Decoded) {
+ bool success = decode(*m_context);
+ if (!success)
+ return {};
+ }
+
+ return { m_context->width, m_context->height };
+}
+
+RefPtr<Gfx::Bitmap> PBMImageDecoderPlugin::bitmap()
+{
+ if (m_context->state == PBMLoadingContext::State::Error)
+ return nullptr;
+
+ if (m_context->state < PBMLoadingContext::State::Decoded) {
+ bool success = decode(*m_context);
+ if (!success)
+ return nullptr;
+ }
+
+ ASSERT(m_context->bitmap);
+ return m_context->bitmap;
+}
+
+void PBMImageDecoderPlugin::set_volatile()
+{
+ if (m_context->bitmap)
+ m_context->bitmap->set_volatile();
+}
+
+bool PBMImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->bitmap)
+ return false;
+
+ return m_context->bitmap->set_nonvolatile();
+}
+
+bool PBMImageDecoderPlugin::sniff()
+{
+ if (m_context->data_size < 2)
+ return false;
+
+ if (m_context->data[0] == 'P' && m_context->data[1] == '1')
+ return true;
+
+ if (m_context->data[0] == 'P' && m_context->data[1] == '4')
+ return true;
+
+ return false;
+}
+
+bool PBMImageDecoderPlugin::is_animated()
+{
+ return false;
+}
+
+size_t PBMImageDecoderPlugin::loop_count()
+{
+ return 0;
+}
+
+size_t PBMImageDecoderPlugin::frame_count()
+{
+ return 1;
+}
+
+ImageFrameDescriptor PBMImageDecoderPlugin::frame(size_t i)
+{
+ if (i > 0) {
+ return { bitmap(), 0 };
+ }
+
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/PBMLoader.h b/Userland/Libraries/LibGfx/PBMLoader.h
new file mode 100644
index 0000000000..93f6e45d77
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PBMLoader.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020, Hüseyin ASLITÜRK <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_pbm(const StringView& path);
+RefPtr<Gfx::Bitmap> load_pbm_from_memory(const u8*, size_t);
+
+struct PBMLoadingContext;
+
+class PBMImageDecoderPlugin final : public ImageDecoderPlugin {
+public:
+ PBMImageDecoderPlugin(const u8*, size_t);
+ virtual ~PBMImageDecoderPlugin() override;
+
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+
+ virtual bool sniff() override;
+
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<PBMLoadingContext> m_context;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/PGMLoader.cpp b/Userland/Libraries/LibGfx/PGMLoader.cpp
new file mode 100644
index 0000000000..122a698a7b
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PGMLoader.cpp
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2020, Hüseyin ASLITÜRK <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "PGMLoader.h"
+#include "PortableImageLoaderCommon.h"
+#include "Streamer.h"
+#include <AK/Endian.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/StringBuilder.h>
+#include <string.h>
+
+namespace Gfx {
+
+struct PGMLoadingContext {
+ enum Type {
+ Unknown,
+ ASCII,
+ RAWBITS
+ };
+
+ enum State {
+ NotDecoded = 0,
+ Error,
+ MagicNumber,
+ Width,
+ Height,
+ Maxval,
+ Bitmap,
+ Decoded
+ };
+
+ static constexpr auto ascii_magic_number = '2';
+ static constexpr auto binary_magic_number = '5';
+ static constexpr auto image_type = "PGM";
+
+ Type type { Type::Unknown };
+ State state { State::NotDecoded };
+ const u8* data { nullptr };
+ size_t data_size { 0 };
+ u16 width { 0 };
+ u16 height { 0 };
+ u16 max_val { 0 };
+ RefPtr<Gfx::Bitmap> bitmap;
+};
+
+static void set_adjusted_pixels(PGMLoadingContext& context, const AK::Vector<Gfx::Color>& color_data)
+{
+ size_t index = 0;
+ for (size_t y = 0; y < context.height; ++y) {
+ for (size_t x = 0; x < context.width; ++x) {
+ Color color = color_data.at(index);
+ if (context.max_val < 255) {
+ color = adjust_color(context.max_val, color);
+ }
+ context.bitmap->set_pixel(x, y, color);
+ ++index;
+ }
+ }
+}
+
+static bool read_image_data(PGMLoadingContext& context, Streamer& streamer)
+{
+ Vector<Gfx::Color> color_data;
+
+ if (context.type == PGMLoadingContext::ASCII) {
+ u16 value;
+
+ while (true) {
+ if (!read_number(streamer, &value))
+ break;
+
+ if (!read_white_space(context, streamer))
+ break;
+
+ color_data.append({ (u8)value, (u8)value, (u8)value });
+ }
+ } else if (context.type == PGMLoadingContext::RAWBITS) {
+ u8 pixel;
+ while (streamer.read(pixel)) {
+ color_data.append({ pixel, pixel, pixel });
+ }
+ }
+
+ size_t context_size = (u32)context.width * (u32)context.height;
+ if (context_size != color_data.size()) {
+ dbgln("Not enough color data in image.");
+ return false;
+ }
+
+ if (!create_bitmap(context))
+ return false;
+
+ set_adjusted_pixels(context, color_data);
+
+ context.state = PGMLoadingContext::State::Bitmap;
+ return true;
+}
+
+RefPtr<Gfx::Bitmap> load_pgm(const StringView& path)
+{
+ return load<PGMLoadingContext>(path);
+}
+
+RefPtr<Gfx::Bitmap> load_pgm_from_memory(const u8* data, size_t length)
+{
+ auto bitmap = load_impl<PGMLoadingContext>(data, length);
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded PGM: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+PGMImageDecoderPlugin::PGMImageDecoderPlugin(const u8* data, size_t size)
+{
+ m_context = make<PGMLoadingContext>();
+ m_context->data = data;
+ m_context->data_size = size;
+}
+
+PGMImageDecoderPlugin::~PGMImageDecoderPlugin()
+{
+}
+
+IntSize PGMImageDecoderPlugin::size()
+{
+ if (m_context->state == PGMLoadingContext::State::Error)
+ return {};
+
+ if (m_context->state < PGMLoadingContext::State::Decoded) {
+ bool success = decode(*m_context);
+ if (!success)
+ return {};
+ }
+
+ return { m_context->width, m_context->height };
+}
+
+RefPtr<Gfx::Bitmap> PGMImageDecoderPlugin::bitmap()
+{
+ if (m_context->state == PGMLoadingContext::State::Error)
+ return nullptr;
+
+ if (m_context->state < PGMLoadingContext::State::Decoded) {
+ bool success = decode(*m_context);
+ if (!success)
+ return nullptr;
+ }
+
+ ASSERT(m_context->bitmap);
+ return m_context->bitmap;
+}
+
+void PGMImageDecoderPlugin::set_volatile()
+{
+ if (m_context->bitmap)
+ m_context->bitmap->set_volatile();
+}
+
+bool PGMImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->bitmap)
+ return false;
+
+ return m_context->bitmap->set_nonvolatile();
+}
+
+bool PGMImageDecoderPlugin::sniff()
+{
+ if (m_context->data_size < 2)
+ return false;
+
+ if (m_context->data[0] == 'P' && m_context->data[1] == '2')
+ return true;
+
+ if (m_context->data[0] == 'P' && m_context->data[1] == '5')
+ return true;
+
+ return false;
+}
+
+bool PGMImageDecoderPlugin::is_animated()
+{
+ return false;
+}
+
+size_t PGMImageDecoderPlugin::loop_count()
+{
+ return 0;
+}
+
+size_t PGMImageDecoderPlugin::frame_count()
+{
+ return 1;
+}
+
+ImageFrameDescriptor PGMImageDecoderPlugin::frame(size_t i)
+{
+ if (i > 0) {
+ return { bitmap(), 0 };
+ }
+
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/PGMLoader.h b/Userland/Libraries/LibGfx/PGMLoader.h
new file mode 100644
index 0000000000..ae6afcebaf
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PGMLoader.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020, Hüseyin ASLITÜRK <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_pgm(const StringView& path);
+RefPtr<Gfx::Bitmap> load_pgm_from_memory(const u8*, size_t);
+
+struct PGMLoadingContext;
+
+class PGMImageDecoderPlugin final : public ImageDecoderPlugin {
+public:
+ PGMImageDecoderPlugin(const u8*, size_t);
+ virtual ~PGMImageDecoderPlugin() override;
+
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+
+ virtual bool sniff() override;
+
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<PGMLoadingContext> m_context;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/PNGLoader.cpp b/Userland/Libraries/LibGfx/PNGLoader.cpp
new file mode 100644
index 0000000000..48405f8bea
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PNGLoader.cpp
@@ -0,0 +1,1078 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Endian.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <LibCore/puff.h>
+#include <LibGfx/PNGLoader.h>
+#include <fcntl.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifdef __serenity__
+# include <serenity.h>
+#endif
+
+//#define PNG_DEBUG
+
+namespace Gfx {
+
+static const u8 png_header[8] = { 0x89, 'P', 'N', 'G', 13, 10, 26, 10 };
+
+struct PNG_IHDR {
+ NetworkOrdered<u32> width;
+ NetworkOrdered<u32> height;
+ u8 bit_depth { 0 };
+ u8 color_type { 0 };
+ u8 compression_method { 0 };
+ u8 filter_method { 0 };
+ u8 interlace_method { 0 };
+};
+
+static_assert(sizeof(PNG_IHDR) == 13);
+
+struct Scanline {
+ u8 filter { 0 };
+ ReadonlyBytes data {};
+};
+
+struct [[gnu::packed]] PaletteEntry {
+ u8 r;
+ u8 g;
+ u8 b;
+ //u8 a;
+};
+
+template<typename T>
+struct [[gnu::packed]] Tuple {
+ T gray;
+ T a;
+};
+
+template<typename T>
+struct [[gnu::packed]] Triplet {
+ T r;
+ T g;
+ T b;
+};
+
+template<typename T>
+struct [[gnu::packed]] Quad {
+ T r;
+ T g;
+ T b;
+ T a;
+};
+
+enum PngInterlaceMethod {
+ Null = 0,
+ Adam7 = 1
+};
+
+struct PNGLoadingContext {
+ enum State {
+ NotDecoded = 0,
+ Error,
+ HeaderDecoded,
+ SizeDecoded,
+ ChunksDecoded,
+ BitmapDecoded,
+ };
+ State state { State::NotDecoded };
+ const u8* data { nullptr };
+ size_t data_size { 0 };
+ int width { -1 };
+ int height { -1 };
+ u8 bit_depth { 0 };
+ u8 color_type { 0 };
+ u8 compression_method { 0 };
+ u8 filter_method { 0 };
+ u8 interlace_method { 0 };
+ u8 channels { 0 };
+ bool has_seen_zlib_header { false };
+ bool has_alpha() const { return color_type & 4 || palette_transparency_data.size() > 0; }
+ Vector<Scanline> scanlines;
+ RefPtr<Gfx::Bitmap> bitmap;
+ u8* decompression_buffer { nullptr };
+ size_t decompression_buffer_size { 0 };
+ Vector<u8> compressed_data;
+ Vector<PaletteEntry> palette_data;
+ Vector<u8> palette_transparency_data;
+
+ Checked<int> compute_row_size_for_width(int width)
+ {
+ Checked<int> row_size = width;
+ row_size *= channels;
+ row_size *= bit_depth;
+ row_size += 7;
+ row_size /= 8;
+ if (row_size.has_overflow()) {
+ dbgln("PNG too large, integer overflow while computing row size");
+ state = State::Error;
+ }
+ return row_size;
+ }
+};
+
+class Streamer {
+public:
+ Streamer(const u8* data, size_t size)
+ : m_data_ptr(data)
+ , m_size_remaining(size)
+ {
+ }
+
+ template<typename T>
+ bool read(T& value)
+ {
+ if (m_size_remaining < sizeof(T))
+ return false;
+ value = *((const NetworkOrdered<T>*)m_data_ptr);
+ m_data_ptr += sizeof(T);
+ m_size_remaining -= sizeof(T);
+ return true;
+ }
+
+ bool read_bytes(u8* buffer, size_t count)
+ {
+ if (m_size_remaining < count)
+ return false;
+ memcpy(buffer, m_data_ptr, count);
+ m_data_ptr += count;
+ m_size_remaining -= count;
+ return true;
+ }
+
+ bool wrap_bytes(ReadonlyBytes& buffer, size_t count)
+ {
+ if (m_size_remaining < count)
+ return false;
+ buffer = ReadonlyBytes { m_data_ptr, count };
+ m_data_ptr += count;
+ m_size_remaining -= count;
+ return true;
+ }
+
+ bool at_end() const { return !m_size_remaining; }
+
+private:
+ const u8* m_data_ptr { nullptr };
+ size_t m_size_remaining { 0 };
+};
+
+static RefPtr<Gfx::Bitmap> load_png_impl(const u8*, size_t);
+static bool process_chunk(Streamer&, PNGLoadingContext& context);
+
+RefPtr<Gfx::Bitmap> load_png(const StringView& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return nullptr;
+ auto bitmap = load_png_impl((const u8*)file_or_error.value()->data(), file_or_error.value()->size());
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded PNG: {}", bitmap->size(), LexicalPath::canonicalized_path(path)));
+ return bitmap;
+}
+
+RefPtr<Gfx::Bitmap> load_png_from_memory(const u8* data, size_t length)
+{
+ auto bitmap = load_png_impl(data, length);
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded PNG: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+ALWAYS_INLINE static u8 paeth_predictor(int a, int b, int c)
+{
+ int p = a + b - c;
+ int pa = abs(p - a);
+ int pb = abs(p - b);
+ int pc = abs(p - c);
+ if (pa <= pb && pa <= pc)
+ return a;
+ if (pb <= pc)
+ return b;
+ return c;
+}
+
+union [[gnu::packed]] Pixel {
+ RGBA32 rgba { 0 };
+ u8 v[4];
+ struct {
+ u8 r;
+ u8 g;
+ u8 b;
+ u8 a;
+ };
+};
+static_assert(sizeof(Pixel) == 4);
+
+template<bool has_alpha, u8 filter_type>
+ALWAYS_INLINE static void unfilter_impl(Gfx::Bitmap& bitmap, int y, const void* dummy_scanline_data)
+{
+ auto* dummy_scanline = (const Pixel*)dummy_scanline_data;
+ if constexpr (filter_type == 0) {
+ auto* pixels = (Pixel*)bitmap.scanline(y);
+ for (int i = 0; i < bitmap.width(); ++i) {
+ auto& x = pixels[i];
+ swap(x.r, x.b);
+ }
+ }
+
+ if constexpr (filter_type == 1) {
+ auto* pixels = (Pixel*)bitmap.scanline(y);
+ swap(pixels[0].r, pixels[0].b);
+ for (int i = 1; i < bitmap.width(); ++i) {
+ auto& x = pixels[i];
+ swap(x.r, x.b);
+ auto& a = (const Pixel&)pixels[i - 1];
+ x.v[0] += a.v[0];
+ x.v[1] += a.v[1];
+ x.v[2] += a.v[2];
+ if constexpr (has_alpha)
+ x.v[3] += a.v[3];
+ }
+ return;
+ }
+ if constexpr (filter_type == 2) {
+ auto* pixels = (Pixel*)bitmap.scanline(y);
+ auto* pixels_y_minus_1 = y == 0 ? dummy_scanline : (const Pixel*)bitmap.scanline(y - 1);
+ for (int i = 0; i < bitmap.width(); ++i) {
+ auto& x = pixels[i];
+ swap(x.r, x.b);
+ const Pixel& b = pixels_y_minus_1[i];
+ x.v[0] += b.v[0];
+ x.v[1] += b.v[1];
+ x.v[2] += b.v[2];
+ if constexpr (has_alpha)
+ x.v[3] += b.v[3];
+ }
+ return;
+ }
+ if constexpr (filter_type == 3) {
+ auto* pixels = (Pixel*)bitmap.scanline(y);
+ auto* pixels_y_minus_1 = y == 0 ? dummy_scanline : (const Pixel*)bitmap.scanline(y - 1);
+ for (int i = 0; i < bitmap.width(); ++i) {
+ auto& x = pixels[i];
+ swap(x.r, x.b);
+ Pixel a;
+ if (i != 0)
+ a = pixels[i - 1];
+ const Pixel& b = pixels_y_minus_1[i];
+ x.v[0] = x.v[0] + ((a.v[0] + b.v[0]) / 2);
+ x.v[1] = x.v[1] + ((a.v[1] + b.v[1]) / 2);
+ x.v[2] = x.v[2] + ((a.v[2] + b.v[2]) / 2);
+ if constexpr (has_alpha)
+ x.v[3] = x.v[3] + ((a.v[3] + b.v[3]) / 2);
+ }
+ return;
+ }
+ if constexpr (filter_type == 4) {
+ auto* pixels = (Pixel*)bitmap.scanline(y);
+ auto* pixels_y_minus_1 = y == 0 ? dummy_scanline : (Pixel*)bitmap.scanline(y - 1);
+ for (int i = 0; i < bitmap.width(); ++i) {
+ auto& x = pixels[i];
+ swap(x.r, x.b);
+ Pixel a;
+ const Pixel& b = pixels_y_minus_1[i];
+ Pixel c;
+ if (i != 0) {
+ a = pixels[i - 1];
+ c = pixels_y_minus_1[i - 1];
+ }
+ x.v[0] += paeth_predictor(a.v[0], b.v[0], c.v[0]);
+ x.v[1] += paeth_predictor(a.v[1], b.v[1], c.v[1]);
+ x.v[2] += paeth_predictor(a.v[2], b.v[2], c.v[2]);
+ if constexpr (has_alpha)
+ x.v[3] += paeth_predictor(a.v[3], b.v[3], c.v[3]);
+ }
+ }
+}
+
+template<typename T>
+ALWAYS_INLINE static void unpack_grayscale_without_alpha(PNGLoadingContext& context)
+{
+ for (int y = 0; y < context.height; ++y) {
+ auto* gray_values = reinterpret_cast<const T*>(context.scanlines[y].data.data());
+ for (int i = 0; i < context.width; ++i) {
+ auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
+ pixel.r = gray_values[i];
+ pixel.g = gray_values[i];
+ pixel.b = gray_values[i];
+ pixel.a = 0xff;
+ }
+ }
+}
+
+template<typename T>
+ALWAYS_INLINE static void unpack_grayscale_with_alpha(PNGLoadingContext& context)
+{
+ for (int y = 0; y < context.height; ++y) {
+ auto* tuples = reinterpret_cast<const Tuple<T>*>(context.scanlines[y].data.data());
+ for (int i = 0; i < context.width; ++i) {
+ auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
+ pixel.r = tuples[i].gray;
+ pixel.g = tuples[i].gray;
+ pixel.b = tuples[i].gray;
+ pixel.a = tuples[i].a;
+ }
+ }
+}
+
+template<typename T>
+ALWAYS_INLINE static void unpack_triplets_without_alpha(PNGLoadingContext& context)
+{
+ for (int y = 0; y < context.height; ++y) {
+ auto* triplets = reinterpret_cast<const Triplet<T>*>(context.scanlines[y].data.data());
+ for (int i = 0; i < context.width; ++i) {
+ auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
+ pixel.r = triplets[i].r;
+ pixel.g = triplets[i].g;
+ pixel.b = triplets[i].b;
+ pixel.a = 0xff;
+ }
+ }
+}
+
+NEVER_INLINE FLATTEN static bool unfilter(PNGLoadingContext& context)
+{
+ // First unpack the scanlines to RGBA:
+ switch (context.color_type) {
+ case 0:
+ if (context.bit_depth == 8) {
+ unpack_grayscale_without_alpha<u8>(context);
+ } else if (context.bit_depth == 16) {
+ unpack_grayscale_without_alpha<u16>(context);
+ } else if (context.bit_depth == 1 || context.bit_depth == 2 || context.bit_depth == 4) {
+ auto bit_depth_squared = context.bit_depth * context.bit_depth;
+ auto pixels_per_byte = 8 / context.bit_depth;
+ auto mask = (1 << context.bit_depth) - 1;
+ for (int y = 0; y < context.height; ++y) {
+ auto* gray_values = context.scanlines[y].data.data();
+ for (int x = 0; x < context.width; ++x) {
+ auto bit_offset = (8 - context.bit_depth) - (context.bit_depth * (x % pixels_per_byte));
+ auto value = (gray_values[x / pixels_per_byte] >> bit_offset) & mask;
+ auto& pixel = (Pixel&)context.bitmap->scanline(y)[x];
+ pixel.r = value * (0xff / bit_depth_squared);
+ pixel.g = value * (0xff / bit_depth_squared);
+ pixel.b = value * (0xff / bit_depth_squared);
+ pixel.a = 0xff;
+ }
+ }
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+ break;
+ case 4:
+ if (context.bit_depth == 8) {
+ unpack_grayscale_with_alpha<u8>(context);
+ } else if (context.bit_depth == 16) {
+ unpack_grayscale_with_alpha<u16>(context);
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+ break;
+ case 2:
+ if (context.bit_depth == 8) {
+ unpack_triplets_without_alpha<u8>(context);
+ } else if (context.bit_depth == 16) {
+ unpack_triplets_without_alpha<u16>(context);
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+ break;
+ case 6:
+ if (context.bit_depth == 8) {
+ for (int y = 0; y < context.height; ++y) {
+ memcpy(context.bitmap->scanline(y), context.scanlines[y].data.data(), context.scanlines[y].data.size());
+ }
+ } else if (context.bit_depth == 16) {
+ for (int y = 0; y < context.height; ++y) {
+ auto* triplets = reinterpret_cast<const Quad<u16>*>(context.scanlines[y].data.data());
+ for (int i = 0; i < context.width; ++i) {
+ auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
+ pixel.r = triplets[i].r & 0xFF;
+ pixel.g = triplets[i].g & 0xFF;
+ pixel.b = triplets[i].b & 0xFF;
+ pixel.a = triplets[i].a & 0xFF;
+ }
+ }
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+ break;
+ case 3:
+ if (context.bit_depth == 8) {
+ for (int y = 0; y < context.height; ++y) {
+ auto* palette_index = context.scanlines[y].data.data();
+ for (int i = 0; i < context.width; ++i) {
+ auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
+ if (palette_index[i] >= context.palette_data.size())
+ return false;
+ auto& color = context.palette_data.at((int)palette_index[i]);
+ auto transparency = context.palette_transparency_data.size() >= palette_index[i] + 1u
+ ? context.palette_transparency_data.data()[palette_index[i]]
+ : 0xff;
+ pixel.r = color.r;
+ pixel.g = color.g;
+ pixel.b = color.b;
+ pixel.a = transparency;
+ }
+ }
+ } else if (context.bit_depth == 1 || context.bit_depth == 2 || context.bit_depth == 4) {
+ auto pixels_per_byte = 8 / context.bit_depth;
+ auto mask = (1 << context.bit_depth) - 1;
+ for (int y = 0; y < context.height; ++y) {
+ auto* palette_indexes = context.scanlines[y].data.data();
+ for (int i = 0; i < context.width; ++i) {
+ auto bit_offset = (8 - context.bit_depth) - (context.bit_depth * (i % pixels_per_byte));
+ auto palette_index = (palette_indexes[i / pixels_per_byte] >> bit_offset) & mask;
+ auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
+ if ((size_t)palette_index >= context.palette_data.size())
+ return false;
+ auto& color = context.palette_data.at(palette_index);
+ auto transparency = context.palette_transparency_data.size() >= palette_index + 1u
+ ? context.palette_transparency_data.data()[palette_index]
+ : 0xff;
+ pixel.r = color.r;
+ pixel.g = color.g;
+ pixel.b = color.b;
+ pixel.a = transparency;
+ }
+ }
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+
+ u8 dummy_scanline[context.width * sizeof(RGBA32)];
+
+ for (int y = 0; y < context.height; ++y) {
+ auto filter = context.scanlines[y].filter;
+ if (filter == 0) {
+ if (context.has_alpha())
+ unfilter_impl<true, 0>(*context.bitmap, y, dummy_scanline);
+ else
+ unfilter_impl<false, 0>(*context.bitmap, y, dummy_scanline);
+ continue;
+ }
+ if (filter == 1) {
+ if (context.has_alpha())
+ unfilter_impl<true, 1>(*context.bitmap, y, dummy_scanline);
+ else
+ unfilter_impl<false, 1>(*context.bitmap, y, dummy_scanline);
+ continue;
+ }
+ if (filter == 2) {
+ if (context.has_alpha())
+ unfilter_impl<true, 2>(*context.bitmap, y, dummy_scanline);
+ else
+ unfilter_impl<false, 2>(*context.bitmap, y, dummy_scanline);
+ continue;
+ }
+ if (filter == 3) {
+ if (context.has_alpha())
+ unfilter_impl<true, 3>(*context.bitmap, y, dummy_scanline);
+ else
+ unfilter_impl<false, 3>(*context.bitmap, y, dummy_scanline);
+ continue;
+ }
+ if (filter == 4) {
+ if (context.has_alpha())
+ unfilter_impl<true, 4>(*context.bitmap, y, dummy_scanline);
+ else
+ unfilter_impl<false, 4>(*context.bitmap, y, dummy_scanline);
+ continue;
+ }
+ }
+
+ return true;
+}
+
+static bool decode_png_header(PNGLoadingContext& context)
+{
+ if (context.state >= PNGLoadingContext::HeaderDecoded)
+ return true;
+
+ if (!context.data || context.data_size < sizeof(png_header)) {
+#ifdef PNG_DEBUG
+ dbgln("Missing PNG header");
+#endif
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+
+ if (memcmp(context.data, png_header, sizeof(png_header)) != 0) {
+#ifdef PNG_DEBUG
+ dbgln("Invalid PNG header");
+#endif
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+
+ context.state = PNGLoadingContext::HeaderDecoded;
+ return true;
+}
+
+static bool decode_png_size(PNGLoadingContext& context)
+{
+ if (context.state >= PNGLoadingContext::SizeDecoded)
+ return true;
+
+ if (context.state < PNGLoadingContext::HeaderDecoded) {
+ if (!decode_png_header(context))
+ return false;
+ }
+
+ const u8* data_ptr = context.data + sizeof(png_header);
+ size_t data_remaining = context.data_size - sizeof(png_header);
+
+ Streamer streamer(data_ptr, data_remaining);
+ while (!streamer.at_end()) {
+ if (!process_chunk(streamer, context)) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+ if (context.width && context.height) {
+ context.state = PNGLoadingContext::State::SizeDecoded;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool decode_png_chunks(PNGLoadingContext& context)
+{
+ if (context.state >= PNGLoadingContext::State::ChunksDecoded)
+ return true;
+
+ if (context.state < PNGLoadingContext::HeaderDecoded) {
+ if (!decode_png_header(context))
+ return false;
+ }
+
+ const u8* data_ptr = context.data + sizeof(png_header);
+ int data_remaining = context.data_size - sizeof(png_header);
+
+ context.compressed_data.ensure_capacity(context.data_size);
+
+ Streamer streamer(data_ptr, data_remaining);
+ while (!streamer.at_end()) {
+ if (!process_chunk(streamer, context)) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+ }
+
+ context.state = PNGLoadingContext::State::ChunksDecoded;
+ return true;
+}
+
+static bool decode_png_bitmap_simple(PNGLoadingContext& context)
+{
+ Streamer streamer(context.decompression_buffer, context.decompression_buffer_size);
+
+ for (int y = 0; y < context.height; ++y) {
+ u8 filter;
+ if (!streamer.read(filter)) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+
+ if (filter > 4) {
+#ifdef PNG_DEBUG
+ dbg() << "Invalid PNG filter: " << filter;
+#endif
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+
+ context.scanlines.append({ filter });
+ auto& scanline_buffer = context.scanlines.last().data;
+ auto row_size = context.compute_row_size_for_width(context.width);
+ if (row_size.has_overflow())
+ return false;
+
+ if (!streamer.wrap_bytes(scanline_buffer, row_size.value())) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+ }
+
+ context.bitmap = Bitmap::create_purgeable(context.has_alpha() ? BitmapFormat::RGBA32 : BitmapFormat::RGB32, { context.width, context.height });
+
+ if (!context.bitmap) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+
+ return unfilter(context);
+}
+
+static int adam7_height(PNGLoadingContext& context, int pass)
+{
+ switch (pass) {
+ case 1:
+ return (context.height + 7) / 8;
+ case 2:
+ return (context.height + 7) / 8;
+ case 3:
+ return (context.height + 3) / 8;
+ case 4:
+ return (context.height + 3) / 4;
+ case 5:
+ return (context.height + 1) / 4;
+ case 6:
+ return (context.height + 1) / 2;
+ case 7:
+ return context.height / 2;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+static int adam7_width(PNGLoadingContext& context, int pass)
+{
+ switch (pass) {
+ case 1:
+ return (context.width + 7) / 8;
+ case 2:
+ return (context.width + 3) / 8;
+ case 3:
+ return (context.width + 3) / 4;
+ case 4:
+ return (context.width + 1) / 4;
+ case 5:
+ return (context.width + 1) / 2;
+ case 6:
+ return context.width / 2;
+ case 7:
+ return context.width;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+// Index 0 unused (non-interlaced case)
+static int adam7_starty[8] = { 0, 0, 0, 4, 0, 2, 0, 1 };
+static int adam7_startx[8] = { 0, 0, 4, 0, 2, 0, 1, 0 };
+static int adam7_stepy[8] = { 1, 8, 8, 8, 4, 4, 2, 2 };
+static int adam7_stepx[8] = { 1, 8, 8, 4, 4, 2, 2, 1 };
+
+static bool decode_adam7_pass(PNGLoadingContext& context, Streamer& streamer, int pass)
+{
+ PNGLoadingContext subimage_context;
+ subimage_context.width = adam7_width(context, pass);
+ subimage_context.height = adam7_height(context, pass);
+ subimage_context.channels = context.channels;
+ subimage_context.color_type = context.color_type;
+ subimage_context.palette_data = context.palette_data;
+ subimage_context.palette_transparency_data = context.palette_transparency_data;
+ subimage_context.bit_depth = context.bit_depth;
+ subimage_context.filter_method = context.filter_method;
+
+ // For small images, some passes might be empty
+ if (!subimage_context.width || !subimage_context.height)
+ return true;
+
+ subimage_context.scanlines.clear_with_capacity();
+ for (int y = 0; y < subimage_context.height; ++y) {
+ u8 filter;
+ if (!streamer.read(filter)) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+
+ if (filter > 4) {
+#ifdef PNG_DEBUG
+ dbg() << "Invalid PNG filter: " << filter;
+#endif
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+
+ subimage_context.scanlines.append({ filter });
+ auto& scanline_buffer = subimage_context.scanlines.last().data;
+
+ auto row_size = context.compute_row_size_for_width(subimage_context.width);
+ if (row_size.has_overflow())
+ return false;
+ if (!streamer.wrap_bytes(scanline_buffer, row_size.value())) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+ }
+
+ subimage_context.bitmap = Bitmap::create(context.bitmap->format(), { subimage_context.width, subimage_context.height });
+ if (!unfilter(subimage_context)) {
+ subimage_context.bitmap = nullptr;
+ return false;
+ }
+
+ // Copy the subimage data into the main image according to the pass pattern
+ for (int y = 0, dy = adam7_starty[pass]; y < subimage_context.height && dy < context.height; ++y, dy += adam7_stepy[pass]) {
+ for (int x = 0, dx = adam7_startx[pass]; x < subimage_context.width && dy < context.width; ++x, dx += adam7_stepx[pass]) {
+ context.bitmap->set_pixel(dx, dy, subimage_context.bitmap->get_pixel(x, y));
+ }
+ }
+ return true;
+}
+
+static bool decode_png_adam7(PNGLoadingContext& context)
+{
+ Streamer streamer(context.decompression_buffer, context.decompression_buffer_size);
+ context.bitmap = Bitmap::create_purgeable(context.has_alpha() ? BitmapFormat::RGBA32 : BitmapFormat::RGB32, { context.width, context.height });
+ if (!context.bitmap)
+ return false;
+
+ for (int pass = 1; pass <= 7; ++pass) {
+ if (!decode_adam7_pass(context, streamer, pass))
+ return false;
+ }
+ return true;
+}
+
+static bool decode_png_bitmap(PNGLoadingContext& context)
+{
+ if (context.state < PNGLoadingContext::State::ChunksDecoded) {
+ if (!decode_png_chunks(context))
+ return false;
+ }
+
+ if (context.state >= PNGLoadingContext::State::BitmapDecoded)
+ return true;
+
+ if (context.width == -1 || context.height == -1)
+ return false; // Didn't see an IHDR chunk.
+
+ if (context.color_type == 3 && context.palette_data.is_empty())
+ return false; // Didn't see a PLTE chunk for a palettized image, or it was empty.
+
+ unsigned long srclen = context.compressed_data.size() - 6;
+ unsigned long destlen = 0;
+ int ret = puff(nullptr, &destlen, context.compressed_data.data() + 2, &srclen);
+ if (ret != 0) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+ context.decompression_buffer_size = destlen;
+#ifdef __serenity__
+ context.decompression_buffer = (u8*)mmap_with_name(nullptr, context.decompression_buffer_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0, "PNG decompression buffer");
+#else
+ context.decompression_buffer = (u8*)mmap(nullptr, context.decompression_buffer_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
+#endif
+
+ ret = puff(context.decompression_buffer, &destlen, context.compressed_data.data() + 2, &srclen);
+ if (ret != 0) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+ context.compressed_data.clear();
+
+ context.scanlines.ensure_capacity(context.height);
+ switch (context.interlace_method) {
+ case PngInterlaceMethod::Null:
+ if (!decode_png_bitmap_simple(context))
+ return false;
+ break;
+ case PngInterlaceMethod::Adam7:
+ if (!decode_png_adam7(context))
+ return false;
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ munmap(context.decompression_buffer, context.decompression_buffer_size);
+ context.decompression_buffer = nullptr;
+ context.decompression_buffer_size = 0;
+
+ context.state = PNGLoadingContext::State::BitmapDecoded;
+ return true;
+}
+
+static RefPtr<Gfx::Bitmap> load_png_impl(const u8* data, size_t data_size)
+{
+ PNGLoadingContext context;
+ context.data = data;
+ context.data_size = data_size;
+
+ if (!decode_png_chunks(context))
+ return nullptr;
+
+ if (!decode_png_bitmap(context))
+ return nullptr;
+
+ return context.bitmap;
+}
+
+static bool is_valid_compression_method(u8 compression_method)
+{
+ return compression_method == 0;
+}
+
+static bool is_valid_filter_method(u8 filter_method)
+{
+ return filter_method <= 4;
+}
+
+static bool process_IHDR(ReadonlyBytes data, PNGLoadingContext& context)
+{
+ if (data.size() < (int)sizeof(PNG_IHDR))
+ return false;
+ auto& ihdr = *(const PNG_IHDR*)data.data();
+
+ if (ihdr.width > maximum_width_for_decoded_images || ihdr.height > maximum_height_for_decoded_images) {
+ dbgln("This PNG is too large for comfort: {}x{}", (u32)ihdr.width, (u32)ihdr.height);
+ return false;
+ }
+
+ if (!is_valid_compression_method(ihdr.compression_method)) {
+ dbgln("PNG has invalid compression method {}", ihdr.compression_method);
+ return false;
+ }
+
+ if (!is_valid_filter_method(ihdr.filter_method)) {
+ dbgln("PNG has invalid filter method {}", ihdr.filter_method);
+ return false;
+ }
+
+ context.width = ihdr.width;
+ context.height = ihdr.height;
+ context.bit_depth = ihdr.bit_depth;
+ context.color_type = ihdr.color_type;
+ context.compression_method = ihdr.compression_method;
+ context.filter_method = ihdr.filter_method;
+ context.interlace_method = ihdr.interlace_method;
+
+#ifdef PNG_DEBUG
+ printf("PNG: %dx%d (%d bpp)\n", context.width, context.height, context.bit_depth);
+ printf(" Color type: %d\n", context.color_type);
+ printf("Compress Method: %d\n", context.compression_method);
+ printf(" Filter Method: %d\n", context.filter_method);
+ printf(" Interlace type: %d\n", context.interlace_method);
+#endif
+
+ if (context.interlace_method != PngInterlaceMethod::Null && context.interlace_method != PngInterlaceMethod::Adam7) {
+#ifdef PNG_DEBUG
+ dbgln("PNGLoader::process_IHDR: unknown interlace method: {}", context.interlace_method);
+#endif
+ return false;
+ }
+
+ switch (context.color_type) {
+ case 0: // Each pixel is a grayscale sample.
+ if (context.bit_depth != 1 && context.bit_depth != 2 && context.bit_depth != 4 && context.bit_depth != 8 && context.bit_depth != 16)
+ return false;
+ context.channels = 1;
+ break;
+ case 4: // Each pixel is a grayscale sample, followed by an alpha sample.
+ if (context.bit_depth != 8 && context.bit_depth != 16)
+ return false;
+ context.channels = 2;
+ break;
+ case 2: // Each pixel is an RGB sample
+ if (context.bit_depth != 8 && context.bit_depth != 16)
+ return false;
+ context.channels = 3;
+ break;
+ case 3: // Each pixel is a palette index; a PLTE chunk must appear.
+ if (context.bit_depth != 1 && context.bit_depth != 2 && context.bit_depth != 4 && context.bit_depth != 8)
+ return false;
+ context.channels = 1;
+ break;
+ case 6: // Each pixel is an RGB sample, followed by an alpha sample.
+ if (context.bit_depth != 8 && context.bit_depth != 16)
+ return false;
+ context.channels = 4;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+static bool process_IDAT(ReadonlyBytes data, PNGLoadingContext& context)
+{
+ context.compressed_data.append(data.data(), data.size());
+ return true;
+}
+
+static bool process_PLTE(ReadonlyBytes data, PNGLoadingContext& context)
+{
+ context.palette_data.append((const PaletteEntry*)data.data(), data.size() / 3);
+ return true;
+}
+
+static bool process_tRNS(ReadonlyBytes data, PNGLoadingContext& context)
+{
+ switch (context.color_type) {
+ case 3:
+ context.palette_transparency_data.append(data.data(), data.size());
+ break;
+ }
+ return true;
+}
+
+static bool process_chunk(Streamer& streamer, PNGLoadingContext& context)
+{
+ u32 chunk_size;
+ if (!streamer.read(chunk_size)) {
+#ifdef PNG_DEBUG
+ printf("Bail at chunk_size\n");
+#endif
+ return false;
+ }
+ u8 chunk_type[5];
+ chunk_type[4] = '\0';
+ if (!streamer.read_bytes(chunk_type, 4)) {
+#ifdef PNG_DEBUG
+ printf("Bail at chunk_type\n");
+#endif
+ return false;
+ }
+ ReadonlyBytes chunk_data;
+ if (!streamer.wrap_bytes(chunk_data, chunk_size)) {
+#ifdef PNG_DEBUG
+ printf("Bail at chunk_data\n");
+#endif
+ return false;
+ }
+ u32 chunk_crc;
+ if (!streamer.read(chunk_crc)) {
+#ifdef PNG_DEBUG
+ printf("Bail at chunk_crc\n");
+#endif
+ return false;
+ }
+#ifdef PNG_DEBUG
+ printf("Chunk type: '%s', size: %u, crc: %x\n", chunk_type, chunk_size, chunk_crc);
+#endif
+
+ if (!strcmp((const char*)chunk_type, "IHDR"))
+ return process_IHDR(chunk_data, context);
+ if (!strcmp((const char*)chunk_type, "IDAT"))
+ return process_IDAT(chunk_data, context);
+ if (!strcmp((const char*)chunk_type, "PLTE"))
+ return process_PLTE(chunk_data, context);
+ if (!strcmp((const char*)chunk_type, "tRNS"))
+ return process_tRNS(chunk_data, context);
+ return true;
+}
+
+PNGImageDecoderPlugin::PNGImageDecoderPlugin(const u8* data, size_t size)
+{
+ m_context = make<PNGLoadingContext>();
+ m_context->data = data;
+ m_context->data_size = size;
+}
+
+PNGImageDecoderPlugin::~PNGImageDecoderPlugin()
+{
+}
+
+IntSize PNGImageDecoderPlugin::size()
+{
+ if (m_context->state == PNGLoadingContext::State::Error)
+ return {};
+
+ if (m_context->state < PNGLoadingContext::State::SizeDecoded) {
+ bool success = decode_png_size(*m_context);
+ if (!success)
+ return {};
+ }
+
+ return { m_context->width, m_context->height };
+}
+
+RefPtr<Gfx::Bitmap> PNGImageDecoderPlugin::bitmap()
+{
+ if (m_context->state == PNGLoadingContext::State::Error)
+ return nullptr;
+
+ if (m_context->state < PNGLoadingContext::State::BitmapDecoded) {
+ // NOTE: This forces the chunk decoding to happen.
+ bool success = decode_png_bitmap(*m_context);
+ if (!success)
+ return nullptr;
+ }
+
+ ASSERT(m_context->bitmap);
+ return m_context->bitmap;
+}
+
+void PNGImageDecoderPlugin::set_volatile()
+{
+ if (m_context->bitmap)
+ m_context->bitmap->set_volatile();
+}
+
+bool PNGImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->bitmap)
+ return false;
+ return m_context->bitmap->set_nonvolatile();
+}
+
+bool PNGImageDecoderPlugin::sniff()
+{
+ return decode_png_header(*m_context);
+}
+
+bool PNGImageDecoderPlugin::is_animated()
+{
+ return false;
+}
+
+size_t PNGImageDecoderPlugin::loop_count()
+{
+ return 0;
+}
+
+size_t PNGImageDecoderPlugin::frame_count()
+{
+ return 1;
+}
+
+ImageFrameDescriptor PNGImageDecoderPlugin::frame(size_t i)
+{
+ if (i > 0) {
+ return { bitmap(), 0 };
+ }
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/PNGLoader.h b/Userland/Libraries/LibGfx/PNGLoader.h
new file mode 100644
index 0000000000..2432f231e0
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PNGLoader.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_png(const StringView& path);
+RefPtr<Gfx::Bitmap> load_png_from_memory(const u8*, size_t);
+
+struct PNGLoadingContext;
+
+class PNGImageDecoderPlugin final : public ImageDecoderPlugin {
+public:
+ virtual ~PNGImageDecoderPlugin() override;
+ PNGImageDecoderPlugin(const u8*, size_t);
+
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+ virtual bool sniff() override;
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<PNGLoadingContext> m_context;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/PPMLoader.cpp b/Userland/Libraries/LibGfx/PPMLoader.cpp
new file mode 100644
index 0000000000..80d99d8966
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PPMLoader.cpp
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2020, Hüseyin ASLITÜRK <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "PPMLoader.h"
+#include "PortableImageLoaderCommon.h"
+#include "Streamer.h"
+#include <AK/Endian.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/ScopeGuard.h>
+#include <AK/StringBuilder.h>
+#include <string.h>
+
+namespace Gfx {
+
+struct PPMLoadingContext {
+ enum Type {
+ Unknown,
+ ASCII,
+ RAWBITS
+ };
+
+ enum State {
+ NotDecoded = 0,
+ Error,
+ MagicNumber,
+ Width,
+ Height,
+ Maxval,
+ Bitmap,
+ Decoded
+ };
+
+ static constexpr auto ascii_magic_number = '3';
+ static constexpr auto binary_magic_number = '6';
+ static constexpr auto image_type = "PPM";
+
+ Type type { Type::Unknown };
+ State state { State::NotDecoded };
+ const u8* data { nullptr };
+ size_t data_size { 0 };
+ u16 width { 0 };
+ u16 height { 0 };
+ u16 max_val { 0 };
+ RefPtr<Gfx::Bitmap> bitmap;
+};
+
+static bool read_image_data(PPMLoadingContext& context, Streamer& streamer)
+{
+ Vector<Gfx::Color> color_data;
+ color_data.ensure_capacity(context.width * context.height);
+
+ if (context.type == PPMLoadingContext::ASCII) {
+ u16 red;
+ u16 green;
+ u16 blue;
+
+ while (true) {
+ if (!read_number(streamer, &red))
+ break;
+
+ if (!read_white_space(context, streamer))
+ break;
+
+ if (!read_number(streamer, &green))
+ break;
+
+ if (!read_white_space(context, streamer))
+ break;
+
+ if (!read_number(streamer, &blue))
+ break;
+
+ if (!read_white_space(context, streamer))
+ break;
+
+ Color color { (u8)red, (u8)green, (u8)blue };
+ if (context.max_val < 255)
+ color = adjust_color(context.max_val, color);
+ color_data.append(color);
+ }
+ } else if (context.type == PPMLoadingContext::RAWBITS) {
+ u8 pixel[3];
+ while (streamer.read_bytes(pixel, 3)) {
+ color_data.append({ pixel[0], pixel[1], pixel[2] });
+ }
+ }
+
+ if (context.width * context.height != color_data.size())
+ return false;
+
+ if (!create_bitmap(context)) {
+ return false;
+ }
+
+ set_pixels(context, color_data);
+
+ context.state = PPMLoadingContext::State::Bitmap;
+ return true;
+}
+
+RefPtr<Gfx::Bitmap> load_ppm(const StringView& path)
+{
+ return load<PPMLoadingContext>(path);
+}
+
+RefPtr<Gfx::Bitmap> load_ppm_from_memory(const u8* data, size_t length)
+{
+ auto bitmap = load_impl<PPMLoadingContext>(data, length);
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded PPM: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+PPMImageDecoderPlugin::PPMImageDecoderPlugin(const u8* data, size_t size)
+{
+ m_context = make<PPMLoadingContext>();
+ m_context->data = data;
+ m_context->data_size = size;
+}
+
+PPMImageDecoderPlugin::~PPMImageDecoderPlugin()
+{
+}
+
+IntSize PPMImageDecoderPlugin::size()
+{
+ if (m_context->state == PPMLoadingContext::State::Error)
+ return {};
+
+ if (m_context->state < PPMLoadingContext::State::Decoded) {
+ bool success = decode(*m_context);
+ if (!success)
+ return {};
+ }
+
+ return { m_context->width, m_context->height };
+}
+
+RefPtr<Gfx::Bitmap> PPMImageDecoderPlugin::bitmap()
+{
+ if (m_context->state == PPMLoadingContext::State::Error)
+ return nullptr;
+
+ if (m_context->state < PPMLoadingContext::State::Decoded) {
+ bool success = decode(*m_context);
+ if (!success)
+ return nullptr;
+ }
+
+ ASSERT(m_context->bitmap);
+ return m_context->bitmap;
+}
+
+void PPMImageDecoderPlugin::set_volatile()
+{
+ if (m_context->bitmap)
+ m_context->bitmap->set_volatile();
+}
+
+bool PPMImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->bitmap)
+ return false;
+
+ return m_context->bitmap->set_nonvolatile();
+}
+
+bool PPMImageDecoderPlugin::sniff()
+{
+ if (m_context->data_size < 2)
+ return false;
+
+ if (m_context->data[0] == 'P' && m_context->data[1] == '3')
+ return true;
+
+ if (m_context->data[0] == 'P' && m_context->data[1] == '6')
+ return true;
+
+ return false;
+}
+
+bool PPMImageDecoderPlugin::is_animated()
+{
+ return false;
+}
+
+size_t PPMImageDecoderPlugin::loop_count()
+{
+ return 0;
+}
+
+size_t PPMImageDecoderPlugin::frame_count()
+{
+ return 1;
+}
+
+ImageFrameDescriptor PPMImageDecoderPlugin::frame(size_t i)
+{
+ if (i > 0) {
+ return { bitmap(), 0 };
+ }
+
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/PPMLoader.h b/Userland/Libraries/LibGfx/PPMLoader.h
new file mode 100644
index 0000000000..bddd019d76
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PPMLoader.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020, Hüseyin ASLITÜRK <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_ppm(const StringView& path);
+RefPtr<Gfx::Bitmap> load_ppm_from_memory(const u8*, size_t);
+
+struct PPMLoadingContext;
+
+class PPMImageDecoderPlugin final : public ImageDecoderPlugin {
+public:
+ PPMImageDecoderPlugin(const u8*, size_t);
+ virtual ~PPMImageDecoderPlugin() override;
+
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+
+ virtual bool sniff() override;
+
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<PPMLoadingContext> m_context;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Painter.cpp b/Userland/Libraries/LibGfx/Painter.cpp
new file mode 100644
index 0000000000..7e7f5f16c7
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Painter.cpp
@@ -0,0 +1,1629 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Painter.h"
+#include "Bitmap.h"
+#include "Emoji.h"
+#include "Font.h"
+#include "FontDatabase.h"
+#include "Gamma.h"
+#include <AK/Assertions.h>
+#include <AK/Function.h>
+#include <AK/Memory.h>
+#include <AK/QuickSort.h>
+#include <AK/StdLibExtras.h>
+#include <AK/StringBuilder.h>
+#include <AK/Utf32View.h>
+#include <AK/Utf8View.h>
+#include <LibGfx/CharacterBitmap.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/Path.h>
+#include <math.h>
+#include <stdio.h>
+
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC optimize("O3")
+#endif
+
+namespace Gfx {
+
+template<BitmapFormat format = BitmapFormat::Invalid>
+ALWAYS_INLINE Color get_pixel(const Gfx::Bitmap& bitmap, int x, int y)
+{
+ if constexpr (format == BitmapFormat::Indexed8)
+ return bitmap.palette_color(bitmap.scanline_u8(y)[x]);
+ if constexpr (format == BitmapFormat::Indexed4)
+ return bitmap.palette_color(bitmap.scanline_u8(y)[x]);
+ if constexpr (format == BitmapFormat::Indexed2)
+ return bitmap.palette_color(bitmap.scanline_u8(y)[x]);
+ if constexpr (format == BitmapFormat::Indexed1)
+ return bitmap.palette_color(bitmap.scanline_u8(y)[x]);
+ if constexpr (format == BitmapFormat::RGB32)
+ return Color::from_rgb(bitmap.scanline(y)[x]);
+ if constexpr (format == BitmapFormat::RGBA32)
+ return Color::from_rgba(bitmap.scanline(y)[x]);
+ return bitmap.get_pixel(x, y);
+}
+
+Painter::Painter(Gfx::Bitmap& bitmap)
+ : m_target(bitmap)
+{
+ ASSERT(bitmap.format() == Gfx::BitmapFormat::RGB32 || bitmap.format() == Gfx::BitmapFormat::RGBA32);
+ m_state_stack.append(State());
+ state().font = &FontDatabase::default_font();
+ state().clip_rect = { { 0, 0 }, bitmap.size() };
+ m_clip_origin = state().clip_rect;
+}
+
+Painter::~Painter()
+{
+}
+
+void Painter::fill_rect_with_draw_op(const IntRect& a_rect, Color color)
+{
+ auto rect = a_rect.translated(translation()).intersected(clip_rect());
+ if (rect.is_empty())
+ return;
+
+ RGBA32* dst = m_target->scanline(rect.top()) + rect.left();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ for (int i = rect.height() - 1; i >= 0; --i) {
+ for (int j = 0; j < rect.width(); ++j)
+ set_pixel_with_draw_op(dst[j], color);
+ dst += dst_skip;
+ }
+}
+
+void Painter::clear_rect(const IntRect& a_rect, Color color)
+{
+ auto rect = a_rect.translated(translation()).intersected(clip_rect());
+ if (rect.is_empty())
+ return;
+
+ ASSERT(m_target->rect().contains(rect));
+
+ RGBA32* dst = m_target->scanline(rect.top()) + rect.left();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ for (int i = rect.height() - 1; i >= 0; --i) {
+ fast_u32_fill(dst, color.value(), rect.width());
+ dst += dst_skip;
+ }
+}
+
+void Painter::fill_rect(const IntRect& a_rect, Color color)
+{
+ if (color.alpha() == 0)
+ return;
+
+ if (draw_op() != DrawOp::Copy) {
+ fill_rect_with_draw_op(a_rect, color);
+ return;
+ }
+
+ if (color.alpha() == 0xff) {
+ clear_rect(a_rect, color);
+ return;
+ }
+
+ auto rect = a_rect.translated(translation()).intersected(clip_rect());
+ if (rect.is_empty())
+ return;
+
+ ASSERT(m_target->rect().contains(rect));
+
+ RGBA32* dst = m_target->scanline(rect.top()) + rect.left();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ for (int i = rect.height() - 1; i >= 0; --i) {
+ for (int j = 0; j < rect.width(); ++j)
+ dst[j] = Color::from_rgba(dst[j]).blend(color).value();
+ dst += dst_skip;
+ }
+}
+
+void Painter::fill_rect_with_dither_pattern(const IntRect& a_rect, Color color_a, Color color_b)
+{
+ auto rect = a_rect.translated(translation()).intersected(clip_rect());
+ if (rect.is_empty())
+ return;
+
+ RGBA32* dst = m_target->scanline(rect.top()) + rect.left();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ for (int i = 0; i < rect.height(); ++i) {
+ for (int j = 0; j < rect.width(); ++j) {
+ bool checkboard_use_a = (i & 1) ^ (j & 1);
+ if (checkboard_use_a && !color_a.alpha())
+ continue;
+ if (!checkboard_use_a && !color_b.alpha())
+ continue;
+ dst[j] = checkboard_use_a ? color_a.value() : color_b.value();
+ }
+ dst += dst_skip;
+ }
+}
+
+void Painter::fill_rect_with_checkerboard(const IntRect& a_rect, const IntSize& cell_size, Color color_dark, Color color_light)
+{
+ auto rect = a_rect.translated(translation()).intersected(clip_rect());
+ if (rect.is_empty())
+ return;
+
+ RGBA32* dst = m_target->scanline(rect.top()) + rect.left();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ for (int i = 0; i < rect.height(); ++i) {
+ for (int j = 0; j < rect.width(); ++j) {
+ int cell_row = i / cell_size.height();
+ int cell_col = j / cell_size.width();
+ dst[j] = ((cell_row % 2) ^ (cell_col % 2)) ? color_light.value() : color_dark.value();
+ }
+ dst += dst_skip;
+ }
+}
+
+void Painter::fill_rect_with_gradient(Orientation orientation, const IntRect& a_rect, Color gradient_start, Color gradient_end)
+{
+#ifdef NO_FPU
+ return fill_rect(a_rect, gradient_start);
+#endif
+ auto rect = a_rect.translated(translation());
+ auto clipped_rect = IntRect::intersection(rect, clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+
+ int offset = clipped_rect.primary_offset_for_orientation(orientation) - rect.primary_offset_for_orientation(orientation);
+
+ RGBA32* dst = m_target->scanline(clipped_rect.top()) + clipped_rect.left();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ float increment = (1.0 / ((rect.primary_size_for_orientation(orientation))));
+
+ if (orientation == Orientation::Horizontal) {
+ for (int i = clipped_rect.height() - 1; i >= 0; --i) {
+ float c = offset * increment;
+ for (int j = 0; j < clipped_rect.width(); ++j) {
+ dst[j] = gamma_accurate_blend(gradient_start, gradient_end, c).value();
+ c += increment;
+ }
+ dst += dst_skip;
+ }
+ } else {
+ float c = offset * increment;
+ for (int i = clipped_rect.height() - 1; i >= 0; --i) {
+ auto color = gamma_accurate_blend(gradient_start, gradient_end, c);
+ for (int j = 0; j < clipped_rect.width(); ++j) {
+ dst[j] = color.value();
+ }
+ c += increment;
+ dst += dst_skip;
+ }
+ }
+}
+
+void Painter::fill_rect_with_gradient(const IntRect& a_rect, Color gradient_start, Color gradient_end)
+{
+ return fill_rect_with_gradient(Orientation::Horizontal, a_rect, gradient_start, gradient_end);
+}
+
+void Painter::fill_ellipse(const IntRect& a_rect, Color color)
+{
+ auto rect = a_rect.translated(translation()).intersected(clip_rect());
+ if (rect.is_empty())
+ return;
+
+ ASSERT(m_target->rect().contains(rect));
+
+ RGBA32* dst = m_target->scanline(rect.top()) + rect.left() + rect.width() / 2;
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ for (int i = 0; i < rect.height(); i++) {
+ double y = rect.height() * 0.5 - i;
+ double x = rect.width() * sqrt(0.25 - y * y / rect.height() / rect.height());
+ fast_u32_fill(dst - (int)x, color.value(), 2 * (int)x);
+ dst += dst_skip;
+ }
+}
+
+void Painter::draw_ellipse_intersecting(const IntRect& rect, Color color, int thickness)
+{
+ constexpr int number_samples = 100; // FIXME: dynamically work out the number of samples based upon the rect size
+ double increment = M_PI / number_samples;
+
+ auto ellipse_x = [&](double theta) -> int {
+ return (cos(theta) * rect.width() / sqrt(2)) + rect.center().x();
+ };
+
+ auto ellipse_y = [&](double theta) -> int {
+ return (sin(theta) * rect.height() / sqrt(2)) + rect.center().y();
+ };
+
+ for (float theta = 0; theta < 2 * M_PI; theta += increment) {
+ draw_line({ ellipse_x(theta), ellipse_y(theta) }, { ellipse_x(theta + increment), ellipse_y(theta + increment) }, color, thickness);
+ }
+}
+
+template<typename RectType, typename Callback>
+static void for_each_pixel_around_rect_clockwise(const RectType& rect, Callback callback)
+{
+ if (rect.is_empty())
+ return;
+ for (auto x = rect.left(); x <= rect.right(); ++x) {
+ callback(x, rect.top());
+ }
+ for (auto y = rect.top() + 1; y <= rect.bottom(); ++y) {
+ callback(rect.right(), y);
+ }
+ for (auto x = rect.right() - 1; x >= rect.left(); --x) {
+ callback(x, rect.bottom());
+ }
+ for (auto y = rect.bottom() - 1; y > rect.top(); --y) {
+ callback(rect.left(), y);
+ }
+}
+
+void Painter::draw_focus_rect(const IntRect& rect, Color color)
+{
+ if (rect.is_empty())
+ return;
+ bool state = false;
+ for_each_pixel_around_rect_clockwise(rect, [&](auto x, auto y) {
+ if (state)
+ set_pixel(x, y, color);
+ state = !state;
+ });
+}
+
+void Painter::draw_rect(const IntRect& a_rect, Color color, bool rough)
+{
+ IntRect rect = a_rect.translated(translation());
+ auto clipped_rect = rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+
+ int min_y = clipped_rect.top();
+ int max_y = clipped_rect.bottom();
+
+ if (rect.top() >= clipped_rect.top() && rect.top() <= clipped_rect.bottom()) {
+ int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x();
+ int width = rough ? min(rect.width() - 2, clipped_rect.width()) : clipped_rect.width();
+ fill_scanline_with_draw_op(rect.top(), start_x, width, color);
+ ++min_y;
+ }
+ if (rect.bottom() >= clipped_rect.top() && rect.bottom() <= clipped_rect.bottom()) {
+ int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x();
+ int width = rough ? min(rect.width() - 2, clipped_rect.width()) : clipped_rect.width();
+ fill_scanline_with_draw_op(rect.bottom(), start_x, width, color);
+ --max_y;
+ }
+
+ bool draw_left_side = rect.left() >= clipped_rect.left();
+ bool draw_right_side = rect.right() == clipped_rect.right();
+
+ if (draw_left_side && draw_right_side) {
+ // Specialized loop when drawing both sides.
+ for (int y = min_y; y <= max_y; ++y) {
+ auto* bits = m_target->scanline(y);
+ set_pixel_with_draw_op(bits[rect.left()], color);
+ set_pixel_with_draw_op(bits[rect.right()], color);
+ }
+ } else {
+ for (int y = min_y; y <= max_y; ++y) {
+ auto* bits = m_target->scanline(y);
+ if (draw_left_side)
+ set_pixel_with_draw_op(bits[rect.left()], color);
+ if (draw_right_side)
+ set_pixel_with_draw_op(bits[rect.right()], color);
+ }
+ }
+}
+
+void Painter::draw_bitmap(const IntPoint& p, const CharacterBitmap& bitmap, Color color)
+{
+ auto rect = IntRect(p, bitmap.size()).translated(translation());
+ auto clipped_rect = rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = clipped_rect.top() - rect.top();
+ const int last_row = clipped_rect.bottom() - rect.top();
+ const int first_column = clipped_rect.left() - rect.left();
+ const int last_column = clipped_rect.right() - rect.left();
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+ const char* bitmap_row = &bitmap.bits()[first_row * bitmap.width() + first_column];
+ const size_t bitmap_skip = bitmap.width();
+
+ for (int row = first_row; row <= last_row; ++row) {
+ for (int j = 0; j <= (last_column - first_column); ++j) {
+ char fc = bitmap_row[j];
+ if (fc == '#')
+ dst[j] = color.value();
+ }
+ bitmap_row += bitmap_skip;
+ dst += dst_skip;
+ }
+}
+
+void Painter::draw_bitmap(const IntPoint& p, const GlyphBitmap& bitmap, Color color)
+{
+ auto dst_rect = IntRect(p, bitmap.size()).translated(translation());
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = clipped_rect.top() - dst_rect.top();
+ const int last_row = clipped_rect.bottom() - dst_rect.top();
+ const int first_column = clipped_rect.left() - dst_rect.left();
+ const int last_column = clipped_rect.right() - dst_rect.left();
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ for (int row = first_row; row <= last_row; ++row) {
+ for (int j = 0; j <= (last_column - first_column); ++j) {
+ if (bitmap.bit_at(j + first_column, row))
+ dst[j] = color.value();
+ }
+ dst += dst_skip;
+ }
+}
+
+void Painter::draw_triangle(const IntPoint& a, const IntPoint& b, const IntPoint& c, Color color)
+{
+ RGBA32 rgba = color.value();
+
+ IntPoint p0(a);
+ IntPoint p1(b);
+ IntPoint p2(c);
+
+ if (p0.y() > p1.y())
+ swap(p0, p1);
+ if (p0.y() > p2.y())
+ swap(p0, p2);
+ if (p1.y() > p2.y())
+ swap(p1, p2);
+
+ auto clip = clip_rect();
+ if (p0.y() >= clip.bottom())
+ return;
+ if (p2.y() < clip.top())
+ return;
+
+ float dx01 = (float)(p1.x() - p0.x()) / (p1.y() - p0.y());
+ float dx02 = (float)(p2.x() - p0.x()) / (p2.y() - p0.y());
+ float dx12 = (float)(p2.x() - p1.x()) / (p2.y() - p1.y());
+
+ float x01 = p0.x();
+ float x02 = p0.x();
+
+ int top = p0.y();
+ if (top < clip.top()) {
+ x01 += dx01 * (clip.top() - top);
+ x02 += dx02 * (clip.top() - top);
+ top = clip.top();
+ }
+
+ for (int y = top; y < p1.y() && y < clip.bottom(); ++y) {
+ int start = x01 > x02 ? max((int)x02, clip.left()) : max((int)x01, clip.left());
+ int end = x01 > x02 ? min((int)x01, clip.right()) : min((int)x02, clip.right());
+ auto* scanline = m_target->scanline(y);
+ for (int x = start; x < end; x++) {
+ scanline[x] = rgba;
+ }
+ x01 += dx01;
+ x02 += dx02;
+ }
+
+ x02 = p0.x() + dx02 * (p1.y() - p0.y());
+ float x12 = p1.x();
+
+ top = p1.y();
+ if (top < clip.top()) {
+ x02 += dx02 * (clip.top() - top);
+ x12 += dx12 * (clip.top() - top);
+ top = clip.top();
+ }
+
+ for (int y = top; y < p2.y() && y < clip.bottom(); ++y) {
+ int start = x12 > x02 ? max((int)x02, clip.left()) : max((int)x12, clip.left());
+ int end = x12 > x02 ? min((int)x12, clip.right()) : min((int)x02, clip.right());
+ auto* scanline = m_target->scanline(y);
+ for (int x = start; x < end; x++) {
+ scanline[x] = rgba;
+ }
+ x02 += dx02;
+ x12 += dx12;
+ }
+}
+
+void Painter::blit_scaled(const IntRect& dst_rect_raw, const Gfx::Bitmap& source, const IntRect& src_rect, float hscale, float vscale)
+{
+ auto dst_rect = IntRect(dst_rect_raw.location(), dst_rect_raw.size()).translated(translation());
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = (clipped_rect.top() - dst_rect.top());
+ const int last_row = (clipped_rect.bottom() - dst_rect.top());
+ const int first_column = (clipped_rect.left() - dst_rect.left());
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ int x_start = first_column + src_rect.left();
+ for (int row = first_row; row <= last_row; ++row) {
+ int sr = (row + src_rect.top()) * vscale;
+ if (sr >= source.size().height() || sr < 0) {
+ dst += dst_skip;
+ continue;
+ }
+ const RGBA32* sl = source.scanline(sr);
+ for (int x = x_start; x < clipped_rect.width() + x_start; ++x) {
+ int sx = x * hscale;
+ if (sx < source.size().width() && sx >= 0)
+ dst[x - x_start] = sl[sx];
+ }
+ dst += dst_skip;
+ }
+ return;
+}
+
+void Painter::blit_with_opacity(const IntPoint& position, const Gfx::Bitmap& source, const IntRect& src_rect, float opacity)
+{
+ ASSERT(!m_target->has_alpha_channel());
+
+ if (!opacity)
+ return;
+ if (opacity >= 1.0f)
+ return blit(position, source, src_rect);
+
+ u8 alpha = 255 * opacity;
+
+ IntRect safe_src_rect = IntRect::intersection(src_rect, source.rect());
+ IntRect dst_rect(position, safe_src_rect.size());
+ dst_rect.move_by(state().translation);
+ auto clipped_rect = IntRect::intersection(dst_rect, clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = clipped_rect.top() - dst_rect.top();
+ const int last_row = clipped_rect.bottom() - dst_rect.top();
+ const int first_column = clipped_rect.left() - dst_rect.left();
+ const int last_column = clipped_rect.right() - dst_rect.left();
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+ const unsigned src_skip = source.pitch() / sizeof(RGBA32);
+
+ for (int row = first_row; row <= last_row; ++row) {
+ for (int x = 0; x <= (last_column - first_column); ++x) {
+ Color src_color_with_alpha = Color::from_rgb(src[x]);
+ src_color_with_alpha.set_alpha(alpha);
+ Color dst_color = Color::from_rgb(dst[x]);
+ dst[x] = dst_color.blend(src_color_with_alpha).value();
+ }
+ dst += dst_skip;
+ src += src_skip;
+ }
+}
+
+void Painter::blit_filtered(const IntPoint& position, const Gfx::Bitmap& source, const IntRect& src_rect, Function<Color(Color)> filter)
+{
+ IntRect safe_src_rect = src_rect.intersected(source.rect());
+ auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = clipped_rect.top() - dst_rect.top();
+ const int last_row = clipped_rect.bottom() - dst_rect.top();
+ const int first_column = clipped_rect.left() - dst_rect.left();
+ const int last_column = clipped_rect.right() - dst_rect.left();
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+ const size_t src_skip = source.pitch() / sizeof(RGBA32);
+
+ for (int row = first_row; row <= last_row; ++row) {
+ for (int x = 0; x <= (last_column - first_column); ++x) {
+ u8 alpha = Color::from_rgba(src[x]).alpha();
+ if (alpha == 0xff)
+ dst[x] = filter(Color::from_rgba(src[x])).value();
+ else if (!alpha)
+ continue;
+ else
+ dst[x] = Color::from_rgba(dst[x]).blend(filter(Color::from_rgba(src[x]))).value();
+ }
+ dst += dst_skip;
+ src += src_skip;
+ }
+}
+
+void Painter::blit_brightened(const IntPoint& position, const Gfx::Bitmap& source, const IntRect& src_rect)
+{
+ return blit_filtered(position, source, src_rect, [](Color src) {
+ return src.lightened();
+ });
+}
+
+void Painter::blit_dimmed(const IntPoint& position, const Gfx::Bitmap& source, const IntRect& src_rect)
+{
+ return blit_filtered(position, source, src_rect, [](Color src) {
+ return src.to_grayscale().lightened();
+ });
+}
+
+void Painter::draw_tiled_bitmap(const IntRect& a_dst_rect, const Gfx::Bitmap& source)
+{
+ auto dst_rect = a_dst_rect.translated(translation());
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = (clipped_rect.top() - dst_rect.top());
+ const int last_row = (clipped_rect.bottom() - dst_rect.top());
+ const int first_column = (clipped_rect.left() - dst_rect.left());
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ if (source.format() == BitmapFormat::RGB32 || source.format() == BitmapFormat::RGBA32) {
+ int x_start = first_column + a_dst_rect.left();
+ for (int row = first_row; row <= last_row; ++row) {
+ const RGBA32* sl = source.scanline((row + a_dst_rect.top())
+ % source.size().height());
+ for (int x = x_start; x < clipped_rect.width() + x_start; ++x) {
+ dst[x - x_start] = sl[x % source.size().width()];
+ }
+ dst += dst_skip;
+ }
+ return;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+void Painter::blit_offset(const IntPoint& position,
+ const Gfx::Bitmap& source,
+ const IntRect& src_rect,
+ const IntPoint& offset)
+{
+ auto dst_rect = IntRect(position, src_rect.size()).translated(translation());
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = (clipped_rect.top() - dst_rect.top());
+ const int last_row = (clipped_rect.bottom() - dst_rect.top());
+ const int first_column = (clipped_rect.left() - dst_rect.left());
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ if (source.format() == BitmapFormat::RGB32 || source.format() == BitmapFormat::RGBA32) {
+ int x_start = first_column + src_rect.left();
+ for (int row = first_row; row <= last_row; ++row) {
+ int sr = row - offset.y() + src_rect.top();
+ if (sr >= source.size().height() || sr < 0) {
+ dst += dst_skip;
+ continue;
+ }
+ const RGBA32* sl = source.scanline(sr);
+ for (int x = x_start; x < clipped_rect.width() + x_start; ++x) {
+ int sx = x - offset.x();
+ if (sx < source.size().width() && sx >= 0)
+ dst[x - x_start] = sl[sx];
+ }
+ dst += dst_skip;
+ }
+ return;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+void Painter::blit_with_alpha(const IntPoint& position, const Gfx::Bitmap& source, const IntRect& src_rect)
+{
+ ASSERT(source.has_alpha_channel());
+ IntRect safe_src_rect = src_rect.intersected(source.rect());
+ auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = clipped_rect.top() - dst_rect.top();
+ const int last_row = clipped_rect.bottom() - dst_rect.top();
+ const int first_column = clipped_rect.left() - dst_rect.left();
+ const int last_column = clipped_rect.right() - dst_rect.left();
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+ const size_t src_skip = source.pitch() / sizeof(RGBA32);
+
+ for (int row = first_row; row <= last_row; ++row) {
+ for (int x = 0; x <= (last_column - first_column); ++x) {
+ u8 alpha = Color::from_rgba(src[x]).alpha();
+ if (alpha == 0xff)
+ dst[x] = src[x];
+ else if (!alpha)
+ continue;
+ else
+ dst[x] = Color::from_rgba(dst[x]).blend(Color::from_rgba(src[x])).value();
+ }
+ dst += dst_skip;
+ src += src_skip;
+ }
+}
+
+void Painter::blit(const IntPoint& position, const Gfx::Bitmap& source, const IntRect& src_rect, float opacity)
+{
+ if (opacity < 1.0f)
+ return blit_with_opacity(position, source, src_rect, opacity);
+ if (source.has_alpha_channel())
+ return blit_with_alpha(position, source, src_rect);
+ auto safe_src_rect = src_rect.intersected(source.rect());
+ ASSERT(source.rect().contains(safe_src_rect));
+ auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = clipped_rect.top() - dst_rect.top();
+ const int last_row = clipped_rect.bottom() - dst_rect.top();
+ const int first_column = clipped_rect.left() - dst_rect.left();
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ if (source.format() == BitmapFormat::RGB32 || source.format() == BitmapFormat::RGBA32) {
+ const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
+ const size_t src_skip = source.pitch() / sizeof(RGBA32);
+ for (int row = first_row; row <= last_row; ++row) {
+ fast_u32_copy(dst, src, clipped_rect.width());
+ dst += dst_skip;
+ src += src_skip;
+ }
+ return;
+ }
+
+ if (Bitmap::is_indexed(source.format())) {
+ const u8* src = source.scanline_u8(src_rect.top() + first_row) + src_rect.left() + first_column;
+ const size_t src_skip = source.pitch();
+ for (int row = first_row; row <= last_row; ++row) {
+ for (int i = 0; i < clipped_rect.width(); ++i)
+ dst[i] = source.palette_color(src[i]).value();
+ dst += dst_skip;
+ src += src_skip;
+ }
+ return;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+template<bool has_alpha_channel, typename GetPixel>
+ALWAYS_INLINE static void do_draw_integer_scaled_bitmap(Gfx::Bitmap& target, const IntRect& dst_rect, const Gfx::Bitmap& source, int hfactor, int vfactor, GetPixel get_pixel, float opacity)
+{
+ bool has_opacity = opacity != 1.0f;
+ for (int y = source.rect().top(); y <= source.rect().bottom(); ++y) {
+ int dst_y = dst_rect.y() + y * vfactor;
+ for (int x = source.rect().left(); x <= source.rect().right(); ++x) {
+ auto src_pixel = get_pixel(source, x, y);
+ if (has_opacity)
+ src_pixel.set_alpha(src_pixel.alpha() * opacity);
+ for (int yo = 0; yo < vfactor; ++yo) {
+ auto* scanline = (Color*)target.scanline(dst_y + yo);
+ int dst_x = dst_rect.x() + x * hfactor;
+ for (int xo = 0; xo < hfactor; ++xo) {
+ if constexpr (has_alpha_channel)
+ scanline[dst_x + xo] = scanline[dst_x + xo].blend(src_pixel);
+ else
+ scanline[dst_x + xo] = src_pixel;
+ }
+ }
+ }
+ }
+}
+
+template<bool has_alpha_channel, typename GetPixel>
+ALWAYS_INLINE static void do_draw_scaled_bitmap(Gfx::Bitmap& target, const IntRect& dst_rect, const IntRect& clipped_rect, const Gfx::Bitmap& source, const IntRect& src_rect, int hscale, int vscale, GetPixel get_pixel, float opacity)
+{
+ if (dst_rect == clipped_rect && !(dst_rect.width() % src_rect.width()) && !(dst_rect.height() % src_rect.height())) {
+ int hfactor = dst_rect.width() / src_rect.width();
+ int vfactor = dst_rect.height() / src_rect.height();
+ if (hfactor == 2 && vfactor == 2)
+ return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, 2, 2, get_pixel, opacity);
+ if (hfactor == 3 && vfactor == 3)
+ return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, 3, 3, get_pixel, opacity);
+ if (hfactor == 4 && vfactor == 4)
+ return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, 4, 4, get_pixel, opacity);
+ return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, hfactor, vfactor, get_pixel, opacity);
+ }
+
+ bool has_opacity = opacity != 1.0f;
+
+ for (int y = clipped_rect.top(); y <= clipped_rect.bottom(); ++y) {
+ auto* scanline = (Color*)target.scanline(y);
+ for (int x = clipped_rect.left(); x <= clipped_rect.right(); ++x) {
+ auto scaled_x = ((x - dst_rect.x()) * hscale) >> 16;
+ auto scaled_y = ((y - dst_rect.y()) * vscale) >> 16;
+ auto src_pixel = get_pixel(source, scaled_x, scaled_y);
+ if (has_opacity)
+ src_pixel.set_alpha(src_pixel.alpha() * opacity);
+ if constexpr (has_alpha_channel) {
+ scanline[x] = scanline[x].blend(src_pixel);
+ } else
+ scanline[x] = src_pixel;
+ }
+ }
+}
+
+void Painter::draw_scaled_bitmap(const IntRect& a_dst_rect, const Gfx::Bitmap& source, const IntRect& src_rect, float opacity)
+{
+ auto dst_rect = a_dst_rect;
+ if (dst_rect.size() == src_rect.size())
+ return blit(dst_rect.location(), source, src_rect, opacity);
+
+ auto safe_src_rect = src_rect.intersected(source.rect());
+ ASSERT(source.rect().contains(safe_src_rect));
+ dst_rect.move_by(state().translation);
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+
+ int hscale = (src_rect.width() << 16) / dst_rect.width();
+ int vscale = (src_rect.height() << 16) / dst_rect.height();
+
+ if (source.has_alpha_channel()) {
+ switch (source.format()) {
+ case BitmapFormat::RGB32:
+ do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::RGB32>, opacity);
+ break;
+ case BitmapFormat::RGBA32:
+ do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::RGBA32>, opacity);
+ break;
+ case BitmapFormat::Indexed8:
+ do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Indexed8>, opacity);
+ break;
+ case BitmapFormat::Indexed4:
+ do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Indexed4>, opacity);
+ break;
+ case BitmapFormat::Indexed2:
+ do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Indexed2>, opacity);
+ break;
+ case BitmapFormat::Indexed1:
+ do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Indexed1>, opacity);
+ break;
+ default:
+ do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Invalid>, opacity);
+ break;
+ }
+ } else {
+ switch (source.format()) {
+ case BitmapFormat::RGB32:
+ do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::RGB32>, opacity);
+ break;
+ case BitmapFormat::RGBA32:
+ do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::RGBA32>, opacity);
+ break;
+ case BitmapFormat::Indexed8:
+ do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Indexed8>, opacity);
+ break;
+ default:
+ do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Invalid>, opacity);
+ break;
+ }
+ }
+}
+
+FLATTEN void Painter::draw_glyph(const IntPoint& point, u32 code_point, Color color)
+{
+ draw_glyph(point, code_point, font(), color);
+}
+
+FLATTEN void Painter::draw_glyph(const IntPoint& point, u32 code_point, const Font& font, Color color)
+{
+ draw_bitmap(point, font.glyph_bitmap(code_point), color);
+}
+
+void Painter::draw_emoji(const IntPoint& point, const Gfx::Bitmap& emoji, const Font& font)
+{
+ if (!font.is_fixed_width())
+ blit(point, emoji, emoji.rect());
+ else {
+ IntRect dst_rect {
+ point.x(),
+ point.y(),
+ font.glyph_width('x'),
+ font.glyph_height()
+ };
+ draw_scaled_bitmap(dst_rect, emoji, emoji.rect());
+ }
+}
+
+void Painter::draw_glyph_or_emoji(const IntPoint& point, u32 code_point, const Font& font, Color color)
+{
+ if (code_point < (u32)font.glyph_count()) {
+ // This looks like a regular character.
+ draw_glyph(point, (size_t)code_point, font, color);
+ return;
+ }
+
+ // Perhaps it's an emoji?
+ auto* emoji = Emoji::emoji_for_code_point(code_point);
+ if (emoji == nullptr) {
+#ifdef EMOJI_DEBUG
+ dbg() << "Failed to find an emoji for code_point " << code_point;
+#endif
+ draw_glyph(point, '?', font, color);
+ return;
+ }
+
+ draw_emoji(point, *emoji, font);
+}
+
+static void apply_elision(Utf8View& final_text, String& elided_text, size_t offset)
+{
+ StringBuilder builder;
+ builder.append(final_text.substring_view(0, offset).as_string());
+ builder.append("...");
+ elided_text = builder.to_string();
+ final_text = Utf8View { elided_text };
+}
+
+static void apply_elision(Utf32View& final_text, Vector<u32>& elided_text, size_t offset)
+{
+ elided_text.append(final_text.code_points(), offset);
+ elided_text.append('.');
+ elided_text.append('.');
+ elided_text.append('.');
+ final_text = Utf32View { elided_text.data(), elided_text.size() };
+}
+
+template<typename TextType>
+struct ElidedText {
+};
+
+template<>
+struct ElidedText<Utf8View> {
+ typedef String Type;
+};
+
+template<>
+struct ElidedText<Utf32View> {
+ typedef Vector<u32> Type;
+};
+
+template<typename TextType, typename DrawGlyphFunction>
+void draw_text_line(const IntRect& a_rect, const TextType& text, const Font& font, TextAlignment alignment, TextElision elision, DrawGlyphFunction draw_glyph)
+{
+ auto rect = a_rect;
+ TextType final_text(text);
+ typename ElidedText<TextType>::Type elided_text;
+ if (elision == TextElision::Right) {
+ int text_width = font.width(final_text);
+ if (font.width(final_text) > rect.width()) {
+ int glyph_spacing = font.glyph_spacing();
+ int new_width = font.width("...");
+ if (new_width < text_width) {
+ size_t offset = 0;
+ for (auto it = text.begin(); it != text.end(); ++it) {
+ auto code_point = *it;
+ int glyph_width = font.glyph_or_emoji_width(code_point);
+ // NOTE: Glyph spacing should not be added after the last glyph on the line,
+ // but since we are here because the last glyph does not actually fit on the line,
+ // we don't have to worry about spacing.
+ int width_with_this_glyph_included = new_width + glyph_width + glyph_spacing;
+ if (width_with_this_glyph_included > rect.width())
+ break;
+ new_width += glyph_width + glyph_spacing;
+ offset = text.iterator_offset(it);
+ }
+ apply_elision(final_text, elided_text, offset);
+ }
+ }
+ }
+
+ switch (alignment) {
+ case TextAlignment::TopLeft:
+ case TextAlignment::CenterLeft:
+ break;
+ case TextAlignment::TopRight:
+ case TextAlignment::CenterRight:
+ case TextAlignment::BottomRight:
+ rect.set_x(rect.right() - font.width(final_text));
+ break;
+ case TextAlignment::Center: {
+ auto shrunken_rect = rect;
+ shrunken_rect.set_width(font.width(final_text));
+ shrunken_rect.center_within(rect);
+ rect = shrunken_rect;
+ break;
+ }
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ if (is_vertically_centered_text_alignment(alignment)) {
+ int distance_from_baseline_to_bottom = (font.glyph_height() - 1) - font.baseline();
+ rect.move_by(0, distance_from_baseline_to_bottom / 2);
+ }
+
+ auto point = rect.location();
+ int space_width = font.glyph_width(' ') + font.glyph_spacing();
+
+ for (u32 code_point : final_text) {
+ if (code_point == ' ') {
+ point.move_by(space_width, 0);
+ continue;
+ }
+ IntSize glyph_size(font.glyph_or_emoji_width(code_point) + font.glyph_spacing(), font.glyph_height());
+ draw_glyph({ point, glyph_size }, code_point);
+ point.move_by(glyph_size.width(), 0);
+ }
+}
+
+static inline size_t draw_text_iterator_offset(const Utf8View& text, const Utf8View::Iterator& it)
+{
+ return text.byte_offset_of(it);
+}
+
+static inline size_t draw_text_iterator_offset(const Utf32View& text, const Utf32View::Iterator& it)
+{
+ return it - text.begin();
+}
+
+static inline size_t draw_text_get_length(const Utf8View& text)
+{
+ return text.byte_length();
+}
+
+static inline size_t draw_text_get_length(const Utf32View& text)
+{
+ return text.length();
+}
+
+template<typename TextType, typename DrawGlyphFunction>
+void do_draw_text(const IntRect& rect, const TextType& text, const Font& font, TextAlignment alignment, TextElision elision, DrawGlyphFunction draw_glyph)
+{
+ Vector<TextType, 32> lines;
+
+ size_t start_of_current_line = 0;
+ for (auto it = text.begin(); it != text.end(); ++it) {
+ u32 code_point = *it;
+ if (code_point == '\n') {
+ auto offset = draw_text_iterator_offset(text, it);
+ TextType line = text.substring_view(start_of_current_line, offset - start_of_current_line);
+ lines.append(line);
+ start_of_current_line = offset + 1;
+ }
+ }
+
+ if (start_of_current_line != draw_text_get_length(text)) {
+ TextType line = text.substring_view(start_of_current_line, draw_text_get_length(text) - start_of_current_line);
+ lines.append(line);
+ }
+
+ static const int line_spacing = 4;
+ int line_height = font.glyph_height() + line_spacing;
+ IntRect bounding_rect { 0, 0, 0, (static_cast<int>(lines.size()) * line_height) - line_spacing };
+
+ for (auto& line : lines) {
+ auto line_width = font.width(line);
+ if (line_width > bounding_rect.width())
+ bounding_rect.set_width(line_width);
+ }
+
+ switch (alignment) {
+ case TextAlignment::TopLeft:
+ bounding_rect.set_location(rect.location());
+ break;
+ case TextAlignment::TopRight:
+ bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.y() });
+ break;
+ case TextAlignment::CenterLeft:
+ bounding_rect.set_location({ rect.x(), rect.center().y() - (bounding_rect.height() / 2) });
+ break;
+ case TextAlignment::CenterRight:
+ bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.center().y() - (bounding_rect.height() / 2) });
+ break;
+ case TextAlignment::Center:
+ bounding_rect.center_within(rect);
+ break;
+ case TextAlignment::BottomRight:
+ bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), (rect.bottom() + 1) - bounding_rect.height() });
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ for (size_t i = 0; i < lines.size(); ++i) {
+ auto& line = lines[i];
+ IntRect line_rect { bounding_rect.x(), bounding_rect.y() + static_cast<int>(i) * line_height, bounding_rect.width(), line_height };
+ line_rect.intersect(rect);
+ draw_text_line(line_rect, line, font, alignment, elision, draw_glyph);
+ }
+}
+
+void Painter::draw_text(const IntRect& rect, const StringView& text, TextAlignment alignment, Color color, TextElision elision)
+{
+ draw_text(rect, text, font(), alignment, color, elision);
+}
+
+void Painter::draw_text(const IntRect& rect, const Utf32View& text, TextAlignment alignment, Color color, TextElision elision)
+{
+ draw_text(rect, text, font(), alignment, color, elision);
+}
+
+void Painter::draw_text(const IntRect& rect, const StringView& raw_text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
+{
+ Utf8View text { raw_text };
+ do_draw_text(rect, Utf8View(text), font, alignment, elision, [&](const IntRect& r, u32 code_point) {
+ draw_glyph_or_emoji(r.location(), code_point, font, color);
+ });
+}
+
+void Painter::draw_text(const IntRect& rect, const Utf32View& text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
+{
+ do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
+ draw_glyph_or_emoji(r.location(), code_point, font, color);
+ });
+}
+
+void Painter::draw_text(Function<void(const IntRect&, u32)> draw_one_glyph, const IntRect& rect, const StringView& raw_text, const Font& font, TextAlignment alignment, TextElision elision)
+{
+ Utf8View text { raw_text };
+ do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
+ draw_one_glyph(r, code_point);
+ });
+}
+
+void Painter::draw_text(Function<void(const IntRect&, u32)> draw_one_glyph, const IntRect& rect, const Utf8View& text, const Font& font, TextAlignment alignment, TextElision elision)
+{
+ do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
+ draw_one_glyph(r, code_point);
+ });
+}
+
+void Painter::draw_text(Function<void(const IntRect&, u32)> draw_one_glyph, const IntRect& rect, const Utf32View& text, const Font& font, TextAlignment alignment, TextElision elision)
+{
+ do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
+ draw_one_glyph(r, code_point);
+ });
+}
+
+void Painter::set_pixel(const IntPoint& p, Color color)
+{
+ auto point = p;
+ point.move_by(state().translation);
+ if (!clip_rect().contains(point))
+ return;
+ m_target->scanline(point.y())[point.x()] = color.value();
+}
+
+ALWAYS_INLINE void Painter::set_pixel_with_draw_op(u32& pixel, const Color& color)
+{
+ switch (draw_op()) {
+ case DrawOp::Copy:
+ pixel = color.value();
+ break;
+ case DrawOp::Xor:
+ pixel = color.xored(Color::from_rgba(pixel)).value();
+ break;
+ case DrawOp::Invert:
+ pixel = Color::from_rgba(pixel).inverted().value();
+ break;
+ }
+}
+
+ALWAYS_INLINE void Painter::fill_scanline_with_draw_op(int y, int x, int width, const Color& color)
+{
+ switch (draw_op()) {
+ case DrawOp::Copy:
+ fast_u32_fill(m_target->scanline(y) + x, color.value(), width);
+ break;
+ case DrawOp::Xor: {
+ auto* pixel = m_target->scanline(y) + x;
+ auto* end = pixel + width;
+ while (pixel < end) {
+ *pixel = Color::from_rgba(*pixel).xored(color).value();
+ pixel++;
+ }
+ break;
+ }
+ case DrawOp::Invert: {
+ auto* pixel = m_target->scanline(y) + x;
+ auto* end = pixel + width;
+ while (pixel < end) {
+ *pixel = Color::from_rgba(*pixel).inverted().value();
+ pixel++;
+ }
+ break;
+ }
+ }
+}
+
+void Painter::draw_pixel(const IntPoint& position, Color color, int thickness)
+{
+ if (thickness == 1)
+ return set_pixel_with_draw_op(m_target->scanline(position.y())[position.x()], color);
+ IntRect rect { position.translated(-(thickness / 2), -(thickness / 2)), { thickness, thickness } };
+ fill_rect(rect.translated(-state().translation), color);
+}
+
+void Painter::draw_line(const IntPoint& p1, const IntPoint& p2, Color color, int thickness, LineStyle style)
+{
+ if (color.alpha() == 0)
+ return;
+
+ auto clip_rect = this->clip_rect();
+
+ auto point1 = p1;
+ point1.move_by(state().translation);
+
+ auto point2 = p2;
+ point2.move_by(state().translation);
+
+ // Special case: vertical line.
+ if (point1.x() == point2.x()) {
+ const int x = point1.x();
+ if (x < clip_rect.left() || x > clip_rect.right())
+ return;
+ if (point1.y() > point2.y())
+ swap(point1, point2);
+ if (point1.y() > clip_rect.bottom())
+ return;
+ if (point2.y() < clip_rect.top())
+ return;
+ int min_y = max(point1.y(), clip_rect.top());
+ int max_y = min(point2.y(), clip_rect.bottom());
+ if (style == LineStyle::Dotted) {
+ for (int y = min_y; y <= max_y; y += thickness * 2)
+ draw_pixel({ x, y }, color, thickness);
+ } else if (style == LineStyle::Dashed) {
+ for (int y = min_y; y <= max_y; y += thickness * 6) {
+ draw_pixel({ x, y }, color, thickness);
+ draw_pixel({ x, min(y + thickness, max_y) }, color, thickness);
+ draw_pixel({ x, min(y + thickness * 2, max_y) }, color, thickness);
+ }
+ } else {
+ for (int y = min_y; y <= max_y; ++y)
+ draw_pixel({ x, y }, color, thickness);
+ }
+ return;
+ }
+
+ // Special case: horizontal line.
+ if (point1.y() == point2.y()) {
+ const int y = point1.y();
+ if (y < clip_rect.top() || y > clip_rect.bottom())
+ return;
+ if (point1.x() > point2.x())
+ swap(point1, point2);
+ if (point1.x() > clip_rect.right())
+ return;
+ if (point2.x() < clip_rect.left())
+ return;
+ int min_x = max(point1.x(), clip_rect.left());
+ int max_x = min(point2.x(), clip_rect.right());
+ if (style == LineStyle::Dotted) {
+ for (int x = min_x; x <= max_x; x += thickness * 2)
+ draw_pixel({ x, y }, color, thickness);
+ } else if (style == LineStyle::Dashed) {
+ for (int x = min_x; x <= max_x; x += thickness * 6) {
+ draw_pixel({ x, y }, color, thickness);
+ draw_pixel({ min(x + thickness, max_x), y }, color, thickness);
+ draw_pixel({ min(x + thickness * 2, max_x), y }, color, thickness);
+ }
+ } else {
+ for (int x = min_x; x <= max_x; ++x)
+ draw_pixel({ x, y }, color, thickness);
+ }
+ return;
+ }
+
+ // FIXME: Implement dotted/dashed diagonal lines.
+ ASSERT(style == LineStyle::Solid);
+
+ const double adx = abs(point2.x() - point1.x());
+ const double ady = abs(point2.y() - point1.y());
+
+ if (adx > ady) {
+ if (point1.x() > point2.x())
+ swap(point1, point2);
+ } else {
+ if (point1.y() > point2.y())
+ swap(point1, point2);
+ }
+
+ // FIXME: Implement clipping below.
+ const double dx = point2.x() - point1.x();
+ const double dy = point2.y() - point1.y();
+ double error = 0;
+
+ if (dx > dy) {
+ const double y_step = dy == 0 ? 0 : (dy > 0 ? 1 : -1);
+ const double delta_error = fabs(dy / dx);
+ int y = point1.y();
+ for (int x = point1.x(); x <= point2.x(); ++x) {
+ if (clip_rect.contains(x, y))
+ draw_pixel({ x, y }, color, thickness);
+ error += delta_error;
+ if (error >= 0.5) {
+ y = (double)y + y_step;
+ error -= 1.0;
+ }
+ }
+ } else {
+ const double x_step = dx == 0 ? 0 : (dx > 0 ? 1 : -1);
+ const double delta_error = fabs(dx / dy);
+ int x = point1.x();
+ for (int y = point1.y(); y <= point2.y(); ++y) {
+ if (clip_rect.contains(x, y))
+ draw_pixel({ x, y }, color, thickness);
+ error += delta_error;
+ if (error >= 0.5) {
+ x = (double)x + x_step;
+ error -= 1.0;
+ }
+ }
+ }
+}
+
+static void split_quadratic_bezier_curve(const FloatPoint& original_control, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>& callback)
+{
+ auto po1_midpoint = original_control + p1;
+ po1_midpoint /= 2;
+
+ auto po2_midpoint = original_control + p2;
+ po2_midpoint /= 2;
+
+ auto new_segment = po1_midpoint + po2_midpoint;
+ new_segment /= 2;
+
+ Painter::for_each_line_segment_on_bezier_curve(po1_midpoint, p1, new_segment, callback);
+ Painter::for_each_line_segment_on_bezier_curve(po2_midpoint, new_segment, p2, callback);
+}
+
+static bool can_approximate_bezier_curve(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& control)
+{
+ constexpr static int tolerance = 15;
+
+ auto p1x = 3 * control.x() - 2 * p1.x() - p2.x();
+ auto p1y = 3 * control.y() - 2 * p1.y() - p2.y();
+ auto p2x = 3 * control.x() - 2 * p2.x() - p1.x();
+ auto p2y = 3 * control.y() - 2 * p2.y() - p1.y();
+
+ p1x = p1x * p1x;
+ p1y = p1y * p1y;
+ p2x = p2x * p2x;
+ p2y = p2y * p2y;
+
+ return max(p1x, p2x) + max(p1y, p2y) <= tolerance;
+}
+
+void Painter::for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>& callback)
+{
+ if (can_approximate_bezier_curve(p1, p2, control_point)) {
+ callback(p1, p2);
+ } else {
+ split_quadratic_bezier_curve(control_point, p1, p2, callback);
+ }
+}
+
+void Painter::for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>&& callback)
+{
+ for_each_line_segment_on_bezier_curve(control_point, p1, p2, callback);
+}
+
+static void split_elliptical_arc(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(const FloatPoint&, const FloatPoint&)>& callback)
+{
+ auto half_theta_delta = theta_delta / 2;
+ auto theta_mid = theta_1 + half_theta_delta;
+
+ auto xc = cosf(x_axis_rotation);
+ auto xs = sinf(x_axis_rotation);
+ auto tc = cosf(theta_1 + half_theta_delta);
+ auto ts = sinf(theta_1 + half_theta_delta);
+
+ auto x2 = xc * radii.x() * tc - xs * radii.y() * ts + center.x();
+ auto y2 = xs * radii.x() * tc + xc * radii.y() * ts + center.y();
+
+ FloatPoint mid_point = { x2, y2 };
+
+ Painter::for_each_line_segment_on_elliptical_arc(p1, mid_point, center, radii, x_axis_rotation, theta_1, half_theta_delta, callback);
+ Painter::for_each_line_segment_on_elliptical_arc(mid_point, p2, center, radii, x_axis_rotation, theta_mid, half_theta_delta, callback);
+}
+
+static bool can_approximate_elliptical_arc(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta)
+{
+ constexpr static float tolerance = 1;
+
+ auto half_theta_delta = theta_delta / 2.0f;
+
+ auto xc = cosf(x_axis_rotation);
+ auto xs = sinf(x_axis_rotation);
+ auto tc = cosf(theta_1 + half_theta_delta);
+ auto ts = sinf(theta_1 + half_theta_delta);
+
+ auto x2 = xc * radii.x() * tc - xs * radii.y() * ts + center.x();
+ auto y2 = xs * radii.x() * tc + xc * radii.y() * ts + center.y();
+
+ auto ellipse_mid_point = FloatPoint { x2, y2 };
+ auto line_mid_point = p1 + (p2 - p1) / 2.0f;
+
+ return ellipse_mid_point.distance_from(line_mid_point) < tolerance;
+}
+
+void Painter::draw_quadratic_bezier_curve(const IntPoint& control_point, const IntPoint& p1, const IntPoint& p2, Color color, int thickness, LineStyle style)
+{
+ for_each_line_segment_on_bezier_curve(FloatPoint(control_point), FloatPoint(p1), FloatPoint(p2), [&](const FloatPoint& fp1, const FloatPoint& fp2) {
+ draw_line(IntPoint(fp1.x(), fp1.y()), IntPoint(fp2.x(), fp2.y()), color, thickness, style);
+ });
+}
+
+void Painter::for_each_line_segment_on_elliptical_arc(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(const FloatPoint&, const FloatPoint&)>& callback)
+{
+ if (can_approximate_elliptical_arc(p1, p2, center, radii, x_axis_rotation, theta_1, theta_delta)) {
+ callback(p1, p2);
+ } else {
+ split_elliptical_arc(p1, p2, center, radii, x_axis_rotation, theta_1, theta_delta, callback);
+ }
+}
+
+void Painter::for_each_line_segment_on_elliptical_arc(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(const FloatPoint&, const FloatPoint&)>&& callback)
+{
+ for_each_line_segment_on_elliptical_arc(p1, p2, center, radii, x_axis_rotation, theta_1, theta_delta, callback);
+}
+
+void Painter::draw_elliptical_arc(const IntPoint& p1, const IntPoint& p2, const IntPoint& center, const FloatPoint& radii, float x_axis_rotation, float theta_1, float theta_delta, Color color, int thickness, LineStyle style)
+{
+ for_each_line_segment_on_elliptical_arc(FloatPoint(p1), FloatPoint(p2), FloatPoint(center), radii, x_axis_rotation, theta_1, theta_delta, [&](const FloatPoint& fp1, const FloatPoint& fp2) {
+ draw_line(IntPoint(fp1.x(), fp1.y()), IntPoint(fp2.x(), fp2.y()), color, thickness, style);
+ });
+}
+
+void Painter::add_clip_rect(const IntRect& rect)
+{
+ state().clip_rect.intersect(rect.translated(translation()));
+ state().clip_rect.intersect(m_target->rect());
+}
+
+void Painter::clear_clip_rect()
+{
+ state().clip_rect = m_clip_origin;
+}
+
+PainterStateSaver::PainterStateSaver(Painter& painter)
+ : m_painter(painter)
+{
+ m_painter.save();
+}
+
+PainterStateSaver::~PainterStateSaver()
+{
+ m_painter.restore();
+}
+
+void Painter::stroke_path(const Path& path, Color color, int thickness)
+{
+ FloatPoint cursor;
+
+ for (auto& segment : path.segments()) {
+ switch (segment.type()) {
+ case Segment::Type::Invalid:
+ ASSERT_NOT_REACHED();
+ break;
+ case Segment::Type::MoveTo:
+ cursor = segment.point();
+ break;
+ case Segment::Type::LineTo:
+ draw_line(cursor.to_type<int>(), segment.point().to_type<int>(), color, thickness);
+ cursor = segment.point();
+ break;
+ case Segment::Type::QuadraticBezierCurveTo: {
+ auto& through = static_cast<const QuadraticBezierCurveSegment&>(segment).through();
+ draw_quadratic_bezier_curve(through.to_type<int>(), cursor.to_type<int>(), segment.point().to_type<int>(), color, thickness);
+ cursor = segment.point();
+ break;
+ }
+ case Segment::Type::EllipticalArcTo:
+ auto& arc = static_cast<const EllipticalArcSegment&>(segment);
+ draw_elliptical_arc(cursor.to_type<int>(), segment.point().to_type<int>(), arc.center().to_type<int>(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), color, thickness);
+ cursor = segment.point();
+ break;
+ }
+ }
+}
+
+//#define FILL_PATH_DEBUG
+
+[[maybe_unused]] static void approximately_place_on_int_grid(FloatPoint ffrom, FloatPoint fto, IntPoint& from, IntPoint& to, Optional<IntPoint> previous_to)
+{
+ auto diffs = fto - ffrom;
+ // Truncate all first (round down).
+ from = ffrom.to_type<int>();
+ to = fto.to_type<int>();
+ // There are 16 possible configurations, by deciding to round each
+ // coord up or down (and there are four coords, from.x from.y to.x to.y)
+ // we will simply choose one which most closely matches the correct slope
+ // with the following heuristic:
+ // - if the x diff is positive or zero (that is, a right-to-left slant), round 'from.x' up and 'to.x' down.
+ // - if the x diff is negative (that is, a left-to-right slant), round 'from.x' down and 'to.x' up.
+ // Note that we do not need to touch the 'y' attribute, as that is our scanline.
+ if (diffs.x() >= 0) {
+ from.set_x(from.x() + 1);
+ } else {
+ to.set_x(to.x() + 1);
+ }
+ if (previous_to.has_value() && from.x() != previous_to.value().x()) // The points have to line up, since we're using these lines to fill a shape.
+ from.set_x(previous_to.value().x());
+}
+
+void Painter::fill_path(Path& path, Color color, WindingRule winding_rule)
+{
+ const auto& segments = path.split_lines();
+
+ if (segments.size() == 0)
+ return;
+
+ Vector<Path::SplitLineSegment> active_list;
+ active_list.ensure_capacity(segments.size());
+
+ // first, grab the segments for the very first scanline
+ int first_y = path.bounding_box().bottom_right().y() + 1;
+ int last_y = path.bounding_box().top_left().y() - 1;
+ float scanline = first_y;
+
+ size_t last_active_segment { 0 };
+
+ for (auto& segment : segments) {
+ if (segment.maximum_y != scanline)
+ break;
+ active_list.append(segment);
+ ++last_active_segment;
+ }
+
+ auto is_inside_shape = [winding_rule](int winding_number) {
+ if (winding_rule == WindingRule::Nonzero)
+ return winding_number != 0;
+
+ if (winding_rule == WindingRule::EvenOdd)
+ return winding_number % 2 == 0;
+
+ ASSERT_NOT_REACHED();
+ };
+
+ auto increment_winding = [winding_rule](int& winding_number, const IntPoint& from, const IntPoint& to) {
+ if (winding_rule == WindingRule::EvenOdd) {
+ ++winding_number;
+ return;
+ }
+
+ if (winding_rule == WindingRule::Nonzero) {
+ if (from.dy_relative_to(to) < 0)
+ ++winding_number;
+ else
+ --winding_number;
+ return;
+ }
+
+ ASSERT_NOT_REACHED();
+ };
+
+ while (scanline >= last_y) {
+ Optional<IntPoint> previous_to;
+ if (active_list.size()) {
+ // sort the active list by 'x' from right to left
+ quick_sort(active_list, [](const auto& line0, const auto& line1) {
+ return line1.x < line0.x;
+ });
+#ifdef FILL_PATH_DEBUG
+ if ((int)scanline % 10 == 0) {
+ draw_text(IntRect(active_list.last().x - 20, scanline, 20, 10), String::number((int)scanline));
+ }
+#endif
+
+ if (active_list.size() > 1) {
+ auto winding_number { 0 };
+ for (size_t i = 1; i < active_list.size(); ++i) {
+ auto& previous = active_list[i - 1];
+ auto& current = active_list[i];
+
+ IntPoint from, to;
+ IntPoint truncated_from { previous.x, scanline };
+ IntPoint truncated_to { current.x, scanline };
+ approximately_place_on_int_grid({ previous.x, scanline }, { current.x, scanline }, from, to, previous_to);
+
+ if (is_inside_shape(winding_number)) {
+ // The points between this segment and the previous are
+ // inside the shape
+#ifdef FILL_PATH_DEBUG
+ dbg() << "y=" << scanline << ": " << winding_number << " at " << i << ": " << from << " -- " << to;
+#endif
+ draw_line(from, to, color, 1);
+ }
+
+ auto is_passing_through_maxima = scanline == previous.maximum_y
+ || scanline == previous.minimum_y
+ || scanline == current.maximum_y
+ || scanline == current.minimum_y;
+
+ auto is_passing_through_vertex = false;
+
+ if (is_passing_through_maxima) {
+ is_passing_through_vertex = previous.x == current.x;
+ }
+
+ if (!is_passing_through_vertex || previous.inverse_slope * current.inverse_slope < 0)
+ increment_winding(winding_number, truncated_from, truncated_to);
+
+ // update the x coord
+ active_list[i - 1].x -= active_list[i - 1].inverse_slope;
+ }
+ active_list.last().x -= active_list.last().inverse_slope;
+ } else {
+ auto point = IntPoint(active_list[0].x, scanline);
+ draw_line(point, point, color);
+
+ // update the x coord
+ active_list.first().x -= active_list.first().inverse_slope;
+ }
+ }
+
+ --scanline;
+ // remove any edge that goes out of bound from the active list
+ for (size_t i = 0, count = active_list.size(); i < count; ++i) {
+ if (scanline <= active_list[i].minimum_y) {
+ active_list.remove(i);
+ --count;
+ --i;
+ }
+ }
+ for (size_t j = last_active_segment; j < segments.size(); ++j, ++last_active_segment) {
+ auto& segment = segments[j];
+ if (segment.maximum_y < scanline)
+ break;
+ if (segment.minimum_y >= scanline)
+ continue;
+
+ active_list.append(segment);
+ }
+ }
+
+#ifdef FILL_PATH_DEBUG
+ size_t i { 0 };
+ for (auto& segment : segments) {
+ draw_line(Point<int>(segment.from), Point<int>(segment.to), Color::from_hsv(i++ * 360.0 / segments.size(), 1.0, 1.0), 1);
+ }
+#endif
+}
+
+void Painter::blit_disabled(const IntPoint& location, const Gfx::Bitmap& bitmap, const IntRect& rect, const Palette& palette)
+{
+ auto bright_color = palette.threed_highlight();
+ auto dark_color = palette.threed_shadow1();
+ blit_filtered(location.translated(1, 1), bitmap, rect, [&](auto) {
+ return bright_color;
+ });
+ blit_filtered(location, bitmap, rect, [&](Color src) {
+ int gray = src.to_grayscale().red();
+ if (gray > 160)
+ return bright_color;
+ return dark_color;
+ });
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Painter.h b/Userland/Libraries/LibGfx/Painter.h
new file mode 100644
index 0000000000..8ae33fc207
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Painter.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/NonnullRefPtr.h>
+#include <AK/Vector.h>
+#include <LibGfx/Color.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/Point.h>
+#include <LibGfx/Rect.h>
+#include <LibGfx/Size.h>
+#include <LibGfx/TextAlignment.h>
+#include <LibGfx/TextElision.h>
+
+namespace Gfx {
+
+class Painter {
+public:
+ explicit Painter(Gfx::Bitmap&);
+ ~Painter();
+
+ enum class LineStyle {
+ Solid,
+ Dotted,
+ Dashed,
+ };
+
+ void clear_rect(const IntRect&, Color);
+ void fill_rect(const IntRect&, Color);
+ void fill_rect_with_dither_pattern(const IntRect&, Color, Color);
+ void fill_rect_with_checkerboard(const IntRect&, const IntSize&, Color color_dark, Color color_light);
+ void fill_rect_with_gradient(Orientation, const IntRect&, Color gradient_start, Color gradient_end);
+ void fill_rect_with_gradient(const IntRect&, Color gradient_start, Color gradient_end);
+ void fill_ellipse(const IntRect&, Color);
+ void draw_rect(const IntRect&, Color, bool rough = false);
+ void draw_focus_rect(const IntRect&, Color);
+ void draw_bitmap(const IntPoint&, const CharacterBitmap&, Color = Color());
+ void draw_bitmap(const IntPoint&, const GlyphBitmap&, Color = Color());
+ void draw_scaled_bitmap(const IntRect& dst_rect, const Gfx::Bitmap&, const IntRect& src_rect, float opacity = 1.0f);
+ void draw_triangle(const IntPoint&, const IntPoint&, const IntPoint&, Color);
+ void draw_ellipse_intersecting(const IntRect&, Color, int thickness = 1);
+ void set_pixel(const IntPoint&, Color);
+ void set_pixel(int x, int y, Color color) { set_pixel({ x, y }, color); }
+ void draw_line(const IntPoint&, const IntPoint&, Color, int thickness = 1, LineStyle style = LineStyle::Solid);
+ void draw_quadratic_bezier_curve(const IntPoint& control_point, const IntPoint&, const IntPoint&, Color, int thickness = 1, LineStyle style = LineStyle::Solid);
+ void draw_elliptical_arc(const IntPoint& p1, const IntPoint& p2, const IntPoint& center, const FloatPoint& radii, float x_axis_rotation, float theta_1, float theta_delta, Color, int thickness = 1, LineStyle style = LineStyle::Solid);
+ void blit(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, float opacity = 1.0f);
+ void blit_dimmed(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect);
+ void blit_brightened(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect);
+ void blit_filtered(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, Function<Color(Color)>);
+ void draw_tiled_bitmap(const IntRect& dst_rect, const Gfx::Bitmap&);
+ void blit_offset(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, const IntPoint&);
+ void blit_scaled(const IntRect&, const Gfx::Bitmap&, const IntRect&, float, float);
+ void blit_disabled(const IntPoint&, const Gfx::Bitmap&, const IntRect&, const Palette&);
+ void draw_text(const IntRect&, const StringView&, const Font&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
+ void draw_text(const IntRect&, const StringView&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
+ void draw_text(const IntRect&, const Utf32View&, const Font&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
+ void draw_text(const IntRect&, const Utf32View&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
+ void draw_text(Function<void(const IntRect&, u32)>, const IntRect&, const StringView&, const Font&, TextAlignment = TextAlignment::TopLeft, TextElision = TextElision::None);
+ void draw_text(Function<void(const IntRect&, u32)>, const IntRect&, const Utf8View&, const Font&, TextAlignment = TextAlignment::TopLeft, TextElision = TextElision::None);
+ void draw_text(Function<void(const IntRect&, u32)>, const IntRect&, const Utf32View&, const Font&, TextAlignment = TextAlignment::TopLeft, TextElision = TextElision::None);
+ void draw_glyph(const IntPoint&, u32, Color);
+ void draw_glyph(const IntPoint&, u32, const Font&, Color);
+ void draw_emoji(const IntPoint&, const Gfx::Bitmap&, const Font&);
+ void draw_glyph_or_emoji(const IntPoint&, u32 code_point, const Font&, Color);
+
+ static void for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>&);
+ static void for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>&&);
+
+ static void for_each_line_segment_on_elliptical_arc(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(const FloatPoint&, const FloatPoint&)>&);
+ static void for_each_line_segment_on_elliptical_arc(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(const FloatPoint&, const FloatPoint&)>&&);
+
+ void stroke_path(const Path&, Color, int thickness);
+
+ enum class WindingRule {
+ Nonzero,
+ EvenOdd,
+ };
+ void fill_path(Path&, Color, WindingRule rule = WindingRule::Nonzero);
+
+ const Font& font() const { return *state().font; }
+ void set_font(const Font& font) { state().font = &font; }
+
+ enum class DrawOp {
+ Copy,
+ Xor,
+ Invert
+ };
+ void set_draw_op(DrawOp op) { state().draw_op = op; }
+ DrawOp draw_op() const { return state().draw_op; }
+
+ void add_clip_rect(const IntRect& rect);
+ void clear_clip_rect();
+ IntRect clip_rect() const { return state().clip_rect; }
+
+ void translate(int dx, int dy) { state().translation.move_by(dx, dy); }
+ void translate(const IntPoint& delta) { state().translation.move_by(delta); }
+
+ IntPoint translation() const { return state().translation; }
+
+ Gfx::Bitmap* target() { return m_target.ptr(); }
+
+ void save() { m_state_stack.append(m_state_stack.last()); }
+ void restore()
+ {
+ ASSERT(m_state_stack.size() > 1);
+ m_state_stack.take_last();
+ }
+
+protected:
+ void set_pixel_with_draw_op(u32& pixel, const Color&);
+ void fill_scanline_with_draw_op(int y, int x, int width, const Color& color);
+ void fill_rect_with_draw_op(const IntRect&, Color);
+ void blit_with_alpha(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect);
+ void blit_with_opacity(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, float opacity);
+ void draw_pixel(const IntPoint&, Color, int thickness = 1);
+
+ struct State {
+ const Font* font;
+ IntPoint translation;
+ IntRect clip_rect;
+ DrawOp draw_op;
+ };
+
+ State& state() { return m_state_stack.last(); }
+ const State& state() const { return m_state_stack.last(); }
+
+ IntRect m_clip_origin;
+ NonnullRefPtr<Gfx::Bitmap> m_target;
+ Vector<State, 4> m_state_stack;
+};
+
+class PainterStateSaver {
+public:
+ explicit PainterStateSaver(Painter&);
+ ~PainterStateSaver();
+
+private:
+ Painter& m_painter;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Palette.cpp b/Userland/Libraries/LibGfx/Palette.cpp
new file mode 100644
index 0000000000..de6915d7ba
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Palette.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Badge.h>
+#include <AK/SharedBuffer.h>
+#include <LibGfx/Palette.h>
+#include <string.h>
+
+namespace Gfx {
+
+NonnullRefPtr<PaletteImpl> PaletteImpl::create_with_shared_buffer(SharedBuffer& buffer)
+{
+ return adopt(*new PaletteImpl(buffer));
+}
+
+PaletteImpl::PaletteImpl(SharedBuffer& buffer)
+ : m_theme_buffer(buffer)
+{
+}
+
+Palette::Palette(const PaletteImpl& impl)
+ : m_impl(impl)
+{
+}
+
+Palette::~Palette()
+{
+}
+
+const SystemTheme& PaletteImpl::theme() const
+{
+ return *m_theme_buffer->data<SystemTheme>();
+}
+
+Color PaletteImpl::color(ColorRole role) const
+{
+ ASSERT((int)role < (int)ColorRole::__Count);
+ return Color::from_rgba(theme().color[(int)role]);
+}
+
+int PaletteImpl::metric(MetricRole role) const
+{
+ ASSERT((int)role < (int)MetricRole::__Count);
+ return theme().metric[(int)role];
+}
+
+String PaletteImpl::path(PathRole role) const
+{
+ ASSERT((int)role < (int)PathRole::__Count);
+ return theme().path[(int)role];
+}
+
+NonnullRefPtr<PaletteImpl> PaletteImpl::clone() const
+{
+ auto new_theme_buffer = SharedBuffer::create_with_size(m_theme_buffer->size());
+ memcpy(new_theme_buffer->data<SystemTheme>(), &theme(), m_theme_buffer->size());
+ return adopt(*new PaletteImpl(*new_theme_buffer));
+}
+
+void Palette::set_color(ColorRole role, Color color)
+{
+ if (m_impl->ref_count() != 1)
+ m_impl = m_impl->clone();
+ auto& theme = const_cast<SystemTheme&>(impl().theme());
+ theme.color[(int)role] = color.value();
+}
+
+void Palette::set_metric(MetricRole role, int value)
+{
+ if (m_impl->ref_count() != 1)
+ m_impl = m_impl->clone();
+ auto& theme = const_cast<SystemTheme&>(impl().theme());
+ theme.metric[(int)role] = value;
+}
+
+void Palette::set_path(PathRole role, String path)
+{
+ if (m_impl->ref_count() != 1)
+ m_impl = m_impl->clone();
+ auto& theme = const_cast<SystemTheme&>(impl().theme());
+ memcpy(theme.path[(int)role], path.characters(), min(path.length() + 1, sizeof(theme.path[(int)role])));
+ theme.path[(int)role][sizeof(theme.path[(int)role]) - 1] = '\0';
+}
+
+PaletteImpl::~PaletteImpl()
+{
+}
+
+void PaletteImpl::replace_internal_buffer(Badge<GUI::Application>, SharedBuffer& buffer)
+{
+ m_theme_buffer = buffer;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Palette.h b/Userland/Libraries/LibGfx/Palette.h
new file mode 100644
index 0000000000..dfd47e90ff
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Palette.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/Noncopyable.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <LibGUI/Forward.h>
+#include <LibGfx/SystemTheme.h>
+
+namespace Gfx {
+
+class PaletteImpl : public RefCounted<PaletteImpl> {
+ AK_MAKE_NONCOPYABLE(PaletteImpl);
+ AK_MAKE_NONMOVABLE(PaletteImpl);
+
+public:
+ ~PaletteImpl();
+ static NonnullRefPtr<PaletteImpl> create_with_shared_buffer(SharedBuffer&);
+ NonnullRefPtr<PaletteImpl> clone() const;
+
+ Color color(ColorRole) const;
+ int metric(MetricRole) const;
+ String path(PathRole) const;
+ const SystemTheme& theme() const;
+
+ void replace_internal_buffer(Badge<GUI::Application>, SharedBuffer& buffer);
+
+private:
+ explicit PaletteImpl(SharedBuffer&);
+
+ RefPtr<SharedBuffer> m_theme_buffer;
+};
+
+class Palette {
+
+public:
+ explicit Palette(const PaletteImpl&);
+ ~Palette();
+
+ Color window() const { return color(ColorRole::Window); }
+ Color window_text() const { return color(ColorRole::WindowText); }
+ Color selection() const { return color(ColorRole::Selection); }
+ Color selection_text() const { return color(ColorRole::SelectionText); }
+ Color inactive_selection() const { return color(ColorRole::InactiveSelection); }
+ Color inactive_selection_text() const { return color(ColorRole::InactiveSelectionText); }
+ Color desktop_background() const { return color(ColorRole::DesktopBackground); }
+ Color active_window_border1() const { return color(ColorRole::ActiveWindowBorder1); }
+ Color active_window_border2() const { return color(ColorRole::ActiveWindowBorder2); }
+ Color active_window_title() const { return color(ColorRole::ActiveWindowTitle); }
+ Color active_window_title_stripes() const { return color(ColorRole::ActiveWindowTitleStripes); }
+ Color active_window_title_shadow() const { return color(ColorRole::ActiveWindowTitleShadow); }
+ Color inactive_window_border1() const { return color(ColorRole::InactiveWindowBorder1); }
+ Color inactive_window_border2() const { return color(ColorRole::InactiveWindowBorder2); }
+ Color inactive_window_title() const { return color(ColorRole::InactiveWindowTitle); }
+ Color inactive_window_title_stripes() const { return color(ColorRole::InactiveWindowTitleStripes); }
+ Color inactive_window_title_shadow() const { return color(ColorRole::InactiveWindowTitleShadow); }
+ Color moving_window_border1() const { return color(ColorRole::MovingWindowBorder1); }
+ Color moving_window_border2() const { return color(ColorRole::MovingWindowBorder2); }
+ Color moving_window_title() const { return color(ColorRole::MovingWindowTitle); }
+ Color moving_window_title_stripes() const { return color(ColorRole::MovingWindowTitleStripes); }
+ Color moving_window_title_shadow() const { return color(ColorRole::MovingWindowTitleShadow); }
+ Color highlight_window_border1() const { return color(ColorRole::HighlightWindowBorder1); }
+ Color highlight_window_border2() const { return color(ColorRole::HighlightWindowBorder2); }
+ Color highlight_window_title() const { return color(ColorRole::HighlightWindowTitle); }
+ Color highlight_window_title_stripes() const { return color(ColorRole::HighlightWindowTitleStripes); }
+ Color highlight_window_title_shadow() const { return color(ColorRole::HighlightWindowTitleShadow); }
+ Color highlight_searching() const { return color(ColorRole::HighlightSearching); }
+ Color highlight_searching_text() const { return color(ColorRole::HighlightSearchingText); }
+ Color menu_stripe() const { return color(ColorRole::MenuStripe); }
+ Color menu_base() const { return color(ColorRole::MenuBase); }
+ Color menu_base_text() const { return color(ColorRole::MenuBaseText); }
+ Color menu_selection() const { return color(ColorRole::MenuSelection); }
+ Color menu_selection_text() const { return color(ColorRole::MenuSelectionText); }
+ Color base() const { return color(ColorRole::Base); }
+ Color base_text() const { return color(ColorRole::BaseText); }
+ Color button() const { return color(ColorRole::Button); }
+ Color button_text() const { return color(ColorRole::ButtonText); }
+ Color threed_highlight() const { return color(ColorRole::ThreedHighlight); }
+ Color threed_shadow1() const { return color(ColorRole::ThreedShadow1); }
+ Color threed_shadow2() const { return color(ColorRole::ThreedShadow2); }
+ Color hover_highlight() const { return color(ColorRole::HoverHighlight); }
+ Color rubber_band_fill() const { return color(ColorRole::RubberBandFill); }
+ Color rubber_band_border() const { return color(ColorRole::RubberBandBorder); }
+ Color ruler() const { return color(ColorRole::Ruler); }
+ Color ruler_border() const { return color(ColorRole::RulerBorder); }
+ Color ruler_active_text() const { return color(ColorRole::RulerActiveText); }
+ Color ruler_inactive_text() const { return color(ColorRole::RulerInactiveText); }
+ Color text_cursor() const { return color(ColorRole::TextCursor); }
+ Color focus_outline() const { return color(ColorRole::FocusOutline); }
+
+ Color link() const { return color(ColorRole::Link); }
+ Color active_link() const { return color(ColorRole::ActiveLink); }
+ Color visited_link() const { return color(ColorRole::VisitedLink); }
+
+ Color syntax_comment() const { return color(ColorRole::SyntaxComment); }
+ Color syntax_number() const { return color(ColorRole::SyntaxNumber); }
+ Color syntax_string() const { return color(ColorRole::SyntaxString); }
+ Color syntax_identifier() const { return color(ColorRole::SyntaxIdentifier); }
+ Color syntax_type() const { return color(ColorRole::SyntaxType); }
+ Color syntax_punctuation() const { return color(ColorRole::SyntaxPunctuation); }
+ Color syntax_operator() const { return color(ColorRole::SyntaxOperator); }
+ Color syntax_keyword() const { return color(ColorRole::SyntaxKeyword); }
+ Color syntax_control_keyword() const { return color(ColorRole::SyntaxControlKeyword); }
+ Color syntax_preprocessor_statement() const { return color(ColorRole::SyntaxPreprocessorStatement); }
+ Color syntax_preprocessor_value() const { return color(ColorRole::SyntaxPreprocessorValue); }
+
+ int window_title_height() const { return metric(MetricRole::TitleHeight); }
+ int window_title_button_width() const { return metric(MetricRole::TitleButtonWidth); }
+ int window_title_button_height() const { return metric(MetricRole::TitleButtonHeight); }
+
+ String title_button_icons_path() const { return path(PathRole::TitleButtonIcons); }
+
+ Color color(ColorRole role) const { return m_impl->color(role); }
+ int metric(MetricRole role) const { return m_impl->metric(role); }
+ String path(PathRole role) const { return m_impl->path(role); }
+
+ void set_color(ColorRole, Color);
+ void set_metric(MetricRole, int);
+ void set_path(PathRole, String);
+
+ const SystemTheme& theme() const { return m_impl->theme(); }
+
+ PaletteImpl& impl() { return *m_impl; }
+ const PaletteImpl& impl() const { return *m_impl; }
+
+private:
+ NonnullRefPtr<PaletteImpl> m_impl;
+};
+
+}
+
+using Gfx::Palette;
diff --git a/Userland/Libraries/LibGfx/Path.cpp b/Userland/Libraries/LibGfx/Path.cpp
new file mode 100644
index 0000000000..5fe2bb2f63
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Path.cpp
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <AK/HashTable.h>
+#include <AK/QuickSort.h>
+#include <AK/StringBuilder.h>
+#include <LibGfx/Painter.h>
+#include <LibGfx/Path.h>
+
+namespace Gfx {
+
+void Path::close()
+{
+ if (m_segments.size() <= 1)
+ return;
+
+ invalidate_split_lines();
+
+ auto& last_point = m_segments.last().point();
+
+ for (ssize_t i = m_segments.size() - 1; i >= 0; --i) {
+ auto& segment = m_segments[i];
+ if (segment.type() == Segment::Type::MoveTo) {
+ if (last_point == segment.point())
+ return;
+ append_segment<LineSegment>(segment.point());
+ return;
+ }
+ }
+}
+
+void Path::close_all_subpaths()
+{
+ if (m_segments.size() <= 1)
+ return;
+
+ invalidate_split_lines();
+
+ Optional<FloatPoint> cursor, start_of_subpath;
+ bool is_first_point_in_subpath { false };
+
+ for (auto& segment : m_segments) {
+ switch (segment.type()) {
+ case Segment::Type::MoveTo: {
+ if (cursor.has_value() && !is_first_point_in_subpath) {
+ // This is a move from a subpath to another
+ // connect the two ends of this subpath before
+ // moving on to the next one
+ ASSERT(start_of_subpath.has_value());
+
+ append_segment<MoveSegment>(cursor.value());
+ append_segment<LineSegment>(start_of_subpath.value());
+ }
+ is_first_point_in_subpath = true;
+ cursor = segment.point();
+ break;
+ }
+ case Segment::Type::LineTo:
+ case Segment::Type::QuadraticBezierCurveTo:
+ case Segment::Type::EllipticalArcTo:
+ if (is_first_point_in_subpath) {
+ start_of_subpath = cursor;
+ is_first_point_in_subpath = false;
+ }
+ cursor = segment.point();
+ break;
+ case Segment::Type::Invalid:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ }
+}
+
+String Path::to_string() const
+{
+ StringBuilder builder;
+ builder.append("Path { ");
+ for (auto& segment : m_segments) {
+ switch (segment.type()) {
+ case Segment::Type::MoveTo:
+ builder.append("MoveTo");
+ break;
+ case Segment::Type::LineTo:
+ builder.append("LineTo");
+ break;
+ case Segment::Type::QuadraticBezierCurveTo:
+ builder.append("QuadraticBezierCurveTo");
+ break;
+ case Segment::Type::EllipticalArcTo:
+ builder.append("EllipticalArcTo");
+ break;
+ case Segment::Type::Invalid:
+ builder.append("Invalid");
+ break;
+ }
+ builder.appendf("(%s", segment.point().to_string().characters());
+
+ switch (segment.type()) {
+ case Segment::Type::QuadraticBezierCurveTo:
+ builder.append(", ");
+ builder.append(static_cast<const QuadraticBezierCurveSegment&>(segment).through().to_string());
+ break;
+ case Segment::Type::EllipticalArcTo: {
+ auto& arc = static_cast<const EllipticalArcSegment&>(segment);
+ builder.appendf(", %s, %s, %f, %f, %f",
+ arc.radii().to_string().characters(),
+ arc.center().to_string().characters(),
+ arc.x_axis_rotation(),
+ arc.theta_1(),
+ arc.theta_delta());
+ break;
+ }
+ default:
+ break;
+ }
+
+ builder.append(") ");
+ }
+ builder.append("}");
+ return builder.to_string();
+}
+
+void Path::segmentize_path()
+{
+ Vector<SplitLineSegment> segments;
+ float min_x = 0;
+ float min_y = 0;
+ float max_x = 0;
+ float max_y = 0;
+
+ auto add_point_to_bbox = [&](const Gfx::FloatPoint& point) {
+ float x = point.x();
+ float y = point.y();
+ min_x = min(min_x, x);
+ min_y = min(min_y, y);
+ max_x = max(max_x, x);
+ max_y = max(max_y, y);
+ };
+
+ auto add_line = [&](const auto& p0, const auto& p1) {
+ float ymax = p0.y(), ymin = p1.y(), x_of_ymin = p1.x(), x_of_ymax = p0.x();
+ auto slope = p0.x() == p1.x() ? 0 : ((float)(p0.y() - p1.y())) / ((float)(p0.x() - p1.x()));
+ if (p0.y() < p1.y()) {
+ swap(ymin, ymax);
+ swap(x_of_ymin, x_of_ymax);
+ }
+
+ segments.append({ FloatPoint(p0.x(), p0.y()),
+ FloatPoint(p1.x(), p1.y()),
+ slope == 0 ? 0 : 1 / slope,
+ x_of_ymin,
+ ymax, ymin, x_of_ymax });
+
+ add_point_to_bbox(p1);
+ };
+
+ FloatPoint cursor { 0, 0 };
+ bool first = true;
+
+ for (auto& segment : m_segments) {
+ switch (segment.type()) {
+ case Segment::Type::MoveTo:
+ if (first) {
+ min_x = segment.point().x();
+ min_y = segment.point().y();
+ max_x = segment.point().x();
+ max_y = segment.point().y();
+ } else {
+ add_point_to_bbox(segment.point());
+ }
+ cursor = segment.point();
+ break;
+ case Segment::Type::LineTo: {
+ add_line(cursor, segment.point());
+ cursor = segment.point();
+ break;
+ }
+ case Segment::Type::QuadraticBezierCurveTo: {
+ auto& control = static_cast<QuadraticBezierCurveSegment&>(segment).through();
+ Painter::for_each_line_segment_on_bezier_curve(control, cursor, segment.point(), [&](const FloatPoint& p0, const FloatPoint& p1) {
+ add_line(p0, p1);
+ });
+ cursor = segment.point();
+ break;
+ }
+ case Segment::Type::EllipticalArcTo: {
+ auto& arc = static_cast<EllipticalArcSegment&>(segment);
+ Painter::for_each_line_segment_on_elliptical_arc(cursor, arc.point(), arc.center(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), [&](const FloatPoint& p0, const FloatPoint& p1) {
+ add_line(p0, p1);
+ });
+ cursor = segment.point();
+ break;
+ }
+ case Segment::Type::Invalid:
+ ASSERT_NOT_REACHED();
+ }
+
+ first = false;
+ }
+
+ // sort segments by ymax
+ quick_sort(segments, [](const auto& line0, const auto& line1) {
+ return line1.maximum_y < line0.maximum_y;
+ });
+
+ m_split_lines = move(segments);
+ m_bounding_box = Gfx::FloatRect { min_x, min_y, max_x - min_x, max_y - min_y };
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Path.h b/Userland/Libraries/LibGfx/Path.h
new file mode 100644
index 0000000000..7a8dfe9567
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Path.h
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/NonnullRefPtrVector.h>
+#include <AK/Optional.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/Point.h>
+#include <LibGfx/Rect.h>
+
+namespace Gfx {
+
+class Segment : public RefCounted<Segment> {
+public:
+ enum class Type {
+ Invalid,
+ MoveTo,
+ LineTo,
+ QuadraticBezierCurveTo,
+ EllipticalArcTo,
+ };
+
+ Segment(const FloatPoint& point)
+ : m_point(point)
+ {
+ }
+
+ virtual ~Segment() = default;
+
+ const FloatPoint& point() const { return m_point; }
+ virtual Type type() const = 0;
+
+protected:
+ FloatPoint m_point;
+};
+
+class MoveSegment final : public Segment {
+public:
+ MoveSegment(const FloatPoint& point)
+ : Segment(point)
+ {
+ }
+
+private:
+ virtual Type type() const override { return Segment::Type::MoveTo; }
+};
+
+class LineSegment final : public Segment {
+public:
+ LineSegment(const FloatPoint& point)
+ : Segment(point)
+ {
+ }
+
+ virtual ~LineSegment() override = default;
+
+private:
+ virtual Type type() const override { return Segment::Type::LineTo; }
+};
+
+class QuadraticBezierCurveSegment final : public Segment {
+public:
+ QuadraticBezierCurveSegment(const FloatPoint& point, const FloatPoint& through)
+ : Segment(point)
+ , m_through(through)
+ {
+ }
+
+ virtual ~QuadraticBezierCurveSegment() override = default;
+
+ const FloatPoint& through() const { return m_through; }
+
+private:
+ virtual Type type() const override { return Segment::Type::QuadraticBezierCurveTo; }
+
+ FloatPoint m_through;
+};
+
+class EllipticalArcSegment final : public Segment {
+public:
+ EllipticalArcSegment(const FloatPoint& point, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta)
+ : Segment(point)
+ , m_center(center)
+ , m_radii(radii)
+ , m_x_axis_rotation(x_axis_rotation)
+ , m_theta_1(theta_1)
+ , m_theta_delta(theta_delta)
+ {
+ }
+
+ virtual ~EllipticalArcSegment() override = default;
+
+ const FloatPoint& center() const { return m_center; }
+ const FloatPoint& radii() const { return m_radii; }
+ float x_axis_rotation() const { return m_x_axis_rotation; }
+ float theta_1() const { return m_theta_1; }
+ float theta_delta() const { return m_theta_delta; }
+
+private:
+ virtual Type type() const override { return Segment::Type::EllipticalArcTo; }
+
+ FloatPoint m_center;
+ FloatPoint m_radii;
+ float m_x_axis_rotation;
+ float m_theta_1;
+ float m_theta_delta;
+};
+
+class Path {
+public:
+ Path() { }
+
+ void move_to(const FloatPoint& point)
+ {
+ append_segment<MoveSegment>(point);
+ }
+
+ void line_to(const FloatPoint& point)
+ {
+ append_segment<LineSegment>(point);
+ invalidate_split_lines();
+ }
+
+ void quadratic_bezier_curve_to(const FloatPoint& through, const FloatPoint& point)
+ {
+ append_segment<QuadraticBezierCurveSegment>(point, through);
+ invalidate_split_lines();
+ }
+
+ void elliptical_arc_to(const FloatPoint& point, const FloatPoint& center, const FloatPoint& radii, float x_axis_rotation, float theta_1, float theta_delta)
+ {
+ append_segment<EllipticalArcSegment>(point, center, radii, x_axis_rotation, theta_1, theta_delta);
+ invalidate_split_lines();
+ }
+
+ void close();
+ void close_all_subpaths();
+
+ struct SplitLineSegment {
+ FloatPoint from, to;
+ float inverse_slope;
+ float x_of_minimum_y;
+ float maximum_y;
+ float minimum_y;
+ float x;
+ };
+
+ const NonnullRefPtrVector<Segment>& segments() const { return m_segments; }
+ const auto& split_lines()
+ {
+ if (!m_split_lines.has_value()) {
+ segmentize_path();
+ ASSERT(m_split_lines.has_value());
+ }
+ return m_split_lines.value();
+ }
+
+ const Gfx::FloatRect& bounding_box()
+ {
+ if (!m_bounding_box.has_value()) {
+ segmentize_path();
+ ASSERT(m_bounding_box.has_value());
+ }
+ return m_bounding_box.value();
+ }
+
+ String to_string() const;
+
+private:
+ void invalidate_split_lines()
+ {
+ m_split_lines.clear();
+ }
+ void segmentize_path();
+
+ template<typename T, typename... Args>
+ void append_segment(Args&&... args)
+ {
+ m_segments.append(adopt(*new T(forward<Args>(args)...)));
+ }
+
+ NonnullRefPtrVector<Segment> m_segments {};
+
+ Optional<Vector<SplitLineSegment>> m_split_lines {};
+ Optional<Gfx::FloatRect> m_bounding_box;
+};
+
+inline const LogStream& operator<<(const LogStream& stream, const Path& path)
+{
+ return stream << path.to_string();
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Point.cpp b/Userland/Libraries/LibGfx/Point.cpp
new file mode 100644
index 0000000000..27ae1248ba
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Point.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <LibGfx/Point.h>
+#include <LibGfx/Rect.h>
+#include <LibIPC/Decoder.h>
+#include <LibIPC/Encoder.h>
+
+namespace Gfx {
+
+template<typename T>
+void Point<T>::constrain(const Rect<T>& rect)
+{
+ if (x() < rect.left()) {
+ set_x(rect.left());
+ } else if (x() > rect.right()) {
+ set_x(rect.right());
+ }
+
+ if (y() < rect.top()) {
+ set_y(rect.top());
+ } else if (y() > rect.bottom()) {
+ set_y(rect.bottom());
+ }
+}
+
+template<>
+String IntPoint::to_string() const
+{
+ return String::formatted("[{},{}]", x(), y());
+}
+
+template<>
+String FloatPoint::to_string() const
+{
+ return String::formatted("[{},{}]", x(), y());
+}
+
+}
+
+namespace IPC {
+
+bool encode(Encoder& encoder, const Gfx::IntPoint& point)
+{
+ encoder << point.x() << point.y();
+ return true;
+}
+
+bool decode(Decoder& decoder, Gfx::IntPoint& point)
+{
+ int x = 0;
+ int y = 0;
+ if (!decoder.decode(x))
+ return false;
+ if (!decoder.decode(y))
+ return false;
+ point = { x, y };
+ return true;
+}
+
+}
+
+template class Gfx::Point<int>;
+template class Gfx::Point<float>;
diff --git a/Userland/Libraries/LibGfx/Point.h b/Userland/Libraries/LibGfx/Point.h
new file mode 100644
index 0000000000..753d04055a
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Point.h
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Format.h>
+#include <AK/StdLibExtras.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/Orientation.h>
+#include <LibIPC/Forward.h>
+#include <math.h>
+#include <stdlib.h>
+
+namespace Gfx {
+
+template<typename T>
+class Point {
+public:
+ Point() { }
+
+ Point(T x, T y)
+ : m_x(x)
+ , m_y(y)
+ {
+ }
+
+ template<typename U>
+ Point(U x, U y)
+ : m_x(x)
+ , m_y(y)
+ {
+ }
+
+ template<typename U>
+ explicit Point(const Point<U>& other)
+ : m_x(other.x())
+ , m_y(other.y())
+ {
+ }
+
+ T x() const { return m_x; }
+ T y() const { return m_y; }
+
+ void set_x(T x) { m_x = x; }
+ void set_y(T y) { m_y = y; }
+
+ void move_by(T dx, T dy)
+ {
+ m_x += dx;
+ m_y += dy;
+ }
+
+ void move_by(const Point<T>& delta)
+ {
+ move_by(delta.x(), delta.y());
+ }
+
+ Point<T> translated(const Point<T>& delta) const
+ {
+ Point<T> point = *this;
+ point.move_by(delta);
+ return point;
+ }
+
+ Point<T> translated(T dx, T dy) const
+ {
+ Point<T> point = *this;
+ point.move_by(dx, dy);
+ return point;
+ }
+
+ Point<T> translated(T dboth) const
+ {
+ Point<T> point = *this;
+ point.move_by(dboth, dboth);
+ return point;
+ }
+
+ void constrain(const Rect<T>&);
+ Point<T> constrained(const Rect<T>& rect) const
+ {
+ Point<T> point = *this;
+ point.constrain(rect);
+ return point;
+ }
+
+ bool operator==(const Point<T>& other) const
+ {
+ return m_x == other.m_x && m_y == other.m_y;
+ }
+
+ bool operator!=(const Point<T>& other) const
+ {
+ return !(*this == other);
+ }
+
+ Point<T> operator+(const Point<T>& other) const { return { m_x + other.m_x, m_y + other.m_y }; }
+
+ Point<T>& operator+=(const Point<T>& other)
+ {
+ m_x += other.m_x;
+ m_y += other.m_y;
+ return *this;
+ }
+
+ Point<T> operator-() const { return { -m_x, -m_y }; }
+
+ Point<T> operator-(const Point<T>& other) const { return { m_x - other.m_x, m_y - other.m_y }; }
+
+ Point<T>& operator-=(const Point<T>& other)
+ {
+ m_x -= other.m_x;
+ m_y -= other.m_y;
+ return *this;
+ }
+
+ Point<T> operator*(T factor) const { return { m_x * factor, m_y * factor }; }
+
+ Point<T>& operator*=(T factor)
+ {
+ m_x *= factor;
+ m_y *= factor;
+ return *this;
+ }
+
+ Point<T> operator/(T factor) const { return { m_x / factor, m_y / factor }; }
+
+ Point<T>& operator/=(T factor)
+ {
+ m_x /= factor;
+ m_y /= factor;
+ return *this;
+ }
+
+ bool is_null() const { return !m_x && !m_y; }
+
+ T primary_offset_for_orientation(Orientation orientation) const
+ {
+ return orientation == Orientation::Vertical ? y() : x();
+ }
+
+ void set_primary_offset_for_orientation(Orientation orientation, T value)
+ {
+ if (orientation == Orientation::Vertical) {
+ set_y(value);
+ } else {
+ set_x(value);
+ }
+ }
+
+ T secondary_offset_for_orientation(Orientation orientation) const
+ {
+ return orientation == Orientation::Vertical ? x() : y();
+ }
+
+ void set_secondary_offset_for_orientation(Orientation orientation, T value)
+ {
+ if (orientation == Orientation::Vertical) {
+ set_x(value);
+ } else {
+ set_y(value);
+ }
+ }
+
+ T dx_relative_to(const Point<T>& other) const
+ {
+ return x() - other.x();
+ }
+
+ T dy_relative_to(const Point<T>& other) const
+ {
+ return y() - other.y();
+ }
+
+ // Returns pixels moved from other in either direction
+ T pixels_moved(const Point<T>& other) const
+ {
+ return max(abs(dx_relative_to(other)), abs(dy_relative_to(other)));
+ }
+
+ float distance_from(const Point<T>& other) const
+ {
+ if (*this == other)
+ return 0;
+ return sqrtf(powf(m_x - other.m_x, 2.0f) + powf(m_y - other.m_y, 2.0f));
+ }
+
+ Point absolute_relative_distance_to(const Point& other) const
+ {
+ return { abs(dx_relative_to(other)), abs(dy_relative_to(other)) };
+ }
+
+ template<typename U>
+ Point<U> to_type() const
+ {
+ return Point<U>(*this);
+ }
+
+ String to_string() const;
+
+private:
+ T m_x { 0 };
+ T m_y { 0 };
+};
+
+template<typename T>
+const LogStream& operator<<(const LogStream& stream, const Point<T>& point)
+{
+ return stream << point.to_string();
+}
+
+using IntPoint = Point<int>;
+using FloatPoint = Point<float>;
+
+}
+
+namespace AK {
+
+template<typename T>
+struct Formatter<Gfx::Point<T>> : Formatter<StringView> {
+ void format(FormatBuilder& builder, const Gfx::Point<T>& value)
+ {
+ Formatter<StringView>::format(builder, value.to_string());
+ }
+};
+
+}
+
+namespace IPC {
+
+bool encode(Encoder&, const Gfx::IntPoint&);
+bool decode(Decoder&, Gfx::IntPoint&);
+
+}
diff --git a/Userland/Libraries/LibGfx/PortableImageLoaderCommon.h b/Userland/Libraries/LibGfx/PortableImageLoaderCommon.h
new file mode 100644
index 0000000000..5f3c51679c
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PortableImageLoaderCommon.h
@@ -0,0 +1,309 @@
+/*
+ * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
+ * Copyright (c) 2020, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Array.h>
+#include <AK/Endian.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/ScopeGuard.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/Types.h>
+#include <AK/Vector.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Color.h>
+#include <LibGfx/ImageDecoder.h>
+#include <LibGfx/Streamer.h>
+
+//#define PORTABLE_IMAGE_LOADER_DEBUG
+
+namespace Gfx {
+
+static constexpr Color adjust_color(u16 max_val, Color color)
+{
+ color.set_red((color.red() * 255) / max_val);
+ color.set_green((color.green() * 255) / max_val);
+ color.set_blue((color.blue() * 255) / max_val);
+
+ return color;
+}
+
+template<typename TValue>
+static bool read_number(Streamer& streamer, TValue* value)
+{
+ u8 byte {};
+ StringBuilder sb {};
+
+ while (streamer.read(byte)) {
+ if (byte == ' ' || byte == '\t' || byte == '\n' || byte == '\r') {
+ streamer.step_back();
+ break;
+ }
+
+ sb.append(byte);
+ }
+
+ const auto opt_value = sb.to_string().to_uint();
+ if (!opt_value.has_value()) {
+ *value = 0;
+ return false;
+ }
+
+ *value = static_cast<u16>(opt_value.value());
+ return true;
+}
+
+template<typename TContext>
+static bool read_comment([[maybe_unused]] TContext& context, Streamer& streamer)
+{
+ bool exist = false;
+ u8 byte {};
+
+ while (streamer.read(byte)) {
+ if (byte == '#') {
+ exist = true;
+ } else if (byte == '\t' || byte == '\n') {
+ return exist;
+ }
+ }
+
+ return exist;
+}
+
+template<typename TContext>
+static bool read_magic_number(TContext& context, Streamer& streamer)
+{
+ if (context.state >= TContext::State::MagicNumber) {
+ return true;
+ }
+
+ if (!context.data || context.data_size < 2) {
+ context.state = TContext::State::Error;
+#ifdef PORTABLE_IMAGE_LOADER_DEBUG
+ dbg() << "There is no enough data for " << TContext::image_type;
+#endif
+ return false;
+ }
+
+ u8 magic_number[2] {};
+ if (!streamer.read_bytes(magic_number, 2)) {
+ context.state = TContext::State::Error;
+#ifdef PORTABLE_IMAGE_LOADER_DEBUG
+ dbg() << "We can't read magic number for " << TContext::image_type;
+#endif
+ return false;
+ }
+
+ if (magic_number[0] == 'P' && magic_number[1] == TContext::ascii_magic_number) {
+ context.type = TContext::Type::ASCII;
+ context.state = TContext::State::MagicNumber;
+ return true;
+ }
+
+ if (magic_number[0] == 'P' && magic_number[1] == TContext::binary_magic_number) {
+ context.type = TContext::Type::RAWBITS;
+ context.state = TContext::State::MagicNumber;
+ return true;
+ }
+
+ context.state = TContext::State::Error;
+#ifdef PORTABLE_IMAGE_LOADER_DEBUG
+ dbg() << "Magic number is not valid for "
+ << magic_number[0] << magic_number[1] << TContext::image_type;
+#endif
+ return false;
+}
+
+template<typename TContext>
+static bool read_white_space(TContext& context, Streamer& streamer)
+{
+ bool exist = false;
+ u8 byte {};
+
+ while (streamer.read(byte)) {
+ if (byte == ' ' || byte == '\t' || byte == '\n' || byte == '\r') {
+ exist = true;
+ } else if (byte == '#') {
+ streamer.step_back();
+ read_comment(context, streamer);
+ } else {
+ streamer.step_back();
+ return exist;
+ }
+ }
+
+ return exist;
+}
+
+template<typename TContext>
+static bool read_width(TContext& context, Streamer& streamer)
+{
+ if (const bool result = read_number(streamer, &context.width);
+ !result || context.width == 0) {
+ return false;
+ }
+
+ context.state = TContext::Width;
+ return true;
+}
+
+template<typename TContext>
+static bool read_height(TContext& context, Streamer& streamer)
+{
+ if (const bool result = read_number(streamer, &context.height);
+ !result || context.height == 0) {
+ return false;
+ }
+
+ context.state = TContext::Height;
+ return true;
+}
+
+template<typename TContext>
+static bool read_max_val(TContext& context, Streamer& streamer)
+{
+ if (const bool result = read_number(streamer, &context.max_val);
+ !result || context.max_val == 0) {
+ return false;
+ }
+
+ if (context.max_val > 255) {
+#ifdef PORTABLE_IMAGE_LOADER_DEBUG
+ dbg() << "We can't parse 2 byte color for " << TContext::image_type;
+#endif
+ context.state = TContext::Error;
+ return false;
+ }
+
+ context.state = TContext::Maxval;
+ return true;
+}
+
+template<typename TContext>
+static bool create_bitmap(TContext& context)
+{
+ context.bitmap = Bitmap::create_purgeable(BitmapFormat::RGB32, { context.width, context.height });
+ if (!context.bitmap) {
+ context.state = TContext::State::Error;
+ return false;
+ }
+ return true;
+}
+
+template<typename TContext>
+static void set_pixels(TContext& context, const AK::Vector<Gfx::Color>& color_data)
+{
+ size_t index = 0;
+ for (size_t y = 0; y < context.height; ++y) {
+ for (size_t x = 0; x < context.width; ++x) {
+ context.bitmap->set_pixel(x, y, color_data.at(index));
+ index++;
+ }
+ }
+}
+
+template<typename TContext>
+static bool decode(TContext& context)
+{
+ if (context.state >= TContext::State::Decoded)
+ return true;
+
+ auto error_guard = ArmedScopeGuard([&] {
+ context.state = TContext::State::Error;
+ });
+
+ Streamer streamer(context.data, context.data_size);
+
+ if (!read_magic_number(context, streamer))
+ return false;
+
+ if (!read_white_space(context, streamer))
+ return false;
+
+ if (!read_width(context, streamer))
+ return false;
+
+ if (!read_white_space(context, streamer))
+ return false;
+
+ if (!read_height(context, streamer))
+ return false;
+
+ if (context.width > maximum_width_for_decoded_images || context.height > maximum_height_for_decoded_images) {
+ dbgln("This portable network image is too large for comfort: {}x{}", context.width, context.height);
+ return false;
+ }
+
+ if (!read_white_space(context, streamer))
+ return false;
+
+ if constexpr (requires { context.max_val; }) {
+ if (!read_max_val(context, streamer))
+ return false;
+
+ if (!read_white_space(context, streamer))
+ return false;
+ }
+
+ if (!read_image_data(context, streamer))
+ return false;
+
+ error_guard.disarm();
+ context.state = TContext::State::Decoded;
+ return true;
+}
+
+template<typename TContext>
+static RefPtr<Gfx::Bitmap> load_impl(const u8* data, size_t data_size)
+{
+ TContext context {};
+ context.data = data;
+ context.data_size = data_size;
+
+ if (!decode(context)) {
+ return nullptr;
+ }
+ return context.bitmap;
+}
+template<typename TContext>
+static RefPtr<Gfx::Bitmap> load(const StringView& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return nullptr;
+ auto bitmap = load_impl<TContext>((const u8*)file_or_error.value()->data(), file_or_error.value()->size());
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded {}: {}",
+ bitmap->size(),
+ TContext::image_type,
+ LexicalPath::canonicalized_path(path)));
+ return bitmap;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Rect.cpp b/Userland/Libraries/LibGfx/Rect.cpp
new file mode 100644
index 0000000000..1f0d39d440
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Rect.cpp
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StdLibExtras.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <LibGfx/Rect.h>
+#include <LibIPC/Decoder.h>
+#include <LibIPC/Encoder.h>
+
+namespace Gfx {
+
+template<typename T>
+void Rect<T>::intersect(const Rect<T>& other)
+{
+ T l = max(left(), other.left());
+ T r = min(right(), other.right());
+ T t = max(top(), other.top());
+ T b = min(bottom(), other.bottom());
+
+ if (l > r || t > b) {
+ m_location = {};
+ m_size = {};
+ return;
+ }
+
+ m_location.set_x(l);
+ m_location.set_y(t);
+ m_size.set_width((r - l) + 1);
+ m_size.set_height((b - t) + 1);
+}
+
+template<typename T>
+Rect<T> Rect<T>::united(const Rect<T>& other) const
+{
+ if (is_null())
+ return other;
+ if (other.is_null())
+ return *this;
+ Rect<T> rect;
+ rect.set_left(min(left(), other.left()));
+ rect.set_top(min(top(), other.top()));
+ rect.set_right(max(right(), other.right()));
+ rect.set_bottom(max(bottom(), other.bottom()));
+ return rect;
+}
+
+template<typename T>
+Vector<Rect<T>, 4> Rect<T>::shatter(const Rect<T>& hammer) const
+{
+ Vector<Rect<T>, 4> pieces;
+ if (!intersects(hammer)) {
+ pieces.unchecked_append(*this);
+ return pieces;
+ }
+ Rect<T> top_shard {
+ x(),
+ y(),
+ width(),
+ hammer.y() - y()
+ };
+ Rect<T> bottom_shard {
+ x(),
+ hammer.y() + hammer.height(),
+ width(),
+ (y() + height()) - (hammer.y() + hammer.height())
+ };
+ Rect<T> left_shard {
+ x(),
+ max(hammer.y(), y()),
+ hammer.x() - x(),
+ min((hammer.y() + hammer.height()), (y() + height())) - max(hammer.y(), y())
+ };
+ Rect<T> right_shard {
+ hammer.x() + hammer.width(),
+ max(hammer.y(), y()),
+ right() - hammer.right(),
+ min((hammer.y() + hammer.height()), (y() + height())) - max(hammer.y(), y())
+ };
+ if (!top_shard.is_empty())
+ pieces.unchecked_append(top_shard);
+ if (!bottom_shard.is_empty())
+ pieces.unchecked_append(bottom_shard);
+ if (!left_shard.is_empty())
+ pieces.unchecked_append(left_shard);
+ if (!right_shard.is_empty())
+ pieces.unchecked_append(right_shard);
+
+ return pieces;
+}
+
+template<typename T>
+void Rect<T>::align_within(const Rect<T>& other, TextAlignment alignment)
+{
+ switch (alignment) {
+ case TextAlignment::Center:
+ center_within(other);
+ return;
+ case TextAlignment::TopLeft:
+ set_location(other.location());
+ return;
+ case TextAlignment::TopRight:
+ set_x(other.x() + other.width() - width());
+ set_y(other.y());
+ return;
+ case TextAlignment::CenterLeft:
+ set_x(other.x());
+ center_vertically_within(other);
+ return;
+ case TextAlignment::CenterRight:
+ set_x(other.x() + other.width() - width());
+ center_vertically_within(other);
+ return;
+ case TextAlignment::BottomRight:
+ set_x(other.x() + other.width() - width());
+ set_y(other.y() + other.height() - height());
+ return;
+ }
+}
+
+template<>
+String IntRect::to_string() const
+{
+ return String::format("[%d,%d %dx%d]", x(), y(), width(), height());
+}
+
+template<>
+String FloatRect::to_string() const
+{
+ return String::format("[%f,%f %fx%f]", x(), y(), width(), height());
+}
+
+}
+
+namespace IPC {
+
+bool encode(Encoder& encoder, const Gfx::IntRect& rect)
+{
+ encoder << rect.location() << rect.size();
+ return true;
+}
+
+bool decode(Decoder& decoder, Gfx::IntRect& rect)
+{
+ Gfx::IntPoint point;
+ Gfx::IntSize size;
+ if (!decoder.decode(point))
+ return false;
+ if (!decoder.decode(size))
+ return false;
+ rect = { point, size };
+ return true;
+}
+
+}
+
+template class Gfx::Rect<int>;
+template class Gfx::Rect<float>;
diff --git a/Userland/Libraries/LibGfx/Rect.h b/Userland/Libraries/LibGfx/Rect.h
new file mode 100644
index 0000000000..f54ff43fbf
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Rect.h
@@ -0,0 +1,471 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Format.h>
+#include <LibGfx/Orientation.h>
+#include <LibGfx/Point.h>
+#include <LibGfx/Size.h>
+#include <LibGfx/TextAlignment.h>
+#include <math.h>
+
+namespace Gfx {
+
+template<typename T>
+T abst(T value)
+{
+ return value < 0 ? -value : value;
+}
+
+template<typename T>
+class Rect {
+public:
+ Rect() { }
+
+ Rect(T x, T y, T width, T height)
+ : m_location(x, y)
+ , m_size(width, height)
+ {
+ }
+
+ template<typename U>
+ Rect(U x, U y, U width, U height)
+ : m_location(x, y)
+ , m_size(width, height)
+ {
+ }
+
+ Rect(const Point<T>& location, const Size<T>& size)
+ : m_location(location)
+ , m_size(size)
+ {
+ }
+
+ template<typename U>
+ Rect(const Point<U>& location, const Size<U>& size)
+ : m_location(location)
+ , m_size(size)
+ {
+ }
+
+ template<typename U>
+ explicit Rect(const Rect<U>& other)
+ : m_location(other.location())
+ , m_size(other.size())
+ {
+ }
+
+ bool is_null() const
+ {
+ return width() == 0 && height() == 0;
+ }
+
+ bool is_empty() const
+ {
+ return width() <= 0 || height() <= 0;
+ }
+
+ void move_by(T dx, T dy)
+ {
+ m_location.move_by(dx, dy);
+ }
+
+ void move_by(const Point<T>& delta)
+ {
+ m_location.move_by(delta);
+ }
+
+ Point<T> center() const
+ {
+ return { x() + width() / 2, y() + height() / 2 };
+ }
+
+ void set_location(const Point<T>& location)
+ {
+ m_location = location;
+ }
+
+ void set_size(const Size<T>& size)
+ {
+ m_size = size;
+ }
+
+ void set_size(T width, T height)
+ {
+ m_size.set_width(width);
+ m_size.set_height(height);
+ }
+
+ void inflate(T w, T h)
+ {
+ set_x(x() - w / 2);
+ set_width(width() + w);
+ set_y(y() - h / 2);
+ set_height(height() + h);
+ }
+
+ void inflate(const Size<T>& size)
+ {
+ set_x(x() - size.width() / 2);
+ set_width(width() + size.width());
+ set_y(y() - size.height() / 2);
+ set_height(height() + size.height());
+ }
+
+ void shrink(T w, T h)
+ {
+ set_x(x() + w / 2);
+ set_width(width() - w);
+ set_y(y() + h / 2);
+ set_height(height() - h);
+ }
+
+ void shrink(const Size<T>& size)
+ {
+ set_x(x() + size.width() / 2);
+ set_width(width() - size.width());
+ set_y(y() + size.height() / 2);
+ set_height(height() - size.height());
+ }
+
+ Rect<T> shrunken(T w, T h) const
+ {
+ Rect<T> rect = *this;
+ rect.shrink(w, h);
+ return rect;
+ }
+
+ Rect<T> shrunken(const Size<T>& size) const
+ {
+ Rect<T> rect = *this;
+ rect.shrink(size);
+ return rect;
+ }
+
+ Rect<T> inflated(T w, T h) const
+ {
+ Rect<T> rect = *this;
+ rect.inflate(w, h);
+ return rect;
+ }
+
+ Rect<T> inflated(const Size<T>& size) const
+ {
+ Rect<T> rect = *this;
+ rect.inflate(size);
+ return rect;
+ }
+
+ Rect<T> translated(T dx, T dy) const
+ {
+ Rect<T> rect = *this;
+ rect.move_by(dx, dy);
+ return rect;
+ }
+
+ Rect<T> translated(const Point<T>& delta) const
+ {
+ Rect<T> rect = *this;
+ rect.move_by(delta);
+ return rect;
+ }
+
+ bool contains_vertically(T y) const
+ {
+ return y >= top() && y <= bottom();
+ }
+
+ bool contains_horizontally(T x) const
+ {
+ return x >= left() && x <= right();
+ }
+
+ bool contains(T x, T y) const
+ {
+ return x >= m_location.x() && x <= right() && y >= m_location.y() && y <= bottom();
+ }
+
+ bool contains(const Point<T>& point) const
+ {
+ return contains(point.x(), point.y());
+ }
+
+ bool contains(const Rect<T>& other) const
+ {
+ return left() <= other.left()
+ && right() >= other.right()
+ && top() <= other.top()
+ && bottom() >= other.bottom();
+ }
+
+ template<typename Container>
+ bool contains(const Container& others) const
+ {
+ bool have_any = false;
+ for (const auto& other : others) {
+ if (!contains(other))
+ return false;
+ have_any = true;
+ }
+ return have_any;
+ }
+
+ int primary_offset_for_orientation(Orientation orientation) const { return m_location.primary_offset_for_orientation(orientation); }
+ void set_primary_offset_for_orientation(Orientation orientation, int value) { m_location.set_primary_offset_for_orientation(orientation, value); }
+ int secondary_offset_for_orientation(Orientation orientation) const { return m_location.secondary_offset_for_orientation(orientation); }
+ void set_secondary_offset_for_orientation(Orientation orientation, int value) { m_location.set_secondary_offset_for_orientation(orientation, value); }
+
+ int primary_size_for_orientation(Orientation orientation) const { return m_size.primary_size_for_orientation(orientation); }
+ int secondary_size_for_orientation(Orientation orientation) const { return m_size.secondary_size_for_orientation(orientation); }
+ void set_primary_size_for_orientation(Orientation orientation, int value) { m_size.set_primary_size_for_orientation(orientation, value); }
+ void set_secondary_size_for_orientation(Orientation orientation, int value) { m_size.set_secondary_size_for_orientation(orientation, value); }
+
+ T first_edge_for_orientation(Orientation orientation) const
+ {
+ if (orientation == Orientation::Vertical)
+ return top();
+ return left();
+ }
+
+ T last_edge_for_orientation(Orientation orientation) const
+ {
+ if (orientation == Orientation::Vertical)
+ return bottom();
+ return right();
+ }
+
+ T left() const { return x(); }
+ T right() const { return x() + width() - 1; }
+ T top() const { return y(); }
+ T bottom() const { return y() + height() - 1; }
+
+ void set_left(T left)
+ {
+ set_x(left);
+ }
+
+ void set_top(T top)
+ {
+ set_y(top);
+ }
+
+ void set_right(T right)
+ {
+ set_width(right - x() + 1);
+ }
+
+ void set_bottom(T bottom)
+ {
+ set_height(bottom - y() + 1);
+ }
+
+ void set_right_without_resize(T new_right)
+ {
+ int delta = new_right - right();
+ move_by(delta, 0);
+ }
+
+ void set_bottom_without_resize(T new_bottom)
+ {
+ int delta = new_bottom - bottom();
+ move_by(0, delta);
+ }
+
+ bool intersects_vertically(const Rect<T>& other) const
+ {
+ return top() <= other.bottom() && other.top() <= bottom();
+ }
+
+ bool intersects_horizontally(const Rect<T>& other) const
+ {
+ return left() <= other.right() && other.left() <= right();
+ }
+
+ bool intersects(const Rect<T>& other) const
+ {
+ return left() <= other.right()
+ && other.left() <= right()
+ && top() <= other.bottom()
+ && other.top() <= bottom();
+ }
+
+ template<typename Container>
+ bool intersects(const Container& others) const
+ {
+ for (const auto& other : others) {
+ if (intersects(other))
+ return true;
+ }
+ return false;
+ }
+
+ template<typename Container, typename Function>
+ IterationDecision for_each_intersected(const Container& others, Function f) const
+ {
+ if (is_empty())
+ return IterationDecision::Continue;
+ for (const auto& other : others) {
+ auto intersected_rect = intersected(other);
+ if (!intersected_rect.is_empty()) {
+ IterationDecision decision = f(intersected_rect);
+ if (decision != IterationDecision::Continue)
+ return decision;
+ }
+ }
+ return IterationDecision::Continue;
+ }
+
+ T x() const { return location().x(); }
+ T y() const { return location().y(); }
+ T width() const { return m_size.width(); }
+ T height() const { return m_size.height(); }
+
+ void set_x(T x) { m_location.set_x(x); }
+ void set_y(T y) { m_location.set_y(y); }
+ void set_width(T width) { m_size.set_width(width); }
+ void set_height(T height) { m_size.set_height(height); }
+
+ const Point<T>& location() const { return m_location; }
+ const Size<T>& size() const { return m_size; }
+
+ Vector<Rect<T>, 4> shatter(const Rect<T>& hammer) const;
+
+ bool operator==(const Rect<T>& other) const
+ {
+ return m_location == other.m_location && m_size == other.m_size;
+ }
+
+ bool operator!=(const Rect<T>& other) const
+ {
+ return !(*this == other);
+ }
+
+ Rect<T> operator*(T factor) const { return { m_location * factor, m_size * factor }; }
+
+ Rect<T>& operator*=(T factor)
+ {
+ m_location *= factor;
+ m_size *= factor;
+ return *this;
+ }
+
+ void intersect(const Rect<T>&);
+
+ static Rect<T> from_two_points(const Point<T>& a, const Point<T>& b)
+ {
+ return { min(a.x(), b.x()), min(a.y(), b.y()), abst(a.x() - b.x()), abst(a.y() - b.y()) };
+ }
+
+ static Rect<T> intersection(const Rect<T>& a, const Rect<T>& b)
+ {
+ Rect<T> r = a;
+ r.intersect(b);
+ return r;
+ }
+
+ Rect<T> intersected(const Rect<T>& other) const
+ {
+ return intersection(*this, other);
+ }
+
+ Rect<T> united(const Rect<T>&) const;
+
+ Point<T> top_left() const { return { left(), top() }; }
+ Point<T> top_right() const { return { right(), top() }; }
+ Point<T> bottom_left() const { return { left(), bottom() }; }
+ Point<T> bottom_right() const { return { right(), bottom() }; }
+
+ void align_within(const Rect<T>&, TextAlignment);
+
+ void center_within(const Rect<T>& other)
+ {
+ center_horizontally_within(other);
+ center_vertically_within(other);
+ }
+
+ void center_horizontally_within(const Rect<T>& other)
+ {
+ set_x(other.center().x() - width() / 2);
+ }
+
+ void center_vertically_within(const Rect<T>& other)
+ {
+ set_y(other.center().y() - height() / 2);
+ }
+
+ template<typename U>
+ Rect<U> to() const
+ {
+ return Rect<U>(*this);
+ }
+
+ String to_string() const;
+
+private:
+ Point<T> m_location;
+ Size<T> m_size;
+};
+
+template<typename T>
+const LogStream& operator<<(const LogStream& stream, const Rect<T>& rect)
+{
+ return stream << rect.to_string();
+}
+
+using IntRect = Rect<int>;
+using FloatRect = Rect<float>;
+
+ALWAYS_INLINE IntRect enclosing_int_rect(const FloatRect& float_rect)
+{
+ return {
+ (int)float_rect.x(),
+ (int)float_rect.y(),
+ (int)ceilf(float_rect.width()),
+ (int)ceilf(float_rect.height()),
+ };
+}
+
+}
+
+namespace AK {
+
+template<typename T>
+struct Formatter<Gfx::Rect<T>> : Formatter<StringView> {
+ void format(FormatBuilder& builder, const Gfx::Rect<T>& value)
+ {
+ Formatter<StringView>::format(builder, value.to_string());
+ }
+};
+
+}
+
+namespace IPC {
+
+bool decode(Decoder&, Gfx::IntRect&);
+bool encode(Encoder&, const Gfx::IntRect&);
+
+}
diff --git a/Userland/Libraries/LibGfx/ShareableBitmap.cpp b/Userland/Libraries/LibGfx/ShareableBitmap.cpp
new file mode 100644
index 0000000000..6413140827
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ShareableBitmap.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/SharedBuffer.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ShareableBitmap.h>
+#include <LibGfx/Size.h>
+#include <LibIPC/Decoder.h>
+#include <LibIPC/Encoder.h>
+
+namespace Gfx {
+
+ShareableBitmap::ShareableBitmap(const Bitmap& bitmap)
+ : m_bitmap(bitmap.to_bitmap_backed_by_shared_buffer())
+{
+}
+
+}
+
+namespace IPC {
+
+bool encode(Encoder& encoder, const Gfx::ShareableBitmap& shareable_bitmap)
+{
+ encoder << shareable_bitmap.shbuf_id();
+ encoder << shareable_bitmap.width();
+ encoder << shareable_bitmap.height();
+ return true;
+}
+
+bool decode(Decoder& decoder, Gfx::ShareableBitmap& shareable_bitmap)
+{
+ i32 shbuf_id = 0;
+ Gfx::IntSize size;
+ if (!decoder.decode(shbuf_id))
+ return false;
+ if (!decoder.decode(size))
+ return false;
+
+ if (shbuf_id == -1)
+ return true;
+
+ dbgln("Decoding a ShareableBitmap with shbuf_id={}, size={}", shbuf_id, size);
+
+ auto shared_buffer = SharedBuffer::create_from_shbuf_id(shbuf_id);
+ if (!shared_buffer)
+ return false;
+
+ auto bitmap = Gfx::Bitmap::create_with_shared_buffer(Gfx::BitmapFormat::RGBA32, shared_buffer.release_nonnull(), size);
+ shareable_bitmap = bitmap->to_shareable_bitmap();
+ return true;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/ShareableBitmap.h b/Userland/Libraries/LibGfx/ShareableBitmap.h
new file mode 100644
index 0000000000..69ed9e7c78
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ShareableBitmap.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/RefPtr.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Size.h>
+
+namespace Gfx {
+
+class ShareableBitmap {
+public:
+ ShareableBitmap() { }
+ explicit ShareableBitmap(const Gfx::Bitmap&);
+
+ bool is_valid() const { return m_bitmap; }
+
+ i32 shbuf_id() const { return m_bitmap ? m_bitmap->shbuf_id() : -1; }
+
+ const Bitmap* bitmap() const { return m_bitmap; }
+ Bitmap* bitmap() { return m_bitmap; }
+
+ IntSize size() const { return m_bitmap ? m_bitmap->size() : IntSize(); }
+ IntRect rect() const { return m_bitmap ? m_bitmap->rect() : IntRect(); }
+
+ int width() const { return size().width(); }
+ int height() const { return size().height(); }
+
+private:
+ RefPtr<Bitmap> m_bitmap;
+};
+
+}
+
+namespace IPC {
+
+bool encode(Encoder&, const Gfx::ShareableBitmap&);
+bool decode(Decoder&, Gfx::ShareableBitmap&);
+
+}
diff --git a/Userland/Libraries/LibGfx/Size.cpp b/Userland/Libraries/LibGfx/Size.cpp
new file mode 100644
index 0000000000..e2cc6151ff
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Size.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <LibGfx/Size.h>
+#include <LibIPC/Decoder.h>
+#include <LibIPC/Encoder.h>
+
+namespace Gfx {
+
+template<>
+String IntSize::to_string() const
+{
+ return String::formatted("[{}x{}]", m_width, m_height);
+}
+
+template<>
+String FloatSize::to_string() const
+{
+ return String::formatted("[{}x{}]", m_width, m_height);
+}
+
+}
+
+namespace IPC {
+
+bool encode(Encoder& encoder, const Gfx::IntSize& size)
+{
+ encoder << size.width() << size.height();
+ return true;
+}
+
+bool decode(Decoder& decoder, Gfx::IntSize& size)
+{
+ int width = 0;
+ int height = 0;
+ if (!decoder.decode(width))
+ return false;
+ if (!decoder.decode(height))
+ return false;
+ size = { width, height };
+ return true;
+}
+
+}
+
+template class Gfx::Size<int>;
+template class Gfx::Size<float>;
diff --git a/Userland/Libraries/LibGfx/Size.h b/Userland/Libraries/LibGfx/Size.h
new file mode 100644
index 0000000000..7b0cbe96fc
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Size.h
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Format.h>
+#include <LibGfx/Orientation.h>
+#include <LibIPC/Forward.h>
+
+namespace Gfx {
+
+template<typename T>
+class Size {
+public:
+ Size() { }
+
+ Size(T w, T h)
+ : m_width(w)
+ , m_height(h)
+ {
+ }
+
+ template<typename U>
+ Size(U width, U height)
+ : m_width(width)
+ , m_height(height)
+ {
+ }
+
+ template<typename U>
+ explicit Size(const Size<U>& other)
+ : m_width(other.width())
+ , m_height(other.height())
+ {
+ }
+
+ bool is_null() const { return !m_width && !m_height; }
+ bool is_empty() const { return m_width <= 0 || m_height <= 0; }
+
+ T width() const { return m_width; }
+ T height() const { return m_height; }
+
+ T area() const { return width() * height(); }
+
+ void set_width(T w) { m_width = w; }
+ void set_height(T h) { m_height = h; }
+
+ template<typename U>
+ bool contains(const Size<U>& other) const
+ {
+ return other.m_width <= m_width && other.m_height <= m_height;
+ }
+
+ bool operator==(const Size<T>& other) const
+ {
+ return m_width == other.m_width && m_height == other.m_height;
+ }
+
+ bool operator!=(const Size<T>& other) const
+ {
+ return !(*this == other);
+ }
+
+ Size<T>& operator-=(const Size<T>& other)
+ {
+ m_width -= other.m_width;
+ m_height -= other.m_height;
+ return *this;
+ }
+
+ Size<T>& operator+=(const Size<T>& other)
+ {
+ m_width += other.m_width;
+ m_height += other.m_height;
+ return *this;
+ }
+
+ Size<T> operator*(T factor) const { return { m_width * factor, m_height * factor }; }
+
+ Size<T>& operator*=(T factor)
+ {
+ m_width *= factor;
+ m_height *= factor;
+ return *this;
+ }
+
+ T primary_size_for_orientation(Orientation orientation) const
+ {
+ return orientation == Orientation::Vertical ? height() : width();
+ }
+
+ void set_primary_size_for_orientation(Orientation orientation, T value)
+ {
+ if (orientation == Orientation::Vertical) {
+ set_height(value);
+ } else {
+ set_width(value);
+ }
+ }
+
+ T secondary_size_for_orientation(Orientation orientation) const
+ {
+ return orientation == Orientation::Vertical ? width() : height();
+ }
+
+ void set_secondary_size_for_orientation(Orientation orientation, T value)
+ {
+ if (orientation == Orientation::Vertical) {
+ set_width(value);
+ } else {
+ set_height(value);
+ }
+ }
+
+ template<typename U>
+ Size<U> to_type() const
+ {
+ return Size<U>(*this);
+ }
+
+ String to_string() const;
+
+private:
+ T m_width { 0 };
+ T m_height { 0 };
+};
+
+template<typename T>
+const LogStream& operator<<(const LogStream& stream, const Gfx::Size<T>& size)
+{
+ return stream << size.to_string();
+}
+
+using IntSize = Size<int>;
+using FloatSize = Size<float>;
+
+}
+
+namespace AK {
+
+template<typename T>
+struct Formatter<Gfx::Size<T>> : Formatter<StringView> {
+ void format(FormatBuilder& builder, const Gfx::Size<T>& value)
+ {
+ Formatter<StringView>::format(builder, value.to_string());
+ }
+};
+
+}
+
+namespace IPC {
+
+bool encode(Encoder&, const Gfx::IntSize&);
+bool decode(Decoder&, Gfx::IntSize&);
+
+}
diff --git a/Userland/Libraries/LibGfx/StandardCursor.h b/Userland/Libraries/LibGfx/StandardCursor.h
new file mode 100644
index 0000000000..38c35b7076
--- /dev/null
+++ b/Userland/Libraries/LibGfx/StandardCursor.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace Gfx {
+
+enum class StandardCursor {
+ None = 0,
+ Hidden,
+ Arrow,
+ Crosshair,
+ IBeam,
+ ResizeHorizontal,
+ ResizeVertical,
+ ResizeDiagonalTLBR,
+ ResizeDiagonalBLTR,
+ ResizeColumn,
+ ResizeRow,
+ Hand,
+ Help,
+ Drag,
+ Move,
+ Wait,
+ __Count,
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Streamer.h b/Userland/Libraries/LibGfx/Streamer.h
new file mode 100644
index 0000000000..475726585b
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Streamer.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2020, Hüseyin ASLITÜRK <asliturk@hotmail.com>, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Array.h>
+#include <AK/Endian.h>
+#include <AK/Types.h>
+#include <string.h>
+
+namespace Gfx {
+
+class Streamer {
+public:
+ constexpr Streamer(const u8* data, size_t size)
+ : m_data_ptr(data)
+ , m_size_remaining(size)
+ {
+ }
+
+ template<typename T>
+ constexpr bool read(T& value)
+ {
+ AK::Array<u8, sizeof(T)> network_buffer {};
+ auto network_value = new (network_buffer.data()) AK::NetworkOrdered<T> {};
+ auto res = read_bytes(network_buffer.data(), sizeof(T));
+ value = T(*network_value);
+ return res;
+ }
+
+ constexpr bool read_bytes(u8* buffer, size_t count)
+ {
+ if (m_size_remaining < count) {
+ return false;
+ }
+ memcpy(buffer, m_data_ptr, count);
+ m_data_ptr += count;
+ m_size_remaining -= count;
+ return true;
+ }
+
+ constexpr bool at_end() const { return !m_size_remaining; }
+
+ constexpr void step_back()
+ {
+ m_data_ptr -= 1;
+ m_size_remaining += 1;
+ }
+
+private:
+ const u8* m_data_ptr { nullptr };
+ size_t m_size_remaining { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/StylePainter.cpp b/Userland/Libraries/LibGfx/StylePainter.cpp
new file mode 100644
index 0000000000..ad830958d8
--- /dev/null
+++ b/Userland/Libraries/LibGfx/StylePainter.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ClassicStylePainter.h>
+#include <LibGfx/Painter.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+namespace Gfx {
+
+BaseStylePainter& StylePainter::current()
+{
+ static ClassicStylePainter style;
+ return style;
+}
+
+void StylePainter::paint_tab_button(Painter& painter, const IntRect& rect, const Palette& palette, bool active, bool hovered, bool enabled, bool top)
+{
+ current().paint_tab_button(painter, rect, palette, active, hovered, enabled, top);
+}
+
+void StylePainter::paint_button(Painter& painter, const IntRect& rect, const Palette& palette, ButtonStyle button_style, bool pressed, bool hovered, bool checked, bool enabled, bool focused)
+{
+ current().paint_button(painter, rect, palette, button_style, pressed, hovered, checked, enabled, focused);
+}
+
+void StylePainter::paint_surface(Painter& painter, const IntRect& rect, const Palette& palette, bool paint_vertical_lines, bool paint_top_line)
+{
+ current().paint_surface(painter, rect, palette, paint_vertical_lines, paint_top_line);
+}
+
+void StylePainter::paint_frame(Painter& painter, const IntRect& rect, const Palette& palette, FrameShape shape, FrameShadow shadow, int thickness, bool skip_vertical_lines)
+{
+ current().paint_frame(painter, rect, palette, shape, shadow, thickness, skip_vertical_lines);
+}
+
+void StylePainter::paint_window_frame(Painter& painter, const IntRect& rect, const Palette& palette)
+{
+ current().paint_window_frame(painter, rect, palette);
+}
+
+void StylePainter::paint_progress_bar(Painter& painter, const IntRect& rect, const Palette& palette, int min, int max, int value, const StringView& text)
+{
+ current().paint_progress_bar(painter, rect, palette, min, max, value, text);
+}
+
+void StylePainter::paint_radio_button(Painter& painter, const IntRect& rect, const Palette& palette, bool is_checked, bool is_being_pressed)
+{
+ current().paint_radio_button(painter, rect, palette, is_checked, is_being_pressed);
+}
+
+void StylePainter::paint_check_box(Painter& painter, const IntRect& rect, const Palette& palette, bool is_enabled, bool is_checked, bool is_being_pressed)
+{
+ current().paint_check_box(painter, rect, palette, is_enabled, is_checked, is_being_pressed);
+}
+
+void StylePainter::paint_transparency_grid(Painter& painter, const IntRect& rect, const Palette& palette)
+{
+ current().paint_transparency_grid(painter, rect, palette);
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/StylePainter.h b/Userland/Libraries/LibGfx/StylePainter.h
new file mode 100644
index 0000000000..779241c252
--- /dev/null
+++ b/Userland/Libraries/LibGfx/StylePainter.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibGfx/Forward.h>
+
+namespace Gfx {
+
+enum class ButtonStyle {
+ Normal,
+ CoolBar
+};
+enum class FrameShadow {
+ Plain,
+ Raised,
+ Sunken
+};
+enum class FrameShape {
+ NoFrame,
+ Box,
+ Container,
+ Panel,
+ VerticalLine,
+ HorizontalLine
+};
+
+// FIXME: should this be in its own header?
+class BaseStylePainter {
+public:
+ virtual ~BaseStylePainter() { }
+
+ virtual void paint_button(Painter&, const IntRect&, const Palette&, ButtonStyle, bool pressed, bool hovered = false, bool checked = false, bool enabled = true, bool focused = false) = 0;
+ virtual void paint_tab_button(Painter&, const IntRect&, const Palette&, bool active, bool hovered, bool enabled, bool top) = 0;
+ virtual void paint_surface(Painter&, const IntRect&, const Palette&, bool paint_vertical_lines = true, bool paint_top_line = true) = 0;
+ virtual void paint_frame(Painter&, const IntRect&, const Palette&, FrameShape, FrameShadow, int thickness, bool skip_vertical_lines = false) = 0;
+ virtual void paint_window_frame(Painter&, const IntRect&, const Palette&) = 0;
+ virtual void paint_progress_bar(Painter&, const IntRect&, const Palette&, int min, int max, int value, const StringView& text) = 0;
+ virtual void paint_radio_button(Painter&, const IntRect&, const Palette&, bool is_checked, bool is_being_pressed) = 0;
+ virtual void paint_check_box(Painter&, const IntRect&, const Palette&, bool is_enabled, bool is_checked, bool is_being_pressed) = 0;
+ virtual void paint_transparency_grid(Painter&, const IntRect&, const Palette&) = 0;
+
+protected:
+ BaseStylePainter() { }
+};
+
+class StylePainter {
+public:
+ static BaseStylePainter& current();
+
+ // FIXME: These are here for API compatibility, we should probably remove them and move BaseStylePainter into here
+ static void paint_button(Painter&, const IntRect&, const Palette&, ButtonStyle, bool pressed, bool hovered = false, bool checked = false, bool enabled = true, bool focused = false);
+ static void paint_tab_button(Painter&, const IntRect&, const Palette&, bool active, bool hovered, bool enabled, bool top);
+ static void paint_surface(Painter&, const IntRect&, const Palette&, bool paint_vertical_lines = true, bool paint_top_line = true);
+ static void paint_frame(Painter&, const IntRect&, const Palette&, FrameShape, FrameShadow, int thickness, bool skip_vertical_lines = false);
+ static void paint_window_frame(Painter&, const IntRect&, const Palette&);
+ static void paint_progress_bar(Painter&, const IntRect&, const Palette&, int min, int max, int value, const StringView& text);
+ static void paint_radio_button(Painter&, const IntRect&, const Palette&, bool is_checked, bool is_being_pressed);
+ static void paint_check_box(Painter&, const IntRect&, const Palette&, bool is_enabled, bool is_checked, bool is_being_pressed);
+ static void paint_transparency_grid(Painter&, const IntRect&, const Palette&);
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/SystemTheme.cpp b/Userland/Libraries/LibGfx/SystemTheme.cpp
new file mode 100644
index 0000000000..5ccb04256d
--- /dev/null
+++ b/Userland/Libraries/LibGfx/SystemTheme.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/SharedBuffer.h>
+#include <LibCore/ConfigFile.h>
+#include <LibGfx/SystemTheme.h>
+#include <string.h>
+
+namespace Gfx {
+
+static SystemTheme dummy_theme;
+static const SystemTheme* theme_page = &dummy_theme;
+static RefPtr<SharedBuffer> theme_buffer;
+
+const SystemTheme& current_system_theme()
+{
+ ASSERT(theme_page);
+ return *theme_page;
+}
+
+int current_system_theme_buffer_id()
+{
+ ASSERT(theme_buffer);
+ return theme_buffer->shbuf_id();
+}
+
+void set_system_theme(SharedBuffer& buffer)
+{
+ theme_buffer = buffer;
+ theme_page = theme_buffer->data<SystemTheme>();
+}
+
+RefPtr<SharedBuffer> load_system_theme(const String& path)
+{
+ auto file = Core::ConfigFile::open(path);
+ auto buffer = SharedBuffer::create_with_size(sizeof(SystemTheme));
+
+ auto* data = buffer->data<SystemTheme>();
+
+ auto get_color = [&](auto& name) {
+ auto color_string = file->read_entry("Colors", name);
+ auto color = Color::from_string(color_string);
+ if (!color.has_value())
+ return Color(Color::Black);
+ return color.value();
+ };
+
+ auto get_metric = [&](auto& name, auto role) {
+ int metric = file->read_num_entry("Metrics", name, -1);
+ if (metric == -1) {
+ switch (role) {
+ case (int)MetricRole::TitleHeight:
+ return 19;
+ case (int)MetricRole::TitleButtonHeight:
+ return 15;
+ case (int)MetricRole::TitleButtonWidth:
+ return 15;
+ default:
+ dbgln("Metric {} has no fallback value!", name);
+ return 16;
+ }
+ }
+ return metric;
+ };
+
+ auto get_path = [&](auto& name, auto role) {
+ auto path = file->read_entry("Paths", name);
+ if (path.is_empty()) {
+ switch (role) {
+ case (int)PathRole::TitleButtonIcons:
+ return "/res/icons/16x16/";
+ default:
+ return "/res/";
+ }
+ }
+ return &path[0];
+ };
+
+#undef __ENUMERATE_COLOR_ROLE
+#define __ENUMERATE_COLOR_ROLE(role) \
+ data->color[(int)ColorRole::role] = get_color(#role).value();
+ ENUMERATE_COLOR_ROLES(__ENUMERATE_COLOR_ROLE)
+#undef __ENUMERATE_COLOR_ROLE
+
+#define DO_METRIC(x) \
+ data->metric[(int)MetricRole::x] = get_metric(#x, (int)MetricRole::x)
+
+ DO_METRIC(TitleHeight);
+ DO_METRIC(TitleButtonWidth);
+ DO_METRIC(TitleButtonHeight);
+
+#define DO_PATH(x) \
+ do { \
+ auto path = get_path(#x, (int)PathRole::x); \
+ memcpy(data->path[(int)PathRole::x], path, min(strlen(path) + 1, sizeof(data->path[(int)PathRole::x]))); \
+ data->path[(int)PathRole::x][sizeof(data->path[(int)PathRole::x]) - 1] = '\0'; \
+ } while (0)
+
+ DO_PATH(TitleButtonIcons);
+
+ buffer->seal();
+ buffer->share_globally();
+
+ return buffer;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/SystemTheme.h b/Userland/Libraries/LibGfx/SystemTheme.h
new file mode 100644
index 0000000000..60e382c65f
--- /dev/null
+++ b/Userland/Libraries/LibGfx/SystemTheme.h
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <LibGfx/Color.h>
+
+namespace Gfx {
+
+#define ENUMERATE_COLOR_ROLES(C) \
+ C(ActiveLink) \
+ C(ActiveWindowBorder1) \
+ C(ActiveWindowBorder2) \
+ C(ActiveWindowTitle) \
+ C(ActiveWindowTitleShadow) \
+ C(ActiveWindowTitleStripes) \
+ C(Base) \
+ C(BaseText) \
+ C(Button) \
+ C(ButtonText) \
+ C(DesktopBackground) \
+ C(FocusOutline) \
+ C(HighlightWindowBorder1) \
+ C(HighlightWindowBorder2) \
+ C(HighlightWindowTitle) \
+ C(HighlightWindowTitleShadow) \
+ C(HighlightWindowTitleStripes) \
+ C(HighlightSearching) \
+ C(HighlightSearchingText) \
+ C(HoverHighlight) \
+ C(InactiveSelection) \
+ C(InactiveSelectionText) \
+ C(InactiveWindowBorder1) \
+ C(InactiveWindowBorder2) \
+ C(InactiveWindowTitle) \
+ C(InactiveWindowTitleShadow) \
+ C(InactiveWindowTitleStripes) \
+ C(Link) \
+ C(MenuBase) \
+ C(MenuBaseText) \
+ C(MenuSelection) \
+ C(MenuSelectionText) \
+ C(MenuStripe) \
+ C(MovingWindowBorder1) \
+ C(MovingWindowBorder2) \
+ C(MovingWindowTitle) \
+ C(MovingWindowTitleShadow) \
+ C(MovingWindowTitleStripes) \
+ C(PlaceholderText) \
+ C(RubberBandBorder) \
+ C(RubberBandFill) \
+ C(Ruler) \
+ C(RulerActiveText) \
+ C(RulerBorder) \
+ C(RulerInactiveText) \
+ C(Selection) \
+ C(SelectionText) \
+ C(SyntaxComment) \
+ C(SyntaxControlKeyword) \
+ C(SyntaxIdentifier) \
+ C(SyntaxKeyword) \
+ C(SyntaxNumber) \
+ C(SyntaxOperator) \
+ C(SyntaxPreprocessorStatement) \
+ C(SyntaxPreprocessorValue) \
+ C(SyntaxPunctuation) \
+ C(SyntaxString) \
+ C(SyntaxType) \
+ C(TextCursor) \
+ C(ThreedHighlight) \
+ C(ThreedShadow1) \
+ C(ThreedShadow2) \
+ C(Tooltip) \
+ C(TooltipText) \
+ C(VisitedLink) \
+ C(Window) \
+ C(WindowText)
+
+enum class ColorRole {
+ NoRole,
+
+#undef __ENUMERATE_COLOR_ROLE
+#define __ENUMERATE_COLOR_ROLE(role) role,
+ ENUMERATE_COLOR_ROLES(__ENUMERATE_COLOR_ROLE)
+#undef __ENUMERATE_COLOR_ROLE
+
+ __Count,
+
+ Background = Window,
+ DisabledText = ThreedShadow1,
+};
+
+inline const char* to_string(ColorRole role)
+{
+ switch (role) {
+ case ColorRole::NoRole:
+ return "NoRole";
+#undef __ENUMERATE_COLOR_ROLE
+#define __ENUMERATE_COLOR_ROLE(role) \
+ case ColorRole::role: \
+ return #role;
+ ENUMERATE_COLOR_ROLES(__ENUMERATE_COLOR_ROLE)
+#undef __ENUMERATE_COLOR_ROLE
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+enum class MetricRole {
+ NoRole,
+ TitleHeight,
+ TitleButtonWidth,
+ TitleButtonHeight,
+ __Count,
+};
+
+enum class PathRole {
+ NoRole,
+ TitleButtonIcons,
+ __Count,
+};
+
+struct SystemTheme {
+ RGBA32 color[(int)ColorRole::__Count];
+ int metric[(int)MetricRole::__Count];
+ char path[(int)PathRole::__Count][256]; // TODO: PATH_MAX?
+};
+
+const SystemTheme& current_system_theme();
+int current_system_theme_buffer_id();
+void set_system_theme(SharedBuffer&);
+RefPtr<SharedBuffer> load_system_theme(const String& path);
+
+}
+
+using Gfx::ColorRole;
diff --git a/Userland/Libraries/LibGfx/TextAlignment.h b/Userland/Libraries/LibGfx/TextAlignment.h
new file mode 100644
index 0000000000..5faea7f480
--- /dev/null
+++ b/Userland/Libraries/LibGfx/TextAlignment.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Optional.h>
+#include <AK/StringView.h>
+
+namespace Gfx {
+
+#define GFX_ENUMERATE_TEXT_ALIGNMENTS(M) \
+ M(TopLeft) \
+ M(CenterLeft) \
+ M(Center) \
+ M(CenterRight) \
+ M(TopRight) \
+ M(BottomRight)
+
+enum class TextAlignment {
+#define __ENUMERATE(x) x,
+ GFX_ENUMERATE_TEXT_ALIGNMENTS(__ENUMERATE)
+#undef __ENUMERATE
+};
+
+inline bool is_right_text_alignment(TextAlignment alignment)
+{
+ switch (alignment) {
+ case TextAlignment::CenterRight:
+ case TextAlignment::TopRight:
+ case TextAlignment::BottomRight:
+ return true;
+ default:
+ return false;
+ }
+}
+
+inline bool is_vertically_centered_text_alignment(TextAlignment alignment)
+{
+ switch (alignment) {
+ case TextAlignment::CenterLeft:
+ case TextAlignment::CenterRight:
+ case TextAlignment::Center:
+ return true;
+ default:
+ return false;
+ }
+}
+
+inline Optional<TextAlignment> text_alignment_from_string(const StringView& string)
+{
+#define __ENUMERATE(x) \
+ if (string == #x) \
+ return TextAlignment::x;
+ GFX_ENUMERATE_TEXT_ALIGNMENTS(__ENUMERATE)
+#undef __ENUMERATE
+ return {};
+}
+
+inline const char* to_string(TextAlignment text_alignment)
+{
+#define __ENUMERATE(x) \
+ if (text_alignment == TextAlignment::x) \
+ return #x;
+ GFX_ENUMERATE_TEXT_ALIGNMENTS(__ENUMERATE)
+#undef __ENUMERATE
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/TextAttributes.h b/Userland/Libraries/LibGfx/TextAttributes.h
new file mode 100644
index 0000000000..1de9e5c01d
--- /dev/null
+++ b/Userland/Libraries/LibGfx/TextAttributes.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Optional.h>
+#include <LibGfx/Color.h>
+
+namespace Gfx {
+
+struct TextAttributes {
+ Color color;
+ Optional<Color> background_color;
+ bool underline { false };
+ bool bold { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/TextElision.h b/Userland/Libraries/LibGfx/TextElision.h
new file mode 100644
index 0000000000..cf8cecc001
--- /dev/null
+++ b/Userland/Libraries/LibGfx/TextElision.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace Gfx {
+
+enum class TextElision {
+ None,
+ Right,
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Triangle.cpp b/Userland/Libraries/LibGfx/Triangle.cpp
new file mode 100644
index 0000000000..d326b59f1c
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Triangle.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, Shannon Booth <shannon.ml.booth@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <LibGfx/Triangle.h>
+
+namespace Gfx {
+
+String Triangle::to_string() const
+{
+ return String::formatted("({},{},{})", m_a, m_b, m_c);
+}
+
+const LogStream& operator<<(const LogStream& stream, const Triangle& value)
+{
+ return stream << value.to_string();
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Triangle.h b/Userland/Libraries/LibGfx/Triangle.h
new file mode 100644
index 0000000000..7d8e69e80d
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Triangle.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2020, Shannon Booth <shannon.ml.booth@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibGfx/Point.h>
+
+namespace Gfx {
+
+class Triangle {
+public:
+ Triangle(IntPoint a, IntPoint b, IntPoint c)
+ : m_a(a)
+ , m_b(b)
+ , m_c(c)
+ {
+ m_det = (m_b.x() - m_a.x()) * (m_c.y() - m_a.y()) - (m_b.y() - m_a.y()) * (m_c.x() - m_a.x());
+ }
+
+ IntPoint a() const { return m_a; }
+ IntPoint b() const { return m_b; }
+ IntPoint c() const { return m_c; }
+
+ bool contains(IntPoint p) const
+ {
+ int x = p.x();
+ int y = p.y();
+
+ int ax = m_a.x();
+ int bx = m_b.x();
+ int cx = m_c.x();
+
+ int ay = m_a.y();
+ int by = m_b.y();
+ int cy = m_c.y();
+
+ if (m_det * ((bx - ax) * (y - ay) - (by - ay) * (x - ax)) <= 0)
+ return false;
+ if (m_det * ((cx - bx) * (y - by) - (cy - by) * (x - bx)) <= 0)
+ return false;
+ if (m_det * ((ax - cx) * (y - cy) - (ay - cy) * (x - cx)) <= 0)
+ return false;
+ return true;
+ }
+
+ String to_string() const;
+
+private:
+ int m_det;
+ IntPoint m_a;
+ IntPoint m_b;
+ IntPoint m_c;
+};
+
+const LogStream& operator<<(const LogStream&, const Triangle&);
+
+}
diff --git a/Userland/Libraries/LibGfx/Vector3.h b/Userland/Libraries/LibGfx/Vector3.h
new file mode 100644
index 0000000000..0a93e5075d
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Vector3.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <math.h>
+
+namespace Gfx {
+template<typename T>
+class Vector3 {
+public:
+ Vector3() = default;
+ Vector3(T x, T y, T z)
+ : m_x(x)
+ , m_y(y)
+ , m_z(z)
+ {
+ }
+
+ T x() const { return m_x; }
+ T y() const { return m_y; }
+ T z() const { return m_z; }
+
+ void set_x(T value) { m_x = value; }
+ void set_y(T value) { m_y = value; }
+ void set_z(T value) { m_z = value; }
+
+ Vector3 operator+(const Vector3& other) const
+ {
+ return Vector3(m_x + other.m_x, m_y + other.m_y, m_z + other.m_z);
+ }
+
+ Vector3 operator-(const Vector3& other) const
+ {
+ return Vector3(m_x - other.m_x, m_y - other.m_y, m_z - other.m_z);
+ }
+
+ Vector3 operator*(T f) const
+ {
+ return Vector3(m_x * f, m_y * f, m_z * f);
+ }
+
+ Vector3 operator/(T f) const
+ {
+ return Vector3(m_x / f, m_y / f, m_z / f);
+ }
+
+ T dot(const Vector3& other) const
+ {
+ return m_x * other.m_x + m_y * other.m_y + m_z * other.m_z;
+ }
+
+ Vector3 cross(const Vector3& other) const
+ {
+ return Vector3(
+ m_y * other.m_z - m_z * other.m_y,
+ m_z * other.m_x - m_x * other.m_z,
+ m_x * other.m_y - m_y * other.m_x);
+ }
+
+ Vector3 normalized() const
+ {
+ T inv_length = 1 / length();
+ return *this * inv_length;
+ }
+
+ Vector3 clamped(T m, T x) const
+ {
+ Vector3 copy { *this };
+ copy.clamp(m, x);
+ return copy;
+ }
+
+ void clamp(T min_value, T max_value)
+ {
+ m_x = max(min_value, m_x);
+ m_y = max(min_value, m_y);
+ m_z = max(min_value, m_z);
+ m_x = min(max_value, m_x);
+ m_y = min(max_value, m_y);
+ m_z = min(max_value, m_z);
+ }
+
+ void normalize()
+ {
+ T inv_length = 1 / length();
+ m_x *= inv_length;
+ m_y *= inv_length;
+ m_z *= inv_length;
+ }
+
+ T length() const
+ {
+ return sqrt(m_x * m_x + m_y * m_y + m_z * m_z);
+ }
+
+private:
+ T m_x;
+ T m_y;
+ T m_z;
+};
+
+typedef Vector3<float> FloatVector3;
+typedef Vector3<double> DoubleVector3;
+
+}
+
+using Gfx::DoubleVector3;
+using Gfx::FloatVector3;
+using Gfx::Vector3;
diff --git a/Userland/Libraries/LibGfx/WindowTheme.cpp b/Userland/Libraries/LibGfx/WindowTheme.cpp
new file mode 100644
index 0000000000..dddf8d0630
--- /dev/null
+++ b/Userland/Libraries/LibGfx/WindowTheme.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/ClassicWindowTheme.h>
+#include <LibGfx/WindowTheme.h>
+
+namespace Gfx {
+
+WindowTheme& WindowTheme::current()
+{
+ static ClassicWindowTheme theme;
+ return theme;
+}
+
+WindowTheme::~WindowTheme()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/WindowTheme.h b/Userland/Libraries/LibGfx/WindowTheme.h
new file mode 100644
index 0000000000..8ae0174723
--- /dev/null
+++ b/Userland/Libraries/LibGfx/WindowTheme.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibGfx/Forward.h>
+
+namespace Gfx {
+
+class WindowTheme {
+public:
+ enum class WindowType {
+ Normal,
+ Notification,
+ Other,
+ };
+
+ enum class WindowState {
+ Active,
+ Inactive,
+ Highlighted,
+ Moving,
+ };
+
+ virtual ~WindowTheme();
+
+ static WindowTheme& current();
+
+ virtual void paint_normal_frame(Painter&, WindowState, const IntRect& window_rect, const StringView& title, const Bitmap& icon, const Palette&, const IntRect& leftmost_button_rect) const = 0;
+ virtual void paint_notification_frame(Painter&, const IntRect& window_rect, const Palette&, const IntRect& close_button_rect) const = 0;
+
+ virtual int title_bar_height(const Palette&) const = 0;
+ virtual IntRect title_bar_rect(WindowType, const IntRect& window_rect, const Palette&) const = 0;
+ virtual IntRect title_bar_icon_rect(WindowType, const IntRect& window_rect, const Palette&) const = 0;
+ virtual IntRect title_bar_text_rect(WindowType, const IntRect& window_rect, const Palette&) const = 0;
+
+ virtual IntRect frame_rect_for_window(WindowType, const IntRect& window_rect, const Palette&) const = 0;
+
+ virtual Vector<IntRect> layout_buttons(WindowType, const IntRect& window_rect, const Palette&, size_t buttons) const = 0;
+
+protected:
+ WindowTheme() { }
+};
+
+}
diff --git a/Userland/Libraries/LibHTTP/CMakeLists.txt b/Userland/Libraries/LibHTTP/CMakeLists.txt
new file mode 100644
index 0000000000..57c79b55e2
--- /dev/null
+++ b/Userland/Libraries/LibHTTP/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(SOURCES
+ HttpJob.cpp
+ HttpRequest.cpp
+ HttpResponse.cpp
+ HttpsJob.cpp
+ Job.cpp
+)
+
+serenity_lib(LibHTTP http)
+target_link_libraries(LibHTTP LibCore LibTLS)
diff --git a/Userland/Libraries/LibHTTP/Forward.h b/Userland/Libraries/LibHTTP/Forward.h
new file mode 100644
index 0000000000..bb64e1d25b
--- /dev/null
+++ b/Userland/Libraries/LibHTTP/Forward.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace HTTP {
+
+class HttpRequest;
+class HttpResponse;
+class HttpJob;
+class HttpsJob;
+class Job;
+
+}
diff --git a/Userland/Libraries/LibHTTP/HttpJob.cpp b/Userland/Libraries/LibHTTP/HttpJob.cpp
new file mode 100644
index 0000000000..87db14756d
--- /dev/null
+++ b/Userland/Libraries/LibHTTP/HttpJob.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/Gzip.h>
+#include <LibCore/TCPSocket.h>
+#include <LibHTTP/HttpJob.h>
+#include <LibHTTP/HttpResponse.h>
+#include <stdio.h>
+#include <unistd.h>
+
+//#define HTTPJOB_DEBUG
+
+namespace HTTP {
+void HttpJob::start()
+{
+ ASSERT(!m_socket);
+ m_socket = Core::TCPSocket::construct(this);
+ m_socket->on_connected = [this] {
+#ifdef CHTTPJOB_DEBUG
+ dbgln("HttpJob: on_connected callback");
+#endif
+ on_socket_connected();
+ };
+ bool success = m_socket->connect(m_request.url().host(), m_request.url().port());
+ if (!success) {
+ deferred_invoke([this](auto&) {
+ return did_fail(Core::NetworkJob::Error::ConnectionFailed);
+ });
+ }
+}
+
+void HttpJob::shutdown()
+{
+ if (!m_socket)
+ return;
+ m_socket->on_ready_to_read = nullptr;
+ m_socket->on_connected = nullptr;
+ remove_child(*m_socket);
+ m_socket = nullptr;
+}
+
+void HttpJob::register_on_ready_to_read(Function<void()> callback)
+{
+ m_socket->on_ready_to_read = move(callback);
+}
+
+void HttpJob::register_on_ready_to_write(Function<void()> callback)
+{
+ // There is no need to wait, the connection is already established
+ callback();
+}
+
+bool HttpJob::can_read_line() const
+{
+ return m_socket->can_read_line();
+}
+
+String HttpJob::read_line(size_t size)
+{
+ return m_socket->read_line(size);
+}
+
+ByteBuffer HttpJob::receive(size_t size)
+{
+ return m_socket->receive(size);
+}
+
+bool HttpJob::can_read() const
+{
+ return m_socket->can_read();
+}
+
+bool HttpJob::eof() const
+{
+ return m_socket->eof();
+}
+
+bool HttpJob::write(ReadonlyBytes bytes)
+{
+ return m_socket->write(bytes);
+}
+
+}
diff --git a/Userland/Libraries/LibHTTP/HttpJob.h b/Userland/Libraries/LibHTTP/HttpJob.h
new file mode 100644
index 0000000000..924ff6bf5f
--- /dev/null
+++ b/Userland/Libraries/LibHTTP/HttpJob.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <LibCore/NetworkJob.h>
+#include <LibCore/TCPSocket.h>
+#include <LibHTTP/HttpRequest.h>
+#include <LibHTTP/HttpResponse.h>
+#include <LibHTTP/Job.h>
+
+namespace HTTP {
+
+class HttpJob final : public Job {
+ C_OBJECT(HttpJob)
+public:
+ explicit HttpJob(const HttpRequest& request, OutputStream& output_stream)
+ : Job(request, output_stream)
+ {
+ }
+
+ virtual ~HttpJob() override
+ {
+ }
+
+ virtual void start() override;
+ virtual void shutdown() override;
+
+protected:
+ virtual bool should_fail_on_empty_payload() const override { return false; }
+ virtual void register_on_ready_to_read(Function<void()>) override;
+ virtual void register_on_ready_to_write(Function<void()>) override;
+ virtual bool can_read_line() const override;
+ virtual String read_line(size_t) override;
+ virtual bool can_read() const override;
+ virtual ByteBuffer receive(size_t) override;
+ virtual bool eof() const override;
+ virtual bool write(ReadonlyBytes) override;
+ virtual bool is_established() const override { return true; }
+
+private:
+ RefPtr<Core::Socket> m_socket;
+};
+
+}
diff --git a/Userland/Libraries/LibHTTP/HttpRequest.cpp b/Userland/Libraries/LibHTTP/HttpRequest.cpp
new file mode 100644
index 0000000000..8ea42a4e84
--- /dev/null
+++ b/Userland/Libraries/LibHTTP/HttpRequest.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibHTTP/HttpJob.h>
+#include <LibHTTP/HttpRequest.h>
+
+namespace HTTP {
+
+HttpRequest::HttpRequest()
+{
+}
+
+HttpRequest::~HttpRequest()
+{
+}
+
+String HttpRequest::method_name() const
+{
+ switch (m_method) {
+ case Method::GET:
+ return "GET";
+ case Method::HEAD:
+ return "HEAD";
+ case Method::POST:
+ return "POST";
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+ByteBuffer HttpRequest::to_raw_request() const
+{
+ StringBuilder builder;
+ builder.append(method_name());
+ builder.append(' ');
+ builder.append(m_url.path());
+ if (!m_url.query().is_empty()) {
+ builder.append('?');
+ builder.append(m_url.query());
+ }
+ builder.append(" HTTP/1.1\r\nHost: ");
+ builder.append(m_url.host());
+ builder.append("\r\n");
+ for (auto& header : m_headers) {
+ builder.append(header.name);
+ builder.append(": ");
+ builder.append(header.value);
+ builder.append("\r\n");
+ }
+ builder.append("Connection: close\r\n");
+ if (!m_body.is_empty()) {
+ builder.appendff("Content-Length: {}\r\n\r\n", m_body.size());
+ builder.append((const char*)m_body.data(), m_body.size());
+ }
+ builder.append("\r\n");
+ return builder.to_byte_buffer();
+}
+
+Optional<HttpRequest> HttpRequest::from_raw_request(ReadonlyBytes raw_request)
+{
+ enum class State {
+ InMethod,
+ InResource,
+ InProtocol,
+ InHeaderName,
+ InHeaderValue,
+ };
+
+ State state { State::InMethod };
+ size_t index = 0;
+
+ auto peek = [&](int offset = 0) -> u8 {
+ if (index + offset >= raw_request.size())
+ return 0;
+ return raw_request[index + offset];
+ };
+
+ auto consume = [&]() -> u8 {
+ ASSERT(index < raw_request.size());
+ return raw_request[index++];
+ };
+
+ Vector<u8, 256> buffer;
+
+ String method;
+ String resource;
+ String protocol;
+ Vector<Header> headers;
+ Header current_header;
+
+ auto commit_and_advance_to = [&](auto& output, State new_state) {
+ output = String::copy(buffer);
+ buffer.clear();
+ state = new_state;
+ };
+
+ while (index < raw_request.size()) {
+ // FIXME: Figure out what the appropriate limitations should be.
+ if (buffer.size() > 65536)
+ return {};
+ switch (state) {
+ case State::InMethod:
+ if (peek() == ' ') {
+ consume();
+ commit_and_advance_to(method, State::InResource);
+ break;
+ }
+ buffer.append(consume());
+ break;
+ case State::InResource:
+ if (peek() == ' ') {
+ consume();
+ commit_and_advance_to(resource, State::InProtocol);
+ break;
+ }
+ buffer.append(consume());
+ break;
+ case State::InProtocol:
+ if (peek(0) == '\r' && peek(1) == '\n') {
+ consume();
+ consume();
+ commit_and_advance_to(protocol, State::InHeaderName);
+ break;
+ }
+ buffer.append(consume());
+ break;
+ case State::InHeaderName:
+ if (peek(0) == ':' && peek(1) == ' ') {
+ consume();
+ consume();
+ commit_and_advance_to(current_header.name, State::InHeaderValue);
+ break;
+ }
+ buffer.append(consume());
+ break;
+ case State::InHeaderValue:
+ if (peek(0) == '\r' && peek(1) == '\n') {
+ consume();
+ consume();
+ commit_and_advance_to(current_header.value, State::InHeaderName);
+ headers.append(move(current_header));
+ break;
+ }
+ buffer.append(consume());
+ break;
+ }
+ }
+
+ HttpRequest request;
+ if (method == "GET")
+ request.m_method = Method::GET;
+ else if (method == "HEAD")
+ request.m_method = Method::HEAD;
+ else if (method == "POST")
+ request.m_method = Method::POST;
+ else
+ return {};
+
+ request.m_resource = resource;
+ request.m_headers = move(headers);
+
+ return request;
+}
+
+void HttpRequest::set_headers(const HashMap<String, String>& headers)
+{
+ for (auto& it : headers)
+ m_headers.append({ it.key, it.value });
+}
+
+}
diff --git a/Userland/Libraries/LibHTTP/HttpRequest.h b/Userland/Libraries/LibHTTP/HttpRequest.h
new file mode 100644
index 0000000000..c1a92d33eb
--- /dev/null
+++ b/Userland/Libraries/LibHTTP/HttpRequest.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/Optional.h>
+#include <AK/String.h>
+#include <AK/URL.h>
+#include <AK/Vector.h>
+#include <LibCore/Forward.h>
+
+namespace HTTP {
+
+class HttpRequest {
+public:
+ enum Method {
+ Invalid,
+ HEAD,
+ GET,
+ POST
+ };
+
+ struct Header {
+ String name;
+ String value;
+ };
+
+ HttpRequest();
+ ~HttpRequest();
+
+ const String& resource() const { return m_resource; }
+ const Vector<Header>& headers() const { return m_headers; }
+
+ const URL& url() const { return m_url; }
+ void set_url(const URL& url) { m_url = url; }
+
+ Method method() const { return m_method; }
+ void set_method(Method method) { m_method = method; }
+
+ const ByteBuffer& body() const { return m_body; }
+ void set_body(ReadonlyBytes body) { m_body = ByteBuffer::copy(body); }
+ void set_body(ByteBuffer&& body) { m_body = move(body); }
+
+ String method_name() const;
+ ByteBuffer to_raw_request() const;
+
+ void set_headers(const HashMap<String, String>&);
+
+ static Optional<HttpRequest> from_raw_request(ReadonlyBytes);
+
+private:
+ URL m_url;
+ String m_resource;
+ Method m_method { GET };
+ Vector<Header> m_headers;
+ ByteBuffer m_body;
+};
+
+}
diff --git a/Userland/Libraries/LibHTTP/HttpResponse.cpp b/Userland/Libraries/LibHTTP/HttpResponse.cpp
new file mode 100644
index 0000000000..61571577ca
--- /dev/null
+++ b/Userland/Libraries/LibHTTP/HttpResponse.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibHTTP/HttpResponse.h>
+
+namespace HTTP {
+
+HttpResponse::HttpResponse(int code, HashMap<String, String, CaseInsensitiveStringTraits>&& headers)
+ : m_code(code)
+ , m_headers(move(headers))
+{
+}
+
+HttpResponse::~HttpResponse()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibHTTP/HttpResponse.h b/Userland/Libraries/LibHTTP/HttpResponse.h
new file mode 100644
index 0000000000..a5e9fa74dc
--- /dev/null
+++ b/Userland/Libraries/LibHTTP/HttpResponse.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/String.h>
+#include <LibCore/NetworkResponse.h>
+
+namespace HTTP {
+
+class HttpResponse : public Core::NetworkResponse {
+public:
+ virtual ~HttpResponse() override;
+ static NonnullRefPtr<HttpResponse> create(int code, HashMap<String, String, CaseInsensitiveStringTraits>&& headers)
+ {
+ return adopt(*new HttpResponse(code, move(headers)));
+ }
+
+ int code() const { return m_code; }
+ const HashMap<String, String, CaseInsensitiveStringTraits>& headers() const { return m_headers; }
+
+private:
+ HttpResponse(int code, HashMap<String, String, CaseInsensitiveStringTraits>&&);
+
+ int m_code { 0 };
+ HashMap<String, String, CaseInsensitiveStringTraits> m_headers;
+};
+
+}
diff --git a/Userland/Libraries/LibHTTP/HttpsJob.cpp b/Userland/Libraries/LibHTTP/HttpsJob.cpp
new file mode 100644
index 0000000000..ab12ee971a
--- /dev/null
+++ b/Userland/Libraries/LibHTTP/HttpsJob.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/EventLoop.h>
+#include <LibCore/Gzip.h>
+#include <LibHTTP/HttpResponse.h>
+#include <LibHTTP/HttpsJob.h>
+#include <LibTLS/TLSv12.h>
+#include <stdio.h>
+#include <unistd.h>
+
+//#define HTTPSJOB_DEBUG
+
+namespace HTTP {
+
+void HttpsJob::start()
+{
+ ASSERT(!m_socket);
+ m_socket = TLS::TLSv12::construct(this);
+ m_socket->set_root_certificates(m_override_ca_certificates ? *m_override_ca_certificates : DefaultRootCACertificates::the().certificates());
+ m_socket->on_tls_connected = [this] {
+#ifdef HTTPSJOB_DEBUG
+ dbgln("HttpsJob: on_connected callback");
+#endif
+ on_socket_connected();
+ };
+ m_socket->on_tls_error = [&](TLS::AlertDescription error) {
+ if (error == TLS::AlertDescription::HandshakeFailure) {
+ deferred_invoke([this](auto&) {
+ return did_fail(Core::NetworkJob::Error::ProtocolFailed);
+ });
+ } else if (error == TLS::AlertDescription::DecryptError) {
+ deferred_invoke([this](auto&) {
+ return did_fail(Core::NetworkJob::Error::ConnectionFailed);
+ });
+ } else {
+ deferred_invoke([this](auto&) {
+ return did_fail(Core::NetworkJob::Error::TransmissionFailed);
+ });
+ }
+ };
+ m_socket->on_tls_finished = [&] {
+ finish_up();
+ };
+ m_socket->on_tls_certificate_request = [this](auto&) {
+ if (on_certificate_requested)
+ on_certificate_requested(*this);
+ };
+ bool success = ((TLS::TLSv12&)*m_socket).connect(m_request.url().host(), m_request.url().port());
+ if (!success) {
+ deferred_invoke([this](auto&) {
+ return did_fail(Core::NetworkJob::Error::ConnectionFailed);
+ });
+ }
+}
+
+void HttpsJob::shutdown()
+{
+ if (!m_socket)
+ return;
+ m_socket->on_tls_ready_to_read = nullptr;
+ m_socket->on_tls_connected = nullptr;
+ remove_child(*m_socket);
+ m_socket = nullptr;
+}
+
+void HttpsJob::set_certificate(String certificate, String private_key)
+{
+ if (!m_socket->add_client_key(certificate.bytes(), private_key.bytes())) {
+ dbgln("LibHTTP: Failed to set a client certificate");
+ // FIXME: Do something about this failure
+ ASSERT_NOT_REACHED();
+ }
+}
+
+void HttpsJob::read_while_data_available(Function<IterationDecision()> read)
+{
+ while (m_socket->can_read()) {
+ if (read() == IterationDecision::Break)
+ break;
+ }
+}
+
+void HttpsJob::register_on_ready_to_read(Function<void()> callback)
+{
+ m_socket->on_tls_ready_to_read = [callback = move(callback)](auto&) {
+ callback();
+ };
+}
+
+void HttpsJob::register_on_ready_to_write(Function<void()> callback)
+{
+ m_socket->on_tls_ready_to_write = [callback = move(callback)](auto&) {
+ callback();
+ };
+}
+
+bool HttpsJob::can_read_line() const
+{
+ return m_socket->can_read_line();
+}
+
+String HttpsJob::read_line(size_t size)
+{
+ return m_socket->read_line(size);
+}
+
+ByteBuffer HttpsJob::receive(size_t size)
+{
+ return m_socket->read(size);
+}
+
+bool HttpsJob::can_read() const
+{
+ return m_socket->can_read();
+}
+
+bool HttpsJob::eof() const
+{
+ return m_socket->eof();
+}
+
+bool HttpsJob::write(ReadonlyBytes data)
+{
+ return m_socket->write(data);
+}
+
+}
diff --git a/Userland/Libraries/LibHTTP/HttpsJob.h b/Userland/Libraries/LibHTTP/HttpsJob.h
new file mode 100644
index 0000000000..7ead5fbf48
--- /dev/null
+++ b/Userland/Libraries/LibHTTP/HttpsJob.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <LibCore/NetworkJob.h>
+#include <LibHTTP/HttpRequest.h>
+#include <LibHTTP/HttpResponse.h>
+#include <LibHTTP/Job.h>
+#include <LibTLS/TLSv12.h>
+
+namespace HTTP {
+
+class HttpsJob final : public Job {
+ C_OBJECT(HttpsJob)
+public:
+ explicit HttpsJob(const HttpRequest& request, OutputStream& output_stream, const Vector<Certificate>* override_certs = nullptr)
+ : Job(request, output_stream)
+ , m_override_ca_certificates(override_certs)
+ {
+ }
+
+ virtual ~HttpsJob() override
+ {
+ }
+
+ virtual void start() override;
+ virtual void shutdown() override;
+ void set_certificate(String certificate, String key);
+
+ Function<void(HttpsJob&)> on_certificate_requested;
+
+protected:
+ virtual void register_on_ready_to_read(Function<void()>) override;
+ virtual void register_on_ready_to_write(Function<void()>) override;
+ virtual bool can_read_line() const override;
+ virtual String read_line(size_t) override;
+ virtual bool can_read() const override;
+ virtual ByteBuffer receive(size_t) override;
+ virtual bool eof() const override;
+ virtual bool write(ReadonlyBytes) override;
+ virtual bool is_established() const override { return m_socket->is_established(); }
+ virtual bool should_fail_on_empty_payload() const override { return false; }
+ virtual void read_while_data_available(Function<IterationDecision()>) override;
+
+private:
+ RefPtr<TLS::TLSv12> m_socket;
+ const Vector<Certificate>* m_override_ca_certificates { nullptr };
+};
+
+}
diff --git a/Userland/Libraries/LibHTTP/Job.cpp b/Userland/Libraries/LibHTTP/Job.cpp
new file mode 100644
index 0000000000..af3f53c19b
--- /dev/null
+++ b/Userland/Libraries/LibHTTP/Job.cpp
@@ -0,0 +1,405 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/Gzip.h>
+#include <LibCore/TCPSocket.h>
+#include <LibHTTP/HttpResponse.h>
+#include <LibHTTP/Job.h>
+#include <stdio.h>
+#include <unistd.h>
+
+//#define JOB_DEBUG
+
+namespace HTTP {
+
+static ByteBuffer handle_content_encoding(const ByteBuffer& buf, const String& content_encoding)
+{
+#ifdef JOB_DEBUG
+ dbg() << "Job::handle_content_encoding: buf has content_encoding = " << content_encoding;
+#endif
+
+ if (content_encoding == "gzip") {
+ if (!Core::Gzip::is_compressed(buf)) {
+ dbgln("Job::handle_content_encoding: buf is not gzip compressed!");
+ }
+
+#ifdef JOB_DEBUG
+ dbgln("Job::handle_content_encoding: buf is gzip compressed!");
+#endif
+
+ auto uncompressed = Core::Gzip::decompress(buf);
+ if (!uncompressed.has_value()) {
+ dbgln("Job::handle_content_encoding: Gzip::decompress() failed. Returning original buffer.");
+ return buf;
+ }
+
+#ifdef JOB_DEBUG
+ dbg() << "Job::handle_content_encoding: Gzip::decompress() successful.\n"
+ << " Input size = " << buf.size() << "\n"
+ << " Output size = " << uncompressed.value().size();
+#endif
+
+ return uncompressed.value();
+ }
+
+ return buf;
+}
+
+Job::Job(const HttpRequest& request, OutputStream& output_stream)
+ : Core::NetworkJob(output_stream)
+ , m_request(request)
+{
+}
+
+Job::~Job()
+{
+}
+
+void Job::flush_received_buffers()
+{
+ if (!m_can_stream_response || m_buffered_size == 0)
+ return;
+#ifdef JOB_DEBUG
+ dbg() << "Job: Flushing received buffers: have " << m_buffered_size << " bytes in " << m_received_buffers.size() << " buffers";
+#endif
+ for (size_t i = 0; i < m_received_buffers.size(); ++i) {
+ auto& payload = m_received_buffers[i];
+ auto written = do_write(payload);
+ m_buffered_size -= written;
+ if (written == payload.size()) {
+ // FIXME: Make this a take-first-friendly object?
+ m_received_buffers.take_first();
+ --i;
+ continue;
+ }
+ ASSERT(written < payload.size());
+ payload = payload.slice(written, payload.size() - written);
+#ifdef JOB_DEBUG
+ dbg() << "Job: Flushing received buffers done: have " << m_buffered_size << " bytes in " << m_received_buffers.size() << " buffers";
+#endif
+ return;
+ }
+#ifdef JOB_DEBUG
+ dbg() << "Job: Flushing received buffers done: have " << m_buffered_size << " bytes in " << m_received_buffers.size() << " buffers";
+#endif
+}
+
+void Job::on_socket_connected()
+{
+ register_on_ready_to_write([&] {
+ if (m_sent_data)
+ return;
+ m_sent_data = true;
+ auto raw_request = m_request.to_raw_request();
+#ifdef JOB_DEBUG
+ dbgln("Job: raw_request:");
+ dbg() << String::copy(raw_request).characters();
+#endif
+ bool success = write(raw_request);
+ if (!success)
+ deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::TransmissionFailed); });
+ });
+ register_on_ready_to_read([&] {
+ if (is_cancelled())
+ return;
+
+ if (m_state == State::Finished) {
+ // This is probably just a EOF notification, which means we should receive nothing
+ // and then get eof() == true.
+ [[maybe_unused]] auto payload = receive(64);
+ // These assertions are only correct if "Connection: close".
+ ASSERT(payload.is_empty());
+ ASSERT(eof());
+ return;
+ }
+
+ if (m_state == State::InStatus) {
+ if (!can_read_line())
+ return;
+ auto line = read_line(PAGE_SIZE);
+ if (line.is_null()) {
+ fprintf(stderr, "Job: Expected HTTP status\n");
+ return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::TransmissionFailed); });
+ }
+ auto parts = line.split_view(' ');
+ if (parts.size() < 3) {
+ warnln("Job: Expected 3-part HTTP status, got '{}'", line);
+ return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); });
+ }
+ auto code = parts[1].to_uint();
+ if (!code.has_value()) {
+ fprintf(stderr, "Job: Expected numeric HTTP status\n");
+ return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); });
+ }
+ m_code = code.value();
+ m_state = State::InHeaders;
+ return;
+ }
+ if (m_state == State::InHeaders || m_state == State::Trailers) {
+ if (!can_read_line())
+ return;
+ auto line = read_line(PAGE_SIZE);
+ if (line.is_null()) {
+ if (m_state == State::Trailers) {
+ // Some servers like to send two ending chunks
+ // use this fact as an excuse to ignore anything after the last chunk
+ // that is not a valid trailing header.
+ return finish_up();
+ }
+ fprintf(stderr, "Job: Expected HTTP header\n");
+ return did_fail(Core::NetworkJob::Error::ProtocolFailed);
+ }
+ if (line.is_empty()) {
+ if (m_state == State::Trailers) {
+ return finish_up();
+ } else {
+ if (on_headers_received)
+ on_headers_received(m_headers, m_code > 0 ? m_code : Optional<u32> {});
+ m_state = State::InBody;
+ }
+ return;
+ }
+ auto parts = line.split_view(':');
+ if (parts.is_empty()) {
+ if (m_state == State::Trailers) {
+ // Some servers like to send two ending chunks
+ // use this fact as an excuse to ignore anything after the last chunk
+ // that is not a valid trailing header.
+ return finish_up();
+ }
+ fprintf(stderr, "Job: Expected HTTP header with key/value\n");
+ return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); });
+ }
+ auto name = parts[0];
+ if (line.length() < name.length() + 2) {
+ if (m_state == State::Trailers) {
+ // Some servers like to send two ending chunks
+ // use this fact as an excuse to ignore anything after the last chunk
+ // that is not a valid trailing header.
+ return finish_up();
+ }
+ warnln("Job: Malformed HTTP header: '{}' ({})", line, line.length());
+ return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); });
+ }
+ auto value = line.substring(name.length() + 2, line.length() - name.length() - 2);
+ m_headers.set(name, value);
+ if (name.equals_ignoring_case("Content-Encoding")) {
+ // Assume that any content-encoding means that we can't decode it as a stream :(
+#ifdef JOB_DEBUG
+ dbg() << "Content-Encoding " << value << " detected, cannot stream output :(";
+#endif
+ m_can_stream_response = false;
+ }
+#ifdef JOB_DEBUG
+ dbg() << "Job: [" << name << "] = '" << value << "'";
+#endif
+ return;
+ }
+ ASSERT(m_state == State::InBody);
+ ASSERT(can_read());
+
+ read_while_data_available([&] {
+ auto read_size = 64 * KiB;
+ if (m_current_chunk_remaining_size.has_value()) {
+ read_chunk_size:;
+ auto remaining = m_current_chunk_remaining_size.value();
+ if (remaining == -1) {
+ // read size
+ auto size_data = read_line(PAGE_SIZE);
+ auto size_lines = size_data.view().lines();
+#ifdef JOB_DEBUG
+ dbg() << "Job: Received a chunk with size _" << size_data << "_";
+#endif
+ if (size_lines.size() == 0) {
+ dbgln("Job: Reached end of stream");
+ finish_up();
+ return IterationDecision::Break;
+ } else {
+ auto chunk = size_lines[0].split_view(';', true);
+ String size_string = chunk[0];
+ char* endptr;
+ auto size = strtoul(size_string.characters(), &endptr, 16);
+ if (*endptr) {
+ // invalid number
+ deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::TransmissionFailed); });
+ return IterationDecision::Break;
+ }
+ if (size == 0) {
+ // This is the last chunk
+ // '0' *[; chunk-ext-name = chunk-ext-value]
+ // We're going to ignore _all_ chunk extensions
+ read_size = 0;
+ m_current_chunk_total_size = 0;
+ m_current_chunk_remaining_size = 0;
+#ifdef JOB_DEBUG
+ dbg() << "Job: Received the last chunk with extensions _" << size_string.substring_view(1, size_string.length() - 1) << "_";
+#endif
+ } else {
+ m_current_chunk_total_size = size;
+ m_current_chunk_remaining_size = size;
+ read_size = size;
+#ifdef JOB_DEBUG
+ dbg() << "Job: Chunk of size _" << size << "_ started";
+#endif
+ }
+ }
+ } else {
+ read_size = remaining;
+#ifdef JOB_DEBUG
+ dbg() << "Job: Resuming chunk with _" << remaining << "_ bytes left over";
+#endif
+ }
+ } else {
+ auto transfer_encoding = m_headers.get("Transfer-Encoding");
+ if (transfer_encoding.has_value()) {
+ auto encoding = transfer_encoding.value();
+#ifdef JOB_DEBUG
+ dbg() << "Job: This content has transfer encoding '" << encoding << "'";
+#endif
+ if (encoding.equals_ignoring_case("chunked")) {
+ m_current_chunk_remaining_size = -1;
+ goto read_chunk_size;
+ } else {
+ dbg() << "Job: Unknown transfer encoding _" << encoding << "_, the result will likely be wrong!";
+ }
+ }
+ }
+
+ auto payload = receive(read_size);
+ if (!payload) {
+ if (eof()) {
+ finish_up();
+ return IterationDecision::Break;
+ }
+
+ if (should_fail_on_empty_payload()) {
+ deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); });
+ return IterationDecision::Break;
+ }
+ }
+
+ m_received_buffers.append(payload);
+ m_buffered_size += payload.size();
+ m_received_size += payload.size();
+ flush_received_buffers();
+
+ if (m_current_chunk_remaining_size.has_value()) {
+ auto size = m_current_chunk_remaining_size.value() - payload.size();
+#ifdef JOB_DEBUG
+ dbg() << "Job: We have " << size << " bytes left over in this chunk";
+#endif
+ if (size == 0) {
+#ifdef JOB_DEBUG
+ dbg() << "Job: Finished a chunk of " << m_current_chunk_total_size.value() << " bytes";
+#endif
+
+ if (m_current_chunk_total_size.value() == 0) {
+ m_state = State::Trailers;
+ return IterationDecision::Break;
+ }
+
+ // we've read everything, now let's get the next chunk
+ size = -1;
+ [[maybe_unused]] auto line = read_line(PAGE_SIZE);
+#ifdef JOB_DEBUG
+ dbg() << "Line following (should be empty): _" << line << "_";
+#endif
+ }
+ m_current_chunk_remaining_size = size;
+ }
+
+ auto content_length_header = m_headers.get("Content-Length");
+ Optional<u32> content_length {};
+
+ if (content_length_header.has_value()) {
+ auto length = content_length_header.value().to_uint();
+ if (length.has_value())
+ content_length = length.value();
+ }
+
+ deferred_invoke([this, content_length](auto&) { did_progress(content_length, m_received_size); });
+
+ if (content_length.has_value()) {
+ auto length = content_length.value();
+ if (m_received_size >= length) {
+ m_received_size = length;
+ finish_up();
+ return IterationDecision::Break;
+ }
+ }
+ return IterationDecision::Continue;
+ });
+
+ if (!is_established()) {
+#ifdef JOB_DEBUG
+ dbgln("Connection appears to have closed, finishing up");
+#endif
+ finish_up();
+ }
+ });
+}
+
+void Job::finish_up()
+{
+ m_state = State::Finished;
+ if (!m_can_stream_response) {
+ auto flattened_buffer = ByteBuffer::create_uninitialized(m_received_size);
+ u8* flat_ptr = flattened_buffer.data();
+ for (auto& received_buffer : m_received_buffers) {
+ memcpy(flat_ptr, received_buffer.data(), received_buffer.size());
+ flat_ptr += received_buffer.size();
+ }
+ m_received_buffers.clear();
+
+ // For the time being, we cannot stream stuff with content-encoding set to _anything_.
+ auto content_encoding = m_headers.get("Content-Encoding");
+ if (content_encoding.has_value()) {
+ flattened_buffer = handle_content_encoding(flattened_buffer, content_encoding.value());
+ }
+
+ m_buffered_size = flattened_buffer.size();
+ m_received_buffers.append(move(flattened_buffer));
+ m_can_stream_response = true;
+ }
+
+ flush_received_buffers();
+ if (m_buffered_size != 0) {
+ // We have to wait for the client to consume all the downloaded data
+ // before we can actually call `did_finish`. in a normal flow, this should
+ // never be hit since the client is reading as we are writing, unless there
+ // are too many concurrent downloads going on.
+ deferred_invoke([this](auto&) {
+ finish_up();
+ });
+ return;
+ }
+
+ auto response = HttpResponse::create(m_code, move(m_headers));
+ deferred_invoke([this, response](auto&) {
+ did_finish(move(response));
+ });
+}
+}
diff --git a/Userland/Libraries/LibHTTP/Job.h b/Userland/Libraries/LibHTTP/Job.h
new file mode 100644
index 0000000000..b57b220396
--- /dev/null
+++ b/Userland/Libraries/LibHTTP/Job.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FileStream.h>
+#include <AK/HashMap.h>
+#include <AK/Optional.h>
+#include <LibCore/NetworkJob.h>
+#include <LibCore/TCPSocket.h>
+#include <LibHTTP/HttpRequest.h>
+#include <LibHTTP/HttpResponse.h>
+
+namespace HTTP {
+
+class Job : public Core::NetworkJob {
+public:
+ explicit Job(const HttpRequest&, OutputStream&);
+ virtual ~Job() override;
+
+ virtual void start() override = 0;
+ virtual void shutdown() override = 0;
+
+ HttpResponse* response() { return static_cast<HttpResponse*>(Core::NetworkJob::response()); }
+ const HttpResponse* response() const { return static_cast<const HttpResponse*>(Core::NetworkJob::response()); }
+
+protected:
+ void finish_up();
+ void on_socket_connected();
+ void flush_received_buffers();
+ virtual void register_on_ready_to_read(Function<void()>) = 0;
+ virtual void register_on_ready_to_write(Function<void()>) = 0;
+ virtual bool can_read_line() const = 0;
+ virtual String read_line(size_t) = 0;
+ virtual bool can_read() const = 0;
+ virtual ByteBuffer receive(size_t) = 0;
+ virtual bool eof() const = 0;
+ virtual bool write(ReadonlyBytes) = 0;
+ virtual bool is_established() const = 0;
+ virtual bool should_fail_on_empty_payload() const { return true; }
+ virtual void read_while_data_available(Function<IterationDecision()> read) { read(); };
+
+ enum class State {
+ InStatus,
+ InHeaders,
+ InBody,
+ Trailers,
+ Finished,
+ };
+
+ HttpRequest m_request;
+ State m_state { State::InStatus };
+ int m_code { -1 };
+ HashMap<String, String, CaseInsensitiveStringTraits> m_headers;
+ Vector<ByteBuffer, 2> m_received_buffers;
+ size_t m_buffered_size { 0 };
+ size_t m_received_size { 0 };
+ bool m_sent_data { 0 };
+ Optional<ssize_t> m_current_chunk_remaining_size;
+ Optional<size_t> m_current_chunk_total_size;
+ bool m_can_stream_response { true };
+};
+
+}
diff --git a/Userland/Libraries/LibIPC/CMakeLists.txt b/Userland/Libraries/LibIPC/CMakeLists.txt
new file mode 100644
index 0000000000..ea1a23640e
--- /dev/null
+++ b/Userland/Libraries/LibIPC/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(SOURCES
+ Decoder.cpp
+ Encoder.cpp
+ Endpoint.cpp
+ Message.cpp
+)
+
+serenity_lib(LibIPC ipc)
+target_link_libraries(LibIPC LibC LibCore)
diff --git a/Userland/Libraries/LibIPC/ClientConnection.h b/Userland/Libraries/LibIPC/ClientConnection.h
new file mode 100644
index 0000000000..a970ebec0e
--- /dev/null
+++ b/Userland/Libraries/LibIPC/ClientConnection.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibIPC/Connection.h>
+
+namespace IPC {
+
+template<typename T, class... Args>
+NonnullRefPtr<T> new_client_connection(Args&&... args)
+{
+ return T::construct(forward<Args>(args)...) /* arghs */;
+}
+
+template<typename ClientEndpoint, typename ServerEndpoint>
+class ClientConnection : public Connection<ServerEndpoint, ClientEndpoint> {
+public:
+ ClientConnection(ServerEndpoint& endpoint, NonnullRefPtr<Core::LocalSocket> socket, int client_id)
+ : IPC::Connection<ServerEndpoint, ClientEndpoint>(endpoint, move(socket))
+ , m_client_id(client_id)
+ {
+ ASSERT(this->socket().is_connected());
+ this->socket().on_ready_to_read = [this] { this->drain_messages_from_peer(); };
+ this->initialize_peer_info();
+ }
+
+ virtual ~ClientConnection() override
+ {
+ }
+
+ void did_misbehave()
+ {
+ dbg() << *this << " (id=" << m_client_id << ", pid=" << client_pid() << ") misbehaved, disconnecting.";
+ this->shutdown();
+ }
+
+ void did_misbehave(const char* message)
+ {
+ dbg() << *this << " (id=" << m_client_id << ", pid=" << client_pid() << ") misbehaved (" << message << "), disconnecting.";
+ this->shutdown();
+ }
+
+ int client_id() const { return m_client_id; }
+
+ pid_t client_pid() const { return this->peer_pid(); }
+ void set_client_pid(pid_t pid) { this->set_peer_pid(pid); }
+
+ virtual void die() = 0;
+
+private:
+ int m_client_id { -1 };
+};
+
+}
diff --git a/Userland/Libraries/LibIPC/Connection.h b/Userland/Libraries/LibIPC/Connection.h
new file mode 100644
index 0000000000..6ceba10ea3
--- /dev/null
+++ b/Userland/Libraries/LibIPC/Connection.h
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <LibCore/Event.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/LocalSocket.h>
+#include <LibCore/Notifier.h>
+#include <LibCore/SyscallUtils.h>
+#include <LibCore/Timer.h>
+#include <LibIPC/Message.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+namespace IPC {
+
+template<typename LocalEndpoint, typename PeerEndpoint>
+class Connection : public Core::Object {
+public:
+ Connection(LocalEndpoint& local_endpoint, NonnullRefPtr<Core::LocalSocket> socket)
+ : m_local_endpoint(local_endpoint)
+ , m_socket(move(socket))
+ , m_notifier(Core::Notifier::construct(m_socket->fd(), Core::Notifier::Read, this))
+ {
+ m_responsiveness_timer = Core::Timer::create_single_shot(3000, [this] { may_have_become_unresponsive(); });
+ m_notifier->on_ready_to_read = [this] {
+ drain_messages_from_peer();
+ handle_messages();
+ };
+ }
+
+ pid_t peer_pid() const { return m_peer_pid; }
+
+ template<typename MessageType>
+ OwnPtr<MessageType> wait_for_specific_message()
+ {
+ return wait_for_specific_endpoint_message<MessageType, LocalEndpoint>();
+ }
+
+ void post_message(const Message& message)
+ {
+ // NOTE: If this connection is being shut down, but has not yet been destroyed,
+ // the socket will be closed. Don't try to send more messages.
+ if (!m_socket->is_open())
+ return;
+
+ auto buffer = message.encode();
+ // Prepend the message size.
+ uint32_t message_size = buffer.data.size();
+ buffer.data.prepend(reinterpret_cast<const u8*>(&message_size), sizeof(message_size));
+
+#ifdef __serenity__
+ for (int fd : buffer.fds) {
+ auto rc = sendfd(m_socket->fd(), fd);
+ if (rc < 0) {
+ perror("sendfd");
+ shutdown();
+ }
+ }
+#else
+ if (!buffer.fds.is_empty())
+ warnln("fd passing is not supported on this platform, sorry :(");
+#endif
+
+ size_t total_nwritten = 0;
+ while (total_nwritten < buffer.data.size()) {
+ auto nwritten = write(m_socket->fd(), buffer.data.data() + total_nwritten, buffer.data.size() - total_nwritten);
+ if (nwritten < 0) {
+ switch (errno) {
+ case EPIPE:
+ dbg() << *this << "::post_message: Disconnected from peer";
+ shutdown();
+ return;
+ case EAGAIN:
+ dbg() << *this << "::post_message: Peer buffer overflowed";
+ shutdown();
+ return;
+ default:
+ perror("Connection::post_message write");
+ shutdown();
+ return;
+ }
+ }
+ total_nwritten += nwritten;
+ }
+
+ m_responsiveness_timer->start();
+ }
+
+ template<typename RequestType, typename... Args>
+ OwnPtr<typename RequestType::ResponseType> send_sync(Args&&... args)
+ {
+ post_message(RequestType(forward<Args>(args)...));
+ auto response = wait_for_specific_endpoint_message<typename RequestType::ResponseType, PeerEndpoint>();
+ ASSERT(response);
+ return response;
+ }
+
+ virtual void may_have_become_unresponsive() { }
+ virtual void did_become_responsive() { }
+
+ void shutdown()
+ {
+ m_notifier->close();
+ m_socket->close();
+ die();
+ }
+
+ virtual void die() { }
+
+protected:
+ Core::LocalSocket& socket() { return *m_socket; }
+ void set_peer_pid(pid_t pid) { m_peer_pid = pid; }
+
+ template<typename MessageType, typename Endpoint>
+ OwnPtr<MessageType> wait_for_specific_endpoint_message()
+ {
+ for (;;) {
+ // Double check we don't already have the event waiting for us.
+ // Otherwise we might end up blocked for a while for no reason.
+ for (size_t i = 0; i < m_unprocessed_messages.size(); ++i) {
+ auto& message = m_unprocessed_messages[i];
+ if (message.endpoint_magic() != Endpoint::static_magic())
+ continue;
+ if (message.message_id() == MessageType::static_message_id())
+ return m_unprocessed_messages.take(i).template release_nonnull<MessageType>();
+ }
+
+ if (!m_socket->is_open())
+ break;
+ fd_set rfds;
+ FD_ZERO(&rfds);
+ FD_SET(m_socket->fd(), &rfds);
+ int rc = Core::safe_syscall(select, m_socket->fd() + 1, &rfds, nullptr, nullptr, nullptr);
+ if (rc < 0) {
+ perror("select");
+ }
+ ASSERT(rc > 0);
+ ASSERT(FD_ISSET(m_socket->fd(), &rfds));
+ if (!drain_messages_from_peer())
+ break;
+ }
+ return {};
+ }
+
+ bool drain_messages_from_peer()
+ {
+ Vector<u8> bytes;
+
+ if (!m_unprocessed_bytes.is_empty()) {
+ bytes.append(m_unprocessed_bytes.data(), m_unprocessed_bytes.size());
+ m_unprocessed_bytes.clear();
+ }
+
+ while (m_socket->is_open()) {
+ u8 buffer[4096];
+ ssize_t nread = recv(m_socket->fd(), buffer, sizeof(buffer), MSG_DONTWAIT);
+ if (nread < 0) {
+ if (errno == EAGAIN)
+ break;
+ perror("recv");
+ exit(1);
+ return false;
+ }
+ if (nread == 0) {
+ if (bytes.is_empty()) {
+ deferred_invoke([this](auto&) { die(); });
+ }
+ return false;
+ }
+ bytes.append(buffer, nread);
+ }
+
+ if (!bytes.is_empty()) {
+ m_responsiveness_timer->stop();
+ did_become_responsive();
+ }
+
+ size_t index = 0;
+ uint32_t message_size = 0;
+ for (; index + sizeof(message_size) < bytes.size(); index += message_size) {
+ message_size = *reinterpret_cast<uint32_t*>(bytes.data() + index);
+ if (message_size == 0 || bytes.size() - index - sizeof(uint32_t) < message_size)
+ break;
+ index += sizeof(message_size);
+ auto remaining_bytes = ReadonlyBytes { bytes.data() + index, bytes.size() - index };
+ if (auto message = LocalEndpoint::decode_message(remaining_bytes, m_socket->fd())) {
+ m_unprocessed_messages.append(message.release_nonnull());
+ } else if (auto message = PeerEndpoint::decode_message(remaining_bytes, m_socket->fd())) {
+ m_unprocessed_messages.append(message.release_nonnull());
+ } else {
+ dbgln("Failed to parse a message");
+ break;
+ }
+ }
+
+ if (index < bytes.size()) {
+ // Sometimes we might receive a partial message. That's okay, just stash away
+ // the unprocessed bytes and we'll prepend them to the next incoming message
+ // in the next run of this function.
+ auto remaining_bytes = ByteBuffer::copy(bytes.data() + index, bytes.size() - index);
+ if (!m_unprocessed_bytes.is_empty()) {
+ dbg() << *this << "::drain_messages_from_peer: Already have unprocessed bytes";
+ shutdown();
+ return false;
+ }
+ m_unprocessed_bytes = remaining_bytes;
+ }
+
+ if (!m_unprocessed_messages.is_empty()) {
+ deferred_invoke([this](auto&) {
+ handle_messages();
+ });
+ }
+ return true;
+ }
+
+ void handle_messages()
+ {
+ auto messages = move(m_unprocessed_messages);
+ for (auto& message : messages) {
+ if (message.endpoint_magic() == LocalEndpoint::static_magic())
+ if (auto response = m_local_endpoint.handle(message))
+ post_message(*response);
+ }
+ }
+
+protected:
+ void initialize_peer_info()
+ {
+ ucred creds;
+ socklen_t creds_size = sizeof(creds);
+ if (getsockopt(this->socket().fd(), SOL_SOCKET, SO_PEERCRED, &creds, &creds_size) < 0) {
+ // FIXME: We should handle this more gracefully.
+ ASSERT_NOT_REACHED();
+ }
+ m_peer_pid = creds.pid;
+ }
+
+ LocalEndpoint& m_local_endpoint;
+ NonnullRefPtr<Core::LocalSocket> m_socket;
+ RefPtr<Core::Timer> m_responsiveness_timer;
+
+ RefPtr<Core::Notifier> m_notifier;
+ NonnullOwnPtrVector<Message> m_unprocessed_messages;
+ ByteBuffer m_unprocessed_bytes;
+ pid_t m_peer_pid { -1 };
+};
+
+}
diff --git a/Userland/Libraries/LibIPC/Decoder.cpp b/Userland/Libraries/LibIPC/Decoder.cpp
new file mode 100644
index 0000000000..ff980bdaa5
--- /dev/null
+++ b/Userland/Libraries/LibIPC/Decoder.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/MemoryStream.h>
+#include <AK/URL.h>
+#include <LibIPC/Decoder.h>
+#include <LibIPC/Dictionary.h>
+#include <LibIPC/File.h>
+#include <string.h>
+#include <sys/socket.h>
+
+namespace IPC {
+
+bool Decoder::decode(bool& value)
+{
+ m_stream >> value;
+ return !m_stream.handle_any_error();
+}
+
+bool Decoder::decode(u8& value)
+{
+ m_stream >> value;
+ return !m_stream.handle_any_error();
+}
+
+bool Decoder::decode(u16& value)
+{
+ m_stream >> value;
+ return !m_stream.handle_any_error();
+}
+
+bool Decoder::decode(u32& value)
+{
+ m_stream >> value;
+ return !m_stream.handle_any_error();
+}
+
+bool Decoder::decode(u64& value)
+{
+ m_stream >> value;
+ return !m_stream.handle_any_error();
+}
+
+bool Decoder::decode(i8& value)
+{
+ m_stream >> value;
+ return !m_stream.handle_any_error();
+}
+
+bool Decoder::decode(i16& value)
+{
+ m_stream >> value;
+ return !m_stream.handle_any_error();
+}
+
+bool Decoder::decode(i32& value)
+{
+ m_stream >> value;
+ return !m_stream.handle_any_error();
+}
+
+bool Decoder::decode(i64& value)
+{
+ m_stream >> value;
+ return !m_stream.handle_any_error();
+}
+
+bool Decoder::decode(float& value)
+{
+ m_stream >> value;
+ return !m_stream.handle_any_error();
+}
+
+bool Decoder::decode(String& value)
+{
+ i32 length = 0;
+ m_stream >> length;
+ if (m_stream.handle_any_error())
+ return false;
+ if (length < 0) {
+ value = {};
+ return true;
+ }
+ if (length == 0) {
+ value = String::empty();
+ return true;
+ }
+ char* text_buffer = nullptr;
+ auto text_impl = StringImpl::create_uninitialized(static_cast<size_t>(length), text_buffer);
+ m_stream >> Bytes { text_buffer, static_cast<size_t>(length) };
+ value = *text_impl;
+ return !m_stream.handle_any_error();
+}
+
+bool Decoder::decode(ByteBuffer& value)
+{
+ i32 length = 0;
+ m_stream >> length;
+ if (m_stream.handle_any_error())
+ return false;
+ if (length < 0) {
+ value = {};
+ return true;
+ }
+ if (length == 0) {
+ value = ByteBuffer::create_uninitialized(0);
+ return true;
+ }
+ value = ByteBuffer::create_uninitialized(length);
+ m_stream >> value.bytes();
+ return !m_stream.handle_any_error();
+}
+
+bool Decoder::decode(URL& value)
+{
+ String string;
+ if (!decode(string))
+ return false;
+ value = URL(string);
+ return true;
+}
+
+bool Decoder::decode(Dictionary& dictionary)
+{
+ u64 size = 0;
+ m_stream >> size;
+ if (m_stream.handle_any_error())
+ return false;
+ if (size >= (size_t)NumericLimits<i32>::max()) {
+ ASSERT_NOT_REACHED();
+ }
+
+ for (size_t i = 0; i < size; ++i) {
+ String key;
+ if (!decode(key))
+ return false;
+ String value;
+ if (!decode(value))
+ return false;
+ dictionary.add(move(key), move(value));
+ }
+
+ return true;
+}
+
+bool Decoder::decode([[maybe_unused]] File& file)
+{
+#ifdef __serenity__
+ int fd = recvfd(m_sockfd);
+ if (fd < 0) {
+ dbgln("recvfd: {}", strerror(errno));
+ return false;
+ }
+ file = File(fd);
+ return true;
+#else
+ [[maybe_unused]] auto fd = m_sockfd;
+ warnln("fd passing is not supported on this platform, sorry :(");
+ return false;
+#endif
+}
+
+}
diff --git a/Userland/Libraries/LibIPC/Decoder.h b/Userland/Libraries/LibIPC/Decoder.h
new file mode 100644
index 0000000000..a8752f7890
--- /dev/null
+++ b/Userland/Libraries/LibIPC/Decoder.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/NumericLimits.h>
+#include <AK/StdLibExtras.h>
+#include <AK/String.h>
+#include <LibIPC/Forward.h>
+#include <LibIPC/Message.h>
+
+namespace IPC {
+
+template<typename T>
+inline bool decode(Decoder&, T&)
+{
+ static_assert(DependentFalse<T>, "Base IPC::decoder() instantiated");
+ ASSERT_NOT_REACHED();
+}
+
+class Decoder {
+public:
+ Decoder(InputMemoryStream& stream, int sockfd)
+ : m_stream(stream)
+ , m_sockfd(sockfd)
+ {
+ }
+
+ bool decode(bool&);
+ bool decode(u8&);
+ bool decode(u16&);
+ bool decode(u32&);
+ bool decode(u64&);
+ bool decode(i8&);
+ bool decode(i16&);
+ bool decode(i32&);
+ bool decode(i64&);
+ bool decode(float&);
+ bool decode(String&);
+ bool decode(ByteBuffer&);
+ bool decode(URL&);
+ bool decode(Dictionary&);
+ bool decode(File&);
+ template<typename K, typename V>
+ bool decode(HashMap<K, V>& hashmap)
+ {
+ u32 size;
+ if (!decode(size) || size > NumericLimits<i32>::max())
+ return false;
+
+ for (size_t i = 0; i < size; ++i) {
+ K key;
+ if (!decode(key))
+ return false;
+
+ V value;
+ if (!decode(value))
+ return false;
+
+ hashmap.set(move(key), move(value));
+ }
+ return true;
+ }
+
+ template<typename T>
+ bool decode(T& value)
+ {
+ return IPC::decode(*this, value);
+ }
+
+ template<typename T>
+ bool decode(Vector<T>& vector)
+ {
+ u64 size;
+ if (!decode(size) || size > NumericLimits<i32>::max())
+ return false;
+ for (size_t i = 0; i < size; ++i) {
+ T value;
+ if (!decode(value))
+ return false;
+ vector.append(move(value));
+ }
+ return true;
+ }
+
+ template<typename T>
+ bool decode(Optional<T>& optional)
+ {
+ bool has_value;
+ if (!decode(has_value))
+ return false;
+ if (!has_value) {
+ optional = {};
+ return true;
+ }
+ T value;
+ if (!decode(value))
+ return false;
+ optional = move(value);
+ return true;
+ }
+
+private:
+ InputMemoryStream& m_stream;
+ int m_sockfd { -1 };
+};
+
+}
diff --git a/Userland/Libraries/LibIPC/Dictionary.h b/Userland/Libraries/LibIPC/Dictionary.h
new file mode 100644
index 0000000000..34632a86e9
--- /dev/null
+++ b/Userland/Libraries/LibIPC/Dictionary.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/String.h>
+
+namespace IPC {
+
+class Dictionary {
+public:
+ Dictionary() { }
+
+ Dictionary(const HashMap<String, String>& initial_entries)
+ : m_entries(initial_entries)
+ {
+ }
+
+ bool is_empty() const { return m_entries.is_empty(); }
+ size_t size() const { return m_entries.size(); }
+
+ void add(String key, String value)
+ {
+ m_entries.set(move(key), move(value));
+ }
+
+ template<typename Callback>
+ void for_each_entry(Callback callback) const
+ {
+ for (auto& it : m_entries) {
+ callback(it.key, it.value);
+ }
+ }
+
+ const HashMap<String, String>& entries() const { return m_entries; }
+
+private:
+ HashMap<String, String> m_entries;
+};
+
+}
diff --git a/Userland/Libraries/LibIPC/Encoder.cpp b/Userland/Libraries/LibIPC/Encoder.cpp
new file mode 100644
index 0000000000..240c3d927f
--- /dev/null
+++ b/Userland/Libraries/LibIPC/Encoder.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ByteBuffer.h>
+#include <AK/String.h>
+#include <AK/URL.h>
+#include <LibIPC/Dictionary.h>
+#include <LibIPC/Encoder.h>
+#include <LibIPC/File.h>
+
+namespace IPC {
+
+Encoder& Encoder::operator<<(bool value)
+{
+ return *this << (u8)value;
+}
+
+Encoder& Encoder::operator<<(u8 value)
+{
+ m_buffer.data.append(value);
+ return *this;
+}
+
+Encoder& Encoder::operator<<(u16 value)
+{
+ m_buffer.data.ensure_capacity(m_buffer.data.size() + 2);
+ m_buffer.data.unchecked_append((u8)value);
+ m_buffer.data.unchecked_append((u8)(value >> 8));
+ return *this;
+}
+
+Encoder& Encoder::operator<<(u32 value)
+{
+ m_buffer.data.ensure_capacity(m_buffer.data.size() + 4);
+ m_buffer.data.unchecked_append((u8)value);
+ m_buffer.data.unchecked_append((u8)(value >> 8));
+ m_buffer.data.unchecked_append((u8)(value >> 16));
+ m_buffer.data.unchecked_append((u8)(value >> 24));
+ return *this;
+}
+
+Encoder& Encoder::operator<<(u64 value)
+{
+ m_buffer.data.ensure_capacity(m_buffer.data.size() + 8);
+ m_buffer.data.unchecked_append((u8)value);
+ m_buffer.data.unchecked_append((u8)(value >> 8));
+ m_buffer.data.unchecked_append((u8)(value >> 16));
+ m_buffer.data.unchecked_append((u8)(value >> 24));
+ m_buffer.data.unchecked_append((u8)(value >> 32));
+ m_buffer.data.unchecked_append((u8)(value >> 40));
+ m_buffer.data.unchecked_append((u8)(value >> 48));
+ m_buffer.data.unchecked_append((u8)(value >> 56));
+ return *this;
+}
+
+Encoder& Encoder::operator<<(i8 value)
+{
+ m_buffer.data.append((u8)value);
+ return *this;
+}
+
+Encoder& Encoder::operator<<(i16 value)
+{
+ m_buffer.data.ensure_capacity(m_buffer.data.size() + 2);
+ m_buffer.data.unchecked_append((u8)value);
+ m_buffer.data.unchecked_append((u8)(value >> 8));
+ return *this;
+}
+
+Encoder& Encoder::operator<<(i32 value)
+{
+ m_buffer.data.ensure_capacity(m_buffer.data.size() + 4);
+ m_buffer.data.unchecked_append((u8)value);
+ m_buffer.data.unchecked_append((u8)(value >> 8));
+ m_buffer.data.unchecked_append((u8)(value >> 16));
+ m_buffer.data.unchecked_append((u8)(value >> 24));
+ return *this;
+}
+
+Encoder& Encoder::operator<<(i64 value)
+{
+ m_buffer.data.ensure_capacity(m_buffer.data.size() + 8);
+ m_buffer.data.unchecked_append((u8)value);
+ m_buffer.data.unchecked_append((u8)(value >> 8));
+ m_buffer.data.unchecked_append((u8)(value >> 16));
+ m_buffer.data.unchecked_append((u8)(value >> 24));
+ m_buffer.data.unchecked_append((u8)(value >> 32));
+ m_buffer.data.unchecked_append((u8)(value >> 40));
+ m_buffer.data.unchecked_append((u8)(value >> 48));
+ m_buffer.data.unchecked_append((u8)(value >> 56));
+ return *this;
+}
+
+Encoder& Encoder::operator<<(float value)
+{
+ union bits {
+ float as_float;
+ u32 as_u32;
+ } u;
+ u.as_float = value;
+ return *this << u.as_u32;
+}
+
+Encoder& Encoder::operator<<(const char* value)
+{
+ return *this << StringView(value);
+}
+
+Encoder& Encoder::operator<<(const StringView& value)
+{
+ m_buffer.data.append((const u8*)value.characters_without_null_termination(), value.length());
+ return *this;
+}
+
+Encoder& Encoder::operator<<(const String& value)
+{
+ if (value.is_null())
+ return *this << (i32)-1;
+ *this << static_cast<i32>(value.length());
+ return *this << value.view();
+}
+
+Encoder& Encoder::operator<<(const ByteBuffer& value)
+{
+ *this << static_cast<i32>(value.size());
+ m_buffer.data.append(value.data(), value.size());
+ return *this;
+}
+
+Encoder& Encoder::operator<<(const URL& value)
+{
+ return *this << value.to_string();
+}
+
+Encoder& Encoder::operator<<(const Dictionary& dictionary)
+{
+ *this << (u64)dictionary.size();
+ dictionary.for_each_entry([this](auto& key, auto& value) {
+ *this << key << value;
+ });
+ return *this;
+}
+
+Encoder& Encoder::operator<<(const File& file)
+{
+ m_buffer.fds.append(file.fd());
+ return *this;
+}
+
+}
diff --git a/Userland/Libraries/LibIPC/Encoder.h b/Userland/Libraries/LibIPC/Encoder.h
new file mode 100644
index 0000000000..da8416f5a5
--- /dev/null
+++ b/Userland/Libraries/LibIPC/Encoder.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibIPC/Forward.h>
+#include <LibIPC/Message.h>
+
+namespace IPC {
+
+template<typename T>
+bool encode(Encoder&, T&)
+{
+ static_assert(DependentFalse<T>, "Base IPC::encode() was instantiated");
+ ASSERT_NOT_REACHED();
+}
+
+class Encoder {
+public:
+ explicit Encoder(MessageBuffer& buffer)
+ : m_buffer(buffer)
+ {
+ }
+
+ Encoder& operator<<(bool);
+ Encoder& operator<<(u8);
+ Encoder& operator<<(u16);
+ Encoder& operator<<(u32);
+ Encoder& operator<<(u64);
+ Encoder& operator<<(i8);
+ Encoder& operator<<(i16);
+ Encoder& operator<<(i32);
+ Encoder& operator<<(i64);
+ Encoder& operator<<(float);
+ Encoder& operator<<(const char*);
+ Encoder& operator<<(const StringView&);
+ Encoder& operator<<(const String&);
+ Encoder& operator<<(const ByteBuffer&);
+ Encoder& operator<<(const URL&);
+ Encoder& operator<<(const Dictionary&);
+ Encoder& operator<<(const File&);
+ template<typename K, typename V>
+ Encoder& operator<<(const HashMap<K, V>& hashmap)
+ {
+ *this << (u32)hashmap.size();
+ for (auto it : hashmap) {
+ *this << it.key;
+ *this << it.value;
+ }
+ return *this;
+ }
+
+ template<typename T>
+ Encoder& operator<<(const Vector<T>& vector)
+ {
+ *this << (u64)vector.size();
+ for (auto& value : vector)
+ *this << value;
+ return *this;
+ }
+
+ template<typename T>
+ Encoder& operator<<(const T& value)
+ {
+ encode(value);
+ return *this;
+ }
+
+ template<typename T>
+ Encoder& operator<<(const Optional<T>& optional)
+ {
+ *this << optional.has_value();
+ if (optional.has_value())
+ *this << optional.value();
+ return *this;
+ }
+
+ template<typename T>
+ void encode(const T& value)
+ {
+ IPC::encode(*this, value);
+ }
+
+private:
+ MessageBuffer& m_buffer;
+};
+
+}
diff --git a/Userland/Libraries/LibIPC/Endpoint.cpp b/Userland/Libraries/LibIPC/Endpoint.cpp
new file mode 100644
index 0000000000..8468419fe9
--- /dev/null
+++ b/Userland/Libraries/LibIPC/Endpoint.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibIPC/Endpoint.h>
+
+namespace IPC {
+
+Endpoint::Endpoint()
+{
+}
+
+Endpoint::~Endpoint()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibIPC/Endpoint.h b/Userland/Libraries/LibIPC/Endpoint.h
new file mode 100644
index 0000000000..65ea70a5e8
--- /dev/null
+++ b/Userland/Libraries/LibIPC/Endpoint.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/OwnPtr.h>
+#include <AK/String.h>
+
+namespace AK {
+class BufferStream;
+}
+
+namespace IPC {
+
+class Message;
+
+class Endpoint {
+public:
+ virtual ~Endpoint();
+
+ virtual int magic() const = 0;
+ virtual String name() const = 0;
+ virtual OwnPtr<Message> handle(const Message&) = 0;
+
+protected:
+ Endpoint();
+
+private:
+ String m_name;
+};
+
+}
diff --git a/Userland/Libraries/LibIPC/File.h b/Userland/Libraries/LibIPC/File.h
new file mode 100644
index 0000000000..85c1137ad6
--- /dev/null
+++ b/Userland/Libraries/LibIPC/File.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace IPC {
+
+class File {
+public:
+ // Must have a default constructor, because LibIPC
+ // default-constructs arguments prior to decoding them.
+ File() { }
+
+ // Intentionally not `explicit`.
+ File(int fd)
+ : m_fd(fd)
+ {
+ }
+
+ int fd() const { return m_fd; }
+
+private:
+ int m_fd { -1 };
+};
+
+}
diff --git a/Userland/Libraries/LibIPC/Forward.h b/Userland/Libraries/LibIPC/Forward.h
new file mode 100644
index 0000000000..105f4faa5e
--- /dev/null
+++ b/Userland/Libraries/LibIPC/Forward.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace IPC {
+
+class Decoder;
+class Dictionary;
+class Encoder;
+class Message;
+class File;
+
+}
diff --git a/Userland/Libraries/LibIPC/Message.cpp b/Userland/Libraries/LibIPC/Message.cpp
new file mode 100644
index 0000000000..7d3e40dcf6
--- /dev/null
+++ b/Userland/Libraries/LibIPC/Message.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibIPC/Message.h>
+
+namespace IPC {
+
+Message::Message()
+{
+}
+
+Message::~Message()
+{
+ if (on_destruction)
+ on_destruction();
+}
+
+}
diff --git a/Userland/Libraries/LibIPC/Message.h b/Userland/Libraries/LibIPC/Message.h
new file mode 100644
index 0000000000..90dc57bb5e
--- /dev/null
+++ b/Userland/Libraries/LibIPC/Message.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/Vector.h>
+
+namespace IPC {
+
+struct MessageBuffer {
+ Vector<u8, 1024> data;
+ Vector<int> fds;
+};
+
+class Message {
+public:
+ virtual ~Message();
+
+ virtual int endpoint_magic() const = 0;
+ virtual int message_id() const = 0;
+ virtual const char* message_name() const = 0;
+ virtual MessageBuffer encode() const = 0;
+
+ Function<void()> on_destruction;
+
+protected:
+ Message();
+};
+
+}
diff --git a/Userland/Libraries/LibIPC/ServerConnection.h b/Userland/Libraries/LibIPC/ServerConnection.h
new file mode 100644
index 0000000000..f90fddccfc
--- /dev/null
+++ b/Userland/Libraries/LibIPC/ServerConnection.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibIPC/Connection.h>
+
+namespace IPC {
+
+template<typename ClientEndpoint, typename ServerEndpoint>
+class ServerConnection : public IPC::Connection<ClientEndpoint, ServerEndpoint> {
+public:
+ ServerConnection(ClientEndpoint& local_endpoint, const StringView& address)
+ : Connection<ClientEndpoint, ServerEndpoint>(local_endpoint, Core::LocalSocket::construct())
+ {
+ // We want to rate-limit our clients
+ this->socket().set_blocking(true);
+
+ if (!this->socket().connect(Core::SocketAddress::local(address))) {
+ perror("connect");
+ ASSERT_NOT_REACHED();
+ }
+
+ ASSERT(this->socket().is_connected());
+
+ this->initialize_peer_info();
+ }
+
+ virtual void handshake() = 0;
+
+ pid_t server_pid() const { return this->peer_pid(); }
+ void set_server_pid(pid_t pid) { this->set_peer_pid(pid); }
+
+ void set_my_client_id(int id) { m_my_client_id = id; }
+ int my_client_id() const { return m_my_client_id; }
+
+ virtual void die() override
+ {
+ // Override this function if you don't want your app to exit if it loses the connection.
+ exit(0);
+ }
+
+private:
+ int m_my_client_id { -1 };
+};
+
+}
diff --git a/Userland/Libraries/LibImageDecoderClient/CMakeLists.txt b/Userland/Libraries/LibImageDecoderClient/CMakeLists.txt
new file mode 100644
index 0000000000..72a5ab2934
--- /dev/null
+++ b/Userland/Libraries/LibImageDecoderClient/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(SOURCES
+ Client.cpp
+)
+
+set(GENERATED_SOURCES
+ ../../Services/ImageDecoder/ImageDecoderClientEndpoint.h
+ ../../Services/ImageDecoder/ImageDecoderServerEndpoint.h
+)
+
+serenity_lib(LibImageDecoderClient imagedecoderclient)
+target_link_libraries(LibImageDecoderClient LibIPC LibGfx)
diff --git a/Userland/Libraries/LibImageDecoderClient/Client.cpp b/Userland/Libraries/LibImageDecoderClient/Client.cpp
new file mode 100644
index 0000000000..cd5fa7243d
--- /dev/null
+++ b/Userland/Libraries/LibImageDecoderClient/Client.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/SharedBuffer.h>
+#include <LibImageDecoderClient/Client.h>
+
+namespace ImageDecoderClient {
+
+Client::Client()
+ : IPC::ServerConnection<ImageDecoderClientEndpoint, ImageDecoderServerEndpoint>(*this, "/tmp/portal/image")
+{
+ handshake();
+}
+
+void Client::handshake()
+{
+ auto response = send_sync<Messages::ImageDecoderServer::Greet>(getpid());
+ set_my_client_id(response->client_id());
+ set_server_pid(response->server_pid());
+}
+
+void Client::handle(const Messages::ImageDecoderClient::Dummy&)
+{
+}
+
+RefPtr<Gfx::Bitmap> Client::decode_image(const ByteBuffer& encoded_data)
+{
+ if (encoded_data.is_empty())
+ return nullptr;
+
+ auto encoded_buffer = SharedBuffer::create_with_size(encoded_data.size());
+ if (!encoded_buffer) {
+ dbgln("Could not allocate encoded shbuf");
+ return nullptr;
+ }
+
+ memcpy(encoded_buffer->data<void>(), encoded_data.data(), encoded_data.size());
+
+ encoded_buffer->seal();
+ encoded_buffer->share_with(server_pid());
+
+ auto response = send_sync<Messages::ImageDecoderServer::DecodeImage>(encoded_buffer->shbuf_id(), encoded_data.size());
+ auto bitmap_format = (Gfx::BitmapFormat)response->bitmap_format();
+ if (bitmap_format == Gfx::BitmapFormat::Invalid) {
+#ifdef IMAGE_DECODER_CLIENT_DEBUG
+ dbgln("Response image was invalid");
+#endif
+ return nullptr;
+ }
+
+ if (response->size().is_empty()) {
+ dbgln("Response image was empty");
+ return nullptr;
+ }
+
+ auto decoded_buffer = SharedBuffer::create_from_shbuf_id(response->decoded_shbuf_id());
+ if (!decoded_buffer) {
+ dbg() << "Could not map decoded image shbuf_id=" << response->decoded_shbuf_id();
+ return nullptr;
+ }
+
+ return Gfx::Bitmap::create_with_shared_buffer(bitmap_format, decoded_buffer.release_nonnull(), response->size(), response->palette());
+}
+
+}
diff --git a/Userland/Libraries/LibImageDecoderClient/Client.h b/Userland/Libraries/LibImageDecoderClient/Client.h
new file mode 100644
index 0000000000..7eeace6b22
--- /dev/null
+++ b/Userland/Libraries/LibImageDecoderClient/Client.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <ImageDecoder/ImageDecoderClientEndpoint.h>
+#include <ImageDecoder/ImageDecoderServerEndpoint.h>
+#include <LibIPC/ServerConnection.h>
+
+namespace ImageDecoderClient {
+
+class Client
+ : public IPC::ServerConnection<ImageDecoderClientEndpoint, ImageDecoderServerEndpoint>
+ , public ImageDecoderClientEndpoint {
+ C_OBJECT(Client);
+
+public:
+ virtual void handshake() override;
+
+ RefPtr<Gfx::Bitmap> decode_image(const ByteBuffer&);
+
+private:
+ Client();
+
+ virtual void handle(const Messages::ImageDecoderClient::Dummy&) override;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp
new file mode 100644
index 0000000000..0f5753d6a3
--- /dev/null
+++ b/Userland/Libraries/LibJS/AST.cpp
@@ -0,0 +1,2247 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/HashMap.h>
+#include <AK/HashTable.h>
+#include <AK/ScopeGuard.h>
+#include <AK/StringBuilder.h>
+#include <AK/TemporaryChange.h>
+#include <LibCrypto/BigInt/SignedBigInteger.h>
+#include <LibJS/AST.h>
+#include <LibJS/Interpreter.h>
+#include <LibJS/Runtime/Accessor.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/BigInt.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/IteratorOperations.h>
+#include <LibJS/Runtime/MarkedValueList.h>
+#include <LibJS/Runtime/NativeFunction.h>
+#include <LibJS/Runtime/PrimitiveString.h>
+#include <LibJS/Runtime/Reference.h>
+#include <LibJS/Runtime/RegExpObject.h>
+#include <LibJS/Runtime/ScriptFunction.h>
+#include <LibJS/Runtime/Shape.h>
+#include <LibJS/Runtime/StringObject.h>
+#include <LibJS/Runtime/WithScope.h>
+
+namespace JS {
+
+static void update_function_name(Value value, const FlyString& name, HashTable<JS::Cell*>& visited)
+{
+ if (!value.is_object())
+ return;
+ if (visited.contains(value.as_cell()))
+ return;
+ visited.set(value.as_cell());
+ auto& object = value.as_object();
+ if (object.is_function()) {
+ auto& function = static_cast<Function&>(object);
+ if (is<ScriptFunction>(function) && function.name().is_empty())
+ static_cast<ScriptFunction&>(function).set_name(name);
+ } else if (object.is_array()) {
+ auto& array = static_cast<Array&>(object);
+ array.indexed_properties().for_each_value([&](auto& array_element_value) {
+ update_function_name(array_element_value, name, visited);
+ });
+ }
+}
+
+static void update_function_name(Value value, const FlyString& name)
+{
+ HashTable<JS::Cell*> visited;
+ update_function_name(value, name, visited);
+}
+
+static String get_function_name(GlobalObject& global_object, Value value)
+{
+ if (value.is_symbol())
+ return String::formatted("[{}]", value.as_symbol().description());
+ if (value.is_string())
+ return value.as_string().string();
+ return value.to_string(global_object);
+}
+
+Value ScopeNode::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ return interpreter.execute_statement(global_object, *this);
+}
+
+Value Program::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ return interpreter.execute_statement(global_object, *this, ScopeType::Block);
+}
+
+Value FunctionDeclaration::execute(Interpreter& interpreter, GlobalObject&) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ return js_undefined();
+}
+
+Value FunctionExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ return ScriptFunction::create(global_object, name(), body(), parameters(), function_length(), interpreter.current_scope(), is_strict_mode() || interpreter.vm().in_strict_mode(), m_is_arrow_function);
+}
+
+Value ExpressionStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ return m_expression->execute(interpreter, global_object);
+}
+
+CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ auto& vm = interpreter.vm();
+
+ if (is<NewExpression>(*this)) {
+ // Computing |this| is irrelevant for "new" expression.
+ return { js_undefined(), m_callee->execute(interpreter, global_object) };
+ }
+
+ if (is<SuperExpression>(*m_callee)) {
+ // If we are calling super, |this| has not been initialized yet, and would not be meaningful to provide.
+ auto new_target = vm.get_new_target();
+ ASSERT(new_target.is_function());
+ return { js_undefined(), new_target };
+ }
+
+ if (is<MemberExpression>(*m_callee)) {
+ auto& member_expression = static_cast<const MemberExpression&>(*m_callee);
+ bool is_super_property_lookup = is<SuperExpression>(member_expression.object());
+ auto lookup_target = is_super_property_lookup ? interpreter.current_environment()->get_super_base() : member_expression.object().execute(interpreter, global_object);
+ if (vm.exception())
+ return {};
+ if (is_super_property_lookup && lookup_target.is_nullish()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, lookup_target.to_string_without_side_effects());
+ return {};
+ }
+
+ auto* this_value = is_super_property_lookup ? &vm.this_value(global_object).as_object() : lookup_target.to_object(global_object);
+ if (vm.exception())
+ return {};
+ auto property_name = member_expression.computed_property_name(interpreter, global_object);
+ if (!property_name.is_valid())
+ return {};
+ auto callee = lookup_target.to_object(global_object)->get(property_name).value_or(js_undefined());
+ return { this_value, callee };
+ }
+ return { &global_object, m_callee->execute(interpreter, global_object) };
+}
+
+Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto& vm = interpreter.vm();
+ auto [this_value, callee] = compute_this_and_callee(interpreter, global_object);
+ if (vm.exception())
+ return {};
+
+ ASSERT(!callee.is_empty());
+
+ if (!callee.is_function()
+ || (is<NewExpression>(*this) && (is<NativeFunction>(callee.as_object()) && !static_cast<NativeFunction&>(callee.as_object()).has_constructor()))) {
+ String error_message;
+ auto call_type = is<NewExpression>(*this) ? "constructor" : "function";
+ if (is<Identifier>(*m_callee) || is<MemberExpression>(*m_callee)) {
+ String expression_string;
+ if (is<Identifier>(*m_callee)) {
+ expression_string = static_cast<const Identifier&>(*m_callee).string();
+ } else {
+ expression_string = static_cast<const MemberExpression&>(*m_callee).to_string_approximation();
+ }
+ vm.throw_exception<TypeError>(global_object, ErrorType::IsNotAEvaluatedFrom, callee.to_string_without_side_effects(), call_type, expression_string);
+ } else {
+ vm.throw_exception<TypeError>(global_object, ErrorType::IsNotA, callee.to_string_without_side_effects(), call_type);
+ }
+ return {};
+ }
+
+ auto& function = callee.as_function();
+
+ MarkedValueList arguments(vm.heap());
+ arguments.ensure_capacity(m_arguments.size());
+
+ for (size_t i = 0; i < m_arguments.size(); ++i) {
+ auto value = m_arguments[i].value->execute(interpreter, global_object);
+ if (vm.exception())
+ return {};
+ if (m_arguments[i].is_spread) {
+ get_iterator_values(global_object, value, [&](Value iterator_value) {
+ if (vm.exception())
+ return IterationDecision::Break;
+ arguments.append(iterator_value);
+ return IterationDecision::Continue;
+ });
+ if (vm.exception())
+ return {};
+ } else {
+ arguments.append(value);
+ }
+ }
+
+ Object* new_object = nullptr;
+ Value result;
+ if (is<NewExpression>(*this)) {
+ result = vm.construct(function, function, move(arguments), global_object);
+ if (result.is_object())
+ new_object = &result.as_object();
+ } else if (is<SuperExpression>(*m_callee)) {
+ auto* super_constructor = interpreter.current_environment()->current_function()->prototype();
+ // FIXME: Functions should track their constructor kind.
+ if (!super_constructor || !super_constructor->is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAConstructor, "Super constructor");
+ return {};
+ }
+ result = vm.construct(static_cast<Function&>(*super_constructor), function, move(arguments), global_object);
+ if (vm.exception())
+ return {};
+
+ interpreter.current_environment()->bind_this_value(global_object, result);
+ } else {
+ result = vm.call(function, this_value, move(arguments));
+ }
+
+ if (vm.exception())
+ return {};
+
+ if (is<NewExpression>(*this)) {
+ if (result.is_object())
+ return result;
+ return new_object;
+ }
+ return result;
+}
+
+Value ReturnStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto value = argument() ? argument()->execute(interpreter, global_object) : js_undefined();
+ if (interpreter.exception())
+ return {};
+ interpreter.vm().unwind(ScopeType::Function);
+ return value;
+}
+
+Value IfStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto predicate_result = m_predicate->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+
+ if (predicate_result.to_boolean())
+ return interpreter.execute_statement(global_object, *m_consequent);
+
+ if (m_alternate)
+ return interpreter.execute_statement(global_object, *m_alternate);
+
+ return js_undefined();
+}
+
+Value WithStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto object_value = m_object->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+
+ auto* object = object_value.to_object(global_object);
+ if (interpreter.exception())
+ return {};
+
+ ASSERT(object);
+
+ auto* with_scope = interpreter.heap().allocate<WithScope>(global_object, *object, interpreter.vm().call_frame().scope);
+ TemporaryChange<ScopeObject*> scope_change(interpreter.vm().call_frame().scope, with_scope);
+ interpreter.execute_statement(global_object, m_body);
+ return {};
+}
+
+Value WhileStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ Value last_value = js_undefined();
+ for (;;) {
+ auto test_result = m_test->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ if (!test_result.to_boolean())
+ break;
+ last_value = interpreter.execute_statement(global_object, *m_body);
+ if (interpreter.exception())
+ return {};
+ if (interpreter.vm().should_unwind()) {
+ if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_label)) {
+ interpreter.vm().stop_unwind();
+ } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_label)) {
+ interpreter.vm().stop_unwind();
+ break;
+ } else {
+ return last_value;
+ }
+ }
+ }
+
+ return last_value;
+}
+
+Value DoWhileStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ Value last_value = js_undefined();
+ for (;;) {
+ if (interpreter.exception())
+ return {};
+ last_value = interpreter.execute_statement(global_object, *m_body);
+ if (interpreter.exception())
+ return {};
+ if (interpreter.vm().should_unwind()) {
+ if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_label)) {
+ interpreter.vm().stop_unwind();
+ } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_label)) {
+ interpreter.vm().stop_unwind();
+ break;
+ } else {
+ return last_value;
+ }
+ }
+ auto test_result = m_test->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ if (!test_result.to_boolean())
+ break;
+ }
+
+ return last_value;
+}
+
+Value ForStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ RefPtr<BlockStatement> wrapper;
+
+ if (m_init && is<VariableDeclaration>(*m_init) && static_cast<const VariableDeclaration&>(*m_init).declaration_kind() != DeclarationKind::Var) {
+ wrapper = create_ast_node<BlockStatement>(source_range());
+ NonnullRefPtrVector<VariableDeclaration> decls;
+ decls.append(*static_cast<const VariableDeclaration*>(m_init.ptr()));
+ wrapper->add_variables(decls);
+ interpreter.enter_scope(*wrapper, ScopeType::Block, global_object);
+ }
+
+ auto wrapper_cleanup = ScopeGuard([&] {
+ if (wrapper)
+ interpreter.exit_scope(*wrapper);
+ });
+
+ Value last_value = js_undefined();
+
+ if (m_init) {
+ m_init->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ }
+
+ if (m_test) {
+ while (true) {
+ auto test_result = m_test->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ if (!test_result.to_boolean())
+ break;
+ last_value = interpreter.execute_statement(global_object, *m_body);
+ if (interpreter.exception())
+ return {};
+ if (interpreter.vm().should_unwind()) {
+ if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_label)) {
+ interpreter.vm().stop_unwind();
+ } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_label)) {
+ interpreter.vm().stop_unwind();
+ break;
+ } else {
+ return last_value;
+ }
+ }
+ if (m_update) {
+ m_update->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ }
+ }
+ } else {
+ while (true) {
+ last_value = interpreter.execute_statement(global_object, *m_body);
+ if (interpreter.exception())
+ return {};
+ if (interpreter.vm().should_unwind()) {
+ if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_label)) {
+ interpreter.vm().stop_unwind();
+ } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_label)) {
+ interpreter.vm().stop_unwind();
+ break;
+ } else {
+ return last_value;
+ }
+ }
+ if (m_update) {
+ m_update->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ }
+ }
+ }
+
+ return last_value;
+}
+
+static FlyString variable_from_for_declaration(Interpreter& interpreter, GlobalObject& global_object, const ASTNode& node, RefPtr<BlockStatement> wrapper)
+{
+ FlyString variable_name;
+ if (is<VariableDeclaration>(node)) {
+ auto& variable_declaration = static_cast<const VariableDeclaration&>(node);
+ ASSERT(!variable_declaration.declarations().is_empty());
+ if (variable_declaration.declaration_kind() != DeclarationKind::Var) {
+ wrapper = create_ast_node<BlockStatement>(node.source_range());
+ interpreter.enter_scope(*wrapper, ScopeType::Block, global_object);
+ }
+ variable_declaration.execute(interpreter, global_object);
+ variable_name = variable_declaration.declarations().first().id().string();
+ } else if (is<Identifier>(node)) {
+ variable_name = static_cast<const Identifier&>(node).string();
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+ return variable_name;
+}
+
+Value ForInStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ if (!is<VariableDeclaration>(*m_lhs) && !is<Identifier>(*m_lhs)) {
+ // FIXME: Implement "for (foo.bar in baz)", "for (foo[0] in bar)"
+ ASSERT_NOT_REACHED();
+ }
+ RefPtr<BlockStatement> wrapper;
+ auto variable_name = variable_from_for_declaration(interpreter, global_object, m_lhs, wrapper);
+ auto wrapper_cleanup = ScopeGuard([&] {
+ if (wrapper)
+ interpreter.exit_scope(*wrapper);
+ });
+ auto last_value = js_undefined();
+ auto rhs_result = m_rhs->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ auto* object = rhs_result.to_object(global_object);
+ while (object) {
+ auto property_names = object->get_own_properties(*object, Object::PropertyKind::Key, true);
+ for (auto& property_name : property_names.as_object().indexed_properties()) {
+ interpreter.vm().set_variable(variable_name, property_name.value_and_attributes(object).value, global_object);
+ if (interpreter.exception())
+ return {};
+ last_value = interpreter.execute_statement(global_object, *m_body);
+ if (interpreter.exception())
+ return {};
+ if (interpreter.vm().should_unwind()) {
+ if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_label)) {
+ interpreter.vm().stop_unwind();
+ } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_label)) {
+ interpreter.vm().stop_unwind();
+ break;
+ } else {
+ return last_value;
+ }
+ }
+ }
+ object = object->prototype();
+ if (interpreter.exception())
+ return {};
+ }
+ return last_value;
+}
+
+Value ForOfStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ if (!is<VariableDeclaration>(*m_lhs) && !is<Identifier>(*m_lhs)) {
+ // FIXME: Implement "for (foo.bar of baz)", "for (foo[0] of bar)"
+ ASSERT_NOT_REACHED();
+ }
+ RefPtr<BlockStatement> wrapper;
+ auto variable_name = variable_from_for_declaration(interpreter, global_object, m_lhs, wrapper);
+ auto wrapper_cleanup = ScopeGuard([&] {
+ if (wrapper)
+ interpreter.exit_scope(*wrapper);
+ });
+ auto last_value = js_undefined();
+ auto rhs_result = m_rhs->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+
+ get_iterator_values(global_object, rhs_result, [&](Value value) {
+ interpreter.vm().set_variable(variable_name, value, global_object);
+ last_value = interpreter.execute_statement(global_object, *m_body);
+ if (interpreter.exception())
+ return IterationDecision::Break;
+ if (interpreter.vm().should_unwind()) {
+ if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_label)) {
+ interpreter.vm().stop_unwind();
+ } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_label)) {
+ interpreter.vm().stop_unwind();
+ return IterationDecision::Break;
+ } else {
+ return IterationDecision::Break;
+ }
+ }
+ return IterationDecision::Continue;
+ });
+
+ if (interpreter.exception())
+ return {};
+
+ return last_value;
+}
+
+Value BinaryExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto lhs_result = m_lhs->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ auto rhs_result = m_rhs->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+
+ switch (m_op) {
+ case BinaryOp::Addition:
+ return add(global_object, lhs_result, rhs_result);
+ case BinaryOp::Subtraction:
+ return sub(global_object, lhs_result, rhs_result);
+ case BinaryOp::Multiplication:
+ return mul(global_object, lhs_result, rhs_result);
+ case BinaryOp::Division:
+ return div(global_object, lhs_result, rhs_result);
+ case BinaryOp::Modulo:
+ return mod(global_object, lhs_result, rhs_result);
+ case BinaryOp::Exponentiation:
+ return exp(global_object, lhs_result, rhs_result);
+ case BinaryOp::TypedEquals:
+ return Value(strict_eq(lhs_result, rhs_result));
+ case BinaryOp::TypedInequals:
+ return Value(!strict_eq(lhs_result, rhs_result));
+ case BinaryOp::AbstractEquals:
+ return Value(abstract_eq(global_object, lhs_result, rhs_result));
+ case BinaryOp::AbstractInequals:
+ return Value(!abstract_eq(global_object, lhs_result, rhs_result));
+ case BinaryOp::GreaterThan:
+ return greater_than(global_object, lhs_result, rhs_result);
+ case BinaryOp::GreaterThanEquals:
+ return greater_than_equals(global_object, lhs_result, rhs_result);
+ case BinaryOp::LessThan:
+ return less_than(global_object, lhs_result, rhs_result);
+ case BinaryOp::LessThanEquals:
+ return less_than_equals(global_object, lhs_result, rhs_result);
+ case BinaryOp::BitwiseAnd:
+ return bitwise_and(global_object, lhs_result, rhs_result);
+ case BinaryOp::BitwiseOr:
+ return bitwise_or(global_object, lhs_result, rhs_result);
+ case BinaryOp::BitwiseXor:
+ return bitwise_xor(global_object, lhs_result, rhs_result);
+ case BinaryOp::LeftShift:
+ return left_shift(global_object, lhs_result, rhs_result);
+ case BinaryOp::RightShift:
+ return right_shift(global_object, lhs_result, rhs_result);
+ case BinaryOp::UnsignedRightShift:
+ return unsigned_right_shift(global_object, lhs_result, rhs_result);
+ case BinaryOp::In:
+ return in(global_object, lhs_result, rhs_result);
+ case BinaryOp::InstanceOf:
+ return instance_of(global_object, lhs_result, rhs_result);
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+Value LogicalExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto lhs_result = m_lhs->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+
+ switch (m_op) {
+ case LogicalOp::And:
+ if (lhs_result.to_boolean()) {
+ auto rhs_result = m_rhs->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ return rhs_result;
+ }
+ return lhs_result;
+ case LogicalOp::Or: {
+ if (lhs_result.to_boolean())
+ return lhs_result;
+ auto rhs_result = m_rhs->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ return rhs_result;
+ }
+ case LogicalOp::NullishCoalescing:
+ if (lhs_result.is_nullish()) {
+ auto rhs_result = m_rhs->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ return rhs_result;
+ }
+ return lhs_result;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+Reference Expression::to_reference(Interpreter&, GlobalObject&) const
+{
+ return {};
+}
+
+Reference Identifier::to_reference(Interpreter& interpreter, GlobalObject&) const
+{
+ return interpreter.vm().get_reference(string());
+}
+
+Reference MemberExpression::to_reference(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ auto object_value = m_object->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ auto property_name = computed_property_name(interpreter, global_object);
+ if (!property_name.is_valid())
+ return {};
+ return { object_value, property_name };
+}
+
+Value UnaryExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto& vm = interpreter.vm();
+ if (m_op == UnaryOp::Delete) {
+ auto reference = m_lhs->to_reference(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ if (reference.is_unresolvable())
+ return Value(true);
+ // FIXME: Support deleting locals
+ ASSERT(!reference.is_local_variable());
+ if (reference.is_global_variable())
+ return global_object.delete_property(reference.name());
+ auto* base_object = reference.base().to_object(global_object);
+ if (!base_object)
+ return {};
+ return base_object->delete_property(reference.name());
+ }
+
+ Value lhs_result;
+ if (m_op == UnaryOp::Typeof && is<Identifier>(*m_lhs)) {
+ auto reference = m_lhs->to_reference(interpreter, global_object);
+ if (interpreter.exception()) {
+ return {};
+ }
+ // FIXME: standard recommends checking with is_unresolvable but it ALWAYS return false here
+ if (reference.is_local_variable() || reference.is_global_variable()) {
+ auto name = reference.name();
+ lhs_result = interpreter.vm().get_variable(name.to_string(), global_object).value_or(js_undefined());
+ if (interpreter.exception())
+ return {};
+ }
+ } else {
+ lhs_result = m_lhs->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ }
+
+ switch (m_op) {
+ case UnaryOp::BitwiseNot:
+ return bitwise_not(global_object, lhs_result);
+ case UnaryOp::Not:
+ return Value(!lhs_result.to_boolean());
+ case UnaryOp::Plus:
+ return unary_plus(global_object, lhs_result);
+ case UnaryOp::Minus:
+ return unary_minus(global_object, lhs_result);
+ case UnaryOp::Typeof:
+ switch (lhs_result.type()) {
+ case Value::Type::Empty:
+ ASSERT_NOT_REACHED();
+ return {};
+ case Value::Type::Undefined:
+ return js_string(vm, "undefined");
+ case Value::Type::Null:
+ // yes, this is on purpose. yes, this is how javascript works.
+ // yes, it's silly.
+ return js_string(vm, "object");
+ case Value::Type::Number:
+ return js_string(vm, "number");
+ case Value::Type::String:
+ return js_string(vm, "string");
+ case Value::Type::Object:
+ if (lhs_result.is_function())
+ return js_string(vm, "function");
+ return js_string(vm, "object");
+ case Value::Type::Boolean:
+ return js_string(vm, "boolean");
+ case Value::Type::Symbol:
+ return js_string(vm, "symbol");
+ case Value::Type::BigInt:
+ return js_string(vm, "bigint");
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ case UnaryOp::Void:
+ return js_undefined();
+ case UnaryOp::Delete:
+ ASSERT_NOT_REACHED();
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+Value SuperExpression::execute(Interpreter& interpreter, GlobalObject&) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ // The semantics for SuperExpressions are handled in CallExpression::compute_this_and_callee()
+ ASSERT_NOT_REACHED();
+}
+
+Value ClassMethod::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ return m_function->execute(interpreter, global_object);
+}
+
+Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto& vm = interpreter.vm();
+ Value class_constructor_value = m_constructor->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+
+ update_function_name(class_constructor_value, m_name);
+
+ ASSERT(class_constructor_value.is_function() && is<ScriptFunction>(class_constructor_value.as_function()));
+ ScriptFunction* class_constructor = static_cast<ScriptFunction*>(&class_constructor_value.as_function());
+ class_constructor->set_is_class_constructor();
+ Value super_constructor = js_undefined();
+ if (!m_super_class.is_null()) {
+ super_constructor = m_super_class->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ if (!super_constructor.is_function() && !super_constructor.is_null()) {
+ interpreter.vm().throw_exception<TypeError>(global_object, ErrorType::ClassDoesNotExtendAConstructorOrNull, super_constructor.to_string_without_side_effects());
+ return {};
+ }
+ class_constructor->set_constructor_kind(Function::ConstructorKind::Derived);
+ Object* prototype = Object::create_empty(global_object);
+
+ Object* super_constructor_prototype = nullptr;
+ if (!super_constructor.is_null()) {
+ super_constructor_prototype = &super_constructor.as_object().get(vm.names.prototype).as_object();
+ if (interpreter.exception())
+ return {};
+ }
+ prototype->set_prototype(super_constructor_prototype);
+
+ prototype->define_property(vm.names.constructor, class_constructor, 0);
+ if (interpreter.exception())
+ return {};
+ class_constructor->define_property(vm.names.prototype, prototype, Attribute::Writable);
+ if (interpreter.exception())
+ return {};
+ class_constructor->set_prototype(super_constructor.is_null() ? global_object.function_prototype() : &super_constructor.as_object());
+ }
+
+ auto class_prototype = class_constructor->get(vm.names.prototype);
+ if (interpreter.exception())
+ return {};
+
+ if (!class_prototype.is_object()) {
+ interpreter.vm().throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Class prototype");
+ return {};
+ }
+ for (const auto& method : m_methods) {
+ auto method_value = method.execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+
+ auto& method_function = method_value.as_function();
+
+ auto key = method.key().execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+
+ auto& target = method.is_static() ? *class_constructor : class_prototype.as_object();
+ method_function.set_home_object(&target);
+
+ if (method.kind() == ClassMethod::Kind::Method) {
+ target.define_property(StringOrSymbol::from_value(global_object, key), method_value);
+ } else {
+ String accessor_name = [&] {
+ switch (method.kind()) {
+ case ClassMethod::Kind::Getter:
+ return String::formatted("get {}", get_function_name(global_object, key));
+ case ClassMethod::Kind::Setter:
+ return String::formatted("set {}", get_function_name(global_object, key));
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ }();
+ update_function_name(method_value, accessor_name);
+ target.define_accessor(StringOrSymbol::from_value(global_object, key), method_function, method.kind() == ClassMethod::Kind::Getter, Attribute::Configurable | Attribute::Enumerable);
+ }
+ if (interpreter.exception())
+ return {};
+ }
+
+ return class_constructor;
+}
+
+Value ClassDeclaration::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ Value class_constructor = m_class_expression->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+
+ interpreter.current_scope()->put_to_scope(m_class_expression->name(), { class_constructor, DeclarationKind::Let });
+
+ return js_undefined();
+}
+
+static void print_indent(int indent)
+{
+ out("{}", String::repeated(' ', indent * 2));
+}
+
+void ASTNode::dump(int indent) const
+{
+ print_indent(indent);
+ outln("{}", class_name());
+}
+
+void ScopeNode::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ if (!m_variables.is_empty()) {
+ print_indent(indent + 1);
+ outln("(Variables)");
+ for (auto& variable : m_variables)
+ variable.dump(indent + 2);
+ }
+ if (!m_children.is_empty()) {
+ print_indent(indent + 1);
+ outln("(Children)");
+ for (auto& child : children())
+ child.dump(indent + 2);
+ }
+}
+
+void BinaryExpression::dump(int indent) const
+{
+ const char* op_string = nullptr;
+ switch (m_op) {
+ case BinaryOp::Addition:
+ op_string = "+";
+ break;
+ case BinaryOp::Subtraction:
+ op_string = "-";
+ break;
+ case BinaryOp::Multiplication:
+ op_string = "*";
+ break;
+ case BinaryOp::Division:
+ op_string = "/";
+ break;
+ case BinaryOp::Modulo:
+ op_string = "%";
+ break;
+ case BinaryOp::Exponentiation:
+ op_string = "**";
+ break;
+ case BinaryOp::TypedEquals:
+ op_string = "===";
+ break;
+ case BinaryOp::TypedInequals:
+ op_string = "!==";
+ break;
+ case BinaryOp::AbstractEquals:
+ op_string = "==";
+ break;
+ case BinaryOp::AbstractInequals:
+ op_string = "!=";
+ break;
+ case BinaryOp::GreaterThan:
+ op_string = ">";
+ break;
+ case BinaryOp::GreaterThanEquals:
+ op_string = ">=";
+ break;
+ case BinaryOp::LessThan:
+ op_string = "<";
+ break;
+ case BinaryOp::LessThanEquals:
+ op_string = "<=";
+ break;
+ case BinaryOp::BitwiseAnd:
+ op_string = "&";
+ break;
+ case BinaryOp::BitwiseOr:
+ op_string = "|";
+ break;
+ case BinaryOp::BitwiseXor:
+ op_string = "^";
+ break;
+ case BinaryOp::LeftShift:
+ op_string = "<<";
+ break;
+ case BinaryOp::RightShift:
+ op_string = ">>";
+ break;
+ case BinaryOp::UnsignedRightShift:
+ op_string = ">>>";
+ break;
+ case BinaryOp::In:
+ op_string = "in";
+ break;
+ case BinaryOp::InstanceOf:
+ op_string = "instanceof";
+ break;
+ }
+
+ print_indent(indent);
+ outln("{}", class_name());
+ m_lhs->dump(indent + 1);
+ print_indent(indent + 1);
+ outln("{}", op_string);
+ m_rhs->dump(indent + 1);
+}
+
+void LogicalExpression::dump(int indent) const
+{
+ const char* op_string = nullptr;
+ switch (m_op) {
+ case LogicalOp::And:
+ op_string = "&&";
+ break;
+ case LogicalOp::Or:
+ op_string = "||";
+ break;
+ case LogicalOp::NullishCoalescing:
+ op_string = "??";
+ break;
+ }
+
+ print_indent(indent);
+ outln("{}", class_name());
+ m_lhs->dump(indent + 1);
+ print_indent(indent + 1);
+ outln("{}", op_string);
+ m_rhs->dump(indent + 1);
+}
+
+void UnaryExpression::dump(int indent) const
+{
+ const char* op_string = nullptr;
+ switch (m_op) {
+ case UnaryOp::BitwiseNot:
+ op_string = "~";
+ break;
+ case UnaryOp::Not:
+ op_string = "!";
+ break;
+ case UnaryOp::Plus:
+ op_string = "+";
+ break;
+ case UnaryOp::Minus:
+ op_string = "-";
+ break;
+ case UnaryOp::Typeof:
+ op_string = "typeof ";
+ break;
+ case UnaryOp::Void:
+ op_string = "void ";
+ break;
+ case UnaryOp::Delete:
+ op_string = "delete ";
+ break;
+ }
+
+ print_indent(indent);
+ outln("{}", class_name());
+ print_indent(indent + 1);
+ outln("{}", op_string);
+ m_lhs->dump(indent + 1);
+}
+
+void CallExpression::dump(int indent) const
+{
+ print_indent(indent);
+ if (is<NewExpression>(*this))
+ outln("CallExpression [new]");
+ else
+ outln("CallExpression");
+ m_callee->dump(indent + 1);
+ for (auto& argument : m_arguments)
+ argument.value->dump(indent + 1);
+}
+
+void ClassDeclaration::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ m_class_expression->dump(indent + 1);
+}
+
+void ClassExpression::dump(int indent) const
+{
+ print_indent(indent);
+ outln("ClassExpression: \"{}\"", m_name);
+
+ print_indent(indent);
+ outln("(Constructor)");
+ m_constructor->dump(indent + 1);
+
+ if (!m_super_class.is_null()) {
+ print_indent(indent);
+ outln("(Super Class)");
+ m_super_class->dump(indent + 1);
+ }
+
+ print_indent(indent);
+ outln("(Methods)");
+ for (auto& method : m_methods)
+ method.dump(indent + 1);
+}
+
+void ClassMethod::dump(int indent) const
+{
+ ASTNode::dump(indent);
+
+ print_indent(indent);
+ outln("(Key)");
+ m_key->dump(indent + 1);
+
+ const char* kind_string = nullptr;
+ switch (m_kind) {
+ case Kind::Method:
+ kind_string = "Method";
+ break;
+ case Kind::Getter:
+ kind_string = "Getter";
+ break;
+ case Kind::Setter:
+ kind_string = "Setter";
+ break;
+ }
+ print_indent(indent);
+ outln("Kind: {}", kind_string);
+
+ print_indent(indent);
+ outln("Static: {}", m_is_static);
+
+ print_indent(indent);
+ outln("(Function)");
+ m_function->dump(indent + 1);
+}
+
+void StringLiteral::dump(int indent) const
+{
+ print_indent(indent);
+ outln("StringLiteral \"{}\"", m_value);
+}
+
+void SuperExpression::dump(int indent) const
+{
+ print_indent(indent);
+ outln("super");
+}
+
+void NumericLiteral::dump(int indent) const
+{
+ print_indent(indent);
+ outln("NumericLiteral {}", m_value);
+}
+
+void BigIntLiteral::dump(int indent) const
+{
+ print_indent(indent);
+ outln("BigIntLiteral {}", m_value);
+}
+
+void BooleanLiteral::dump(int indent) const
+{
+ print_indent(indent);
+ outln("BooleanLiteral {}", m_value);
+}
+
+void NullLiteral::dump(int indent) const
+{
+ print_indent(indent);
+ outln("null");
+}
+
+void FunctionNode::dump(int indent, const char* class_name) const
+{
+ print_indent(indent);
+ outln("{} '{}'", class_name, name());
+ if (!m_parameters.is_empty()) {
+ print_indent(indent + 1);
+ outln("(Parameters)\n");
+
+ for (auto& parameter : m_parameters) {
+ print_indent(indent + 2);
+ if (parameter.is_rest)
+ out("...");
+ outln("{}", parameter.name);
+ if (parameter.default_value)
+ parameter.default_value->dump(indent + 3);
+ }
+ }
+ if (!m_variables.is_empty()) {
+ print_indent(indent + 1);
+ outln("(Variables)");
+
+ for (auto& variable : m_variables)
+ variable.dump(indent + 2);
+ }
+ print_indent(indent + 1);
+ outln("(Body)");
+ body().dump(indent + 2);
+}
+
+void FunctionDeclaration::dump(int indent) const
+{
+ FunctionNode::dump(indent, class_name());
+}
+
+void FunctionExpression::dump(int indent) const
+{
+ FunctionNode::dump(indent, class_name());
+}
+
+void ReturnStatement::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ if (argument())
+ argument()->dump(indent + 1);
+}
+
+void IfStatement::dump(int indent) const
+{
+ ASTNode::dump(indent);
+
+ print_indent(indent);
+ outln("If");
+ predicate().dump(indent + 1);
+ consequent().dump(indent + 1);
+ if (alternate()) {
+ print_indent(indent);
+ outln("Else");
+ alternate()->dump(indent + 1);
+ }
+}
+
+void WhileStatement::dump(int indent) const
+{
+ ASTNode::dump(indent);
+
+ print_indent(indent);
+ outln("While");
+ test().dump(indent + 1);
+ body().dump(indent + 1);
+}
+
+void WithStatement::dump(int indent) const
+{
+ ASTNode::dump(indent);
+
+ print_indent(indent + 1);
+ outln("Object");
+ object().dump(indent + 2);
+ print_indent(indent + 1);
+ outln("Body");
+ body().dump(indent + 2);
+}
+
+void DoWhileStatement::dump(int indent) const
+{
+ ASTNode::dump(indent);
+
+ print_indent(indent);
+ outln("DoWhile");
+ test().dump(indent + 1);
+ body().dump(indent + 1);
+}
+
+void ForStatement::dump(int indent) const
+{
+ ASTNode::dump(indent);
+
+ print_indent(indent);
+ outln("For");
+ if (init())
+ init()->dump(indent + 1);
+ if (test())
+ test()->dump(indent + 1);
+ if (update())
+ update()->dump(indent + 1);
+ body().dump(indent + 1);
+}
+
+void ForInStatement::dump(int indent) const
+{
+ ASTNode::dump(indent);
+
+ print_indent(indent);
+ outln("ForIn");
+ lhs().dump(indent + 1);
+ rhs().dump(indent + 1);
+ body().dump(indent + 1);
+}
+
+void ForOfStatement::dump(int indent) const
+{
+ ASTNode::dump(indent);
+
+ print_indent(indent);
+ outln("ForOf");
+ lhs().dump(indent + 1);
+ rhs().dump(indent + 1);
+ body().dump(indent + 1);
+}
+
+Value Identifier::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto value = interpreter.vm().get_variable(string(), global_object);
+ if (value.is_empty()) {
+ interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, string());
+ return {};
+ }
+ return value;
+}
+
+void Identifier::dump(int indent) const
+{
+ print_indent(indent);
+ outln("Identifier \"{}\"", m_string);
+}
+
+void SpreadExpression::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ m_target->dump(indent + 1);
+}
+
+Value SpreadExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ return m_target->execute(interpreter, global_object);
+}
+
+Value ThisExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ return interpreter.vm().resolve_this_binding(global_object);
+}
+
+void ThisExpression::dump(int indent) const
+{
+ ASTNode::dump(indent);
+}
+
+Value AssignmentExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+#define EXECUTE_LHS_AND_RHS() \
+ do { \
+ lhs_result = m_lhs->execute(interpreter, global_object); \
+ if (interpreter.exception()) \
+ return {}; \
+ rhs_result = m_rhs->execute(interpreter, global_object); \
+ if (interpreter.exception()) \
+ return {}; \
+ } while (0)
+
+ Value lhs_result;
+ Value rhs_result;
+ switch (m_op) {
+ case AssignmentOp::Assignment:
+ break;
+ case AssignmentOp::AdditionAssignment:
+ EXECUTE_LHS_AND_RHS();
+ rhs_result = add(global_object, lhs_result, rhs_result);
+ break;
+ case AssignmentOp::SubtractionAssignment:
+ EXECUTE_LHS_AND_RHS();
+ rhs_result = sub(global_object, lhs_result, rhs_result);
+ break;
+ case AssignmentOp::MultiplicationAssignment:
+ EXECUTE_LHS_AND_RHS();
+ rhs_result = mul(global_object, lhs_result, rhs_result);
+ break;
+ case AssignmentOp::DivisionAssignment:
+ EXECUTE_LHS_AND_RHS();
+ rhs_result = div(global_object, lhs_result, rhs_result);
+ break;
+ case AssignmentOp::ModuloAssignment:
+ EXECUTE_LHS_AND_RHS();
+ rhs_result = mod(global_object, lhs_result, rhs_result);
+ break;
+ case AssignmentOp::ExponentiationAssignment:
+ EXECUTE_LHS_AND_RHS();
+ rhs_result = exp(global_object, lhs_result, rhs_result);
+ break;
+ case AssignmentOp::BitwiseAndAssignment:
+ EXECUTE_LHS_AND_RHS();
+ rhs_result = bitwise_and(global_object, lhs_result, rhs_result);
+ break;
+ case AssignmentOp::BitwiseOrAssignment:
+ EXECUTE_LHS_AND_RHS();
+ rhs_result = bitwise_or(global_object, lhs_result, rhs_result);
+ break;
+ case AssignmentOp::BitwiseXorAssignment:
+ EXECUTE_LHS_AND_RHS();
+ rhs_result = bitwise_xor(global_object, lhs_result, rhs_result);
+ break;
+ case AssignmentOp::LeftShiftAssignment:
+ EXECUTE_LHS_AND_RHS();
+ rhs_result = left_shift(global_object, lhs_result, rhs_result);
+ break;
+ case AssignmentOp::RightShiftAssignment:
+ EXECUTE_LHS_AND_RHS();
+ rhs_result = right_shift(global_object, lhs_result, rhs_result);
+ break;
+ case AssignmentOp::UnsignedRightShiftAssignment:
+ EXECUTE_LHS_AND_RHS();
+ rhs_result = unsigned_right_shift(global_object, lhs_result, rhs_result);
+ break;
+ case AssignmentOp::AndAssignment:
+ lhs_result = m_lhs->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ if (!lhs_result.to_boolean())
+ return lhs_result;
+ rhs_result = m_rhs->execute(interpreter, global_object);
+ break;
+ case AssignmentOp::OrAssignment:
+ lhs_result = m_lhs->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ if (lhs_result.to_boolean())
+ return lhs_result;
+ rhs_result = m_rhs->execute(interpreter, global_object);
+ break;
+ case AssignmentOp::NullishAssignment:
+ lhs_result = m_lhs->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ if (!lhs_result.is_nullish())
+ return lhs_result;
+ rhs_result = m_rhs->execute(interpreter, global_object);
+ break;
+ }
+ if (interpreter.exception())
+ return {};
+
+ auto reference = m_lhs->to_reference(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+
+ if (m_op == AssignmentOp::Assignment) {
+ rhs_result = m_rhs->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ }
+
+ if (reference.is_unresolvable()) {
+ interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::InvalidLeftHandAssignment);
+ return {};
+ }
+ update_function_name(rhs_result, get_function_name(global_object, reference.name().to_value(interpreter.vm())));
+ reference.put(global_object, rhs_result);
+
+ if (interpreter.exception())
+ return {};
+ return rhs_result;
+}
+
+Value UpdateExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto reference = m_argument->to_reference(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ auto old_value = reference.get(global_object);
+ if (interpreter.exception())
+ return {};
+ old_value = old_value.to_numeric(global_object);
+ if (interpreter.exception())
+ return {};
+
+ Value new_value;
+ switch (m_op) {
+ case UpdateOp::Increment:
+ if (old_value.is_number())
+ new_value = Value(old_value.as_double() + 1);
+ else
+ new_value = js_bigint(interpreter.heap(), old_value.as_bigint().big_integer().plus(Crypto::SignedBigInteger { 1 }));
+ break;
+ case UpdateOp::Decrement:
+ if (old_value.is_number())
+ new_value = Value(old_value.as_double() - 1);
+ else
+ new_value = js_bigint(interpreter.heap(), old_value.as_bigint().big_integer().minus(Crypto::SignedBigInteger { 1 }));
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ reference.put(global_object, new_value);
+ if (interpreter.exception())
+ return {};
+ return m_prefixed ? new_value : old_value;
+}
+
+void AssignmentExpression::dump(int indent) const
+{
+ const char* op_string = nullptr;
+ switch (m_op) {
+ case AssignmentOp::Assignment:
+ op_string = "=";
+ break;
+ case AssignmentOp::AdditionAssignment:
+ op_string = "+=";
+ break;
+ case AssignmentOp::SubtractionAssignment:
+ op_string = "-=";
+ break;
+ case AssignmentOp::MultiplicationAssignment:
+ op_string = "*=";
+ break;
+ case AssignmentOp::DivisionAssignment:
+ op_string = "/=";
+ break;
+ case AssignmentOp::ModuloAssignment:
+ op_string = "%=";
+ break;
+ case AssignmentOp::ExponentiationAssignment:
+ op_string = "**=";
+ break;
+ case AssignmentOp::BitwiseAndAssignment:
+ op_string = "&=";
+ break;
+ case AssignmentOp::BitwiseOrAssignment:
+ op_string = "|=";
+ break;
+ case AssignmentOp::BitwiseXorAssignment:
+ op_string = "^=";
+ break;
+ case AssignmentOp::LeftShiftAssignment:
+ op_string = "<<=";
+ break;
+ case AssignmentOp::RightShiftAssignment:
+ op_string = ">>=";
+ break;
+ case AssignmentOp::UnsignedRightShiftAssignment:
+ op_string = ">>>=";
+ break;
+ case AssignmentOp::AndAssignment:
+ op_string = "&&=";
+ break;
+ case AssignmentOp::OrAssignment:
+ op_string = "||=";
+ break;
+ case AssignmentOp::NullishAssignment:
+ op_string = "\?\?=";
+ break;
+ }
+
+ ASTNode::dump(indent);
+ print_indent(indent + 1);
+ outln("{}", op_string);
+ m_lhs->dump(indent + 1);
+ m_rhs->dump(indent + 1);
+}
+
+void UpdateExpression::dump(int indent) const
+{
+ const char* op_string = nullptr;
+ switch (m_op) {
+ case UpdateOp::Increment:
+ op_string = "++";
+ break;
+ case UpdateOp::Decrement:
+ op_string = "--";
+ break;
+ }
+
+ ASTNode::dump(indent);
+ if (m_prefixed) {
+ print_indent(indent + 1);
+ outln("{}", op_string);
+ }
+ m_argument->dump(indent + 1);
+ if (!m_prefixed) {
+ print_indent(indent + 1);
+ outln("{}", op_string);
+ }
+}
+
+Value VariableDeclaration::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ for (auto& declarator : m_declarations) {
+ if (auto* init = declarator.init()) {
+ auto initalizer_result = init->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ auto variable_name = declarator.id().string();
+ update_function_name(initalizer_result, variable_name);
+ interpreter.vm().set_variable(variable_name, initalizer_result, global_object, true);
+ }
+ }
+ return js_undefined();
+}
+
+Value VariableDeclarator::execute(Interpreter& interpreter, GlobalObject&) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ // NOTE: VariableDeclarator execution is handled by VariableDeclaration.
+ ASSERT_NOT_REACHED();
+}
+
+void VariableDeclaration::dump(int indent) const
+{
+ const char* declaration_kind_string = nullptr;
+ switch (m_declaration_kind) {
+ case DeclarationKind::Let:
+ declaration_kind_string = "Let";
+ break;
+ case DeclarationKind::Var:
+ declaration_kind_string = "Var";
+ break;
+ case DeclarationKind::Const:
+ declaration_kind_string = "Const";
+ break;
+ }
+
+ ASTNode::dump(indent);
+ print_indent(indent + 1);
+ outln("{}", declaration_kind_string);
+
+ for (auto& declarator : m_declarations)
+ declarator.dump(indent + 1);
+}
+
+void VariableDeclarator::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ m_id->dump(indent + 1);
+ if (m_init)
+ m_init->dump(indent + 1);
+}
+
+void ObjectProperty::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ m_key->dump(indent + 1);
+ m_value->dump(indent + 1);
+}
+
+void ObjectExpression::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ for (auto& property : m_properties) {
+ property.dump(indent + 1);
+ }
+}
+
+void ExpressionStatement::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ m_expression->dump(indent + 1);
+}
+
+Value ObjectProperty::execute(Interpreter& interpreter, GlobalObject&) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ // NOTE: ObjectProperty execution is handled by ObjectExpression.
+ ASSERT_NOT_REACHED();
+}
+
+Value ObjectExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto* object = Object::create_empty(global_object);
+ for (auto& property : m_properties) {
+ auto key = property.key().execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+
+ if (property.type() == ObjectProperty::Type::Spread) {
+ if (key.is_array()) {
+ auto& array_to_spread = static_cast<Array&>(key.as_object());
+ for (auto& entry : array_to_spread.indexed_properties()) {
+ object->indexed_properties().put(object, entry.index(), entry.value_and_attributes(&array_to_spread).value);
+ if (interpreter.exception())
+ return {};
+ }
+ } else if (key.is_object()) {
+ auto& obj_to_spread = key.as_object();
+
+ for (auto& it : obj_to_spread.shape().property_table_ordered()) {
+ if (it.value.attributes.is_enumerable()) {
+ object->define_property(it.key, obj_to_spread.get(it.key));
+ if (interpreter.exception())
+ return {};
+ }
+ }
+ } else if (key.is_string()) {
+ auto& str_to_spread = key.as_string().string();
+
+ for (size_t i = 0; i < str_to_spread.length(); i++) {
+ object->define_property(i, js_string(interpreter.heap(), str_to_spread.substring(i, 1)));
+ if (interpreter.exception())
+ return {};
+ }
+ }
+
+ continue;
+ }
+
+ auto value = property.value().execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+
+ if (value.is_function() && property.is_method())
+ value.as_function().set_home_object(object);
+
+ String name = get_function_name(global_object, key);
+ if (property.type() == ObjectProperty::Type::Getter) {
+ name = String::formatted("get {}", name);
+ } else if (property.type() == ObjectProperty::Type::Setter) {
+ name = String::formatted("set {}", name);
+ }
+
+ update_function_name(value, name);
+
+ if (property.type() == ObjectProperty::Type::Getter || property.type() == ObjectProperty::Type::Setter) {
+ ASSERT(value.is_function());
+ object->define_accessor(PropertyName::from_value(global_object, key), value.as_function(), property.type() == ObjectProperty::Type::Getter, Attribute::Configurable | Attribute::Enumerable);
+ if (interpreter.exception())
+ return {};
+ } else {
+ object->define_property(PropertyName::from_value(global_object, key), value);
+ if (interpreter.exception())
+ return {};
+ }
+ }
+ return object;
+}
+
+void MemberExpression::dump(int indent) const
+{
+ print_indent(indent);
+ outln("%{}(computed={})", class_name(), is_computed());
+ m_object->dump(indent + 1);
+ m_property->dump(indent + 1);
+}
+
+PropertyName MemberExpression::computed_property_name(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ if (!is_computed()) {
+ ASSERT(is<Identifier>(*m_property));
+ return static_cast<const Identifier&>(*m_property).string();
+ }
+ auto value = m_property->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ ASSERT(!value.is_empty());
+ return PropertyName::from_value(global_object, value);
+}
+
+String MemberExpression::to_string_approximation() const
+{
+ String object_string = "<object>";
+ if (is<Identifier>(*m_object))
+ object_string = static_cast<const Identifier&>(*m_object).string();
+ if (is_computed())
+ return String::formatted("{}[<computed>]", object_string);
+ ASSERT(is<Identifier>(*m_property));
+ return String::formatted("{}.{}", object_string, static_cast<const Identifier&>(*m_property).string());
+}
+
+Value MemberExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto object_value = m_object->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ auto* object_result = object_value.to_object(global_object);
+ if (interpreter.exception())
+ return {};
+ auto property_name = computed_property_name(interpreter, global_object);
+ if (!property_name.is_valid())
+ return {};
+ return object_result->get(property_name).value_or(js_undefined());
+}
+
+void MetaProperty::dump(int indent) const
+{
+ String name;
+ if (m_type == MetaProperty::Type::NewTarget)
+ name = "new.target";
+ else if (m_type == MetaProperty::Type::ImportMeta)
+ name = "import.meta";
+ else
+ ASSERT_NOT_REACHED();
+ print_indent(indent);
+ outln("{} {}", class_name(), name);
+}
+
+Value MetaProperty::execute(Interpreter& interpreter, GlobalObject&) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ if (m_type == MetaProperty::Type::NewTarget)
+ return interpreter.vm().get_new_target().value_or(js_undefined());
+ if (m_type == MetaProperty::Type::ImportMeta)
+ TODO();
+ ASSERT_NOT_REACHED();
+}
+
+Value StringLiteral::execute(Interpreter& interpreter, GlobalObject&) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ return js_string(interpreter.heap(), m_value);
+}
+
+Value NumericLiteral::execute(Interpreter& interpreter, GlobalObject&) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ return Value(m_value);
+}
+
+Value BigIntLiteral::execute(Interpreter& interpreter, GlobalObject&) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ return js_bigint(interpreter.heap(), Crypto::SignedBigInteger::from_base10(m_value.substring(0, m_value.length() - 1)));
+}
+
+Value BooleanLiteral::execute(Interpreter& interpreter, GlobalObject&) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ return Value(m_value);
+}
+
+Value NullLiteral::execute(Interpreter& interpreter, GlobalObject&) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ return js_null();
+}
+
+void RegExpLiteral::dump(int indent) const
+{
+ print_indent(indent);
+ outln("{} (/{}/{})", class_name(), content(), flags());
+}
+
+Value RegExpLiteral::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ return RegExpObject::create(global_object, content(), flags());
+}
+
+void ArrayExpression::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ for (auto& element : m_elements) {
+ if (element) {
+ element->dump(indent + 1);
+ } else {
+ print_indent(indent + 1);
+ outln("<empty>");
+ }
+ }
+}
+
+Value ArrayExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto* array = Array::create(global_object);
+ for (auto& element : m_elements) {
+ auto value = Value();
+ if (element) {
+ value = element->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+
+ if (is<SpreadExpression>(*element)) {
+ get_iterator_values(global_object, value, [&](Value iterator_value) {
+ array->indexed_properties().append(iterator_value);
+ return IterationDecision::Continue;
+ });
+ if (interpreter.exception())
+ return {};
+ continue;
+ }
+ }
+ array->indexed_properties().append(value);
+ }
+ return array;
+}
+
+void TemplateLiteral::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ for (auto& expression : m_expressions)
+ expression.dump(indent + 1);
+}
+
+Value TemplateLiteral::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ StringBuilder string_builder;
+
+ for (auto& expression : m_expressions) {
+ auto expr = expression.execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ auto string = expr.to_string(global_object);
+ if (interpreter.exception())
+ return {};
+ string_builder.append(string);
+ }
+
+ return js_string(interpreter.heap(), string_builder.build());
+}
+
+void TaggedTemplateLiteral::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ print_indent(indent + 1);
+ outln("(Tag)");
+ m_tag->dump(indent + 2);
+ print_indent(indent + 1);
+ outln("(Template Literal)");
+ m_template_literal->dump(indent + 2);
+}
+
+Value TaggedTemplateLiteral::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto& vm = interpreter.vm();
+ auto tag = m_tag->execute(interpreter, global_object);
+ if (vm.exception())
+ return {};
+ if (!tag.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, tag.to_string_without_side_effects());
+ return {};
+ }
+ auto& tag_function = tag.as_function();
+ auto& expressions = m_template_literal->expressions();
+ auto* strings = Array::create(global_object);
+ MarkedValueList arguments(vm.heap());
+ arguments.append(strings);
+ for (size_t i = 0; i < expressions.size(); ++i) {
+ auto value = expressions[i].execute(interpreter, global_object);
+ if (vm.exception())
+ return {};
+ // tag`${foo}` -> "", foo, "" -> tag(["", ""], foo)
+ // tag`foo${bar}baz${qux}` -> "foo", bar, "baz", qux, "" -> tag(["foo", "baz", ""], bar, qux)
+ if (i % 2 == 0) {
+ strings->indexed_properties().append(value);
+ } else {
+ arguments.append(value);
+ }
+ }
+
+ auto* raw_strings = Array::create(global_object);
+ for (auto& raw_string : m_template_literal->raw_strings()) {
+ auto value = raw_string.execute(interpreter, global_object);
+ if (vm.exception())
+ return {};
+ raw_strings->indexed_properties().append(value);
+ }
+ strings->define_property(vm.names.raw, raw_strings, 0);
+ return vm.call(tag_function, js_undefined(), move(arguments));
+}
+
+void TryStatement::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ print_indent(indent);
+ outln("(Block)");
+ block().dump(indent + 1);
+
+ if (handler()) {
+ print_indent(indent);
+ outln("(Handler)");
+ handler()->dump(indent + 1);
+ }
+
+ if (finalizer()) {
+ print_indent(indent);
+ outln("(Finalizer)");
+ finalizer()->dump(indent + 1);
+ }
+}
+
+void CatchClause::dump(int indent) const
+{
+ print_indent(indent);
+ if (m_parameter.is_null())
+ outln("CatchClause");
+ else
+ outln("CatchClause ({})", m_parameter);
+ body().dump(indent + 1);
+}
+
+void ThrowStatement::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ argument().dump(indent + 1);
+}
+
+Value TryStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ interpreter.execute_statement(global_object, m_block, ScopeType::Try);
+ if (auto* exception = interpreter.exception()) {
+ if (m_handler) {
+ interpreter.vm().clear_exception();
+
+ HashMap<FlyString, Variable> parameters;
+ parameters.set(m_handler->parameter(), Variable { exception->value(), DeclarationKind::Var });
+ auto* catch_scope = interpreter.heap().allocate<LexicalEnvironment>(global_object, move(parameters), interpreter.vm().call_frame().scope);
+ TemporaryChange<ScopeObject*> scope_change(interpreter.vm().call_frame().scope, catch_scope);
+ interpreter.execute_statement(global_object, m_handler->body());
+ }
+ }
+
+ if (m_finalizer) {
+ // Keep, if any, and then clear the current exception so we can
+ // execute() the finalizer without an exception in our way.
+ auto* previous_exception = interpreter.exception();
+ interpreter.vm().clear_exception();
+ interpreter.vm().stop_unwind();
+ m_finalizer->execute(interpreter, global_object);
+ // If we previously had an exception and the finalizer didn't
+ // throw a new one, restore the old one.
+ // FIXME: This will print debug output in throw_exception() for
+ // a seconds time with m_should_log_exceptions enabled.
+ if (previous_exception && !interpreter.exception())
+ interpreter.vm().throw_exception(previous_exception);
+ }
+
+ return js_undefined();
+}
+
+Value CatchClause::execute(Interpreter& interpreter, GlobalObject&) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ // NOTE: CatchClause execution is handled by TryStatement.
+ ASSERT_NOT_REACHED();
+ return {};
+}
+
+Value ThrowStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto value = m_argument->execute(interpreter, global_object);
+ if (interpreter.vm().exception())
+ return {};
+ interpreter.vm().throw_exception(global_object, value);
+ return {};
+}
+
+Value SwitchStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto discriminant_result = m_discriminant->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+
+ bool falling_through = false;
+
+ for (auto& switch_case : m_cases) {
+ if (!falling_through && switch_case.test()) {
+ auto test_result = switch_case.test()->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ if (!strict_eq(discriminant_result, test_result))
+ continue;
+ }
+ falling_through = true;
+
+ for (auto& statement : switch_case.consequent()) {
+ auto last_value = statement.execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ if (interpreter.vm().should_unwind()) {
+ if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_label)) {
+ // No stop_unwind(), the outer loop will handle that - we just need to break out of the switch/case.
+ return {};
+ } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_label)) {
+ interpreter.vm().stop_unwind();
+ return {};
+ } else {
+ return last_value;
+ }
+ }
+ }
+ }
+
+ return js_undefined();
+}
+
+Value SwitchCase::execute(Interpreter& interpreter, GlobalObject&) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ // NOTE: SwitchCase execution is handled by SwitchStatement.
+ ASSERT_NOT_REACHED();
+ return {};
+}
+
+Value BreakStatement::execute(Interpreter& interpreter, GlobalObject&) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ interpreter.vm().unwind(ScopeType::Breakable, m_target_label);
+ return js_undefined();
+}
+
+Value ContinueStatement::execute(Interpreter& interpreter, GlobalObject&) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ interpreter.vm().unwind(ScopeType::Continuable, m_target_label);
+ return js_undefined();
+}
+
+void SwitchStatement::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ m_discriminant->dump(indent + 1);
+ for (auto& switch_case : m_cases) {
+ switch_case.dump(indent + 1);
+ }
+}
+
+void SwitchCase::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ print_indent(indent + 1);
+ if (m_test) {
+ outln("(Test)");
+ m_test->dump(indent + 2);
+ } else {
+ outln("(Default)");
+ }
+ print_indent(indent + 1);
+ outln("(Consequent)");
+ for (auto& statement : m_consequent)
+ statement.dump(indent + 2);
+}
+
+Value ConditionalExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ auto test_result = m_test->execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ Value result;
+ if (test_result.to_boolean()) {
+ result = m_consequent->execute(interpreter, global_object);
+ } else {
+ result = m_alternate->execute(interpreter, global_object);
+ }
+ if (interpreter.exception())
+ return {};
+ return result;
+}
+
+void ConditionalExpression::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ print_indent(indent + 1);
+ outln("(Test)");
+ m_test->dump(indent + 2);
+ print_indent(indent + 1);
+ outln("(Consequent)");
+ m_consequent->dump(indent + 2);
+ print_indent(indent + 1);
+ outln("(Alternate)");
+ m_alternate->dump(indent + 2);
+}
+
+void SequenceExpression::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ for (auto& expression : m_expressions)
+ expression.dump(indent + 1);
+}
+
+Value SequenceExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ Value last_value;
+ for (auto& expression : m_expressions) {
+ last_value = expression.execute(interpreter, global_object);
+ if (interpreter.exception())
+ return {};
+ }
+ return last_value;
+}
+
+Value DebuggerStatement::execute(Interpreter& interpreter, GlobalObject&) const
+{
+ interpreter.enter_node(*this);
+ ScopeGuard exit_node { [&] { interpreter.exit_node(*this); } };
+
+ // Sorry, no JavaScript debugger available (yet)!
+ return js_undefined();
+}
+
+void ScopeNode::add_variables(NonnullRefPtrVector<VariableDeclaration> variables)
+{
+ m_variables.append(move(variables));
+}
+
+void ScopeNode::add_functions(NonnullRefPtrVector<FunctionDeclaration> functions)
+{
+ m_functions.append(move(functions));
+}
+
+}
diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h
new file mode 100644
index 0000000000..fcc12d0460
--- /dev/null
+++ b/Userland/Libraries/LibJS/AST.h
@@ -0,0 +1,1385 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <AK/HashMap.h>
+#include <AK/NonnullRefPtrVector.h>
+#include <AK/RefPtr.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <LibJS/Forward.h>
+#include <LibJS/Runtime/PropertyName.h>
+#include <LibJS/Runtime/Value.h>
+#include <LibJS/SourceRange.h>
+
+namespace JS {
+
+class VariableDeclaration;
+class FunctionDeclaration;
+
+template<class T, class... Args>
+static inline NonnullRefPtr<T>
+create_ast_node(SourceRange range, Args&&... args)
+{
+ return adopt(*new T(range, forward<Args>(args)...));
+}
+
+class ASTNode : public RefCounted<ASTNode> {
+public:
+ virtual ~ASTNode() { }
+ virtual const char* class_name() const = 0;
+ virtual Value execute(Interpreter&, GlobalObject&) const = 0;
+ virtual void dump(int indent) const;
+
+ const SourceRange& source_range() const { return m_source_range; }
+ SourceRange& source_range() { return m_source_range; }
+
+protected:
+ ASTNode(SourceRange source_range)
+ : m_source_range(move(source_range))
+ {
+ }
+
+private:
+ SourceRange m_source_range;
+};
+
+class Statement : public ASTNode {
+public:
+ Statement(SourceRange source_range)
+ : ASTNode(move(source_range))
+ {
+ }
+
+ const FlyString& label() const { return m_label; }
+ void set_label(FlyString string) { m_label = string; }
+
+protected:
+ FlyString m_label;
+};
+
+class EmptyStatement final : public Statement {
+public:
+ EmptyStatement(SourceRange source_range)
+ : Statement(move(source_range))
+ {
+ }
+ Value execute(Interpreter&, GlobalObject&) const override { return js_undefined(); }
+ const char* class_name() const override { return "EmptyStatement"; }
+};
+
+class ErrorStatement final : public Statement {
+public:
+ ErrorStatement(SourceRange source_range)
+ : Statement(move(source_range))
+ {
+ }
+ Value execute(Interpreter&, GlobalObject&) const override { return js_undefined(); }
+ const char* class_name() const override { return "ErrorStatement"; }
+};
+
+class ExpressionStatement final : public Statement {
+public:
+ ExpressionStatement(SourceRange source_range, NonnullRefPtr<Expression> expression)
+ : Statement(move(source_range))
+ , m_expression(move(expression))
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+ const Expression& expression() const { return m_expression; };
+
+private:
+ virtual const char* class_name() const override { return "ExpressionStatement"; }
+
+ NonnullRefPtr<Expression> m_expression;
+};
+
+class ScopeNode : public Statement {
+public:
+ template<typename T, typename... Args>
+ T& append(SourceRange range, Args&&... args)
+ {
+ auto child = create_ast_node<T>(range, forward<Args>(args)...);
+ m_children.append(move(child));
+ return static_cast<T&>(m_children.last());
+ }
+ void append(NonnullRefPtr<Statement> child)
+ {
+ m_children.append(move(child));
+ }
+
+ const NonnullRefPtrVector<Statement>& children() const { return m_children; }
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+ void add_variables(NonnullRefPtrVector<VariableDeclaration>);
+ void add_functions(NonnullRefPtrVector<FunctionDeclaration>);
+ const NonnullRefPtrVector<VariableDeclaration>& variables() const { return m_variables; }
+ const NonnullRefPtrVector<FunctionDeclaration>& functions() const { return m_functions; }
+
+protected:
+ ScopeNode(SourceRange source_range)
+ : Statement(move(source_range))
+ {
+ }
+
+private:
+ NonnullRefPtrVector<Statement> m_children;
+ NonnullRefPtrVector<VariableDeclaration> m_variables;
+ NonnullRefPtrVector<FunctionDeclaration> m_functions;
+};
+
+class Program final : public ScopeNode {
+public:
+ Program(SourceRange source_range)
+ : ScopeNode(move(source_range))
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+
+ bool is_strict_mode() const { return m_is_strict_mode; }
+ void set_strict_mode() { m_is_strict_mode = true; }
+
+private:
+ bool m_is_strict_mode { false };
+
+ virtual const char* class_name() const override { return "Program"; }
+};
+
+class BlockStatement final : public ScopeNode {
+public:
+ BlockStatement(SourceRange source_range)
+ : ScopeNode(move(source_range))
+ {
+ }
+
+private:
+ virtual const char* class_name() const override { return "BlockStatement"; }
+};
+
+class Expression : public ASTNode {
+public:
+ Expression(SourceRange source_range)
+ : ASTNode(move(source_range))
+ {
+ }
+ virtual Reference to_reference(Interpreter&, GlobalObject&) const;
+};
+
+class Declaration : public Statement {
+public:
+ Declaration(SourceRange source_range)
+ : Statement(move(source_range))
+ {
+ }
+};
+
+class ErrorDeclaration final : public Declaration {
+public:
+ ErrorDeclaration(SourceRange source_range)
+ : Declaration(move(source_range))
+ {
+ }
+ Value execute(Interpreter&, GlobalObject&) const override { return js_undefined(); }
+ const char* class_name() const override { return "ErrorDeclaration"; }
+};
+
+class FunctionNode {
+public:
+ struct Parameter {
+ FlyString name;
+ RefPtr<Expression> default_value;
+ bool is_rest { false };
+ };
+
+ const FlyString& name() const { return m_name; }
+ const Statement& body() const { return *m_body; }
+ const Vector<Parameter>& parameters() const { return m_parameters; };
+ i32 function_length() const { return m_function_length; }
+ bool is_strict_mode() const { return m_is_strict_mode; }
+
+protected:
+ FunctionNode(const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, i32 function_length, NonnullRefPtrVector<VariableDeclaration> variables, bool is_strict_mode)
+ : m_name(name)
+ , m_body(move(body))
+ , m_parameters(move(parameters))
+ , m_variables(move(variables))
+ , m_function_length(function_length)
+ , m_is_strict_mode(is_strict_mode)
+ {
+ }
+
+ void dump(int indent, const char* class_name) const;
+
+ const NonnullRefPtrVector<VariableDeclaration>& variables() const { return m_variables; }
+
+private:
+ FlyString m_name;
+ NonnullRefPtr<Statement> m_body;
+ const Vector<Parameter> m_parameters;
+ NonnullRefPtrVector<VariableDeclaration> m_variables;
+ const i32 m_function_length;
+ bool m_is_strict_mode;
+};
+
+class FunctionDeclaration final
+ : public Declaration
+ , public FunctionNode {
+public:
+ static bool must_have_name() { return true; }
+
+ FunctionDeclaration(SourceRange source_range, const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, i32 function_length, NonnullRefPtrVector<VariableDeclaration> variables, bool is_strict_mode = false)
+ : Declaration(move(source_range))
+ , FunctionNode(name, move(body), move(parameters), function_length, move(variables), is_strict_mode)
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "FunctionDeclaration"; }
+};
+
+class FunctionExpression final
+ : public Expression
+ , public FunctionNode {
+public:
+ static bool must_have_name() { return false; }
+
+ FunctionExpression(SourceRange source_range, const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, i32 function_length, NonnullRefPtrVector<VariableDeclaration> variables, bool is_strict_mode, bool is_arrow_function = false)
+ : Expression(move(source_range))
+ , FunctionNode(name, move(body), move(parameters), function_length, move(variables), is_strict_mode)
+ , m_is_arrow_function(is_arrow_function)
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "FunctionExpression"; }
+
+ bool m_is_arrow_function;
+};
+
+class ErrorExpression final : public Expression {
+public:
+ explicit ErrorExpression(SourceRange source_range)
+ : Expression(move(source_range))
+ {
+ }
+
+ Value execute(Interpreter&, GlobalObject&) const override { return js_undefined(); }
+ const char* class_name() const override { return "ErrorExpression"; }
+};
+
+class ReturnStatement final : public Statement {
+public:
+ explicit ReturnStatement(SourceRange source_range, RefPtr<Expression> argument)
+ : Statement(move(source_range))
+ , m_argument(move(argument))
+ {
+ }
+
+ const Expression* argument() const { return m_argument; }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "ReturnStatement"; }
+
+ RefPtr<Expression> m_argument;
+};
+
+class IfStatement final : public Statement {
+public:
+ IfStatement(SourceRange source_range, NonnullRefPtr<Expression> predicate, NonnullRefPtr<Statement> consequent, RefPtr<Statement> alternate)
+ : Statement(move(source_range))
+ , m_predicate(move(predicate))
+ , m_consequent(move(consequent))
+ , m_alternate(move(alternate))
+ {
+ }
+
+ const Expression& predicate() const { return *m_predicate; }
+ const Statement& consequent() const { return *m_consequent; }
+ const Statement* alternate() const { return m_alternate; }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "IfStatement"; }
+
+ NonnullRefPtr<Expression> m_predicate;
+ NonnullRefPtr<Statement> m_consequent;
+ RefPtr<Statement> m_alternate;
+};
+
+class WhileStatement final : public Statement {
+public:
+ WhileStatement(SourceRange source_range, NonnullRefPtr<Expression> test, NonnullRefPtr<Statement> body)
+ : Statement(move(source_range))
+ , m_test(move(test))
+ , m_body(move(body))
+ {
+ }
+
+ const Expression& test() const { return *m_test; }
+ const Statement& body() const { return *m_body; }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "WhileStatement"; }
+
+ NonnullRefPtr<Expression> m_test;
+ NonnullRefPtr<Statement> m_body;
+};
+
+class DoWhileStatement final : public Statement {
+public:
+ DoWhileStatement(SourceRange source_range, NonnullRefPtr<Expression> test, NonnullRefPtr<Statement> body)
+ : Statement(move(source_range))
+ , m_test(move(test))
+ , m_body(move(body))
+ {
+ }
+
+ const Expression& test() const { return *m_test; }
+ const Statement& body() const { return *m_body; }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "DoWhileStatement"; }
+
+ NonnullRefPtr<Expression> m_test;
+ NonnullRefPtr<Statement> m_body;
+};
+
+class WithStatement final : public Statement {
+public:
+ WithStatement(SourceRange source_range, NonnullRefPtr<Expression> object, NonnullRefPtr<Statement> body)
+ : Statement(move(source_range))
+ , m_object(move(object))
+ , m_body(move(body))
+ {
+ }
+
+ const Expression& object() const { return *m_object; }
+ const Statement& body() const { return *m_body; }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "WithStatement"; }
+
+ NonnullRefPtr<Expression> m_object;
+ NonnullRefPtr<Statement> m_body;
+};
+
+class ForStatement final : public Statement {
+public:
+ ForStatement(SourceRange source_range, RefPtr<ASTNode> init, RefPtr<Expression> test, RefPtr<Expression> update, NonnullRefPtr<Statement> body)
+ : Statement(move(source_range))
+ , m_init(move(init))
+ , m_test(move(test))
+ , m_update(move(update))
+ , m_body(move(body))
+ {
+ }
+
+ const ASTNode* init() const { return m_init; }
+ const Expression* test() const { return m_test; }
+ const Expression* update() const { return m_update; }
+ const Statement& body() const { return *m_body; }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "ForStatement"; }
+
+ RefPtr<ASTNode> m_init;
+ RefPtr<Expression> m_test;
+ RefPtr<Expression> m_update;
+ NonnullRefPtr<Statement> m_body;
+};
+
+class ForInStatement final : public Statement {
+public:
+ ForInStatement(SourceRange source_range, NonnullRefPtr<ASTNode> lhs, NonnullRefPtr<Expression> rhs, NonnullRefPtr<Statement> body)
+ : Statement(move(source_range))
+ , m_lhs(move(lhs))
+ , m_rhs(move(rhs))
+ , m_body(move(body))
+ {
+ }
+
+ const ASTNode& lhs() const { return *m_lhs; }
+ const Expression& rhs() const { return *m_rhs; }
+ const Statement& body() const { return *m_body; }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "ForInStatement"; }
+
+ NonnullRefPtr<ASTNode> m_lhs;
+ NonnullRefPtr<Expression> m_rhs;
+ NonnullRefPtr<Statement> m_body;
+};
+
+class ForOfStatement final : public Statement {
+public:
+ ForOfStatement(SourceRange source_range, NonnullRefPtr<ASTNode> lhs, NonnullRefPtr<Expression> rhs, NonnullRefPtr<Statement> body)
+ : Statement(move(source_range))
+ , m_lhs(move(lhs))
+ , m_rhs(move(rhs))
+ , m_body(move(body))
+ {
+ }
+
+ const ASTNode& lhs() const { return *m_lhs; }
+ const Expression& rhs() const { return *m_rhs; }
+ const Statement& body() const { return *m_body; }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "ForOfStatement"; }
+
+ NonnullRefPtr<ASTNode> m_lhs;
+ NonnullRefPtr<Expression> m_rhs;
+ NonnullRefPtr<Statement> m_body;
+};
+
+enum class BinaryOp {
+ Addition,
+ Subtraction,
+ Multiplication,
+ Division,
+ Modulo,
+ Exponentiation,
+ TypedEquals,
+ TypedInequals,
+ AbstractEquals,
+ AbstractInequals,
+ GreaterThan,
+ GreaterThanEquals,
+ LessThan,
+ LessThanEquals,
+ BitwiseAnd,
+ BitwiseOr,
+ BitwiseXor,
+ LeftShift,
+ RightShift,
+ UnsignedRightShift,
+ In,
+ InstanceOf,
+};
+
+class BinaryExpression final : public Expression {
+public:
+ BinaryExpression(SourceRange source_range, BinaryOp op, NonnullRefPtr<Expression> lhs, NonnullRefPtr<Expression> rhs)
+ : Expression(move(source_range))
+ , m_op(op)
+ , m_lhs(move(lhs))
+ , m_rhs(move(rhs))
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "BinaryExpression"; }
+
+ BinaryOp m_op;
+ NonnullRefPtr<Expression> m_lhs;
+ NonnullRefPtr<Expression> m_rhs;
+};
+
+enum class LogicalOp {
+ And,
+ Or,
+ NullishCoalescing,
+};
+
+class LogicalExpression final : public Expression {
+public:
+ LogicalExpression(SourceRange source_range, LogicalOp op, NonnullRefPtr<Expression> lhs, NonnullRefPtr<Expression> rhs)
+ : Expression(move(source_range))
+ , m_op(op)
+ , m_lhs(move(lhs))
+ , m_rhs(move(rhs))
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "LogicalExpression"; }
+
+ LogicalOp m_op;
+ NonnullRefPtr<Expression> m_lhs;
+ NonnullRefPtr<Expression> m_rhs;
+};
+
+enum class UnaryOp {
+ BitwiseNot,
+ Not,
+ Plus,
+ Minus,
+ Typeof,
+ Void,
+ Delete,
+};
+
+class UnaryExpression final : public Expression {
+public:
+ UnaryExpression(SourceRange source_range, UnaryOp op, NonnullRefPtr<Expression> lhs)
+ : Expression(move(source_range))
+ , m_op(op)
+ , m_lhs(move(lhs))
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "UnaryExpression"; }
+
+ UnaryOp m_op;
+ NonnullRefPtr<Expression> m_lhs;
+};
+
+class SequenceExpression final : public Expression {
+public:
+ SequenceExpression(SourceRange source_range, NonnullRefPtrVector<Expression> expressions)
+ : Expression(move(source_range))
+ , m_expressions(move(expressions))
+ {
+ }
+
+ virtual void dump(int indent) const override;
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+
+private:
+ virtual const char* class_name() const override { return "SequenceExpression"; }
+
+ NonnullRefPtrVector<Expression> m_expressions;
+};
+
+class Literal : public Expression {
+protected:
+ explicit Literal(SourceRange source_range)
+ : Expression(move(source_range))
+ {
+ }
+};
+
+class BooleanLiteral final : public Literal {
+public:
+ explicit BooleanLiteral(SourceRange source_range, bool value)
+ : Literal(move(source_range))
+ , m_value(value)
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "BooleanLiteral"; }
+
+ bool m_value { false };
+};
+
+class NumericLiteral final : public Literal {
+public:
+ explicit NumericLiteral(SourceRange source_range, double value)
+ : Literal(move(source_range))
+ , m_value(value)
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "NumericLiteral"; }
+
+ double m_value { 0 };
+};
+
+class BigIntLiteral final : public Literal {
+public:
+ explicit BigIntLiteral(SourceRange source_range, String value)
+ : Literal(move(source_range))
+ , m_value(move(value))
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "BigIntLiteral"; }
+
+ String m_value;
+};
+
+class StringLiteral final : public Literal {
+public:
+ explicit StringLiteral(SourceRange source_range, String value, bool is_use_strict_directive = false)
+ : Literal(move(source_range))
+ , m_value(move(value))
+ , m_is_use_strict_directive(is_use_strict_directive)
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+ StringView value() const { return m_value; }
+ bool is_use_strict_directive() const { return m_is_use_strict_directive; };
+
+private:
+ virtual const char* class_name() const override { return "StringLiteral"; }
+
+ String m_value;
+ bool m_is_use_strict_directive;
+};
+
+class NullLiteral final : public Literal {
+public:
+ explicit NullLiteral(SourceRange source_range)
+ : Literal(move(source_range))
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "NullLiteral"; }
+};
+
+class RegExpLiteral final : public Literal {
+public:
+ explicit RegExpLiteral(SourceRange source_range, String content, String flags)
+ : Literal(move(source_range))
+ , m_content(content)
+ , m_flags(flags)
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+ const String& content() const { return m_content; }
+ const String& flags() const { return m_flags; }
+
+private:
+ virtual const char* class_name() const override { return "RegexLiteral"; }
+
+ String m_content;
+ String m_flags;
+};
+
+class Identifier final : public Expression {
+public:
+ explicit Identifier(SourceRange source_range, const FlyString& string)
+ : Expression(move(source_range))
+ , m_string(string)
+ {
+ }
+
+ const FlyString& string() const { return m_string; }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+ virtual Reference to_reference(Interpreter&, GlobalObject&) const override;
+
+private:
+ virtual const char* class_name() const override { return "Identifier"; }
+
+ FlyString m_string;
+};
+
+class ClassMethod final : public ASTNode {
+public:
+ enum class Kind {
+ Method,
+ Getter,
+ Setter,
+ };
+
+ ClassMethod(SourceRange source_range, NonnullRefPtr<Expression> key, NonnullRefPtr<FunctionExpression> function, Kind kind, bool is_static)
+ : ASTNode(move(source_range))
+ , m_key(move(key))
+ , m_function(move(function))
+ , m_kind(kind)
+ , m_is_static(is_static)
+ {
+ }
+
+ const Expression& key() const { return *m_key; }
+ Kind kind() const { return m_kind; }
+ bool is_static() const { return m_is_static; }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "ClassMethod"; }
+
+ NonnullRefPtr<Expression> m_key;
+ NonnullRefPtr<FunctionExpression> m_function;
+ Kind m_kind;
+ bool m_is_static;
+};
+
+class SuperExpression final : public Expression {
+public:
+ SuperExpression(SourceRange source_range)
+ : Expression(move(source_range))
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "SuperExpression"; }
+};
+
+class ClassExpression final : public Expression {
+public:
+ ClassExpression(SourceRange source_range, String name, RefPtr<FunctionExpression> constructor, RefPtr<Expression> super_class, NonnullRefPtrVector<ClassMethod> methods)
+ : Expression(move(source_range))
+ , m_name(move(name))
+ , m_constructor(move(constructor))
+ , m_super_class(move(super_class))
+ , m_methods(move(methods))
+ {
+ }
+
+ StringView name() const { return m_name; }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "ClassExpression"; }
+
+ String m_name;
+ RefPtr<FunctionExpression> m_constructor;
+ RefPtr<Expression> m_super_class;
+ NonnullRefPtrVector<ClassMethod> m_methods;
+};
+
+class ClassDeclaration final : public Declaration {
+public:
+ ClassDeclaration(SourceRange source_range, NonnullRefPtr<ClassExpression> class_expression)
+ : Declaration(move(source_range))
+ , m_class_expression(move(class_expression))
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "ClassDeclaration"; }
+ NonnullRefPtr<ClassExpression> m_class_expression;
+};
+
+class SpreadExpression final : public Expression {
+public:
+ explicit SpreadExpression(SourceRange source_range, NonnullRefPtr<Expression> target)
+ : Expression(move(source_range))
+ , m_target(target)
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "SpreadExpression"; }
+
+ NonnullRefPtr<Expression> m_target;
+};
+
+class ThisExpression final : public Expression {
+public:
+ ThisExpression(SourceRange source_range)
+ : Expression(move(source_range))
+ {
+ }
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "ThisExpression"; }
+};
+
+class CallExpression : public Expression {
+public:
+ struct Argument {
+ NonnullRefPtr<Expression> value;
+ bool is_spread;
+ };
+
+ CallExpression(SourceRange source_range, NonnullRefPtr<Expression> callee, Vector<Argument> arguments = {})
+ : Expression(move(source_range))
+ , m_callee(move(callee))
+ , m_arguments(move(arguments))
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "CallExpression"; }
+
+ struct ThisAndCallee {
+ Value this_value;
+ Value callee;
+ };
+ ThisAndCallee compute_this_and_callee(Interpreter&, GlobalObject&) const;
+
+ NonnullRefPtr<Expression> m_callee;
+ const Vector<Argument> m_arguments;
+};
+
+class NewExpression final : public CallExpression {
+public:
+ NewExpression(SourceRange source_range, NonnullRefPtr<Expression> callee, Vector<Argument> arguments = {})
+ : CallExpression(move(source_range), move(callee), move(arguments))
+ {
+ }
+
+private:
+ virtual const char* class_name() const override { return "NewExpression"; }
+};
+
+enum class AssignmentOp {
+ Assignment,
+ AdditionAssignment,
+ SubtractionAssignment,
+ MultiplicationAssignment,
+ DivisionAssignment,
+ ModuloAssignment,
+ ExponentiationAssignment,
+ BitwiseAndAssignment,
+ BitwiseOrAssignment,
+ BitwiseXorAssignment,
+ LeftShiftAssignment,
+ RightShiftAssignment,
+ UnsignedRightShiftAssignment,
+ AndAssignment,
+ OrAssignment,
+ NullishAssignment,
+};
+
+class AssignmentExpression final : public Expression {
+public:
+ AssignmentExpression(SourceRange source_range, AssignmentOp op, NonnullRefPtr<Expression> lhs, NonnullRefPtr<Expression> rhs)
+ : Expression(move(source_range))
+ , m_op(op)
+ , m_lhs(move(lhs))
+ , m_rhs(move(rhs))
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "AssignmentExpression"; }
+
+ AssignmentOp m_op;
+ NonnullRefPtr<Expression> m_lhs;
+ NonnullRefPtr<Expression> m_rhs;
+};
+
+enum class UpdateOp {
+ Increment,
+ Decrement,
+};
+
+class UpdateExpression final : public Expression {
+public:
+ UpdateExpression(SourceRange source_range, UpdateOp op, NonnullRefPtr<Expression> argument, bool prefixed = false)
+ : Expression(move(source_range))
+ , m_op(op)
+ , m_argument(move(argument))
+ , m_prefixed(prefixed)
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "UpdateExpression"; }
+
+ UpdateOp m_op;
+ NonnullRefPtr<Expression> m_argument;
+ bool m_prefixed;
+};
+
+enum class DeclarationKind {
+ Var,
+ Let,
+ Const,
+};
+
+class VariableDeclarator final : public ASTNode {
+public:
+ VariableDeclarator(SourceRange source_range, NonnullRefPtr<Identifier> id)
+ : ASTNode(move(source_range))
+ , m_id(move(id))
+ {
+ }
+
+ VariableDeclarator(SourceRange source_range, NonnullRefPtr<Identifier> id, RefPtr<Expression> init)
+ : ASTNode(move(source_range))
+ , m_id(move(id))
+ , m_init(move(init))
+ {
+ }
+
+ const Identifier& id() const { return m_id; }
+ const Expression* init() const { return m_init; }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "VariableDeclarator"; }
+
+ NonnullRefPtr<Identifier> m_id;
+ RefPtr<Expression> m_init;
+};
+
+class VariableDeclaration final : public Declaration {
+public:
+ VariableDeclaration(SourceRange source_range, DeclarationKind declaration_kind, NonnullRefPtrVector<VariableDeclarator> declarations)
+ : Declaration(move(source_range))
+ , m_declaration_kind(declaration_kind)
+ , m_declarations(move(declarations))
+ {
+ }
+
+ DeclarationKind declaration_kind() const { return m_declaration_kind; }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+ const NonnullRefPtrVector<VariableDeclarator>& declarations() const { return m_declarations; }
+
+private:
+ virtual const char* class_name() const override { return "VariableDeclaration"; }
+
+ DeclarationKind m_declaration_kind;
+ NonnullRefPtrVector<VariableDeclarator> m_declarations;
+};
+
+class ObjectProperty final : public ASTNode {
+public:
+ enum class Type {
+ KeyValue,
+ Getter,
+ Setter,
+ Spread,
+ };
+
+ ObjectProperty(SourceRange source_range, NonnullRefPtr<Expression> key, RefPtr<Expression> value, Type property_type, bool is_method)
+ : ASTNode(move(source_range))
+ , m_key(move(key))
+ , m_value(move(value))
+ , m_property_type(property_type)
+ , m_is_method(is_method)
+ {
+ }
+
+ const Expression& key() const { return m_key; }
+ const Expression& value() const
+ {
+ ASSERT(m_value);
+ return *m_value;
+ }
+
+ Type type() const { return m_property_type; }
+ bool is_method() const { return m_is_method; }
+
+ virtual void dump(int indent) const override;
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+
+private:
+ virtual const char* class_name() const override { return "ObjectProperty"; }
+
+ NonnullRefPtr<Expression> m_key;
+ RefPtr<Expression> m_value;
+ Type m_property_type;
+ bool m_is_method { false };
+};
+
+class ObjectExpression final : public Expression {
+public:
+ ObjectExpression(SourceRange source_range, NonnullRefPtrVector<ObjectProperty> properties = {})
+ : Expression(move(source_range))
+ , m_properties(move(properties))
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "ObjectExpression"; }
+
+ NonnullRefPtrVector<ObjectProperty> m_properties;
+};
+
+class ArrayExpression final : public Expression {
+public:
+ ArrayExpression(SourceRange source_range, Vector<RefPtr<Expression>> elements)
+ : Expression(move(source_range))
+ , m_elements(move(elements))
+ {
+ }
+
+ const Vector<RefPtr<Expression>>& elements() const { return m_elements; }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "ArrayExpression"; }
+
+ Vector<RefPtr<Expression>> m_elements;
+};
+
+class TemplateLiteral final : public Expression {
+public:
+ TemplateLiteral(SourceRange source_range, NonnullRefPtrVector<Expression> expressions)
+ : Expression(move(source_range))
+ , m_expressions(move(expressions))
+ {
+ }
+
+ TemplateLiteral(SourceRange source_range, NonnullRefPtrVector<Expression> expressions, NonnullRefPtrVector<Expression> raw_strings)
+ : Expression(move(source_range))
+ , m_expressions(move(expressions))
+ , m_raw_strings(move(raw_strings))
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+ const NonnullRefPtrVector<Expression>& expressions() const { return m_expressions; }
+ const NonnullRefPtrVector<Expression>& raw_strings() const { return m_raw_strings; }
+
+private:
+ virtual const char* class_name() const override { return "TemplateLiteral"; }
+
+ const NonnullRefPtrVector<Expression> m_expressions;
+ const NonnullRefPtrVector<Expression> m_raw_strings;
+};
+
+class TaggedTemplateLiteral final : public Expression {
+public:
+ TaggedTemplateLiteral(SourceRange source_range, NonnullRefPtr<Expression> tag, NonnullRefPtr<TemplateLiteral> template_literal)
+ : Expression(move(source_range))
+ , m_tag(move(tag))
+ , m_template_literal(move(template_literal))
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "TaggedTemplateLiteral"; }
+
+ const NonnullRefPtr<Expression> m_tag;
+ const NonnullRefPtr<TemplateLiteral> m_template_literal;
+};
+
+class MemberExpression final : public Expression {
+public:
+ MemberExpression(SourceRange source_range, NonnullRefPtr<Expression> object, NonnullRefPtr<Expression> property, bool computed = false)
+ : Expression(move(source_range))
+ , m_object(move(object))
+ , m_property(move(property))
+ , m_computed(computed)
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+ virtual Reference to_reference(Interpreter&, GlobalObject&) const override;
+
+ bool is_computed() const { return m_computed; }
+ const Expression& object() const { return *m_object; }
+ const Expression& property() const { return *m_property; }
+
+ PropertyName computed_property_name(Interpreter&, GlobalObject&) const;
+
+ String to_string_approximation() const;
+
+private:
+ virtual const char* class_name() const override { return "MemberExpression"; }
+
+ NonnullRefPtr<Expression> m_object;
+ NonnullRefPtr<Expression> m_property;
+ bool m_computed { false };
+};
+
+class MetaProperty final : public Expression {
+public:
+ enum class Type {
+ NewTarget,
+ ImportMeta,
+ };
+
+ MetaProperty(SourceRange source_range, Type type)
+ : Expression(move(source_range))
+ , m_type(type)
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "MetaProperty"; }
+
+ Type m_type;
+};
+
+class ConditionalExpression final : public Expression {
+public:
+ ConditionalExpression(SourceRange source_range, NonnullRefPtr<Expression> test, NonnullRefPtr<Expression> consequent, NonnullRefPtr<Expression> alternate)
+ : Expression(move(source_range))
+ , m_test(move(test))
+ , m_consequent(move(consequent))
+ , m_alternate(move(alternate))
+ {
+ }
+
+ virtual void dump(int indent) const override;
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+
+private:
+ virtual const char* class_name() const override { return "ConditionalExpression"; }
+
+ NonnullRefPtr<Expression> m_test;
+ NonnullRefPtr<Expression> m_consequent;
+ NonnullRefPtr<Expression> m_alternate;
+};
+
+class CatchClause final : public ASTNode {
+public:
+ CatchClause(SourceRange source_range, const FlyString& parameter, NonnullRefPtr<BlockStatement> body)
+ : ASTNode(move(source_range))
+ , m_parameter(parameter)
+ , m_body(move(body))
+ {
+ }
+
+ const FlyString& parameter() const { return m_parameter; }
+ const BlockStatement& body() const { return m_body; }
+
+ virtual void dump(int indent) const override;
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+
+private:
+ virtual const char* class_name() const override { return "CatchClause"; }
+
+ FlyString m_parameter;
+ NonnullRefPtr<BlockStatement> m_body;
+};
+
+class TryStatement final : public Statement {
+public:
+ TryStatement(SourceRange source_range, NonnullRefPtr<BlockStatement> block, RefPtr<CatchClause> handler, RefPtr<BlockStatement> finalizer)
+ : Statement(move(source_range))
+ , m_block(move(block))
+ , m_handler(move(handler))
+ , m_finalizer(move(finalizer))
+ {
+ }
+
+ const BlockStatement& block() const { return m_block; }
+ const CatchClause* handler() const { return m_handler; }
+ const BlockStatement* finalizer() const { return m_finalizer; }
+
+ virtual void dump(int indent) const override;
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+
+private:
+ virtual const char* class_name() const override { return "TryStatement"; }
+
+ NonnullRefPtr<BlockStatement> m_block;
+ RefPtr<CatchClause> m_handler;
+ RefPtr<BlockStatement> m_finalizer;
+};
+
+class ThrowStatement final : public Statement {
+public:
+ explicit ThrowStatement(SourceRange source_range, NonnullRefPtr<Expression> argument)
+ : Statement(move(source_range))
+ , m_argument(move(argument))
+ {
+ }
+
+ const Expression& argument() const { return m_argument; }
+
+ virtual void dump(int indent) const override;
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+
+private:
+ virtual const char* class_name() const override { return "ThrowStatement"; }
+
+ NonnullRefPtr<Expression> m_argument;
+};
+
+class SwitchCase final : public ASTNode {
+public:
+ SwitchCase(SourceRange source_range, RefPtr<Expression> test, NonnullRefPtrVector<Statement> consequent)
+ : ASTNode(move(source_range))
+ , m_test(move(test))
+ , m_consequent(move(consequent))
+ {
+ }
+
+ const Expression* test() const { return m_test; }
+ const NonnullRefPtrVector<Statement>& consequent() const { return m_consequent; }
+
+ virtual void dump(int indent) const override;
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+
+private:
+ virtual const char* class_name() const override { return "SwitchCase"; }
+
+ RefPtr<Expression> m_test;
+ NonnullRefPtrVector<Statement> m_consequent;
+};
+
+class SwitchStatement final : public Statement {
+public:
+ SwitchStatement(SourceRange source_range, NonnullRefPtr<Expression> discriminant, NonnullRefPtrVector<SwitchCase> cases)
+ : Statement(move(source_range))
+ , m_discriminant(move(discriminant))
+ , m_cases(move(cases))
+ {
+ }
+
+ virtual void dump(int indent) const override;
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+
+private:
+ virtual const char* class_name() const override { return "SwitchStatement"; }
+
+ NonnullRefPtr<Expression> m_discriminant;
+ NonnullRefPtrVector<SwitchCase> m_cases;
+};
+
+class BreakStatement final : public Statement {
+public:
+ BreakStatement(SourceRange source_range, FlyString target_label)
+ : Statement(move(source_range))
+ , m_target_label(target_label)
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+
+ const FlyString& target_label() const { return m_target_label; }
+
+private:
+ virtual const char* class_name() const override { return "BreakStatement"; }
+
+ FlyString m_target_label;
+};
+
+class ContinueStatement final : public Statement {
+public:
+ ContinueStatement(SourceRange source_range, FlyString target_label)
+ : Statement(move(source_range))
+ , m_target_label(target_label)
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+
+ const FlyString& target_label() const { return m_target_label; }
+
+private:
+ virtual const char* class_name() const override { return "ContinueStatement"; }
+
+ FlyString m_target_label;
+};
+
+class DebuggerStatement final : public Statement {
+public:
+ DebuggerStatement(SourceRange source_range)
+ : Statement(move(source_range))
+ {
+ }
+
+ virtual Value execute(Interpreter&, GlobalObject&) const override;
+
+private:
+ virtual const char* class_name() const override { return "DebuggerStatement"; }
+};
+
+}
diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt
new file mode 100644
index 0000000000..5d149be98e
--- /dev/null
+++ b/Userland/Libraries/LibJS/CMakeLists.txt
@@ -0,0 +1,88 @@
+set(SOURCES
+ AST.cpp
+ Console.cpp
+ Heap/Allocator.cpp
+ Heap/Handle.cpp
+ Heap/HeapBlock.cpp
+ Heap/Heap.cpp
+ Interpreter.cpp
+ Lexer.cpp
+ MarkupGenerator.cpp
+ Parser.cpp
+ Runtime/Array.cpp
+ Runtime/ArrayBuffer.cpp
+ Runtime/ArrayBufferConstructor.cpp
+ Runtime/ArrayBufferPrototype.cpp
+ Runtime/ArrayConstructor.cpp
+ Runtime/ArrayIterator.cpp
+ Runtime/ArrayIteratorPrototype.cpp
+ Runtime/ArrayPrototype.cpp
+ Runtime/BigInt.cpp
+ Runtime/BigIntConstructor.cpp
+ Runtime/BigIntObject.cpp
+ Runtime/BigIntPrototype.cpp
+ Runtime/BooleanConstructor.cpp
+ Runtime/BooleanObject.cpp
+ Runtime/BooleanPrototype.cpp
+ Runtime/BoundFunction.cpp
+ Runtime/Cell.cpp
+ Runtime/ConsoleObject.cpp
+ Runtime/DateConstructor.cpp
+ Runtime/Date.cpp
+ Runtime/DatePrototype.cpp
+ Runtime/ErrorConstructor.cpp
+ Runtime/Error.cpp
+ Runtime/ErrorPrototype.cpp
+ Runtime/ErrorTypes.cpp
+ Runtime/Exception.cpp
+ Runtime/FunctionConstructor.cpp
+ Runtime/Function.cpp
+ Runtime/FunctionPrototype.cpp
+ Runtime/GlobalObject.cpp
+ Runtime/IndexedProperties.cpp
+ Runtime/IteratorOperations.cpp
+ Runtime/IteratorPrototype.cpp
+ Runtime/JSONObject.cpp
+ Runtime/LexicalEnvironment.cpp
+ Runtime/MarkedValueList.cpp
+ Runtime/MathObject.cpp
+ Runtime/NativeFunction.cpp
+ Runtime/NativeProperty.cpp
+ Runtime/NumberConstructor.cpp
+ Runtime/NumberObject.cpp
+ Runtime/NumberPrototype.cpp
+ Runtime/ObjectConstructor.cpp
+ Runtime/Object.cpp
+ Runtime/ObjectPrototype.cpp
+ Runtime/PrimitiveString.cpp
+ Runtime/ProxyConstructor.cpp
+ Runtime/ProxyObject.cpp
+ Runtime/Reference.cpp
+ Runtime/ReflectObject.cpp
+ Runtime/RegExpConstructor.cpp
+ Runtime/RegExpObject.cpp
+ Runtime/RegExpPrototype.cpp
+ Runtime/ScopeObject.cpp
+ Runtime/ScriptFunction.cpp
+ Runtime/Shape.cpp
+ Runtime/StringConstructor.cpp
+ Runtime/StringIterator.cpp
+ Runtime/StringIteratorPrototype.cpp
+ Runtime/StringObject.cpp
+ Runtime/StringPrototype.cpp
+ Runtime/Symbol.cpp
+ Runtime/SymbolConstructor.cpp
+ Runtime/SymbolObject.cpp
+ Runtime/SymbolPrototype.cpp
+ Runtime/TypedArray.cpp
+ Runtime/TypedArrayConstructor.cpp
+ Runtime/TypedArrayPrototype.cpp
+ Runtime/Uint8ClampedArray.cpp
+ Runtime/VM.cpp
+ Runtime/Value.cpp
+ Runtime/WithScope.cpp
+ Token.cpp
+)
+
+serenity_lib(LibJS js)
+target_link_libraries(LibJS LibM LibCore LibCrypto LibRegex)
diff --git a/Userland/Libraries/LibJS/Console.cpp b/Userland/Libraries/LibJS/Console.cpp
new file mode 100644
index 0000000000..5d549d0c47
--- /dev/null
+++ b/Userland/Libraries/LibJS/Console.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2020, Emanuele Torre <torreemanuele6@gmail.com>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Console.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+Console::Console(GlobalObject& global_object)
+ : m_global_object(global_object)
+{
+}
+
+Value Console::debug()
+{
+ if (m_client)
+ return m_client->debug();
+ return js_undefined();
+}
+
+Value Console::error()
+{
+ if (m_client)
+ return m_client->error();
+ return js_undefined();
+}
+
+Value Console::info()
+{
+ if (m_client)
+ return m_client->info();
+ return js_undefined();
+}
+
+Value Console::log()
+{
+ if (m_client)
+ return m_client->log();
+ return js_undefined();
+}
+
+Value Console::warn()
+{
+ if (m_client)
+ return m_client->warn();
+ return js_undefined();
+}
+
+Value Console::clear()
+{
+ if (m_client)
+ return m_client->clear();
+ return js_undefined();
+}
+
+Value Console::trace()
+{
+ if (m_client)
+ return m_client->trace();
+ return js_undefined();
+}
+
+Value Console::count()
+{
+ if (m_client)
+ return m_client->count();
+ return js_undefined();
+}
+
+Value Console::count_reset()
+{
+ if (m_client)
+ return m_client->count_reset();
+ return js_undefined();
+}
+
+unsigned Console::counter_increment(String label)
+{
+ auto value = m_counters.get(label);
+ if (!value.has_value()) {
+ m_counters.set(label, 1);
+ return 1;
+ }
+
+ auto new_value = value.value() + 1;
+ m_counters.set(label, new_value);
+ return new_value;
+}
+
+bool Console::counter_reset(String label)
+{
+ if (!m_counters.contains(label))
+ return false;
+
+ m_counters.remove(label);
+ return true;
+}
+
+VM& ConsoleClient::vm()
+{
+ return global_object().vm();
+}
+
+Vector<String> ConsoleClient::get_trace() const
+{
+ Vector<String> trace;
+ auto& call_stack = m_console.global_object().vm().call_stack();
+ // -2 to skip the console.trace() call frame
+ for (ssize_t i = call_stack.size() - 2; i >= 0; --i)
+ trace.append(call_stack[i]->function_name);
+ return trace;
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Console.h b/Userland/Libraries/LibJS/Console.h
new file mode 100644
index 0000000000..d8d96be85c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Console.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2020, Emanuele Torre <torreemanuele6@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/HashMap.h>
+#include <AK/Noncopyable.h>
+#include <LibJS/Forward.h>
+
+namespace JS {
+
+class ConsoleClient;
+
+class Console {
+ AK_MAKE_NONCOPYABLE(Console);
+ AK_MAKE_NONMOVABLE(Console);
+
+public:
+ explicit Console(GlobalObject&);
+
+ void set_client(ConsoleClient& client) { m_client = &client; }
+
+ GlobalObject& global_object() { return m_global_object; }
+ const GlobalObject& global_object() const { return m_global_object; }
+
+ HashMap<String, unsigned>& counters() { return m_counters; }
+ const HashMap<String, unsigned>& counters() const { return m_counters; }
+
+ Value debug();
+ Value error();
+ Value info();
+ Value log();
+ Value warn();
+
+ Value clear();
+
+ Value trace();
+
+ Value count();
+ Value count_reset();
+
+ unsigned counter_increment(String label);
+ bool counter_reset(String label);
+
+private:
+ GlobalObject& m_global_object;
+ ConsoleClient* m_client { nullptr };
+
+ HashMap<String, unsigned> m_counters;
+};
+
+class ConsoleClient {
+public:
+ ConsoleClient(Console& console)
+ : m_console(console)
+ {
+ }
+
+ virtual Value debug() = 0;
+ virtual Value error() = 0;
+ virtual Value info() = 0;
+ virtual Value log() = 0;
+ virtual Value warn() = 0;
+ virtual Value clear() = 0;
+ virtual Value trace() = 0;
+ virtual Value count() = 0;
+ virtual Value count_reset() = 0;
+
+protected:
+ VM& vm();
+
+ GlobalObject& global_object() { return m_console.global_object(); }
+ const GlobalObject& global_object() const { return m_console.global_object(); }
+
+ Vector<String> get_trace() const;
+
+ Console& m_console;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h
new file mode 100644
index 0000000000..40c37ad4bd
--- /dev/null
+++ b/Userland/Libraries/LibJS/Forward.h
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#define JS_DECLARE_NATIVE_FUNCTION(name) \
+ static JS::Value name(JS::VM&, JS::GlobalObject&)
+
+#define JS_DECLARE_NATIVE_GETTER(name) \
+ static JS::Value name(JS::VM&, JS::GlobalObject&)
+
+#define JS_DECLARE_NATIVE_SETTER(name) \
+ static void name(JS::VM&, JS::GlobalObject&, JS::Value)
+
+#define JS_DEFINE_NATIVE_FUNCTION(name) \
+ JS::Value name([[maybe_unused]] JS::VM& vm, [[maybe_unused]] JS::GlobalObject& global_object)
+
+#define JS_DEFINE_NATIVE_GETTER(name) \
+ JS::Value name([[maybe_unused]] JS::VM& vm, [[maybe_unused]] JS::GlobalObject& global_object)
+
+#define JS_DEFINE_NATIVE_SETTER(name) \
+ void name([[maybe_unused]] JS::VM& vm, [[maybe_unused]] JS::GlobalObject& global_object, [[maybe_unused]] JS::Value value)
+
+// NOTE: Proxy is not included here as it doesn't have a prototype - m_proxy_constructor is initialized separately.
+#define JS_ENUMERATE_NATIVE_OBJECTS_EXCLUDING_TEMPLATES \
+ __JS_ENUMERATE(Array, array, ArrayPrototype, ArrayConstructor, void) \
+ __JS_ENUMERATE(ArrayBuffer, array_buffer, ArrayBufferPrototype, ArrayBufferConstructor, void) \
+ __JS_ENUMERATE(BigIntObject, bigint, BigIntPrototype, BigIntConstructor, void) \
+ __JS_ENUMERATE(BooleanObject, boolean, BooleanPrototype, BooleanConstructor, void) \
+ __JS_ENUMERATE(Date, date, DatePrototype, DateConstructor, void) \
+ __JS_ENUMERATE(Error, error, ErrorPrototype, ErrorConstructor, void) \
+ __JS_ENUMERATE(Function, function, FunctionPrototype, FunctionConstructor, void) \
+ __JS_ENUMERATE(NumberObject, number, NumberPrototype, NumberConstructor, void) \
+ __JS_ENUMERATE(Object, object, ObjectPrototype, ObjectConstructor, void) \
+ __JS_ENUMERATE(RegExpObject, regexp, RegExpPrototype, RegExpConstructor, void) \
+ __JS_ENUMERATE(StringObject, string, StringPrototype, StringConstructor, void) \
+ __JS_ENUMERATE(SymbolObject, symbol, SymbolPrototype, SymbolConstructor, void)
+
+#define JS_ENUMERATE_NATIVE_OBJECTS \
+ JS_ENUMERATE_NATIVE_OBJECTS_EXCLUDING_TEMPLATES \
+ __JS_ENUMERATE(TypedArray, typed_array, TypedArrayPrototype, TypedArrayConstructor, void)
+
+#define JS_ENUMERATE_ERROR_SUBCLASSES \
+ __JS_ENUMERATE(EvalError, eval_error, EvalErrorPrototype, EvalErrorConstructor, void) \
+ __JS_ENUMERATE(InternalError, internal_error, InternalErrorPrototype, InternalErrorConstructor, void) \
+ __JS_ENUMERATE(InvalidCharacterError, invalid_character_error, InvalidCharacterErrorPrototype, InvalidCharacterErrorConstructor, void) \
+ __JS_ENUMERATE(RangeError, range_error, RangeErrorPrototype, RangeErrorConstructor, void) \
+ __JS_ENUMERATE(ReferenceError, reference_error, ReferenceErrorPrototype, ReferenceErrorConstructor, void) \
+ __JS_ENUMERATE(SyntaxError, syntax_error, SyntaxErrorPrototype, SyntaxErrorConstructor, void) \
+ __JS_ENUMERATE(TypeError, type_error, TypeErrorPrototype, TypeErrorConstructor, void) \
+ __JS_ENUMERATE(URIError, uri_error, URIErrorPrototype, URIErrorConstructor, void)
+
+#define JS_ENUMERATE_TYPED_ARRAYS \
+ __JS_ENUMERATE(Uint8Array, uint8_array, Uint8ArrayPrototype, Uint8ArrayConstructor, u8) \
+ __JS_ENUMERATE(Uint16Array, uint16_array, Uint16ArrayPrototype, Uint16ArrayConstructor, u16) \
+ __JS_ENUMERATE(Uint32Array, uint32_array, Uint32ArrayPrototype, Uint32ArrayConstructor, u32) \
+ __JS_ENUMERATE(Int8Array, int8_array, Int8ArrayPrototype, Int8ArrayConstructor, i8) \
+ __JS_ENUMERATE(Int16Array, int16_array, Int16ArrayPrototype, Int16ArrayConstructor, i16) \
+ __JS_ENUMERATE(Int32Array, int32_array, Int32ArrayPrototype, Int32ArrayConstructor, i32) \
+ __JS_ENUMERATE(Float32Array, float32_array, Float32ArrayPrototype, Float32ArrayConstructor, float) \
+ __JS_ENUMERATE(Float64Array, float64_array, Float64ArrayPrototype, Float64ArrayConstructor, double)
+
+#define JS_ENUMERATE_ITERATOR_PROTOTYPES \
+ __JS_ENUMERATE(Iterator, iterator) \
+ __JS_ENUMERATE(ArrayIterator, array_iterator) \
+ __JS_ENUMERATE(StringIterator, string_iterator)
+
+#define JS_ENUMERATE_BUILTIN_TYPES \
+ JS_ENUMERATE_NATIVE_OBJECTS \
+ JS_ENUMERATE_ERROR_SUBCLASSES \
+ JS_ENUMERATE_TYPED_ARRAYS
+
+#define JS_ENUMERATE_WELL_KNOWN_SYMBOLS \
+ __JS_ENUMERATE(iterator, iterator) \
+ __JS_ENUMERATE(asyncIterator, async_iterator) \
+ __JS_ENUMERATE(match, match) \
+ __JS_ENUMERATE(matchAll, match_all) \
+ __JS_ENUMERATE(replace, replace) \
+ __JS_ENUMERATE(search, search) \
+ __JS_ENUMERATE(split, split) \
+ __JS_ENUMERATE(hasInstance, has_instance) \
+ __JS_ENUMERATE(isConcatSpreadable, is_concat_spreadable) \
+ __JS_ENUMERATE(unscopables, unscopables) \
+ __JS_ENUMERATE(species, species) \
+ __JS_ENUMERATE(toPrimitive, to_primitive) \
+ __JS_ENUMERATE(toStringTag, to_string_tag)
+
+#define JS_ENUMERATE_REGEXP_FLAGS \
+ __JS_ENUMERATE(global, global, g, Global) \
+ __JS_ENUMERATE(ignoreCase, ignore_case, i, Insensitive) \
+ __JS_ENUMERATE(multiline, multiline, m, Multiline) \
+ __JS_ENUMERATE(dotAll, dot_all, s, SingleLine) \
+ __JS_ENUMERATE(unicode, unicode, u, Unicode) \
+ __JS_ENUMERATE(sticky, sticky, y, Sticky)
+
+namespace JS {
+
+class ASTNode;
+class Allocator;
+class BigInt;
+class BoundFunction;
+class Cell;
+class Console;
+class DeferGC;
+class Error;
+class Exception;
+class Expression;
+class Accessor;
+class GlobalObject;
+class HandleImpl;
+class Heap;
+class HeapBlock;
+class Interpreter;
+class LexicalEnvironment;
+class MarkedValueList;
+class NativeProperty;
+class PrimitiveString;
+class Reference;
+class ScopeNode;
+class ScopeObject;
+class Shape;
+class Statement;
+class Symbol;
+class Token;
+class Uint8ClampedArray;
+class VM;
+class Value;
+enum class DeclarationKind;
+
+// Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype
+class ProxyObject;
+class ProxyConstructor;
+
+class TypedArrayConstructor;
+class TypedArrayPrototype;
+
+#define __JS_ENUMERATE(ClassName, snake_name, ConstructorName, PrototypeName, ArrayType) \
+ class ClassName; \
+ class ConstructorName; \
+ class PrototypeName;
+JS_ENUMERATE_NATIVE_OBJECTS_EXCLUDING_TEMPLATES
+JS_ENUMERATE_ERROR_SUBCLASSES
+JS_ENUMERATE_TYPED_ARRAYS
+#undef __JS_ENUMERATE
+
+template<class T>
+class Handle;
+
+}
diff --git a/Userland/Libraries/LibJS/Heap/Allocator.cpp b/Userland/Libraries/LibJS/Heap/Allocator.cpp
new file mode 100644
index 0000000000..03190382c4
--- /dev/null
+++ b/Userland/Libraries/LibJS/Heap/Allocator.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Badge.h>
+#include <LibJS/Heap/Allocator.h>
+#include <LibJS/Heap/HeapBlock.h>
+
+namespace JS {
+
+Allocator::Allocator(size_t cell_size)
+ : m_cell_size(cell_size)
+{
+}
+
+Allocator::~Allocator()
+{
+}
+
+Cell* Allocator::allocate_cell(Heap& heap)
+{
+ if (m_usable_blocks.is_empty()) {
+ auto block = HeapBlock::create_with_cell_size(heap, m_cell_size);
+ m_usable_blocks.append(*block.leak_ptr());
+ }
+
+ auto& block = *m_usable_blocks.last();
+ auto* cell = block.allocate();
+ ASSERT(cell);
+ if (block.is_full())
+ m_full_blocks.append(*m_usable_blocks.last());
+ return cell;
+}
+
+void Allocator::block_did_become_empty(Badge<Heap>, HeapBlock& block)
+{
+ block.m_list_node.remove();
+ delete &block;
+}
+
+void Allocator::block_did_become_usable(Badge<Heap>, HeapBlock& block)
+{
+ ASSERT(!block.is_full());
+ m_usable_blocks.append(block);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Heap/Allocator.h b/Userland/Libraries/LibJS/Heap/Allocator.h
new file mode 100644
index 0000000000..fd89323de7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Heap/Allocator.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/IntrusiveList.h>
+#include <AK/NonnullOwnPtr.h>
+#include <AK/Vector.h>
+#include <LibJS/Forward.h>
+#include <LibJS/Heap/HeapBlock.h>
+
+namespace JS {
+
+class Allocator {
+public:
+ Allocator(size_t cell_size);
+ ~Allocator();
+
+ size_t cell_size() const { return m_cell_size; }
+
+ Cell* allocate_cell(Heap&);
+
+ template<typename Callback>
+ IterationDecision for_each_block(Callback callback)
+ {
+ for (auto& block : m_full_blocks) {
+ if (callback(block) == IterationDecision::Break)
+ return IterationDecision::Break;
+ }
+ for (auto& block : m_usable_blocks) {
+ if (callback(block) == IterationDecision::Break)
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ }
+
+ void block_did_become_empty(Badge<Heap>, HeapBlock&);
+ void block_did_become_usable(Badge<Heap>, HeapBlock&);
+
+private:
+ const size_t m_cell_size;
+
+ typedef IntrusiveList<HeapBlock, &HeapBlock::m_list_node> BlockList;
+ BlockList m_full_blocks;
+ BlockList m_usable_blocks;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Heap/DeferGC.h b/Userland/Libraries/LibJS/Heap/DeferGC.h
new file mode 100644
index 0000000000..7280c70285
--- /dev/null
+++ b/Userland/Libraries/LibJS/Heap/DeferGC.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Heap/Heap.h>
+
+namespace JS {
+
+class DeferGC {
+public:
+ explicit DeferGC(Heap& heap)
+ : m_heap(heap)
+ {
+ m_heap.defer_gc({});
+ }
+
+ ~DeferGC()
+ {
+ m_heap.undefer_gc({});
+ }
+
+private:
+ Heap& m_heap;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Heap/Handle.cpp b/Userland/Libraries/LibJS/Heap/Handle.cpp
new file mode 100644
index 0000000000..c1758ee429
--- /dev/null
+++ b/Userland/Libraries/LibJS/Heap/Handle.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Heap/Handle.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/Cell.h>
+
+namespace JS {
+
+HandleImpl::HandleImpl(Cell* cell)
+ : m_cell(cell)
+{
+ m_cell->heap().did_create_handle({}, *this);
+}
+
+HandleImpl::~HandleImpl()
+{
+ m_cell->heap().did_destroy_handle({}, *this);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Heap/Handle.h b/Userland/Libraries/LibJS/Heap/Handle.h
new file mode 100644
index 0000000000..33fb022d21
--- /dev/null
+++ b/Userland/Libraries/LibJS/Heap/Handle.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Badge.h>
+#include <AK/Noncopyable.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <LibJS/Forward.h>
+
+namespace JS {
+
+class HandleImpl : public RefCounted<HandleImpl> {
+ AK_MAKE_NONCOPYABLE(HandleImpl);
+ AK_MAKE_NONMOVABLE(HandleImpl);
+
+public:
+ ~HandleImpl();
+
+ Cell* cell() { return m_cell; }
+ const Cell* cell() const { return m_cell; }
+
+private:
+ template<class T>
+ friend class Handle;
+
+ explicit HandleImpl(Cell*);
+ Cell* m_cell { nullptr };
+};
+
+template<class T>
+class Handle {
+public:
+ Handle() { }
+
+ static Handle create(T* cell)
+ {
+ return Handle(adopt(*new HandleImpl(cell)));
+ }
+
+ T* cell() { return static_cast<T*>(m_impl->cell()); }
+ const T* cell() const { return static_cast<const T*>(m_impl->cell()); }
+
+ bool is_null() const { return m_impl.is_null(); }
+
+private:
+ explicit Handle(NonnullRefPtr<HandleImpl> impl)
+ : m_impl(move(impl))
+ {
+ }
+
+ RefPtr<HandleImpl> m_impl;
+};
+
+template<class T>
+inline Handle<T> make_handle(T* cell)
+{
+ return Handle<T>::create(cell);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Heap/Heap.cpp b/Userland/Libraries/LibJS/Heap/Heap.cpp
new file mode 100644
index 0000000000..8385b8ff1e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Heap/Heap.cpp
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Badge.h>
+#include <AK/HashTable.h>
+#include <AK/StackInfo.h>
+#include <AK/TemporaryChange.h>
+#include <LibCore/ElapsedTimer.h>
+#include <LibJS/Heap/Allocator.h>
+#include <LibJS/Heap/Handle.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Heap/HeapBlock.h>
+#include <LibJS/Interpreter.h>
+#include <LibJS/Runtime/Object.h>
+#include <setjmp.h>
+
+//#define HEAP_DEBUG
+
+namespace JS {
+
+Heap::Heap(VM& vm)
+ : m_vm(vm)
+{
+ m_allocators.append(make<Allocator>(16));
+ m_allocators.append(make<Allocator>(32));
+ m_allocators.append(make<Allocator>(64));
+ m_allocators.append(make<Allocator>(128));
+ m_allocators.append(make<Allocator>(256));
+ m_allocators.append(make<Allocator>(512));
+ m_allocators.append(make<Allocator>(1024));
+ m_allocators.append(make<Allocator>(3172));
+}
+
+Heap::~Heap()
+{
+ collect_garbage(CollectionType::CollectEverything);
+}
+
+ALWAYS_INLINE Allocator& Heap::allocator_for_size(size_t cell_size)
+{
+ for (auto& allocator : m_allocators) {
+ if (allocator->cell_size() >= cell_size)
+ return *allocator;
+ }
+ ASSERT_NOT_REACHED();
+}
+
+Cell* Heap::allocate_cell(size_t size)
+{
+ if (should_collect_on_every_allocation()) {
+ collect_garbage();
+ } else if (m_allocations_since_last_gc > m_max_allocations_between_gc) {
+ m_allocations_since_last_gc = 0;
+ collect_garbage();
+ } else {
+ ++m_allocations_since_last_gc;
+ }
+
+ auto& allocator = allocator_for_size(size);
+ return allocator.allocate_cell(*this);
+}
+
+void Heap::collect_garbage(CollectionType collection_type, bool print_report)
+{
+ ASSERT(!m_collecting_garbage);
+ TemporaryChange change(m_collecting_garbage, true);
+
+ Core::ElapsedTimer collection_measurement_timer;
+ collection_measurement_timer.start();
+ if (collection_type == CollectionType::CollectGarbage) {
+ if (m_gc_deferrals) {
+ m_should_gc_when_deferral_ends = true;
+ return;
+ }
+ HashTable<Cell*> roots;
+ gather_roots(roots);
+ mark_live_cells(roots);
+ }
+ sweep_dead_cells(print_report, collection_measurement_timer);
+}
+
+void Heap::gather_roots(HashTable<Cell*>& roots)
+{
+ vm().gather_roots(roots);
+ gather_conservative_roots(roots);
+
+ for (auto* handle : m_handles)
+ roots.set(handle->cell());
+
+ for (auto* list : m_marked_value_lists) {
+ for (auto& value : list->values()) {
+ if (value.is_cell())
+ roots.set(value.as_cell());
+ }
+ }
+
+#ifdef HEAP_DEBUG
+ dbgln("gather_roots:");
+ for (auto* root : roots)
+ dbgln(" + {}", root);
+#endif
+}
+
+__attribute__((no_sanitize("address"))) void Heap::gather_conservative_roots(HashTable<Cell*>& roots)
+{
+ FlatPtr dummy;
+
+#ifdef HEAP_DEBUG
+ dbgln("gather_conservative_roots:");
+#endif
+
+ jmp_buf buf;
+ setjmp(buf);
+
+ HashTable<FlatPtr> possible_pointers;
+
+ const FlatPtr* raw_jmp_buf = reinterpret_cast<const FlatPtr*>(buf);
+
+ for (size_t i = 0; i < ((size_t)sizeof(buf)) / sizeof(FlatPtr); i += sizeof(FlatPtr))
+ possible_pointers.set(raw_jmp_buf[i]);
+
+ FlatPtr stack_reference = reinterpret_cast<FlatPtr>(&dummy);
+ auto& stack_info = m_vm.stack_info();
+
+ for (FlatPtr stack_address = stack_reference; stack_address < stack_info.top(); stack_address += sizeof(FlatPtr)) {
+ auto data = *reinterpret_cast<FlatPtr*>(stack_address);
+ possible_pointers.set(data);
+ }
+
+ HashTable<HeapBlock*> all_live_heap_blocks;
+ for_each_block([&](auto& block) {
+ all_live_heap_blocks.set(&block);
+ return IterationDecision::Continue;
+ });
+
+ for (auto possible_pointer : possible_pointers) {
+ if (!possible_pointer)
+ continue;
+#ifdef HEAP_DEBUG
+ dbgln(" ? {}", (const void*)possible_pointer);
+#endif
+ auto* possible_heap_block = HeapBlock::from_cell(reinterpret_cast<const Cell*>(possible_pointer));
+ if (all_live_heap_blocks.contains(possible_heap_block)) {
+ if (auto* cell = possible_heap_block->cell_from_possible_pointer(possible_pointer)) {
+ if (cell->is_live()) {
+#ifdef HEAP_DEBUG
+ dbgln(" ?-> {}", (const void*)cell);
+#endif
+ roots.set(cell);
+ } else {
+#ifdef HEAP_DEBUG
+ dbgln(" #-> {}", (const void*)cell);
+#endif
+ }
+ }
+ }
+ }
+}
+
+class MarkingVisitor final : public Cell::Visitor {
+public:
+ MarkingVisitor() { }
+
+ virtual void visit_impl(Cell* cell)
+ {
+ if (cell->is_marked())
+ return;
+#ifdef HEAP_DEBUG
+ dbgln(" ! {}", cell);
+#endif
+ cell->set_marked(true);
+ cell->visit_edges(*this);
+ }
+};
+
+void Heap::mark_live_cells(const HashTable<Cell*>& roots)
+{
+#ifdef HEAP_DEBUG
+ dbgln("mark_live_cells:");
+#endif
+ MarkingVisitor visitor;
+ for (auto* root : roots)
+ visitor.visit(root);
+}
+
+void Heap::sweep_dead_cells(bool print_report, const Core::ElapsedTimer& measurement_timer)
+{
+#ifdef HEAP_DEBUG
+ dbgln("sweep_dead_cells:");
+#endif
+ Vector<HeapBlock*, 32> empty_blocks;
+ Vector<HeapBlock*, 32> full_blocks_that_became_usable;
+
+ size_t collected_cells = 0;
+ size_t live_cells = 0;
+ size_t collected_cell_bytes = 0;
+ size_t live_cell_bytes = 0;
+
+ for_each_block([&](auto& block) {
+ bool block_has_live_cells = false;
+ bool block_was_full = block.is_full();
+ block.for_each_cell([&](Cell* cell) {
+ if (cell->is_live()) {
+ if (!cell->is_marked()) {
+#ifdef HEAP_DEBUG
+ dbgln(" ~ {}", cell);
+#endif
+ block.deallocate(cell);
+ ++collected_cells;
+ collected_cell_bytes += block.cell_size();
+ } else {
+ cell->set_marked(false);
+ block_has_live_cells = true;
+ ++live_cells;
+ live_cell_bytes += block.cell_size();
+ }
+ }
+ });
+ if (!block_has_live_cells)
+ empty_blocks.append(&block);
+ else if (block_was_full != block.is_full())
+ full_blocks_that_became_usable.append(&block);
+ return IterationDecision::Continue;
+ });
+
+ for (auto* block : empty_blocks) {
+#ifdef HEAP_DEBUG
+ dbgln(" - HeapBlock empty @ {}: cell_size={}", block, block->cell_size());
+#endif
+ allocator_for_size(block->cell_size()).block_did_become_empty({}, *block);
+ }
+
+ for (auto* block : full_blocks_that_became_usable) {
+#ifdef HEAP_DEBUG
+ dbgln(" - HeapBlock usable again @ {}: cell_size={}", block, block->cell_size());
+#endif
+ allocator_for_size(block->cell_size()).block_did_become_usable({}, *block);
+ }
+
+#ifdef HEAP_DEBUG
+ for_each_block([&](auto& block) {
+ dbgln(" > Live HeapBlock @ {}: cell_size={}", &block, block.cell_size());
+ return IterationDecision::Continue;
+ });
+#endif
+
+ int time_spent = measurement_timer.elapsed();
+
+ if (print_report) {
+ size_t live_block_count = 0;
+ for_each_block([&](auto&) {
+ ++live_block_count;
+ return IterationDecision::Continue;
+ });
+
+ dbgln("Garbage collection report");
+ dbgln("=============================================");
+ dbgln(" Time spent: {} ms", time_spent);
+ dbgln(" Live cells: {} ({} bytes)", live_cells, live_cell_bytes);
+ dbgln("Collected cells: {} ({} bytes)", collected_cells, collected_cell_bytes);
+ dbgln(" Live blocks: {} ({} bytes)", live_block_count, live_block_count * HeapBlock::block_size);
+ dbgln(" Freed blocks: {} ({} bytes)", empty_blocks.size(), empty_blocks.size() * HeapBlock::block_size);
+ dbgln("=============================================");
+ }
+}
+
+void Heap::did_create_handle(Badge<HandleImpl>, HandleImpl& impl)
+{
+ ASSERT(!m_handles.contains(&impl));
+ m_handles.set(&impl);
+}
+
+void Heap::did_destroy_handle(Badge<HandleImpl>, HandleImpl& impl)
+{
+ ASSERT(m_handles.contains(&impl));
+ m_handles.remove(&impl);
+}
+
+void Heap::did_create_marked_value_list(Badge<MarkedValueList>, MarkedValueList& list)
+{
+ ASSERT(!m_marked_value_lists.contains(&list));
+ m_marked_value_lists.set(&list);
+}
+
+void Heap::did_destroy_marked_value_list(Badge<MarkedValueList>, MarkedValueList& list)
+{
+ ASSERT(m_marked_value_lists.contains(&list));
+ m_marked_value_lists.remove(&list);
+}
+
+void Heap::defer_gc(Badge<DeferGC>)
+{
+ ++m_gc_deferrals;
+}
+
+void Heap::undefer_gc(Badge<DeferGC>)
+{
+ ASSERT(m_gc_deferrals > 0);
+ --m_gc_deferrals;
+
+ if (!m_gc_deferrals) {
+ if (m_should_gc_when_deferral_ends)
+ collect_garbage();
+ m_should_gc_when_deferral_ends = false;
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Heap/Heap.h b/Userland/Libraries/LibJS/Heap/Heap.h
new file mode 100644
index 0000000000..8d7d32298f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Heap/Heap.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashTable.h>
+#include <AK/Noncopyable.h>
+#include <AK/NonnullOwnPtr.h>
+#include <AK/Types.h>
+#include <AK/Vector.h>
+#include <LibCore/Forward.h>
+#include <LibJS/Forward.h>
+#include <LibJS/Heap/Allocator.h>
+#include <LibJS/Heap/Handle.h>
+#include <LibJS/Runtime/Cell.h>
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class Heap {
+ AK_MAKE_NONCOPYABLE(Heap);
+ AK_MAKE_NONMOVABLE(Heap);
+
+public:
+ explicit Heap(VM&);
+ ~Heap();
+
+ template<typename T, typename... Args>
+ T* allocate_without_global_object(Args&&... args)
+ {
+ auto* memory = allocate_cell(sizeof(T));
+ new (memory) T(forward<Args>(args)...);
+ return static_cast<T*>(memory);
+ }
+
+ template<typename T, typename... Args>
+ T* allocate(GlobalObject& global_object, Args&&... args)
+ {
+ auto* memory = allocate_cell(sizeof(T));
+ new (memory) T(forward<Args>(args)...);
+ auto* cell = static_cast<T*>(memory);
+ constexpr bool is_object = IsBaseOf<Object, T>::value;
+ if constexpr (is_object)
+ static_cast<Object*>(cell)->disable_transitions();
+ cell->initialize(global_object);
+ if constexpr (is_object)
+ static_cast<Object*>(cell)->enable_transitions();
+ return cell;
+ }
+
+ enum class CollectionType {
+ CollectGarbage,
+ CollectEverything,
+ };
+
+ void collect_garbage(CollectionType = CollectionType::CollectGarbage, bool print_report = false);
+
+ VM& vm() { return m_vm; }
+
+ bool should_collect_on_every_allocation() const { return m_should_collect_on_every_allocation; }
+ void set_should_collect_on_every_allocation(bool b) { m_should_collect_on_every_allocation = b; }
+
+ void did_create_handle(Badge<HandleImpl>, HandleImpl&);
+ void did_destroy_handle(Badge<HandleImpl>, HandleImpl&);
+
+ void did_create_marked_value_list(Badge<MarkedValueList>, MarkedValueList&);
+ void did_destroy_marked_value_list(Badge<MarkedValueList>, MarkedValueList&);
+
+ void defer_gc(Badge<DeferGC>);
+ void undefer_gc(Badge<DeferGC>);
+
+private:
+ Cell* allocate_cell(size_t);
+
+ void gather_roots(HashTable<Cell*>&);
+ void gather_conservative_roots(HashTable<Cell*>&);
+ void mark_live_cells(const HashTable<Cell*>& live_cells);
+ void sweep_dead_cells(bool print_report, const Core::ElapsedTimer&);
+
+ Allocator& allocator_for_size(size_t);
+
+ template<typename Callback>
+ void for_each_block(Callback callback)
+ {
+ for (auto& allocator : m_allocators) {
+ if (allocator->for_each_block(callback) == IterationDecision::Break)
+ return;
+ }
+ }
+
+ size_t m_max_allocations_between_gc { 10000 };
+ size_t m_allocations_since_last_gc { false };
+
+ bool m_should_collect_on_every_allocation { false };
+
+ VM& m_vm;
+
+ Vector<NonnullOwnPtr<Allocator>> m_allocators;
+ HashTable<HandleImpl*> m_handles;
+
+ HashTable<MarkedValueList*> m_marked_value_lists;
+
+ size_t m_gc_deferrals { 0 };
+ bool m_should_gc_when_deferral_ends { false };
+
+ bool m_collecting_garbage { false };
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Heap/HeapBlock.cpp b/Userland/Libraries/LibJS/Heap/HeapBlock.cpp
new file mode 100644
index 0000000000..250fcd6d3c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Heap/HeapBlock.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/NonnullOwnPtr.h>
+#include <AK/kmalloc.h>
+#include <LibJS/Heap/HeapBlock.h>
+#include <stdio.h>
+#include <sys/mman.h>
+
+namespace JS {
+
+NonnullOwnPtr<HeapBlock> HeapBlock::create_with_cell_size(Heap& heap, size_t cell_size)
+{
+ char name[64];
+ snprintf(name, sizeof(name), "LibJS: HeapBlock(%zu)", cell_size);
+#ifdef __serenity__
+ auto* block = (HeapBlock*)serenity_mmap(nullptr, block_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0, block_size, name);
+#else
+ auto* block = (HeapBlock*)aligned_alloc(block_size, block_size);
+#endif
+ ASSERT(block != MAP_FAILED);
+ new (block) HeapBlock(heap, cell_size);
+ return NonnullOwnPtr<HeapBlock>(NonnullOwnPtr<HeapBlock>::Adopt, *block);
+}
+
+void HeapBlock::operator delete(void* ptr)
+{
+#ifdef __serenity__
+ int rc = munmap(ptr, block_size);
+ ASSERT(rc == 0);
+#else
+ free(ptr);
+#endif
+}
+
+HeapBlock::HeapBlock(Heap& heap, size_t cell_size)
+ : m_heap(heap)
+ , m_cell_size(cell_size)
+{
+ ASSERT(cell_size >= sizeof(FreelistEntry));
+
+ FreelistEntry* next = nullptr;
+ for (ssize_t i = cell_count() - 1; i >= 0; i--) {
+ auto* freelist_entry = init_freelist_entry(i);
+ freelist_entry->set_live(false);
+ freelist_entry->next = next;
+ next = freelist_entry;
+ }
+ m_freelist = next;
+}
+
+void HeapBlock::deallocate(Cell* cell)
+{
+ ASSERT(cell->is_live());
+ ASSERT(!cell->is_marked());
+ cell->~Cell();
+ auto* freelist_entry = new (cell) FreelistEntry();
+ freelist_entry->set_live(false);
+ freelist_entry->next = m_freelist;
+ m_freelist = freelist_entry;
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Heap/HeapBlock.h b/Userland/Libraries/LibJS/Heap/HeapBlock.h
new file mode 100644
index 0000000000..f5a3493c57
--- /dev/null
+++ b/Userland/Libraries/LibJS/Heap/HeapBlock.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/IntrusiveList.h>
+#include <AK/Types.h>
+#include <LibJS/Forward.h>
+#include <LibJS/Runtime/Cell.h>
+
+namespace JS {
+
+class HeapBlock {
+ AK_MAKE_NONCOPYABLE(HeapBlock);
+ AK_MAKE_NONMOVABLE(HeapBlock);
+
+public:
+ static constexpr size_t block_size = 16 * KiB;
+ static NonnullOwnPtr<HeapBlock> create_with_cell_size(Heap&, size_t);
+
+ void operator delete(void*);
+
+ size_t cell_size() const { return m_cell_size; }
+ size_t cell_count() const { return (block_size - sizeof(HeapBlock)) / m_cell_size; }
+ bool is_full() const { return !m_freelist; }
+
+ ALWAYS_INLINE Cell* allocate()
+ {
+ if (!m_freelist)
+ return nullptr;
+ return exchange(m_freelist, m_freelist->next);
+ }
+
+ void deallocate(Cell*);
+
+ template<typename Callback>
+ void for_each_cell(Callback callback)
+ {
+ for (size_t i = 0; i < cell_count(); ++i)
+ callback(cell(i));
+ }
+
+ Heap& heap() { return m_heap; }
+
+ static HeapBlock* from_cell(const Cell* cell)
+ {
+ return reinterpret_cast<HeapBlock*>((FlatPtr)cell & ~(block_size - 1));
+ }
+
+ Cell* cell_from_possible_pointer(FlatPtr pointer)
+ {
+ if (pointer < reinterpret_cast<FlatPtr>(m_storage))
+ return nullptr;
+ size_t cell_index = (pointer - reinterpret_cast<FlatPtr>(m_storage)) / m_cell_size;
+ if (cell_index >= cell_count())
+ return nullptr;
+ return cell(cell_index);
+ }
+
+ IntrusiveListNode m_list_node;
+
+private:
+ HeapBlock(Heap&, size_t cell_size);
+
+ struct FreelistEntry final : public Cell {
+ FreelistEntry* next { nullptr };
+
+ virtual const char* class_name() const override { return "FreelistEntry"; }
+ };
+
+ Cell* cell(size_t index)
+ {
+ return reinterpret_cast<Cell*>(&m_storage[index * cell_size()]);
+ }
+
+ FreelistEntry* init_freelist_entry(size_t index)
+ {
+ return new (&m_storage[index * cell_size()]) FreelistEntry();
+ }
+
+ Heap& m_heap;
+ size_t m_cell_size { 0 };
+ FreelistEntry* m_freelist { nullptr };
+ alignas(Cell) u8 m_storage[];
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Interpreter.cpp b/Userland/Libraries/LibJS/Interpreter.cpp
new file mode 100644
index 0000000000..72e4a9587a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Interpreter.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Badge.h>
+#include <AK/StringBuilder.h>
+#include <LibJS/AST.h>
+#include <LibJS/Interpreter.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/LexicalEnvironment.h>
+#include <LibJS/Runtime/MarkedValueList.h>
+#include <LibJS/Runtime/NativeFunction.h>
+#include <LibJS/Runtime/Object.h>
+#include <LibJS/Runtime/Reference.h>
+#include <LibJS/Runtime/ScriptFunction.h>
+#include <LibJS/Runtime/Shape.h>
+#include <LibJS/Runtime/SymbolObject.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+NonnullOwnPtr<Interpreter> Interpreter::create_with_existing_global_object(GlobalObject& global_object)
+{
+ DeferGC defer_gc(global_object.heap());
+ auto interpreter = adopt_own(*new Interpreter(global_object.vm()));
+ interpreter->m_global_object = make_handle(static_cast<Object*>(&global_object));
+ return interpreter;
+}
+
+Interpreter::Interpreter(VM& vm)
+ : m_vm(vm)
+{
+}
+
+Interpreter::~Interpreter()
+{
+}
+
+Value Interpreter::run(GlobalObject& global_object, const Program& program)
+{
+ auto& vm = this->vm();
+ ASSERT(!vm.exception());
+
+ VM::InterpreterExecutionScope scope(*this);
+
+ CallFrame global_call_frame;
+ global_call_frame.this_value = &global_object;
+ static FlyString global_execution_context_name = "(global execution context)";
+ global_call_frame.function_name = global_execution_context_name;
+ global_call_frame.scope = &global_object;
+ ASSERT(!vm.exception());
+ global_call_frame.is_strict_mode = program.is_strict_mode();
+ vm.push_call_frame(global_call_frame, global_object);
+ ASSERT(!vm.exception());
+ auto result = program.execute(*this, global_object);
+ vm.pop_call_frame();
+ return result;
+}
+
+GlobalObject& Interpreter::global_object()
+{
+ return static_cast<GlobalObject&>(*m_global_object.cell());
+}
+
+const GlobalObject& Interpreter::global_object() const
+{
+ return static_cast<const GlobalObject&>(*m_global_object.cell());
+}
+
+void Interpreter::enter_scope(const ScopeNode& scope_node, ScopeType scope_type, GlobalObject& global_object)
+{
+ for (auto& declaration : scope_node.functions()) {
+ auto* function = ScriptFunction::create(global_object, declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), current_scope(), declaration.is_strict_mode());
+ vm().set_variable(declaration.name(), function, global_object);
+ }
+
+ if (scope_type == ScopeType::Function) {
+ push_scope({ scope_type, scope_node, false });
+ return;
+ }
+
+ HashMap<FlyString, Variable> scope_variables_with_declaration_kind;
+ scope_variables_with_declaration_kind.ensure_capacity(16);
+
+ for (auto& declaration : scope_node.variables()) {
+ for (auto& declarator : declaration.declarations()) {
+ if (is<Program>(scope_node)) {
+ global_object.put(declarator.id().string(), js_undefined());
+ if (exception())
+ return;
+ } else {
+ scope_variables_with_declaration_kind.set(declarator.id().string(), { js_undefined(), declaration.declaration_kind() });
+ }
+ }
+ }
+
+ bool pushed_lexical_environment = false;
+
+ if (!scope_variables_with_declaration_kind.is_empty()) {
+ auto* block_lexical_environment = heap().allocate<LexicalEnvironment>(global_object, move(scope_variables_with_declaration_kind), current_scope());
+ vm().call_frame().scope = block_lexical_environment;
+ pushed_lexical_environment = true;
+ }
+
+ push_scope({ scope_type, scope_node, pushed_lexical_environment });
+}
+
+void Interpreter::exit_scope(const ScopeNode& scope_node)
+{
+ while (!m_scope_stack.is_empty()) {
+ auto popped_scope = m_scope_stack.take_last();
+ if (popped_scope.pushed_environment)
+ vm().call_frame().scope = vm().call_frame().scope->parent();
+ if (popped_scope.scope_node.ptr() == &scope_node)
+ break;
+ }
+
+ // If we unwind all the way, just reset m_unwind_until so that future "return" doesn't break.
+ if (m_scope_stack.is_empty())
+ vm().unwind(ScopeType::None);
+}
+
+void Interpreter::enter_node(const ASTNode& node)
+{
+ vm().push_ast_node(node);
+}
+
+void Interpreter::exit_node(const ASTNode&)
+{
+ vm().pop_ast_node();
+}
+
+void Interpreter::push_scope(ScopeFrame frame)
+{
+ m_scope_stack.append(move(frame));
+}
+
+Value Interpreter::execute_statement(GlobalObject& global_object, const Statement& statement, ScopeType scope_type)
+{
+ if (!is<ScopeNode>(statement))
+ return statement.execute(*this, global_object);
+
+ auto& block = static_cast<const ScopeNode&>(statement);
+ enter_scope(block, scope_type, global_object);
+
+ if (block.children().is_empty())
+ vm().set_last_value({}, js_undefined());
+
+ for (auto& node : block.children()) {
+ vm().set_last_value({}, node.execute(*this, global_object));
+ if (vm().should_unwind()) {
+ if (!block.label().is_null() && vm().should_unwind_until(ScopeType::Breakable, block.label()))
+ vm().stop_unwind();
+ break;
+ }
+ }
+
+ bool did_return = vm().unwind_until() == ScopeType::Function;
+
+ if (vm().unwind_until() == scope_type)
+ vm().unwind(ScopeType::None);
+
+ exit_scope(block);
+
+ return did_return ? vm().last_value() : js_undefined();
+}
+
+LexicalEnvironment* Interpreter::current_environment()
+{
+ ASSERT(is<LexicalEnvironment>(vm().call_frame().scope));
+ return static_cast<LexicalEnvironment*>(vm().call_frame().scope);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Interpreter.h b/Userland/Libraries/LibJS/Interpreter.h
new file mode 100644
index 0000000000..f32e4e1ccb
--- /dev/null
+++ b/Userland/Libraries/LibJS/Interpreter.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <AK/HashMap.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <AK/Weakable.h>
+#include <LibJS/AST.h>
+#include <LibJS/Forward.h>
+#include <LibJS/Heap/DeferGC.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/ErrorTypes.h>
+#include <LibJS/Runtime/Exception.h>
+#include <LibJS/Runtime/LexicalEnvironment.h>
+#include <LibJS/Runtime/MarkedValueList.h>
+#include <LibJS/Runtime/VM.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+class Interpreter : public Weakable<Interpreter> {
+public:
+ template<typename GlobalObjectType, typename... Args>
+ static NonnullOwnPtr<Interpreter> create(VM& vm, Args&&... args)
+ {
+ DeferGC defer_gc(vm.heap());
+ auto interpreter = adopt_own(*new Interpreter(vm));
+ VM::InterpreterExecutionScope scope(*interpreter);
+ interpreter->m_global_object = make_handle(static_cast<Object*>(interpreter->heap().allocate_without_global_object<GlobalObjectType>(forward<Args>(args)...)));
+ static_cast<GlobalObjectType*>(interpreter->m_global_object.cell())->initialize();
+ return interpreter;
+ }
+
+ static NonnullOwnPtr<Interpreter> create_with_existing_global_object(GlobalObject&);
+
+ ~Interpreter();
+
+ Value run(GlobalObject&, const Program&);
+
+ GlobalObject& global_object();
+ const GlobalObject& global_object() const;
+
+ VM& vm() { return *m_vm; }
+ const VM& vm() const { return *m_vm; }
+ Heap& heap() { return vm().heap(); }
+ Exception* exception() { return vm().exception(); }
+
+ ScopeObject* current_scope() { return vm().current_scope(); }
+ LexicalEnvironment* current_environment();
+
+ void enter_scope(const ScopeNode&, ScopeType, GlobalObject&);
+ void exit_scope(const ScopeNode&);
+
+ void enter_node(const ASTNode&);
+ void exit_node(const ASTNode&);
+
+ Value execute_statement(GlobalObject&, const Statement&, ScopeType = ScopeType::Block);
+
+private:
+ explicit Interpreter(VM&);
+
+ void push_scope(ScopeFrame frame);
+
+ Vector<ScopeFrame> m_scope_stack;
+
+ NonnullRefPtr<VM> m_vm;
+
+ Handle<Object> m_global_object;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Lexer.cpp b/Userland/Libraries/LibJS/Lexer.cpp
new file mode 100644
index 0000000000..89758ded1c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Lexer.cpp
@@ -0,0 +1,655 @@
+/*
+ * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Lexer.h"
+#include <AK/HashMap.h>
+#include <AK/StringBuilder.h>
+#include <ctype.h>
+#include <stdio.h>
+
+//#define LEXER_DEBUG
+
+namespace JS {
+
+HashMap<String, TokenType> Lexer::s_keywords;
+HashMap<String, TokenType> Lexer::s_three_char_tokens;
+HashMap<String, TokenType> Lexer::s_two_char_tokens;
+HashMap<char, TokenType> Lexer::s_single_char_tokens;
+
+Lexer::Lexer(StringView source)
+ : m_source(source)
+ , m_current_token(TokenType::Eof, {}, StringView(nullptr), StringView(nullptr), 0, 0)
+{
+ if (s_keywords.is_empty()) {
+ s_keywords.set("await", TokenType::Await);
+ s_keywords.set("break", TokenType::Break);
+ s_keywords.set("case", TokenType::Case);
+ s_keywords.set("catch", TokenType::Catch);
+ s_keywords.set("class", TokenType::Class);
+ s_keywords.set("const", TokenType::Const);
+ s_keywords.set("continue", TokenType::Continue);
+ s_keywords.set("debugger", TokenType::Debugger);
+ s_keywords.set("default", TokenType::Default);
+ s_keywords.set("delete", TokenType::Delete);
+ s_keywords.set("do", TokenType::Do);
+ s_keywords.set("else", TokenType::Else);
+ s_keywords.set("enum", TokenType::Enum);
+ s_keywords.set("export", TokenType::Export);
+ s_keywords.set("extends", TokenType::Extends);
+ s_keywords.set("false", TokenType::BoolLiteral);
+ s_keywords.set("finally", TokenType::Finally);
+ s_keywords.set("for", TokenType::For);
+ s_keywords.set("function", TokenType::Function);
+ s_keywords.set("if", TokenType::If);
+ s_keywords.set("import", TokenType::Import);
+ s_keywords.set("in", TokenType::In);
+ s_keywords.set("instanceof", TokenType::Instanceof);
+ s_keywords.set("let", TokenType::Let);
+ s_keywords.set("new", TokenType::New);
+ s_keywords.set("null", TokenType::NullLiteral);
+ s_keywords.set("return", TokenType::Return);
+ s_keywords.set("super", TokenType::Super);
+ s_keywords.set("switch", TokenType::Switch);
+ s_keywords.set("this", TokenType::This);
+ s_keywords.set("throw", TokenType::Throw);
+ s_keywords.set("true", TokenType::BoolLiteral);
+ s_keywords.set("try", TokenType::Try);
+ s_keywords.set("typeof", TokenType::Typeof);
+ s_keywords.set("var", TokenType::Var);
+ s_keywords.set("void", TokenType::Void);
+ s_keywords.set("while", TokenType::While);
+ s_keywords.set("with", TokenType::With);
+ s_keywords.set("yield", TokenType::Yield);
+ }
+
+ if (s_three_char_tokens.is_empty()) {
+ s_three_char_tokens.set("===", TokenType::EqualsEqualsEquals);
+ s_three_char_tokens.set("!==", TokenType::ExclamationMarkEqualsEquals);
+ s_three_char_tokens.set("**=", TokenType::DoubleAsteriskEquals);
+ s_three_char_tokens.set("<<=", TokenType::ShiftLeftEquals);
+ s_three_char_tokens.set(">>=", TokenType::ShiftRightEquals);
+ s_three_char_tokens.set("&&=", TokenType::DoubleAmpersandEquals);
+ s_three_char_tokens.set("||=", TokenType::DoublePipeEquals);
+ s_three_char_tokens.set("\?\?=", TokenType::DoubleQuestionMarkEquals);
+ s_three_char_tokens.set(">>>", TokenType::UnsignedShiftRight);
+ s_three_char_tokens.set("...", TokenType::TripleDot);
+ }
+
+ if (s_two_char_tokens.is_empty()) {
+ s_two_char_tokens.set("=>", TokenType::Arrow);
+ s_two_char_tokens.set("+=", TokenType::PlusEquals);
+ s_two_char_tokens.set("-=", TokenType::MinusEquals);
+ s_two_char_tokens.set("*=", TokenType::AsteriskEquals);
+ s_two_char_tokens.set("/=", TokenType::SlashEquals);
+ s_two_char_tokens.set("%=", TokenType::PercentEquals);
+ s_two_char_tokens.set("&=", TokenType::AmpersandEquals);
+ s_two_char_tokens.set("|=", TokenType::PipeEquals);
+ s_two_char_tokens.set("^=", TokenType::CaretEquals);
+ s_two_char_tokens.set("&&", TokenType::DoubleAmpersand);
+ s_two_char_tokens.set("||", TokenType::DoublePipe);
+ s_two_char_tokens.set("??", TokenType::DoubleQuestionMark);
+ s_two_char_tokens.set("**", TokenType::DoubleAsterisk);
+ s_two_char_tokens.set("==", TokenType::EqualsEquals);
+ s_two_char_tokens.set("<=", TokenType::LessThanEquals);
+ s_two_char_tokens.set(">=", TokenType::GreaterThanEquals);
+ s_two_char_tokens.set("!=", TokenType::ExclamationMarkEquals);
+ s_two_char_tokens.set("--", TokenType::MinusMinus);
+ s_two_char_tokens.set("++", TokenType::PlusPlus);
+ s_two_char_tokens.set("<<", TokenType::ShiftLeft);
+ s_two_char_tokens.set(">>", TokenType::ShiftRight);
+ s_two_char_tokens.set("?.", TokenType::QuestionMarkPeriod);
+ }
+
+ if (s_single_char_tokens.is_empty()) {
+ s_single_char_tokens.set('&', TokenType::Ampersand);
+ s_single_char_tokens.set('*', TokenType::Asterisk);
+ s_single_char_tokens.set('[', TokenType::BracketOpen);
+ s_single_char_tokens.set(']', TokenType::BracketClose);
+ s_single_char_tokens.set('^', TokenType::Caret);
+ s_single_char_tokens.set(':', TokenType::Colon);
+ s_single_char_tokens.set(',', TokenType::Comma);
+ s_single_char_tokens.set('{', TokenType::CurlyOpen);
+ s_single_char_tokens.set('}', TokenType::CurlyClose);
+ s_single_char_tokens.set('=', TokenType::Equals);
+ s_single_char_tokens.set('!', TokenType::ExclamationMark);
+ s_single_char_tokens.set('-', TokenType::Minus);
+ s_single_char_tokens.set('(', TokenType::ParenOpen);
+ s_single_char_tokens.set(')', TokenType::ParenClose);
+ s_single_char_tokens.set('%', TokenType::Percent);
+ s_single_char_tokens.set('.', TokenType::Period);
+ s_single_char_tokens.set('|', TokenType::Pipe);
+ s_single_char_tokens.set('+', TokenType::Plus);
+ s_single_char_tokens.set('?', TokenType::QuestionMark);
+ s_single_char_tokens.set(';', TokenType::Semicolon);
+ s_single_char_tokens.set('/', TokenType::Slash);
+ s_single_char_tokens.set('~', TokenType::Tilde);
+ s_single_char_tokens.set('<', TokenType::LessThan);
+ s_single_char_tokens.set('>', TokenType::GreaterThan);
+ }
+ consume();
+}
+
+void Lexer::consume()
+{
+ auto did_reach_eof = [this] {
+ if (m_position != m_source.length())
+ return false;
+ m_position++;
+ m_line_column++;
+ m_current_char = EOF;
+ return true;
+ };
+
+ if (m_position > m_source.length())
+ return;
+
+ if (did_reach_eof())
+ return;
+
+ if (is_line_terminator()) {
+#ifdef LEXER_DEBUG
+ String type;
+ if (m_current_char == '\n')
+ type = "LINE FEED";
+ else if (m_current_char == '\r')
+ type = "CARRIAGE RETURN";
+ else if (m_source[m_position + 1] == (char)0xa8)
+ type = "LINE SEPARATOR";
+ else
+ type = "PARAGRAPH SEPARATOR";
+ dbgln("Found a line terminator: {}", type);
+#endif
+ // This is a three-char line terminator, we need to increase m_position some more.
+ // We might reach EOF and need to check again.
+ if (m_current_char != '\n' && m_current_char != '\r') {
+ m_position += 2;
+ if (did_reach_eof())
+ return;
+ }
+
+ // If the previous character is \r and the current one \n we already updated line number
+ // and column - don't do it again. From https://tc39.es/ecma262/#sec-line-terminators:
+ // The sequence <CR><LF> is commonly used as a line terminator.
+ // It should be considered a single SourceCharacter for the purpose of reporting line numbers.
+ auto second_char_of_crlf = m_position > 1 && m_source[m_position - 2] == '\r' && m_current_char == '\n';
+
+ if (!second_char_of_crlf) {
+ m_line_number++;
+ m_line_column = 1;
+#ifdef LEXER_DEBUG
+ dbgln("Incremented line number, now at: line {}, column 1", m_line_number);
+ } else {
+ dbgln("Previous was CR, this is LF - not incrementing line number again.");
+#endif
+ }
+ } else {
+ m_line_column++;
+ }
+
+ m_current_char = m_source[m_position++];
+}
+
+bool Lexer::consume_exponent()
+{
+ consume();
+ if (m_current_char == '-' || m_current_char == '+')
+ consume();
+
+ if (!isdigit(m_current_char))
+ return false;
+
+ while (isdigit(m_current_char)) {
+ consume();
+ }
+ return true;
+}
+
+bool Lexer::consume_octal_number()
+{
+ consume();
+ if (!(m_current_char >= '0' && m_current_char <= '7'))
+ return false;
+
+ while (m_current_char >= '0' && m_current_char <= '7') {
+ consume();
+ }
+
+ return true;
+}
+
+bool Lexer::consume_hexadecimal_number()
+{
+ consume();
+ if (!isxdigit(m_current_char))
+ return false;
+
+ while (isxdigit(m_current_char))
+ consume();
+
+ return true;
+}
+
+bool Lexer::consume_binary_number()
+{
+ consume();
+ if (!(m_current_char == '0' || m_current_char == '1'))
+ return false;
+
+ while (m_current_char == '0' || m_current_char == '1')
+ consume();
+
+ return true;
+}
+
+bool Lexer::match(char a, char b) const
+{
+ if (m_position >= m_source.length())
+ return false;
+
+ return m_current_char == a
+ && m_source[m_position] == b;
+}
+
+bool Lexer::match(char a, char b, char c) const
+{
+ if (m_position + 1 >= m_source.length())
+ return false;
+
+ return m_current_char == a
+ && m_source[m_position] == b
+ && m_source[m_position + 1] == c;
+}
+
+bool Lexer::match(char a, char b, char c, char d) const
+{
+ if (m_position + 2 >= m_source.length())
+ return false;
+
+ return m_current_char == a
+ && m_source[m_position] == b
+ && m_source[m_position + 1] == c
+ && m_source[m_position + 2] == d;
+}
+
+bool Lexer::is_eof() const
+{
+ return m_current_char == EOF;
+}
+
+bool Lexer::is_line_terminator() const
+{
+ if (m_current_char == '\n' || m_current_char == '\r')
+ return true;
+ if (m_position > 0 && m_position + 1 < m_source.length()) {
+ auto three_chars_view = m_source.substring_view(m_position - 1, 3);
+ return (three_chars_view == LINE_SEPARATOR) || (three_chars_view == PARAGRAPH_SEPARATOR);
+ }
+ return false;
+}
+
+bool Lexer::is_identifier_start() const
+{
+ return isalpha(m_current_char) || m_current_char == '_' || m_current_char == '$';
+}
+
+bool Lexer::is_identifier_middle() const
+{
+ return is_identifier_start() || isdigit(m_current_char);
+}
+
+bool Lexer::is_line_comment_start(bool line_has_token_yet) const
+{
+ return match('/', '/')
+ || match('<', '!', '-', '-')
+ // "-->" is considered a line comment start if the current line is only whitespace and/or
+ // other block comment(s); or in other words: the current line does not have a token or
+ // ongoing line comment yet
+ || (match('-', '-', '>') && !line_has_token_yet);
+}
+
+bool Lexer::is_block_comment_start() const
+{
+ return match('/', '*');
+}
+
+bool Lexer::is_block_comment_end() const
+{
+ return match('*', '/');
+}
+
+bool Lexer::is_numeric_literal_start() const
+{
+ return isdigit(m_current_char) || (m_current_char == '.' && m_position < m_source.length() && isdigit(m_source[m_position]));
+}
+
+bool Lexer::slash_means_division() const
+{
+ auto type = m_current_token.type();
+ return type == TokenType::BigIntLiteral
+ || type == TokenType::BoolLiteral
+ || type == TokenType::BracketClose
+ || type == TokenType::CurlyClose
+ || type == TokenType::Identifier
+ || type == TokenType::NullLiteral
+ || type == TokenType::NumericLiteral
+ || type == TokenType::ParenClose
+ || type == TokenType::RegexLiteral
+ || type == TokenType::StringLiteral
+ || type == TokenType::TemplateLiteralEnd
+ || type == TokenType::This;
+}
+
+Token Lexer::next()
+{
+ size_t trivia_start = m_position;
+ auto in_template = !m_template_states.is_empty();
+ bool line_has_token_yet = m_line_column > 1;
+ bool unterminated_comment = false;
+
+ if (!in_template || m_template_states.last().in_expr) {
+ // consume whitespace and comments
+ while (true) {
+ if (is_line_terminator()) {
+ line_has_token_yet = false;
+ do {
+ consume();
+ } while (is_line_terminator());
+ } else if (isspace(m_current_char)) {
+ do {
+ consume();
+ } while (isspace(m_current_char));
+ } else if (is_line_comment_start(line_has_token_yet)) {
+ consume();
+ do {
+ consume();
+ } while (!is_eof() && !is_line_terminator());
+ } else if (is_block_comment_start()) {
+ consume();
+ do {
+ consume();
+ } while (!is_eof() && !is_block_comment_end());
+ if (is_eof())
+ unterminated_comment = true;
+ consume(); // consume *
+ if (is_eof())
+ unterminated_comment = true;
+ consume(); // consume /
+ } else {
+ break;
+ }
+ }
+ }
+
+ size_t value_start = m_position;
+ size_t value_start_line_number = m_line_number;
+ size_t value_start_column_number = m_line_column;
+ auto token_type = TokenType::Invalid;
+ // This is being used to communicate info about invalid tokens to the parser, which then
+ // can turn that into more specific error messages - instead of us having to make up a
+ // bunch of Invalid* tokens (bad numeric literals, unterminated comments etc.)
+ String token_message;
+
+ if (m_current_token.type() == TokenType::RegexLiteral && !is_eof() && isalpha(m_current_char)) {
+ token_type = TokenType::RegexFlags;
+ while (!is_eof() && isalpha(m_current_char))
+ consume();
+ } else if (m_current_char == '`') {
+ consume();
+
+ if (!in_template) {
+ token_type = TokenType::TemplateLiteralStart;
+ m_template_states.append({ false, 0 });
+ } else {
+ if (m_template_states.last().in_expr) {
+ m_template_states.append({ false, 0 });
+ token_type = TokenType::TemplateLiteralStart;
+ } else {
+ m_template_states.take_last();
+ token_type = TokenType::TemplateLiteralEnd;
+ }
+ }
+ } else if (in_template && m_template_states.last().in_expr && m_template_states.last().open_bracket_count == 0 && m_current_char == '}') {
+ consume();
+ token_type = TokenType::TemplateLiteralExprEnd;
+ m_template_states.last().in_expr = false;
+ } else if (in_template && !m_template_states.last().in_expr) {
+ if (is_eof()) {
+ token_type = TokenType::UnterminatedTemplateLiteral;
+ m_template_states.take_last();
+ } else if (match('$', '{')) {
+ token_type = TokenType::TemplateLiteralExprStart;
+ consume();
+ consume();
+ m_template_states.last().in_expr = true;
+ } else {
+ while (!match('$', '{') && m_current_char != '`' && !is_eof()) {
+ if (match('\\', '$') || match('\\', '`'))
+ consume();
+ consume();
+ }
+ if (is_eof() && !m_template_states.is_empty())
+ token_type = TokenType::UnterminatedTemplateLiteral;
+ else
+ token_type = TokenType::TemplateLiteralString;
+ }
+ } else if (is_identifier_start()) {
+ // identifier or keyword
+ do {
+ consume();
+ } while (is_identifier_middle());
+
+ StringView value = m_source.substring_view(value_start - 1, m_position - value_start);
+ auto it = s_keywords.find(value.hash(), [&](auto& entry) { return entry.key == value; });
+ if (it == s_keywords.end()) {
+ token_type = TokenType::Identifier;
+ } else {
+ token_type = it->value;
+ }
+ } else if (is_numeric_literal_start()) {
+ token_type = TokenType::NumericLiteral;
+ bool is_invalid_numeric_literal = false;
+ if (m_current_char == '0') {
+ consume();
+ if (m_current_char == '.') {
+ // decimal
+ consume();
+ while (isdigit(m_current_char))
+ consume();
+ if (m_current_char == 'e' || m_current_char == 'E')
+ is_invalid_numeric_literal = !consume_exponent();
+ } else if (m_current_char == 'e' || m_current_char == 'E') {
+ is_invalid_numeric_literal = !consume_exponent();
+ } else if (m_current_char == 'o' || m_current_char == 'O') {
+ // octal
+ is_invalid_numeric_literal = !consume_octal_number();
+ } else if (m_current_char == 'b' || m_current_char == 'B') {
+ // binary
+ is_invalid_numeric_literal = !consume_binary_number();
+ } else if (m_current_char == 'x' || m_current_char == 'X') {
+ // hexadecimal
+ is_invalid_numeric_literal = !consume_hexadecimal_number();
+ } else if (m_current_char == 'n') {
+ consume();
+ token_type = TokenType::BigIntLiteral;
+ } else if (isdigit(m_current_char)) {
+ // octal without '0o' prefix. Forbidden in 'strict mode'
+ do {
+ consume();
+ } while (isdigit(m_current_char));
+ }
+ } else {
+ // 1...9 or period
+ while (isdigit(m_current_char))
+ consume();
+ if (m_current_char == 'n') {
+ consume();
+ token_type = TokenType::BigIntLiteral;
+ } else {
+ if (m_current_char == '.') {
+ consume();
+ while (isdigit(m_current_char))
+ consume();
+ }
+ if (m_current_char == 'e' || m_current_char == 'E')
+ is_invalid_numeric_literal = !consume_exponent();
+ }
+ }
+ if (is_invalid_numeric_literal) {
+ token_type = TokenType::Invalid;
+ token_message = "Invalid numeric literal";
+ }
+ } else if (m_current_char == '"' || m_current_char == '\'') {
+ char stop_char = m_current_char;
+ consume();
+ // Note: LS/PS line terminators are allowed in string literals.
+ while (m_current_char != stop_char && m_current_char != '\r' && m_current_char != '\n' && !is_eof()) {
+ if (m_current_char == '\\') {
+ consume();
+ }
+ consume();
+ }
+ if (m_current_char != stop_char) {
+ token_type = TokenType::UnterminatedStringLiteral;
+ } else {
+ consume();
+ token_type = TokenType::StringLiteral;
+ }
+ } else if (m_current_char == '/' && !slash_means_division()) {
+ consume();
+ token_type = TokenType::RegexLiteral;
+
+ while (!is_eof()) {
+ if (m_current_char == '[') {
+ m_regex_is_in_character_class = true;
+ } else if (m_current_char == ']') {
+ m_regex_is_in_character_class = false;
+ } else if (!m_regex_is_in_character_class && m_current_char == '/') {
+ break;
+ }
+
+ if (match('\\', '/') || match('\\', '[') || match('\\', '\\') || (m_regex_is_in_character_class && match('\\', ']')))
+ consume();
+ consume();
+ }
+
+ if (is_eof()) {
+ token_type = TokenType::UnterminatedRegexLiteral;
+ } else {
+ consume();
+ }
+ } else if (m_current_char == EOF) {
+ if (unterminated_comment) {
+ token_type = TokenType::Invalid;
+ token_message = "Unterminated multi-line comment";
+ } else {
+ token_type = TokenType::Eof;
+ }
+ } else {
+ // There is only one four-char operator: >>>=
+ bool found_four_char_token = false;
+ if (match('>', '>', '>', '=')) {
+ found_four_char_token = true;
+ consume();
+ consume();
+ consume();
+ consume();
+ token_type = TokenType::UnsignedShiftRightEquals;
+ }
+
+ bool found_three_char_token = false;
+ if (!found_four_char_token && m_position + 1 < m_source.length()) {
+ auto three_chars_view = m_source.substring_view(m_position - 1, 3);
+ auto it = s_three_char_tokens.find(three_chars_view.hash(), [&](auto& entry) { return entry.key == three_chars_view; });
+ if (it != s_three_char_tokens.end()) {
+ found_three_char_token = true;
+ consume();
+ consume();
+ consume();
+ token_type = it->value;
+ }
+ }
+
+ bool found_two_char_token = false;
+ if (!found_four_char_token && !found_three_char_token && m_position < m_source.length()) {
+ auto two_chars_view = m_source.substring_view(m_position - 1, 2);
+ auto it = s_two_char_tokens.find(two_chars_view.hash(), [&](auto& entry) { return entry.key == two_chars_view; });
+ if (it != s_two_char_tokens.end()) {
+ // OptionalChainingPunctuator :: ?. [lookahead ∉ DecimalDigit]
+ if (!(it->value == TokenType::QuestionMarkPeriod && m_position + 1 < m_source.length() && isdigit(m_source[m_position + 1]))) {
+ found_two_char_token = true;
+ consume();
+ consume();
+ token_type = it->value;
+ }
+ }
+ }
+
+ bool found_one_char_token = false;
+ if (!found_four_char_token && !found_three_char_token && !found_two_char_token) {
+ auto it = s_single_char_tokens.find(m_current_char);
+ if (it != s_single_char_tokens.end()) {
+ found_one_char_token = true;
+ consume();
+ token_type = it->value;
+ }
+ }
+
+ if (!found_four_char_token && !found_three_char_token && !found_two_char_token && !found_one_char_token) {
+ consume();
+ token_type = TokenType::Invalid;
+ }
+ }
+
+ if (!m_template_states.is_empty() && m_template_states.last().in_expr) {
+ if (token_type == TokenType::CurlyOpen) {
+ m_template_states.last().open_bracket_count++;
+ } else if (token_type == TokenType::CurlyClose) {
+ m_template_states.last().open_bracket_count--;
+ }
+ }
+
+ m_current_token = Token(
+ token_type,
+ token_message,
+ m_source.substring_view(trivia_start - 1, value_start - trivia_start),
+ m_source.substring_view(value_start - 1, m_position - value_start),
+ value_start_line_number,
+ value_start_column_number);
+
+#ifdef LEXER_DEBUG
+ dbgln("------------------------------");
+ dbgln("Token: {}", m_current_token.name());
+ dbgln("Trivia: _{}_", m_current_token.trivia());
+ dbgln("Value: _{}_", m_current_token.value());
+ dbgln("Line: {}, Column: {}", m_current_token.line_number(), m_current_token.line_column());
+ dbgln("------------------------------");
+#endif
+
+ return m_current_token;
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Lexer.h b/Userland/Libraries/LibJS/Lexer.h
new file mode 100644
index 0000000000..3efe290aa8
--- /dev/null
+++ b/Userland/Libraries/LibJS/Lexer.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Token.h"
+
+#include <AK/HashMap.h>
+#include <AK/String.h>
+#include <AK/StringView.h>
+
+namespace JS {
+
+class Lexer {
+public:
+ explicit Lexer(StringView source);
+
+ Token next();
+
+ const StringView& source() const { return m_source; };
+
+private:
+ void consume();
+ bool consume_exponent();
+ bool consume_octal_number();
+ bool consume_hexadecimal_number();
+ bool consume_binary_number();
+ bool is_eof() const;
+ bool is_line_terminator() const;
+ bool is_identifier_start() const;
+ bool is_identifier_middle() const;
+ bool is_line_comment_start(bool line_has_token_yet) const;
+ bool is_block_comment_start() const;
+ bool is_block_comment_end() const;
+ bool is_numeric_literal_start() const;
+ bool match(char, char) const;
+ bool match(char, char, char) const;
+ bool match(char, char, char, char) const;
+ bool slash_means_division() const;
+
+ StringView m_source;
+ size_t m_position { 0 };
+ Token m_current_token;
+ char m_current_char { 0 };
+ size_t m_line_number { 1 };
+ size_t m_line_column { 0 };
+
+ bool m_regex_is_in_character_class { false };
+
+ struct TemplateState {
+ bool in_expr;
+ u8 open_bracket_count;
+ };
+ Vector<TemplateState> m_template_states;
+
+ static HashMap<String, TokenType> s_keywords;
+ static HashMap<String, TokenType> s_three_char_tokens;
+ static HashMap<String, TokenType> s_two_char_tokens;
+ static HashMap<char, TokenType> s_single_char_tokens;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/MarkupGenerator.cpp b/Userland/Libraries/LibJS/MarkupGenerator.cpp
new file mode 100644
index 0000000000..afdd347098
--- /dev/null
+++ b/Userland/Libraries/LibJS/MarkupGenerator.cpp
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2020, Hunter Salyer <thefalsehonesty@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/HashTable.h>
+#include <AK/StringBuilder.h>
+#include <LibJS/Lexer.h>
+#include <LibJS/MarkupGenerator.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/Date.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+String MarkupGenerator::html_from_source(const StringView& source)
+{
+ StringBuilder builder;
+ auto lexer = Lexer(source);
+ for (auto token = lexer.next(); token.type() != TokenType::Eof; token = lexer.next()) {
+ builder.append(token.trivia());
+ builder.append(wrap_string_in_style(token.value(), style_type_for_token(token)));
+ }
+ return builder.to_string();
+}
+
+String MarkupGenerator::html_from_value(Value value)
+{
+ StringBuilder output_html;
+ value_to_html(value, output_html);
+ return output_html.to_string();
+}
+
+void MarkupGenerator::value_to_html(Value value, StringBuilder& output_html, HashTable<Object*> seen_objects)
+{
+ if (value.is_empty()) {
+ output_html.append("&lt;empty&gt;");
+ return;
+ }
+
+ if (value.is_object()) {
+ if (seen_objects.contains(&value.as_object())) {
+ // FIXME: Maybe we should only do this for circular references,
+ // not for all reoccurring objects.
+ output_html.appendff("&lt;already printed Object {:p}&gt;", &value.as_object());
+ return;
+ }
+ seen_objects.set(&value.as_object());
+ }
+
+ if (value.is_array())
+ return array_to_html(static_cast<const Array&>(value.as_object()), output_html, seen_objects);
+
+ if (value.is_object()) {
+ auto& object = value.as_object();
+ if (object.is_function())
+ return function_to_html(object, output_html, seen_objects);
+ if (is<Date>(object))
+ return date_to_html(object, output_html, seen_objects);
+ if (is<Error>(object))
+ return error_to_html(object, output_html, seen_objects);
+ return object_to_html(object, output_html, seen_objects);
+ }
+
+ if (value.is_string())
+ output_html.append(open_style_type(StyleType::String));
+ else if (value.is_number())
+ output_html.append(open_style_type(StyleType::Number));
+ else if (value.is_boolean() || value.is_nullish())
+ output_html.append(open_style_type(StyleType::KeywordBold));
+
+ if (value.is_string())
+ output_html.append('"');
+ output_html.append(escape_html_entities(value.to_string_without_side_effects()));
+ if (value.is_string())
+ output_html.append('"');
+
+ output_html.append("</span>");
+}
+
+void MarkupGenerator::array_to_html(const Array& array, StringBuilder& html_output, HashTable<Object*>& seen_objects)
+{
+ html_output.append(wrap_string_in_style("[ ", StyleType::Punctuation));
+ bool first = true;
+ for (auto it = array.indexed_properties().begin(false); it != array.indexed_properties().end(); ++it) {
+ if (!first)
+ html_output.append(wrap_string_in_style(", ", StyleType::Punctuation));
+ first = false;
+ // FIXME: Exception check
+ value_to_html(it.value_and_attributes(const_cast<Array*>(&array)).value, html_output, seen_objects);
+ }
+ html_output.append(wrap_string_in_style(" ]", StyleType::Punctuation));
+}
+
+void MarkupGenerator::object_to_html(const Object& object, StringBuilder& html_output, HashTable<Object*>& seen_objects)
+{
+ html_output.append(wrap_string_in_style("{ ", StyleType::Punctuation));
+ bool first = true;
+ for (auto& entry : object.indexed_properties()) {
+ if (!first)
+ html_output.append(wrap_string_in_style(", ", StyleType::Punctuation));
+ first = false;
+ html_output.append(wrap_string_in_style(String::number(entry.index()), StyleType::Number));
+ html_output.append(wrap_string_in_style(": ", StyleType::Punctuation));
+ // FIXME: Exception check
+ value_to_html(entry.value_and_attributes(const_cast<Object*>(&object)).value, html_output, seen_objects);
+ }
+
+ if (!object.indexed_properties().is_empty() && object.shape().property_count())
+ html_output.append(wrap_string_in_style(", ", StyleType::Punctuation));
+
+ size_t index = 0;
+ for (auto& it : object.shape().property_table_ordered()) {
+ html_output.append(wrap_string_in_style(String::formatted("\"{}\"", escape_html_entities(it.key.to_display_string())), StyleType::String));
+ html_output.append(wrap_string_in_style(": ", StyleType::Punctuation));
+ value_to_html(object.get_direct(it.value.offset), html_output, seen_objects);
+ if (index != object.shape().property_count() - 1)
+ html_output.append(wrap_string_in_style(", ", StyleType::Punctuation));
+ ++index;
+ }
+
+ html_output.append(wrap_string_in_style(" }", StyleType::Punctuation));
+}
+
+void MarkupGenerator::function_to_html(const Object& function, StringBuilder& html_output, HashTable<Object*>&)
+{
+ html_output.appendff("[{}]", function.class_name());
+}
+
+void MarkupGenerator::date_to_html(const Object& date, StringBuilder& html_output, HashTable<Object*>&)
+{
+ html_output.appendff("Date {}", static_cast<const JS::Date&>(date).string());
+}
+
+void MarkupGenerator::error_to_html(const Object& object, StringBuilder& html_output, HashTable<Object*>&)
+{
+ auto& error = static_cast<const Error&>(object);
+ html_output.append(wrap_string_in_style(String::formatted("[{}]", error.name()), StyleType::Invalid));
+ if (!error.message().is_empty()) {
+ html_output.appendff(": {}", escape_html_entities(error.message()));
+ }
+}
+
+String MarkupGenerator::style_from_style_type(StyleType type)
+{
+ switch (type) {
+ case StyleType::Invalid:
+ return "color: red;";
+ case StyleType::String:
+ return "color: -libweb-palette-syntax-string;";
+ case StyleType::Number:
+ return "color: -libweb-palette-syntax-number;";
+ case StyleType::KeywordBold:
+ return "color: -libweb-palette-syntax-keyword; font-weight: bold;";
+ case StyleType::Punctuation:
+ return "color: -libweb-palette-syntax-punctuation;";
+ case StyleType::Operator:
+ return "color: -libweb-palette-syntax-operator;";
+ case StyleType::Keyword:
+ return "color: -libweb-palette-syntax-keyword;";
+ case StyleType::ControlKeyword:
+ return "color: -libweb-palette-syntax-control-keyword;";
+ case StyleType::Identifier:
+ return "color: -libweb-palette-syntax-identifier;";
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+MarkupGenerator::StyleType MarkupGenerator::style_type_for_token(Token token)
+{
+ switch (token.category()) {
+ case TokenCategory::Invalid:
+ return StyleType::Invalid;
+ case TokenCategory::Number:
+ return StyleType::Number;
+ case TokenCategory::String:
+ return StyleType::String;
+ case TokenCategory::Punctuation:
+ return StyleType::Punctuation;
+ case TokenCategory::Operator:
+ return StyleType::Operator;
+ case TokenCategory::Keyword:
+ switch (token.type()) {
+ case TokenType::BoolLiteral:
+ case TokenType::NullLiteral:
+ return StyleType::KeywordBold;
+ default:
+ return StyleType::Keyword;
+ }
+ case TokenCategory::ControlKeyword:
+ return StyleType::ControlKeyword;
+ case TokenCategory::Identifier:
+ return StyleType::Identifier;
+ default:
+ dbgln("Unknown style type for token {}", token.name());
+ ASSERT_NOT_REACHED();
+ }
+}
+
+String MarkupGenerator::open_style_type(StyleType type)
+{
+ return String::formatted("<span style=\"{}\">", style_from_style_type(type));
+}
+
+String MarkupGenerator::wrap_string_in_style(String source, StyleType type)
+{
+ return String::formatted("<span style=\"{}\">{}</span>", style_from_style_type(type), escape_html_entities(source));
+}
+
+}
diff --git a/Userland/Libraries/LibJS/MarkupGenerator.h b/Userland/Libraries/LibJS/MarkupGenerator.h
new file mode 100644
index 0000000000..834c094539
--- /dev/null
+++ b/Userland/Libraries/LibJS/MarkupGenerator.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020, Hunter Salyer <thefalsehonesty@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashTable.h>
+#include <AK/String.h>
+#include <LibJS/Forward.h>
+
+namespace JS {
+
+class MarkupGenerator {
+public:
+ static String html_from_source(const StringView&);
+ static String html_from_value(Value);
+
+private:
+ enum class StyleType {
+ Invalid,
+ String,
+ Number,
+ KeywordBold,
+ Punctuation,
+ Operator,
+ Keyword,
+ ControlKeyword,
+ Identifier
+ };
+
+ static void value_to_html(Value, StringBuilder& output_html, HashTable<Object*> seen_objects = {});
+ static void array_to_html(const Array&, StringBuilder& output_html, HashTable<Object*>&);
+ static void object_to_html(const Object&, StringBuilder& output_html, HashTable<Object*>&);
+ static void function_to_html(const Object&, StringBuilder& output_html, HashTable<Object*>&);
+ static void date_to_html(const Object&, StringBuilder& output_html, HashTable<Object*>&);
+ static void error_to_html(const Object&, StringBuilder& output_html, HashTable<Object*>&);
+
+ static String style_from_style_type(StyleType);
+ static StyleType style_type_for_token(Token);
+ static String open_style_type(StyleType type);
+ static String wrap_string_in_style(String source, StyleType type);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp
new file mode 100644
index 0000000000..ac8fbeb2e9
--- /dev/null
+++ b/Userland/Libraries/LibJS/Parser.cpp
@@ -0,0 +1,2049 @@
+/*
+ * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Parser.h"
+#include <AK/ScopeGuard.h>
+#include <AK/StdLibExtras.h>
+#include <AK/TemporaryChange.h>
+#include <ctype.h>
+
+namespace JS {
+
+static bool statement_is_use_strict_directive(NonnullRefPtr<Statement> statement)
+{
+ if (!is<ExpressionStatement>(*statement))
+ return false;
+ auto& expression_statement = static_cast<ExpressionStatement&>(*statement);
+ auto& expression = expression_statement.expression();
+ if (!is<StringLiteral>(expression))
+ return false;
+ return static_cast<const StringLiteral&>(expression).is_use_strict_directive();
+}
+
+class ScopePusher {
+public:
+ enum Type {
+ Var = 1,
+ Let = 2,
+ Function = 3,
+ };
+
+ ScopePusher(Parser& parser, unsigned mask)
+ : m_parser(parser)
+ , m_mask(mask)
+ {
+ if (m_mask & Var)
+ m_parser.m_parser_state.m_var_scopes.append(NonnullRefPtrVector<VariableDeclaration>());
+ if (m_mask & Let)
+ m_parser.m_parser_state.m_let_scopes.append(NonnullRefPtrVector<VariableDeclaration>());
+ if (m_mask & Function)
+ m_parser.m_parser_state.m_function_scopes.append(NonnullRefPtrVector<FunctionDeclaration>());
+ }
+
+ ~ScopePusher()
+ {
+ if (m_mask & Var)
+ m_parser.m_parser_state.m_var_scopes.take_last();
+ if (m_mask & Let)
+ m_parser.m_parser_state.m_let_scopes.take_last();
+ if (m_mask & Function)
+ m_parser.m_parser_state.m_function_scopes.take_last();
+ }
+
+ Parser& m_parser;
+ unsigned m_mask { 0 };
+};
+
+class OperatorPrecedenceTable {
+public:
+ constexpr OperatorPrecedenceTable()
+ : m_token_precedence()
+ {
+ for (size_t i = 0; i < array_size(m_operator_precedence); ++i) {
+ auto& op = m_operator_precedence[i];
+ m_token_precedence[static_cast<size_t>(op.token)] = op.precedence;
+ }
+ }
+
+ constexpr int get(TokenType token) const
+ {
+ int p = m_token_precedence[static_cast<size_t>(token)];
+ if (p == 0) {
+ warnln("Internal Error: No precedence for operator {}", Token::name(token));
+ ASSERT_NOT_REACHED();
+ return -1;
+ }
+
+ return p;
+ }
+
+private:
+ int m_token_precedence[cs_num_of_js_tokens];
+
+ struct OperatorPrecedence {
+ TokenType token;
+ int precedence;
+ };
+
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
+ static constexpr const OperatorPrecedence m_operator_precedence[] = {
+ { TokenType::Period, 20 },
+ { TokenType::BracketOpen, 20 },
+ { TokenType::ParenOpen, 20 },
+ { TokenType::QuestionMarkPeriod, 20 },
+
+ { TokenType::New, 19 },
+
+ { TokenType::PlusPlus, 18 },
+ { TokenType::MinusMinus, 18 },
+
+ { TokenType::ExclamationMark, 17 },
+ { TokenType::Tilde, 17 },
+ { TokenType::Typeof, 17 },
+ { TokenType::Void, 17 },
+ { TokenType::Delete, 17 },
+ { TokenType::Await, 17 },
+
+ { TokenType::DoubleAsterisk, 16 },
+
+ { TokenType::Asterisk, 15 },
+ { TokenType::Slash, 15 },
+ { TokenType::Percent, 15 },
+
+ { TokenType::Plus, 14 },
+ { TokenType::Minus, 14 },
+
+ { TokenType::ShiftLeft, 13 },
+ { TokenType::ShiftRight, 13 },
+ { TokenType::UnsignedShiftRight, 13 },
+
+ { TokenType::LessThan, 12 },
+ { TokenType::LessThanEquals, 12 },
+ { TokenType::GreaterThan, 12 },
+ { TokenType::GreaterThanEquals, 12 },
+ { TokenType::In, 12 },
+ { TokenType::Instanceof, 12 },
+
+ { TokenType::EqualsEquals, 11 },
+ { TokenType::ExclamationMarkEquals, 11 },
+ { TokenType::EqualsEqualsEquals, 11 },
+ { TokenType::ExclamationMarkEqualsEquals, 11 },
+
+ { TokenType::Ampersand, 10 },
+
+ { TokenType::Caret, 9 },
+
+ { TokenType::Pipe, 8 },
+
+ { TokenType::DoubleQuestionMark, 7 },
+
+ { TokenType::DoubleAmpersand, 6 },
+
+ { TokenType::DoublePipe, 5 },
+
+ { TokenType::QuestionMark, 4 },
+
+ { TokenType::Equals, 3 },
+ { TokenType::PlusEquals, 3 },
+ { TokenType::MinusEquals, 3 },
+ { TokenType::DoubleAsteriskEquals, 3 },
+ { TokenType::AsteriskEquals, 3 },
+ { TokenType::SlashEquals, 3 },
+ { TokenType::PercentEquals, 3 },
+ { TokenType::ShiftLeftEquals, 3 },
+ { TokenType::ShiftRightEquals, 3 },
+ { TokenType::UnsignedShiftRightEquals, 3 },
+ { TokenType::AmpersandEquals, 3 },
+ { TokenType::CaretEquals, 3 },
+ { TokenType::PipeEquals, 3 },
+ { TokenType::DoubleAmpersandEquals, 3 },
+ { TokenType::DoublePipeEquals, 3 },
+ { TokenType::DoubleQuestionMarkEquals, 3 },
+
+ { TokenType::Yield, 2 },
+
+ { TokenType::Comma, 1 },
+ };
+};
+
+constexpr OperatorPrecedenceTable g_operator_precedence;
+
+Parser::ParserState::ParserState(Lexer lexer)
+ : m_lexer(move(lexer))
+ , m_current_token(m_lexer.next())
+{
+}
+
+Parser::Parser(Lexer lexer)
+ : m_parser_state(move(lexer))
+{
+}
+
+Associativity Parser::operator_associativity(TokenType type) const
+{
+ switch (type) {
+ case TokenType::Period:
+ case TokenType::BracketOpen:
+ case TokenType::ParenOpen:
+ case TokenType::QuestionMarkPeriod:
+ case TokenType::Asterisk:
+ case TokenType::Slash:
+ case TokenType::Percent:
+ case TokenType::Plus:
+ case TokenType::Minus:
+ case TokenType::ShiftLeft:
+ case TokenType::ShiftRight:
+ case TokenType::UnsignedShiftRight:
+ case TokenType::LessThan:
+ case TokenType::LessThanEquals:
+ case TokenType::GreaterThan:
+ case TokenType::GreaterThanEquals:
+ case TokenType::In:
+ case TokenType::Instanceof:
+ case TokenType::EqualsEquals:
+ case TokenType::ExclamationMarkEquals:
+ case TokenType::EqualsEqualsEquals:
+ case TokenType::ExclamationMarkEqualsEquals:
+ case TokenType::Typeof:
+ case TokenType::Void:
+ case TokenType::Delete:
+ case TokenType::Ampersand:
+ case TokenType::Caret:
+ case TokenType::Pipe:
+ case TokenType::DoubleQuestionMark:
+ case TokenType::DoubleAmpersand:
+ case TokenType::DoublePipe:
+ case TokenType::Comma:
+ return Associativity::Left;
+ default:
+ return Associativity::Right;
+ }
+}
+
+NonnullRefPtr<Program> Parser::parse_program()
+{
+ auto rule_start = push_start();
+ ScopePusher scope(*this, ScopePusher::Var | ScopePusher::Let | ScopePusher::Function);
+ auto program = adopt(*new Program({ rule_start.position(), position() }));
+
+ bool first = true;
+ while (!done()) {
+ if (match_declaration()) {
+ program->append(parse_declaration());
+ } else if (match_statement()) {
+ auto statement = parse_statement();
+ program->append(statement);
+ if (statement_is_use_strict_directive(statement)) {
+ if (first) {
+ program->set_strict_mode();
+ m_parser_state.m_strict_mode = true;
+ }
+ if (m_parser_state.m_string_legacy_octal_escape_sequence_in_scope)
+ syntax_error("Octal escape sequence in string literal not allowed in strict mode");
+ }
+ } else {
+ expected("statement or declaration");
+ consume();
+ }
+ first = false;
+ }
+ if (m_parser_state.m_var_scopes.size() == 1) {
+ program->add_variables(m_parser_state.m_var_scopes.last());
+ program->add_variables(m_parser_state.m_let_scopes.last());
+ program->add_functions(m_parser_state.m_function_scopes.last());
+ } else {
+ syntax_error("Unclosed scope");
+ }
+ program->source_range().end = position();
+ return program;
+}
+
+NonnullRefPtr<Declaration> Parser::parse_declaration()
+{
+ auto rule_start = push_start();
+ switch (m_parser_state.m_current_token.type()) {
+ case TokenType::Class:
+ return parse_class_declaration();
+ case TokenType::Function: {
+ auto declaration = parse_function_node<FunctionDeclaration>();
+ m_parser_state.m_function_scopes.last().append(declaration);
+ return declaration;
+ }
+ case TokenType::Let:
+ case TokenType::Const:
+ return parse_variable_declaration();
+ default:
+ expected("declaration");
+ consume();
+ return create_ast_node<ErrorDeclaration>({ rule_start.position(), position() });
+ }
+}
+
+NonnullRefPtr<Statement> Parser::parse_statement()
+{
+ auto rule_start = push_start();
+ switch (m_parser_state.m_current_token.type()) {
+ case TokenType::CurlyOpen:
+ return parse_block_statement();
+ case TokenType::Return:
+ return parse_return_statement();
+ case TokenType::Var:
+ return parse_variable_declaration();
+ case TokenType::For:
+ return parse_for_statement();
+ case TokenType::If:
+ return parse_if_statement();
+ case TokenType::Throw:
+ return parse_throw_statement();
+ case TokenType::Try:
+ return parse_try_statement();
+ case TokenType::Break:
+ return parse_break_statement();
+ case TokenType::Continue:
+ return parse_continue_statement();
+ case TokenType::Switch:
+ return parse_switch_statement();
+ case TokenType::Do:
+ return parse_do_while_statement();
+ case TokenType::While:
+ return parse_while_statement();
+ case TokenType::With:
+ if (m_parser_state.m_strict_mode)
+ syntax_error("'with' statement not allowed in strict mode");
+ return parse_with_statement();
+ case TokenType::Debugger:
+ return parse_debugger_statement();
+ case TokenType::Semicolon:
+ consume();
+ return create_ast_node<EmptyStatement>({ rule_start.position(), position() });
+ default:
+ if (match(TokenType::Identifier)) {
+ auto result = try_parse_labelled_statement();
+ if (!result.is_null())
+ return result.release_nonnull();
+ }
+ if (match_expression()) {
+ if (match(TokenType::Function))
+ syntax_error("Function declaration not allowed in single-statement context");
+ auto expr = parse_expression(0);
+ consume_or_insert_semicolon();
+ return create_ast_node<ExpressionStatement>({ rule_start.position(), position() }, move(expr));
+ }
+ expected("statement");
+ consume();
+ return create_ast_node<ErrorStatement>({ rule_start.position(), position() });
+ }
+}
+
+RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expect_parens)
+{
+ save_state();
+ m_parser_state.m_var_scopes.append(NonnullRefPtrVector<VariableDeclaration>());
+ auto rule_start = push_start();
+
+ ArmedScopeGuard state_rollback_guard = [&] {
+ m_parser_state.m_var_scopes.take_last();
+ load_state();
+ };
+
+ Vector<FunctionNode::Parameter> parameters;
+ i32 function_length = -1;
+ if (expect_parens) {
+ // We have parens around the function parameters and can re-use the same parsing
+ // logic used for regular functions: multiple parameters, default values, rest
+ // parameter, maybe a trailing comma. If we have a new syntax error afterwards we
+ // check if it's about a wrong token (something like duplicate parameter name must
+ // not abort), know parsing failed and rollback the parser state.
+ auto previous_syntax_errors = m_parser_state.m_errors.size();
+ parameters = parse_function_parameters(function_length, FunctionNodeParseOptions::IsArrowFunction);
+ if (m_parser_state.m_errors.size() > previous_syntax_errors && m_parser_state.m_errors[previous_syntax_errors].message.starts_with("Unexpected token"))
+ return nullptr;
+ if (!match(TokenType::ParenClose))
+ return nullptr;
+ consume();
+ } else {
+ // No parens - this must be an identifier followed by arrow. That's it.
+ if (!match(TokenType::Identifier))
+ return nullptr;
+ parameters.append({ consume().value(), {} });
+ }
+ // If there's a newline between the closing paren and arrow it's not a valid arrow function,
+ // ASI should kick in instead (it'll then fail with "Unexpected token Arrow")
+ if (m_parser_state.m_current_token.trivia_contains_line_terminator())
+ return nullptr;
+ if (!match(TokenType::Arrow))
+ return nullptr;
+ consume();
+
+ if (function_length == -1)
+ function_length = parameters.size();
+
+ auto old_labels_in_scope = move(m_parser_state.m_labels_in_scope);
+ ScopeGuard guard([&]() {
+ m_parser_state.m_labels_in_scope = move(old_labels_in_scope);
+ });
+
+ bool is_strict = false;
+
+ auto function_body_result = [&]() -> RefPtr<BlockStatement> {
+ TemporaryChange change(m_parser_state.m_in_arrow_function_context, true);
+ if (match(TokenType::CurlyOpen)) {
+ // Parse a function body with statements
+ return parse_block_statement(is_strict);
+ }
+ if (match_expression()) {
+ // Parse a function body which returns a single expression
+
+ // FIXME: We synthesize a block with a return statement
+ // for arrow function bodies which are a single expression.
+ // Esprima generates a single "ArrowFunctionExpression"
+ // with a "body" property.
+ auto return_expression = parse_expression(2);
+ auto return_block = create_ast_node<BlockStatement>({ rule_start.position(), position() });
+ return_block->append<ReturnStatement>({ rule_start.position(), position() }, move(return_expression));
+ return return_block;
+ }
+ // Invalid arrow function body
+ return nullptr;
+ }();
+
+ if (!function_body_result.is_null()) {
+ state_rollback_guard.disarm();
+ discard_saved_state();
+ auto body = function_body_result.release_nonnull();
+ return create_ast_node<FunctionExpression>({ rule_start.position(), position() }, "", move(body), move(parameters), function_length, m_parser_state.m_var_scopes.take_last(), is_strict, true);
+ }
+
+ return nullptr;
+}
+
+RefPtr<Statement> Parser::try_parse_labelled_statement()
+{
+ save_state();
+ auto rule_start = push_start();
+ ArmedScopeGuard state_rollback_guard = [&] {
+ load_state();
+ };
+
+ auto identifier = consume(TokenType::Identifier).value();
+ if (!match(TokenType::Colon))
+ return {};
+ consume(TokenType::Colon);
+
+ if (!match_statement())
+ return {};
+ m_parser_state.m_labels_in_scope.set(identifier);
+ auto statement = parse_statement();
+ m_parser_state.m_labels_in_scope.remove(identifier);
+
+ statement->set_label(identifier);
+ state_rollback_guard.disarm();
+ discard_saved_state();
+ return statement;
+}
+
+RefPtr<MetaProperty> Parser::try_parse_new_target_expression()
+{
+ save_state();
+ auto rule_start = push_start();
+ ArmedScopeGuard state_rollback_guard = [&] {
+ load_state();
+ };
+
+ consume(TokenType::New);
+ if (!match(TokenType::Period))
+ return {};
+ consume();
+ if (!match(TokenType::Identifier))
+ return {};
+ if (consume().value() != "target")
+ return {};
+
+ state_rollback_guard.disarm();
+ discard_saved_state();
+ return create_ast_node<MetaProperty>({ rule_start.position(), position() }, MetaProperty::Type::NewTarget);
+}
+
+NonnullRefPtr<ClassDeclaration> Parser::parse_class_declaration()
+{
+ auto rule_start = push_start();
+ return create_ast_node<ClassDeclaration>({ rule_start.position(), position() }, parse_class_expression(true));
+}
+
+NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_name)
+{
+ auto rule_start = push_start();
+ // Classes are always in strict mode.
+ TemporaryChange strict_mode_rollback(m_parser_state.m_strict_mode, true);
+
+ consume(TokenType::Class);
+
+ NonnullRefPtrVector<ClassMethod> methods;
+ RefPtr<Expression> super_class;
+ RefPtr<FunctionExpression> constructor;
+
+ String class_name = expect_class_name || match(TokenType::Identifier) ? consume(TokenType::Identifier).value().to_string() : "";
+
+ if (match(TokenType::Extends)) {
+ consume();
+ super_class = parse_primary_expression();
+ }
+
+ consume(TokenType::CurlyOpen);
+
+ while (!done() && !match(TokenType::CurlyClose)) {
+ RefPtr<Expression> property_key;
+ bool is_static = false;
+ bool is_constructor = false;
+ auto method_kind = ClassMethod::Kind::Method;
+
+ if (match(TokenType::Semicolon)) {
+ consume();
+ continue;
+ }
+
+ if (match_property_key()) {
+ StringView name;
+ if (match(TokenType::Identifier) && m_parser_state.m_current_token.value() == "static") {
+ consume();
+ is_static = true;
+ }
+
+ if (match(TokenType::Identifier)) {
+ auto identifier_name = m_parser_state.m_current_token.value();
+
+ if (identifier_name == "get") {
+ method_kind = ClassMethod::Kind::Getter;
+ consume();
+ } else if (identifier_name == "set") {
+ method_kind = ClassMethod::Kind::Setter;
+ consume();
+ }
+ }
+
+ if (match_property_key()) {
+ switch (m_parser_state.m_current_token.type()) {
+ case TokenType::Identifier:
+ name = consume().value();
+ property_key = create_ast_node<StringLiteral>({ rule_start.position(), position() }, name);
+ break;
+ case TokenType::StringLiteral: {
+ auto string_literal = parse_string_literal(consume());
+ name = string_literal->value();
+ property_key = move(string_literal);
+ break;
+ }
+ default:
+ property_key = parse_property_key();
+ break;
+ }
+
+ } else {
+ expected("property key");
+ }
+
+ // Constructor may be a StringLiteral or an Identifier.
+ if (!is_static && name == "constructor") {
+ if (method_kind != ClassMethod::Kind::Method)
+ syntax_error("Class constructor may not be an accessor");
+ if (!constructor.is_null())
+ syntax_error("Classes may not have more than one constructor");
+
+ is_constructor = true;
+ }
+ }
+
+ if (match(TokenType::ParenOpen)) {
+ u8 parse_options = FunctionNodeParseOptions::AllowSuperPropertyLookup;
+ if (!super_class.is_null())
+ parse_options |= FunctionNodeParseOptions::AllowSuperConstructorCall;
+ if (method_kind == ClassMethod::Kind::Getter)
+ parse_options |= FunctionNodeParseOptions::IsGetterFunction;
+ if (method_kind == ClassMethod::Kind::Setter)
+ parse_options |= FunctionNodeParseOptions::IsSetterFunction;
+ auto function = parse_function_node<FunctionExpression>(parse_options);
+ if (is_constructor) {
+ constructor = move(function);
+ } else if (!property_key.is_null()) {
+ methods.append(create_ast_node<ClassMethod>({ rule_start.position(), position() }, property_key.release_nonnull(), move(function), method_kind, is_static));
+ } else {
+ syntax_error("No key for class method");
+ }
+ } else {
+ expected("ParenOpen");
+ consume();
+ }
+ }
+
+ consume(TokenType::CurlyClose);
+
+ if (constructor.is_null()) {
+ auto constructor_body = create_ast_node<BlockStatement>({ rule_start.position(), position() });
+ if (!super_class.is_null()) {
+ // Set constructor to the result of parsing the source text
+ // constructor(... args){ super (...args);}
+ auto super_call = create_ast_node<CallExpression>({ rule_start.position(), position() }, create_ast_node<SuperExpression>({ rule_start.position(), position() }), Vector { CallExpression::Argument { create_ast_node<Identifier>({ rule_start.position(), position() }, "args"), true } });
+ constructor_body->append(create_ast_node<ExpressionStatement>({ rule_start.position(), position() }, move(super_call)));
+ constructor_body->add_variables(m_parser_state.m_var_scopes.last());
+
+ constructor = create_ast_node<FunctionExpression>({ rule_start.position(), position() }, class_name, move(constructor_body), Vector { FunctionNode::Parameter { "args", nullptr, true } }, 0, NonnullRefPtrVector<VariableDeclaration>(), true);
+ } else {
+ constructor = create_ast_node<FunctionExpression>({ rule_start.position(), position() }, class_name, move(constructor_body), Vector<FunctionNode::Parameter> {}, 0, NonnullRefPtrVector<VariableDeclaration>(), true);
+ }
+ }
+
+ return create_ast_node<ClassExpression>({ rule_start.position(), position() }, move(class_name), move(constructor), move(super_class), move(methods));
+}
+
+NonnullRefPtr<Expression> Parser::parse_primary_expression()
+{
+ auto rule_start = push_start();
+ if (match_unary_prefixed_expression())
+ return parse_unary_prefixed_expression();
+
+ switch (m_parser_state.m_current_token.type()) {
+ case TokenType::ParenOpen: {
+ consume(TokenType::ParenOpen);
+ if (match(TokenType::ParenClose) || match(TokenType::Identifier) || match(TokenType::TripleDot)) {
+ auto arrow_function_result = try_parse_arrow_function_expression(true);
+ if (!arrow_function_result.is_null())
+ return arrow_function_result.release_nonnull();
+ }
+ auto expression = parse_expression(0);
+ consume(TokenType::ParenClose);
+ return expression;
+ }
+ case TokenType::This:
+ consume();
+ return create_ast_node<ThisExpression>({ rule_start.position(), position() });
+ case TokenType::Class:
+ return parse_class_expression(false);
+ case TokenType::Super:
+ consume();
+ if (!m_parser_state.m_allow_super_property_lookup)
+ syntax_error("'super' keyword unexpected here");
+ return create_ast_node<SuperExpression>({ rule_start.position(), position() });
+ case TokenType::Identifier: {
+ auto arrow_function_result = try_parse_arrow_function_expression(false);
+ if (!arrow_function_result.is_null())
+ return arrow_function_result.release_nonnull();
+ return create_ast_node<Identifier>({ rule_start.position(), position() }, consume().value());
+ }
+ case TokenType::NumericLiteral:
+ return create_ast_node<NumericLiteral>({ rule_start.position(), position() }, consume_and_validate_numeric_literal().double_value());
+ case TokenType::BigIntLiteral:
+ return create_ast_node<BigIntLiteral>({ rule_start.position(), position() }, consume().value());
+ case TokenType::BoolLiteral:
+ return create_ast_node<BooleanLiteral>({ rule_start.position(), position() }, consume().bool_value());
+ case TokenType::StringLiteral:
+ return parse_string_literal(consume());
+ case TokenType::NullLiteral:
+ consume();
+ return create_ast_node<NullLiteral>({ rule_start.position(), position() });
+ case TokenType::CurlyOpen:
+ return parse_object_expression();
+ case TokenType::Function:
+ return parse_function_node<FunctionExpression>();
+ case TokenType::BracketOpen:
+ return parse_array_expression();
+ case TokenType::RegexLiteral:
+ return parse_regexp_literal();
+ case TokenType::TemplateLiteralStart:
+ return parse_template_literal(false);
+ case TokenType::New: {
+ auto new_start = position();
+ auto new_target_result = try_parse_new_target_expression();
+ if (!new_target_result.is_null()) {
+ if (!m_parser_state.m_in_function_context)
+ syntax_error("'new.target' not allowed outside of a function", new_start);
+ return new_target_result.release_nonnull();
+ }
+ return parse_new_expression();
+ }
+ default:
+ expected("primary expression");
+ consume();
+ return create_ast_node<ErrorExpression>({ rule_start.position(), position() });
+ }
+}
+
+NonnullRefPtr<RegExpLiteral> Parser::parse_regexp_literal()
+{
+ auto rule_start = push_start();
+ auto content = consume().value();
+ auto flags = match(TokenType::RegexFlags) ? consume().value() : "";
+ return create_ast_node<RegExpLiteral>({ rule_start.position(), position() }, content.substring_view(1, content.length() - 2), flags);
+}
+
+NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
+{
+ auto rule_start = push_start();
+ auto precedence = g_operator_precedence.get(m_parser_state.m_current_token.type());
+ auto associativity = operator_associativity(m_parser_state.m_current_token.type());
+ switch (m_parser_state.m_current_token.type()) {
+ case TokenType::PlusPlus: {
+ consume();
+ auto rhs_start = position();
+ auto rhs = parse_expression(precedence, associativity);
+ // FIXME: Apparently for functions this should also not be enforced on a parser level,
+ // other engines throw ReferenceError for ++foo()
+ if (!is<Identifier>(*rhs) && !is<MemberExpression>(*rhs))
+ syntax_error(String::formatted("Right-hand side of prefix increment operator must be identifier or member expression, got {}", rhs->class_name()), rhs_start);
+ return create_ast_node<UpdateExpression>({ rule_start.position(), position() }, UpdateOp::Increment, move(rhs), true);
+ }
+ case TokenType::MinusMinus: {
+ consume();
+ auto rhs_start = position();
+ auto rhs = parse_expression(precedence, associativity);
+ // FIXME: Apparently for functions this should also not be enforced on a parser level,
+ // other engines throw ReferenceError for --foo()
+ if (!is<Identifier>(*rhs) && !is<MemberExpression>(*rhs))
+ syntax_error(String::formatted("Right-hand side of prefix decrement operator must be identifier or member expression, got {}", rhs->class_name()), rhs_start);
+ return create_ast_node<UpdateExpression>({ rule_start.position(), position() }, UpdateOp::Decrement, move(rhs), true);
+ }
+ case TokenType::ExclamationMark:
+ consume();
+ return create_ast_node<UnaryExpression>({ rule_start.position(), position() }, UnaryOp::Not, parse_expression(precedence, associativity));
+ case TokenType::Tilde:
+ consume();
+ return create_ast_node<UnaryExpression>({ rule_start.position(), position() }, UnaryOp::BitwiseNot, parse_expression(precedence, associativity));
+ case TokenType::Plus:
+ consume();
+ return create_ast_node<UnaryExpression>({ rule_start.position(), position() }, UnaryOp::Plus, parse_expression(precedence, associativity));
+ case TokenType::Minus:
+ consume();
+ return create_ast_node<UnaryExpression>({ rule_start.position(), position() }, UnaryOp::Minus, parse_expression(precedence, associativity));
+ case TokenType::Typeof:
+ consume();
+ return create_ast_node<UnaryExpression>({ rule_start.position(), position() }, UnaryOp::Typeof, parse_expression(precedence, associativity));
+ case TokenType::Void:
+ consume();
+ return create_ast_node<UnaryExpression>({ rule_start.position(), position() }, UnaryOp::Void, parse_expression(precedence, associativity));
+ case TokenType::Delete:
+ consume();
+ return create_ast_node<UnaryExpression>({ rule_start.position(), position() }, UnaryOp::Delete, parse_expression(precedence, associativity));
+ default:
+ expected("primary expression");
+ consume();
+ return create_ast_node<ErrorExpression>({ rule_start.position(), position() });
+ }
+}
+
+NonnullRefPtr<Expression> Parser::parse_property_key()
+{
+ auto rule_start = push_start();
+ if (match(TokenType::StringLiteral)) {
+ return parse_string_literal(consume());
+ } else if (match(TokenType::NumericLiteral)) {
+ return create_ast_node<NumericLiteral>({ rule_start.position(), position() }, consume().double_value());
+ } else if (match(TokenType::BigIntLiteral)) {
+ return create_ast_node<BigIntLiteral>({ rule_start.position(), position() }, consume().value());
+ } else if (match(TokenType::BracketOpen)) {
+ consume(TokenType::BracketOpen);
+ auto result = parse_expression(0);
+ consume(TokenType::BracketClose);
+ return result;
+ } else {
+ if (!match_identifier_name())
+ expected("IdentifierName");
+ return create_ast_node<StringLiteral>({ rule_start.position(), position() }, consume().value());
+ }
+}
+
+NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
+{
+ auto rule_start = push_start();
+ consume(TokenType::CurlyOpen);
+
+ NonnullRefPtrVector<ObjectProperty> properties;
+ ObjectProperty::Type property_type;
+
+ auto skip_to_next_property = [&] {
+ while (!done() && !match(TokenType::Comma) && !match(TokenType::CurlyOpen))
+ consume();
+ };
+
+ while (!done() && !match(TokenType::CurlyClose)) {
+ property_type = ObjectProperty::Type::KeyValue;
+ RefPtr<Expression> property_name;
+ RefPtr<Expression> property_value;
+
+ if (match(TokenType::TripleDot)) {
+ consume();
+ property_name = parse_expression(4);
+ properties.append(create_ast_node<ObjectProperty>({ rule_start.position(), position() }, *property_name, nullptr, ObjectProperty::Type::Spread, false));
+ if (!match(TokenType::Comma))
+ break;
+ consume(TokenType::Comma);
+ continue;
+ }
+
+ if (match(TokenType::Identifier)) {
+ auto identifier = consume().value();
+ if (identifier == "get" && match_property_key()) {
+ property_type = ObjectProperty::Type::Getter;
+ property_name = parse_property_key();
+ } else if (identifier == "set" && match_property_key()) {
+ property_type = ObjectProperty::Type::Setter;
+ property_name = parse_property_key();
+ } else {
+ property_name = create_ast_node<StringLiteral>({ rule_start.position(), position() }, identifier);
+ property_value = create_ast_node<Identifier>({ rule_start.position(), position() }, identifier);
+ }
+ } else {
+ property_name = parse_property_key();
+ }
+
+ if (property_type == ObjectProperty::Type::Getter || property_type == ObjectProperty::Type::Setter) {
+ if (!match(TokenType::ParenOpen)) {
+ syntax_error("Expected '(' for object getter or setter property");
+ skip_to_next_property();
+ continue;
+ }
+ }
+
+ if (match(TokenType::ParenOpen)) {
+ ASSERT(property_name);
+ u8 parse_options = FunctionNodeParseOptions::AllowSuperPropertyLookup;
+ if (property_type == ObjectProperty::Type::Getter)
+ parse_options |= FunctionNodeParseOptions::IsGetterFunction;
+ if (property_type == ObjectProperty::Type::Setter)
+ parse_options |= FunctionNodeParseOptions::IsSetterFunction;
+ auto function = parse_function_node<FunctionExpression>(parse_options);
+ properties.append(create_ast_node<ObjectProperty>({ rule_start.position(), position() }, *property_name, function, property_type, true));
+ } else if (match(TokenType::Colon)) {
+ if (!property_name) {
+ syntax_error("Expected a property name");
+ skip_to_next_property();
+ continue;
+ }
+ consume();
+ properties.append(create_ast_node<ObjectProperty>({ rule_start.position(), position() }, *property_name, parse_expression(2), property_type, false));
+ } else if (property_name && property_value) {
+ properties.append(create_ast_node<ObjectProperty>({ rule_start.position(), position() }, *property_name, *property_value, property_type, false));
+ } else {
+ syntax_error("Expected a property");
+ skip_to_next_property();
+ continue;
+ }
+
+ if (!match(TokenType::Comma))
+ break;
+ consume(TokenType::Comma);
+ }
+
+ consume(TokenType::CurlyClose);
+ return create_ast_node<ObjectExpression>({ rule_start.position(), position() }, properties);
+}
+
+NonnullRefPtr<ArrayExpression> Parser::parse_array_expression()
+{
+ auto rule_start = push_start();
+ consume(TokenType::BracketOpen);
+
+ Vector<RefPtr<Expression>> elements;
+ while (match_expression() || match(TokenType::TripleDot) || match(TokenType::Comma)) {
+ RefPtr<Expression> expression;
+
+ if (match(TokenType::TripleDot)) {
+ consume(TokenType::TripleDot);
+ expression = create_ast_node<SpreadExpression>({ rule_start.position(), position() }, parse_expression(2));
+ } else if (match_expression()) {
+ expression = parse_expression(2);
+ }
+
+ elements.append(expression);
+ if (!match(TokenType::Comma))
+ break;
+ consume(TokenType::Comma);
+ }
+
+ consume(TokenType::BracketClose);
+ return create_ast_node<ArrayExpression>({ rule_start.position(), position() }, move(elements));
+}
+
+NonnullRefPtr<StringLiteral> Parser::parse_string_literal(Token token, bool in_template_literal)
+{
+ auto rule_start = push_start();
+ auto status = Token::StringValueStatus::Ok;
+ auto string = token.string_value(status);
+ if (status != Token::StringValueStatus::Ok) {
+ String message;
+ if (status == Token::StringValueStatus::LegacyOctalEscapeSequence) {
+ m_parser_state.m_string_legacy_octal_escape_sequence_in_scope = true;
+ if (in_template_literal)
+ message = "Octal escape sequence not allowed in template literal";
+ else if (m_parser_state.m_strict_mode)
+ message = "Octal escape sequence in string literal not allowed in strict mode";
+ } else if (status == Token::StringValueStatus::MalformedHexEscape || status == Token::StringValueStatus::MalformedUnicodeEscape) {
+ auto type = status == Token::StringValueStatus::MalformedUnicodeEscape ? "unicode" : "hexadecimal";
+ message = String::formatted("Malformed {} escape sequence", type);
+ } else if (status == Token::StringValueStatus::UnicodeEscapeOverflow) {
+ message = "Unicode code_point must not be greater than 0x10ffff in escape sequence";
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+
+ if (!message.is_empty())
+ syntax_error(message, Position { token.line_number(), token.line_column() });
+ }
+
+ auto is_use_strict_directive = !in_template_literal && (token.value() == "'use strict'" || token.value() == "\"use strict\"");
+
+ return create_ast_node<StringLiteral>({ rule_start.position(), position() }, string, is_use_strict_directive);
+}
+
+NonnullRefPtr<TemplateLiteral> Parser::parse_template_literal(bool is_tagged)
+{
+ auto rule_start = push_start();
+ consume(TokenType::TemplateLiteralStart);
+
+ NonnullRefPtrVector<Expression> expressions;
+ NonnullRefPtrVector<Expression> raw_strings;
+
+ auto append_empty_string = [this, &rule_start, &expressions, &raw_strings, is_tagged]() {
+ auto string_literal = create_ast_node<StringLiteral>({ rule_start.position(), position() }, "");
+ expressions.append(string_literal);
+ if (is_tagged)
+ raw_strings.append(string_literal);
+ };
+
+ if (!match(TokenType::TemplateLiteralString))
+ append_empty_string();
+
+ while (!done() && !match(TokenType::TemplateLiteralEnd) && !match(TokenType::UnterminatedTemplateLiteral)) {
+ if (match(TokenType::TemplateLiteralString)) {
+ auto token = consume();
+ expressions.append(parse_string_literal(token, true));
+ if (is_tagged)
+ raw_strings.append(create_ast_node<StringLiteral>({ rule_start.position(), position() }, token.value()));
+ } else if (match(TokenType::TemplateLiteralExprStart)) {
+ consume(TokenType::TemplateLiteralExprStart);
+ if (match(TokenType::TemplateLiteralExprEnd)) {
+ syntax_error("Empty template literal expression block");
+ return create_ast_node<TemplateLiteral>({ rule_start.position(), position() }, expressions);
+ }
+
+ expressions.append(parse_expression(0));
+ if (match(TokenType::UnterminatedTemplateLiteral)) {
+ syntax_error("Unterminated template literal");
+ return create_ast_node<TemplateLiteral>({ rule_start.position(), position() }, expressions);
+ }
+ consume(TokenType::TemplateLiteralExprEnd);
+
+ if (!match(TokenType::TemplateLiteralString))
+ append_empty_string();
+ } else {
+ expected("Template literal string or expression");
+ break;
+ }
+ }
+
+ if (match(TokenType::UnterminatedTemplateLiteral)) {
+ syntax_error("Unterminated template literal");
+ } else {
+ consume(TokenType::TemplateLiteralEnd);
+ }
+
+ if (is_tagged)
+ return create_ast_node<TemplateLiteral>({ rule_start.position(), position() }, expressions, raw_strings);
+ return create_ast_node<TemplateLiteral>({ rule_start.position(), position() }, expressions);
+}
+
+NonnullRefPtr<Expression> Parser::parse_expression(int min_precedence, Associativity associativity, Vector<TokenType> forbidden)
+{
+ auto rule_start = push_start();
+ auto expression = parse_primary_expression();
+ while (match(TokenType::TemplateLiteralStart)) {
+ auto template_literal = parse_template_literal(true);
+ expression = create_ast_node<TaggedTemplateLiteral>({ rule_start.position(), position() }, move(expression), move(template_literal));
+ }
+ while (match_secondary_expression(forbidden)) {
+ int new_precedence = g_operator_precedence.get(m_parser_state.m_current_token.type());
+ if (new_precedence < min_precedence)
+ break;
+ if (new_precedence == min_precedence && associativity == Associativity::Left)
+ break;
+
+ Associativity new_associativity = operator_associativity(m_parser_state.m_current_token.type());
+ expression = parse_secondary_expression(move(expression), new_precedence, new_associativity);
+ while (match(TokenType::TemplateLiteralStart)) {
+ auto template_literal = parse_template_literal(true);
+ expression = create_ast_node<TaggedTemplateLiteral>({ rule_start.position(), position() }, move(expression), move(template_literal));
+ }
+ }
+ if (match(TokenType::Comma) && min_precedence <= 1) {
+ NonnullRefPtrVector<Expression> expressions;
+ expressions.append(expression);
+ while (match(TokenType::Comma)) {
+ consume();
+ expressions.append(parse_expression(2));
+ }
+ expression = create_ast_node<SequenceExpression>({ rule_start.position(), position() }, move(expressions));
+ }
+ return expression;
+}
+
+NonnullRefPtr<Expression> Parser::parse_secondary_expression(NonnullRefPtr<Expression> lhs, int min_precedence, Associativity associativity)
+{
+ auto rule_start = push_start();
+ switch (m_parser_state.m_current_token.type()) {
+ case TokenType::Plus:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::Addition, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::PlusEquals:
+ return parse_assignment_expression(AssignmentOp::AdditionAssignment, move(lhs), min_precedence, associativity);
+ case TokenType::Minus:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::Subtraction, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::MinusEquals:
+ return parse_assignment_expression(AssignmentOp::SubtractionAssignment, move(lhs), min_precedence, associativity);
+ case TokenType::Asterisk:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::Multiplication, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::AsteriskEquals:
+ return parse_assignment_expression(AssignmentOp::MultiplicationAssignment, move(lhs), min_precedence, associativity);
+ case TokenType::Slash:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::Division, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::SlashEquals:
+ return parse_assignment_expression(AssignmentOp::DivisionAssignment, move(lhs), min_precedence, associativity);
+ case TokenType::Percent:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::Modulo, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::PercentEquals:
+ return parse_assignment_expression(AssignmentOp::ModuloAssignment, move(lhs), min_precedence, associativity);
+ case TokenType::DoubleAsterisk:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::Exponentiation, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::DoubleAsteriskEquals:
+ return parse_assignment_expression(AssignmentOp::ExponentiationAssignment, move(lhs), min_precedence, associativity);
+ case TokenType::GreaterThan:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::GreaterThan, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::GreaterThanEquals:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::GreaterThanEquals, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::LessThan:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::LessThan, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::LessThanEquals:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::LessThanEquals, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::EqualsEqualsEquals:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::TypedEquals, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::ExclamationMarkEqualsEquals:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::TypedInequals, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::EqualsEquals:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::AbstractEquals, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::ExclamationMarkEquals:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::AbstractInequals, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::In:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::In, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::Instanceof:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::InstanceOf, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::Ampersand:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::BitwiseAnd, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::AmpersandEquals:
+ return parse_assignment_expression(AssignmentOp::BitwiseAndAssignment, move(lhs), min_precedence, associativity);
+ case TokenType::Pipe:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::BitwiseOr, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::PipeEquals:
+ return parse_assignment_expression(AssignmentOp::BitwiseOrAssignment, move(lhs), min_precedence, associativity);
+ case TokenType::Caret:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::BitwiseXor, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::CaretEquals:
+ return parse_assignment_expression(AssignmentOp::BitwiseXorAssignment, move(lhs), min_precedence, associativity);
+ case TokenType::ShiftLeft:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::LeftShift, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::ShiftLeftEquals:
+ return parse_assignment_expression(AssignmentOp::LeftShiftAssignment, move(lhs), min_precedence, associativity);
+ case TokenType::ShiftRight:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::RightShift, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::ShiftRightEquals:
+ return parse_assignment_expression(AssignmentOp::RightShiftAssignment, move(lhs), min_precedence, associativity);
+ case TokenType::UnsignedShiftRight:
+ consume();
+ return create_ast_node<BinaryExpression>({ rule_start.position(), position() }, BinaryOp::UnsignedRightShift, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::UnsignedShiftRightEquals:
+ return parse_assignment_expression(AssignmentOp::UnsignedRightShiftAssignment, move(lhs), min_precedence, associativity);
+ case TokenType::ParenOpen:
+ return parse_call_expression(move(lhs));
+ case TokenType::Equals:
+ return parse_assignment_expression(AssignmentOp::Assignment, move(lhs), min_precedence, associativity);
+ case TokenType::Period:
+ consume();
+ if (!match_identifier_name())
+ expected("IdentifierName");
+ return create_ast_node<MemberExpression>({ rule_start.position(), position() }, move(lhs), create_ast_node<Identifier>({ rule_start.position(), position() }, consume().value()));
+ case TokenType::BracketOpen: {
+ consume(TokenType::BracketOpen);
+ auto expression = create_ast_node<MemberExpression>({ rule_start.position(), position() }, move(lhs), parse_expression(0), true);
+ consume(TokenType::BracketClose);
+ return expression;
+ }
+ case TokenType::PlusPlus:
+ // FIXME: Apparently for functions this should also not be enforced on a parser level,
+ // other engines throw ReferenceError for foo()++
+ if (!is<Identifier>(*lhs) && !is<MemberExpression>(*lhs))
+ syntax_error(String::formatted("Left-hand side of postfix increment operator must be identifier or member expression, got {}", lhs->class_name()));
+ consume();
+ return create_ast_node<UpdateExpression>({ rule_start.position(), position() }, UpdateOp::Increment, move(lhs));
+ case TokenType::MinusMinus:
+ // FIXME: Apparently for functions this should also not be enforced on a parser level,
+ // other engines throw ReferenceError for foo()--
+ if (!is<Identifier>(*lhs) && !is<MemberExpression>(*lhs))
+ syntax_error(String::formatted("Left-hand side of postfix increment operator must be identifier or member expression, got {}", lhs->class_name()));
+ consume();
+ return create_ast_node<UpdateExpression>({ rule_start.position(), position() }, UpdateOp::Decrement, move(lhs));
+ case TokenType::DoubleAmpersand:
+ consume();
+ return create_ast_node<LogicalExpression>({ rule_start.position(), position() }, LogicalOp::And, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::DoubleAmpersandEquals:
+ return parse_assignment_expression(AssignmentOp::AndAssignment, move(lhs), min_precedence, associativity);
+ case TokenType::DoublePipe:
+ consume();
+ return create_ast_node<LogicalExpression>({ rule_start.position(), position() }, LogicalOp::Or, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::DoublePipeEquals:
+ return parse_assignment_expression(AssignmentOp::OrAssignment, move(lhs), min_precedence, associativity);
+ case TokenType::DoubleQuestionMark:
+ consume();
+ return create_ast_node<LogicalExpression>({ rule_start.position(), position() }, LogicalOp::NullishCoalescing, move(lhs), parse_expression(min_precedence, associativity));
+ case TokenType::DoubleQuestionMarkEquals:
+ return parse_assignment_expression(AssignmentOp::NullishAssignment, move(lhs), min_precedence, associativity);
+ case TokenType::QuestionMark:
+ return parse_conditional_expression(move(lhs));
+ default:
+ expected("secondary expression");
+ consume();
+ return create_ast_node<ErrorExpression>({ rule_start.position(), position() });
+ }
+}
+
+NonnullRefPtr<AssignmentExpression> Parser::parse_assignment_expression(AssignmentOp assignment_op, NonnullRefPtr<Expression> lhs, int min_precedence, Associativity associativity)
+{
+ auto rule_start = push_start();
+ ASSERT(match(TokenType::Equals)
+ || match(TokenType::PlusEquals)
+ || match(TokenType::MinusEquals)
+ || match(TokenType::AsteriskEquals)
+ || match(TokenType::SlashEquals)
+ || match(TokenType::PercentEquals)
+ || match(TokenType::DoubleAsteriskEquals)
+ || match(TokenType::AmpersandEquals)
+ || match(TokenType::PipeEquals)
+ || match(TokenType::CaretEquals)
+ || match(TokenType::ShiftLeftEquals)
+ || match(TokenType::ShiftRightEquals)
+ || match(TokenType::UnsignedShiftRightEquals)
+ || match(TokenType::DoubleAmpersandEquals)
+ || match(TokenType::DoublePipeEquals)
+ || match(TokenType::DoubleQuestionMarkEquals));
+ consume();
+ if (!is<Identifier>(*lhs) && !is<MemberExpression>(*lhs) && !is<CallExpression>(*lhs)) {
+ syntax_error("Invalid left-hand side in assignment");
+ } else if (m_parser_state.m_strict_mode && is<Identifier>(*lhs)) {
+ auto name = static_cast<const Identifier&>(*lhs).string();
+ if (name == "eval" || name == "arguments")
+ syntax_error(String::formatted("'{}' cannot be assigned to in strict mode code", name));
+ } else if (m_parser_state.m_strict_mode && is<CallExpression>(*lhs)) {
+ syntax_error("Cannot assign to function call");
+ }
+ return create_ast_node<AssignmentExpression>({ rule_start.position(), position() }, assignment_op, move(lhs), parse_expression(min_precedence, associativity));
+}
+
+NonnullRefPtr<CallExpression> Parser::parse_call_expression(NonnullRefPtr<Expression> lhs)
+{
+ auto rule_start = push_start();
+ if (!m_parser_state.m_allow_super_constructor_call && is<SuperExpression>(*lhs))
+ syntax_error("'super' keyword unexpected here");
+
+ consume(TokenType::ParenOpen);
+
+ Vector<CallExpression::Argument> arguments;
+
+ while (match_expression() || match(TokenType::TripleDot)) {
+ if (match(TokenType::TripleDot)) {
+ consume();
+ arguments.append({ parse_expression(2), true });
+ } else {
+ arguments.append({ parse_expression(2), false });
+ }
+ if (!match(TokenType::Comma))
+ break;
+ consume();
+ }
+
+ consume(TokenType::ParenClose);
+
+ return create_ast_node<CallExpression>({ rule_start.position(), position() }, move(lhs), move(arguments));
+}
+
+NonnullRefPtr<NewExpression> Parser::parse_new_expression()
+{
+ auto rule_start = push_start();
+ consume(TokenType::New);
+
+ auto callee = parse_expression(g_operator_precedence.get(TokenType::New), Associativity::Right, { TokenType::ParenOpen });
+
+ Vector<CallExpression::Argument> arguments;
+
+ if (match(TokenType::ParenOpen)) {
+ consume(TokenType::ParenOpen);
+ while (match_expression() || match(TokenType::TripleDot)) {
+ if (match(TokenType::TripleDot)) {
+ consume();
+ arguments.append({ parse_expression(2), true });
+ } else {
+ arguments.append({ parse_expression(2), false });
+ }
+ if (!match(TokenType::Comma))
+ break;
+ consume();
+ }
+ consume(TokenType::ParenClose);
+ }
+
+ return create_ast_node<NewExpression>({ rule_start.position(), position() }, move(callee), move(arguments));
+}
+
+NonnullRefPtr<ReturnStatement> Parser::parse_return_statement()
+{
+ auto rule_start = push_start();
+ if (!m_parser_state.m_in_function_context && !m_parser_state.m_in_arrow_function_context)
+ syntax_error("'return' not allowed outside of a function");
+
+ consume(TokenType::Return);
+
+ // Automatic semicolon insertion: terminate statement when return is followed by newline
+ if (m_parser_state.m_current_token.trivia_contains_line_terminator())
+ return create_ast_node<ReturnStatement>({ rule_start.position(), position() }, nullptr);
+
+ if (match_expression()) {
+ auto expression = parse_expression(0);
+ consume_or_insert_semicolon();
+ return create_ast_node<ReturnStatement>({ rule_start.position(), position() }, move(expression));
+ }
+
+ consume_or_insert_semicolon();
+ return create_ast_node<ReturnStatement>({ rule_start.position(), position() }, nullptr);
+}
+
+NonnullRefPtr<BlockStatement> Parser::parse_block_statement()
+{
+ auto rule_start = push_start();
+ bool dummy = false;
+ return parse_block_statement(dummy);
+}
+
+NonnullRefPtr<BlockStatement> Parser::parse_block_statement(bool& is_strict)
+{
+ auto rule_start = push_start();
+ ScopePusher scope(*this, ScopePusher::Let);
+ auto block = create_ast_node<BlockStatement>({ rule_start.position(), position() });
+ consume(TokenType::CurlyOpen);
+
+ bool first = true;
+ bool initial_strict_mode_state = m_parser_state.m_strict_mode;
+ if (initial_strict_mode_state)
+ is_strict = true;
+
+ while (!done() && !match(TokenType::CurlyClose)) {
+ if (match_declaration()) {
+ block->append(parse_declaration());
+ } else if (match_statement()) {
+ auto statement = parse_statement();
+ block->append(statement);
+ if (statement_is_use_strict_directive(statement)) {
+ if (first && !initial_strict_mode_state) {
+ is_strict = true;
+ m_parser_state.m_strict_mode = true;
+ }
+ if (m_parser_state.m_string_legacy_octal_escape_sequence_in_scope)
+ syntax_error("Octal escape sequence in string literal not allowed in strict mode");
+ }
+ } else {
+ expected("statement or declaration");
+ consume();
+ }
+ first = false;
+ }
+ m_parser_state.m_strict_mode = initial_strict_mode_state;
+ m_parser_state.m_string_legacy_octal_escape_sequence_in_scope = false;
+ consume(TokenType::CurlyClose);
+ block->add_variables(m_parser_state.m_let_scopes.last());
+ block->add_functions(m_parser_state.m_function_scopes.last());
+ return block;
+}
+
+template<typename FunctionNodeType>
+NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options)
+{
+ auto rule_start = push_start();
+ ASSERT(!(parse_options & FunctionNodeParseOptions::IsGetterFunction && parse_options & FunctionNodeParseOptions::IsSetterFunction));
+
+ TemporaryChange super_property_access_rollback(m_parser_state.m_allow_super_property_lookup, !!(parse_options & FunctionNodeParseOptions::AllowSuperPropertyLookup));
+ TemporaryChange super_constructor_call_rollback(m_parser_state.m_allow_super_constructor_call, !!(parse_options & FunctionNodeParseOptions::AllowSuperConstructorCall));
+
+ ScopePusher scope(*this, ScopePusher::Var | ScopePusher::Function);
+
+ String name;
+ if (parse_options & FunctionNodeParseOptions::CheckForFunctionAndName) {
+ consume(TokenType::Function);
+ if (FunctionNodeType::must_have_name() || match(TokenType::Identifier))
+ name = consume(TokenType::Identifier).value();
+ }
+ consume(TokenType::ParenOpen);
+ i32 function_length = -1;
+ auto parameters = parse_function_parameters(function_length, parse_options);
+ consume(TokenType::ParenClose);
+
+ if (function_length == -1)
+ function_length = parameters.size();
+
+ TemporaryChange change(m_parser_state.m_in_function_context, true);
+ auto old_labels_in_scope = move(m_parser_state.m_labels_in_scope);
+ ScopeGuard guard([&]() {
+ m_parser_state.m_labels_in_scope = move(old_labels_in_scope);
+ });
+
+ bool is_strict = false;
+ auto body = parse_block_statement(is_strict);
+ body->add_variables(m_parser_state.m_var_scopes.last());
+ body->add_functions(m_parser_state.m_function_scopes.last());
+ return create_ast_node<FunctionNodeType>({ rule_start.position(), position() }, name, move(body), move(parameters), function_length, NonnullRefPtrVector<VariableDeclaration>(), is_strict);
+}
+
+Vector<FunctionNode::Parameter> Parser::parse_function_parameters(int& function_length, u8 parse_options)
+{
+ auto rule_start = push_start();
+ bool has_default_parameter = false;
+ bool has_rest_parameter = false;
+
+ Vector<FunctionNode::Parameter> parameters;
+
+ auto consume_and_validate_identifier = [&]() -> Token {
+ auto token = consume(TokenType::Identifier);
+ auto parameter_name = token.value();
+
+ for (auto& parameter : parameters) {
+ if (parameter_name != parameter.name)
+ continue;
+ String message;
+ if (parse_options & FunctionNodeParseOptions::IsArrowFunction)
+ message = String::formatted("Duplicate parameter '{}' not allowed in arrow function", parameter_name);
+ else if (m_parser_state.m_strict_mode)
+ message = String::formatted("Duplicate parameter '{}' not allowed in strict mode", parameter_name);
+ else if (has_default_parameter || match(TokenType::Equals))
+ message = String::formatted("Duplicate parameter '{}' not allowed in function with default parameter", parameter_name);
+ else if (has_rest_parameter)
+ message = String::formatted("Duplicate parameter '{}' not allowed in function with rest parameter", parameter_name);
+ if (!message.is_empty())
+ syntax_error(message, Position { token.line_number(), token.line_column() });
+ break;
+ }
+ return token;
+ };
+
+ while (match(TokenType::Identifier) || match(TokenType::TripleDot)) {
+ if (parse_options & FunctionNodeParseOptions::IsGetterFunction)
+ syntax_error("Getter function must have no arguments");
+ if (parse_options & FunctionNodeParseOptions::IsSetterFunction && (parameters.size() >= 1 || match(TokenType::TripleDot)))
+ syntax_error("Setter function must have one argument");
+ if (match(TokenType::TripleDot)) {
+ consume();
+ has_rest_parameter = true;
+ auto parameter_name = consume_and_validate_identifier().value();
+ function_length = parameters.size();
+ parameters.append({ parameter_name, nullptr, true });
+ break;
+ }
+ auto parameter_name = consume_and_validate_identifier().value();
+ RefPtr<Expression> default_value;
+ if (match(TokenType::Equals)) {
+ consume();
+ has_default_parameter = true;
+ function_length = parameters.size();
+ default_value = parse_expression(2);
+ }
+ parameters.append({ parameter_name, default_value });
+ if (match(TokenType::ParenClose))
+ break;
+ consume(TokenType::Comma);
+ }
+ if (parse_options & FunctionNodeParseOptions::IsSetterFunction && parameters.is_empty())
+ syntax_error("Setter function must have one argument");
+ return parameters;
+}
+
+NonnullRefPtr<VariableDeclaration> Parser::parse_variable_declaration(bool for_loop_variable_declaration)
+{
+ auto rule_start = push_start();
+ DeclarationKind declaration_kind;
+
+ switch (m_parser_state.m_current_token.type()) {
+ case TokenType::Var:
+ declaration_kind = DeclarationKind::Var;
+ break;
+ case TokenType::Let:
+ declaration_kind = DeclarationKind::Let;
+ break;
+ case TokenType::Const:
+ declaration_kind = DeclarationKind::Const;
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ consume();
+
+ NonnullRefPtrVector<VariableDeclarator> declarations;
+ for (;;) {
+ auto id = consume(TokenType::Identifier).value();
+ RefPtr<Expression> init;
+ if (match(TokenType::Equals)) {
+ consume();
+ init = parse_expression(2);
+ } else if (!for_loop_variable_declaration && declaration_kind == DeclarationKind::Const) {
+ syntax_error("Missing initializer in 'const' variable declaration");
+ }
+ declarations.append(create_ast_node<VariableDeclarator>({ rule_start.position(), position() }, create_ast_node<Identifier>({ rule_start.position(), position() }, move(id)), move(init)));
+ if (match(TokenType::Comma)) {
+ consume();
+ continue;
+ }
+ break;
+ }
+ if (!for_loop_variable_declaration)
+ consume_or_insert_semicolon();
+
+ auto declaration = create_ast_node<VariableDeclaration>({ rule_start.position(), position() }, declaration_kind, move(declarations));
+ if (declaration_kind == DeclarationKind::Var)
+ m_parser_state.m_var_scopes.last().append(declaration);
+ else
+ m_parser_state.m_let_scopes.last().append(declaration);
+ return declaration;
+}
+
+NonnullRefPtr<ThrowStatement> Parser::parse_throw_statement()
+{
+ auto rule_start = push_start();
+ consume(TokenType::Throw);
+
+ // Automatic semicolon insertion: terminate statement when throw is followed by newline
+ if (m_parser_state.m_current_token.trivia_contains_line_terminator()) {
+ syntax_error("No line break is allowed between 'throw' and its expression");
+ return create_ast_node<ThrowStatement>({ rule_start.position(), position() }, create_ast_node<ErrorExpression>({ rule_start.position(), position() }));
+ }
+
+ auto expression = parse_expression(0);
+ consume_or_insert_semicolon();
+ return create_ast_node<ThrowStatement>({ rule_start.position(), position() }, move(expression));
+}
+
+NonnullRefPtr<BreakStatement> Parser::parse_break_statement()
+{
+ auto rule_start = push_start();
+ consume(TokenType::Break);
+ FlyString target_label;
+ if (match(TokenType::Semicolon)) {
+ consume();
+ } else {
+ if (match(TokenType::Identifier) && !m_parser_state.m_current_token.trivia_contains_line_terminator()) {
+ target_label = consume().value();
+ if (!m_parser_state.m_labels_in_scope.contains(target_label))
+ syntax_error(String::formatted("Label '{}' not found", target_label));
+ }
+ consume_or_insert_semicolon();
+ }
+
+ if (target_label.is_null() && !m_parser_state.m_in_break_context)
+ syntax_error("Unlabeled 'break' not allowed outside of a loop or switch statement");
+
+ return create_ast_node<BreakStatement>({ rule_start.position(), position() }, target_label);
+}
+
+NonnullRefPtr<ContinueStatement> Parser::parse_continue_statement()
+{
+ auto rule_start = push_start();
+ if (!m_parser_state.m_in_continue_context)
+ syntax_error("'continue' not allow outside of a loop");
+
+ consume(TokenType::Continue);
+ FlyString target_label;
+ if (match(TokenType::Semicolon)) {
+ consume();
+ return create_ast_node<ContinueStatement>({ rule_start.position(), position() }, target_label);
+ }
+ if (match(TokenType::Identifier) && !m_parser_state.m_current_token.trivia_contains_line_terminator()) {
+ target_label = consume().value();
+ if (!m_parser_state.m_labels_in_scope.contains(target_label))
+ syntax_error(String::formatted("Label '{}' not found", target_label));
+ }
+ consume_or_insert_semicolon();
+ return create_ast_node<ContinueStatement>({ rule_start.position(), position() }, target_label);
+}
+
+NonnullRefPtr<ConditionalExpression> Parser::parse_conditional_expression(NonnullRefPtr<Expression> test)
+{
+ auto rule_start = push_start();
+ consume(TokenType::QuestionMark);
+ auto consequent = parse_expression(2);
+ consume(TokenType::Colon);
+ auto alternate = parse_expression(2);
+ return create_ast_node<ConditionalExpression>({ rule_start.position(), position() }, move(test), move(consequent), move(alternate));
+}
+
+NonnullRefPtr<TryStatement> Parser::parse_try_statement()
+{
+ auto rule_start = push_start();
+ consume(TokenType::Try);
+
+ auto block = parse_block_statement();
+
+ RefPtr<CatchClause> handler;
+ if (match(TokenType::Catch))
+ handler = parse_catch_clause();
+
+ RefPtr<BlockStatement> finalizer;
+ if (match(TokenType::Finally)) {
+ consume();
+ finalizer = parse_block_statement();
+ }
+
+ if (!handler && !finalizer)
+ syntax_error("try statement must have a 'catch' or 'finally' clause");
+
+ return create_ast_node<TryStatement>({ rule_start.position(), position() }, move(block), move(handler), move(finalizer));
+}
+
+NonnullRefPtr<DoWhileStatement> Parser::parse_do_while_statement()
+{
+ auto rule_start = push_start();
+ consume(TokenType::Do);
+
+ auto body = [&]() -> NonnullRefPtr<Statement> {
+ TemporaryChange break_change(m_parser_state.m_in_break_context, true);
+ TemporaryChange continue_change(m_parser_state.m_in_continue_context, true);
+ return parse_statement();
+ }();
+
+ consume(TokenType::While);
+ consume(TokenType::ParenOpen);
+
+ auto test = parse_expression(0);
+
+ consume(TokenType::ParenClose);
+
+ // Since ES 2015 a missing semicolon is inserted here, despite the regular ASI rules not applying
+ if (match(TokenType::Semicolon))
+ consume();
+
+ return create_ast_node<DoWhileStatement>({ rule_start.position(), position() }, move(test), move(body));
+}
+
+NonnullRefPtr<WhileStatement> Parser::parse_while_statement()
+{
+ auto rule_start = push_start();
+ consume(TokenType::While);
+ consume(TokenType::ParenOpen);
+
+ auto test = parse_expression(0);
+
+ consume(TokenType::ParenClose);
+
+ TemporaryChange break_change(m_parser_state.m_in_break_context, true);
+ TemporaryChange continue_change(m_parser_state.m_in_continue_context, true);
+ auto body = parse_statement();
+
+ return create_ast_node<WhileStatement>({ rule_start.position(), position() }, move(test), move(body));
+}
+
+NonnullRefPtr<SwitchStatement> Parser::parse_switch_statement()
+{
+ auto rule_start = push_start();
+ consume(TokenType::Switch);
+
+ consume(TokenType::ParenOpen);
+ auto determinant = parse_expression(0);
+ consume(TokenType::ParenClose);
+
+ consume(TokenType::CurlyOpen);
+
+ NonnullRefPtrVector<SwitchCase> cases;
+
+ auto has_default = false;
+ while (match(TokenType::Case) || match(TokenType::Default)) {
+ if (match(TokenType::Default)) {
+ if (has_default)
+ syntax_error("Multiple 'default' clauses in switch statement");
+ has_default = true;
+ }
+ cases.append(parse_switch_case());
+ }
+
+ consume(TokenType::CurlyClose);
+
+ return create_ast_node<SwitchStatement>({ rule_start.position(), position() }, move(determinant), move(cases));
+}
+
+NonnullRefPtr<WithStatement> Parser::parse_with_statement()
+{
+ auto rule_start = push_start();
+ consume(TokenType::With);
+ consume(TokenType::ParenOpen);
+
+ auto object = parse_expression(0);
+
+ consume(TokenType::ParenClose);
+
+ auto body = parse_statement();
+ return create_ast_node<WithStatement>({ rule_start.position(), position() }, move(object), move(body));
+}
+
+NonnullRefPtr<SwitchCase> Parser::parse_switch_case()
+{
+ auto rule_start = push_start();
+ RefPtr<Expression> test;
+
+ if (consume().type() == TokenType::Case) {
+ test = parse_expression(0);
+ }
+
+ consume(TokenType::Colon);
+
+ NonnullRefPtrVector<Statement> consequent;
+ TemporaryChange break_change(m_parser_state.m_in_break_context, true);
+ for (;;) {
+ if (match_declaration())
+ consequent.append(parse_declaration());
+ else if (match_statement())
+ consequent.append(parse_statement());
+ else
+ break;
+ }
+
+ return create_ast_node<SwitchCase>({ rule_start.position(), position() }, move(test), move(consequent));
+}
+
+NonnullRefPtr<CatchClause> Parser::parse_catch_clause()
+{
+ auto rule_start = push_start();
+ consume(TokenType::Catch);
+
+ String parameter;
+ if (match(TokenType::ParenOpen)) {
+ consume();
+ parameter = consume(TokenType::Identifier).value();
+ consume(TokenType::ParenClose);
+ }
+
+ auto body = parse_block_statement();
+ return create_ast_node<CatchClause>({ rule_start.position(), position() }, parameter, move(body));
+}
+
+NonnullRefPtr<IfStatement> Parser::parse_if_statement()
+{
+ auto rule_start = push_start();
+ auto parse_function_declaration_as_block_statement = [&] {
+ // https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses
+ // Code matching this production is processed as if each matching occurrence of
+ // FunctionDeclaration[?Yield, ?Await, ~Default] was the sole StatementListItem
+ // of a BlockStatement occupying that position in the source code.
+ ScopePusher scope(*this, ScopePusher::Let);
+ auto block = create_ast_node<BlockStatement>({ rule_start.position(), position() });
+ block->append(parse_declaration());
+ block->add_functions(m_parser_state.m_function_scopes.last());
+ return block;
+ };
+
+ consume(TokenType::If);
+ consume(TokenType::ParenOpen);
+ auto predicate = parse_expression(0);
+ consume(TokenType::ParenClose);
+
+ RefPtr<Statement> consequent;
+ if (!m_parser_state.m_strict_mode && match(TokenType::Function))
+ consequent = parse_function_declaration_as_block_statement();
+ else
+ consequent = parse_statement();
+
+ RefPtr<Statement> alternate;
+ if (match(TokenType::Else)) {
+ consume();
+ if (!m_parser_state.m_strict_mode && match(TokenType::Function))
+ alternate = parse_function_declaration_as_block_statement();
+ else
+ alternate = parse_statement();
+ }
+ return create_ast_node<IfStatement>({ rule_start.position(), position() }, move(predicate), move(*consequent), move(alternate));
+}
+
+NonnullRefPtr<Statement> Parser::parse_for_statement()
+{
+ auto rule_start = push_start();
+ auto match_for_in_of = [&]() {
+ return match(TokenType::In) || (match(TokenType::Identifier) && m_parser_state.m_current_token.value() == "of");
+ };
+
+ consume(TokenType::For);
+
+ consume(TokenType::ParenOpen);
+
+ bool in_scope = false;
+ RefPtr<ASTNode> init;
+ if (!match(TokenType::Semicolon)) {
+ if (match_expression()) {
+ init = parse_expression(0, Associativity::Right, { TokenType::In });
+ if (match_for_in_of())
+ return parse_for_in_of_statement(*init);
+ } else if (match_variable_declaration()) {
+ if (!match(TokenType::Var)) {
+ m_parser_state.m_let_scopes.append(NonnullRefPtrVector<VariableDeclaration>());
+ in_scope = true;
+ }
+ init = parse_variable_declaration(true);
+ if (match_for_in_of())
+ return parse_for_in_of_statement(*init);
+ if (static_cast<VariableDeclaration&>(*init).declaration_kind() == DeclarationKind::Const) {
+ for (auto& declaration : static_cast<VariableDeclaration&>(*init).declarations()) {
+ if (!declaration.init())
+ syntax_error("Missing initializer in 'const' variable declaration");
+ }
+ }
+ } else {
+ syntax_error("Unexpected token in for loop");
+ }
+ }
+ consume(TokenType::Semicolon);
+
+ RefPtr<Expression> test;
+ if (!match(TokenType::Semicolon))
+ test = parse_expression(0);
+
+ consume(TokenType::Semicolon);
+
+ RefPtr<Expression> update;
+ if (!match(TokenType::ParenClose))
+ update = parse_expression(0);
+
+ consume(TokenType::ParenClose);
+
+ TemporaryChange break_change(m_parser_state.m_in_break_context, true);
+ TemporaryChange continue_change(m_parser_state.m_in_continue_context, true);
+ auto body = parse_statement();
+
+ if (in_scope) {
+ m_parser_state.m_let_scopes.take_last();
+ }
+
+ return create_ast_node<ForStatement>({ rule_start.position(), position() }, move(init), move(test), move(update), move(body));
+}
+
+NonnullRefPtr<Statement> Parser::parse_for_in_of_statement(NonnullRefPtr<ASTNode> lhs)
+{
+ auto rule_start = push_start();
+ if (is<VariableDeclaration>(*lhs)) {
+ auto declarations = static_cast<VariableDeclaration&>(*lhs).declarations();
+ if (declarations.size() > 1)
+ syntax_error("multiple declarations not allowed in for..in/of");
+ if (declarations.first().init() != nullptr)
+ syntax_error("variable initializer not allowed in for..in/of");
+ }
+ auto in_or_of = consume();
+ auto rhs = parse_expression(0);
+ consume(TokenType::ParenClose);
+
+ TemporaryChange break_change(m_parser_state.m_in_break_context, true);
+ TemporaryChange continue_change(m_parser_state.m_in_continue_context, true);
+ auto body = parse_statement();
+ if (in_or_of.type() == TokenType::In)
+ return create_ast_node<ForInStatement>({ rule_start.position(), position() }, move(lhs), move(rhs), move(body));
+ return create_ast_node<ForOfStatement>({ rule_start.position(), position() }, move(lhs), move(rhs), move(body));
+}
+
+NonnullRefPtr<DebuggerStatement> Parser::parse_debugger_statement()
+{
+ auto rule_start = push_start();
+ consume(TokenType::Debugger);
+ consume_or_insert_semicolon();
+ return create_ast_node<DebuggerStatement>({ rule_start.position(), position() });
+}
+
+bool Parser::match(TokenType type) const
+{
+ return m_parser_state.m_current_token.type() == type;
+}
+
+bool Parser::match_expression() const
+{
+ auto type = m_parser_state.m_current_token.type();
+ return type == TokenType::BoolLiteral
+ || type == TokenType::NumericLiteral
+ || type == TokenType::BigIntLiteral
+ || type == TokenType::StringLiteral
+ || type == TokenType::TemplateLiteralStart
+ || type == TokenType::NullLiteral
+ || type == TokenType::Identifier
+ || type == TokenType::New
+ || type == TokenType::CurlyOpen
+ || type == TokenType::BracketOpen
+ || type == TokenType::ParenOpen
+ || type == TokenType::Function
+ || type == TokenType::This
+ || type == TokenType::Super
+ || type == TokenType::RegexLiteral
+ || match_unary_prefixed_expression();
+}
+
+bool Parser::match_unary_prefixed_expression() const
+{
+ auto type = m_parser_state.m_current_token.type();
+ return type == TokenType::PlusPlus
+ || type == TokenType::MinusMinus
+ || type == TokenType::ExclamationMark
+ || type == TokenType::Tilde
+ || type == TokenType::Plus
+ || type == TokenType::Minus
+ || type == TokenType::Typeof
+ || type == TokenType::Void
+ || type == TokenType::Delete;
+}
+
+bool Parser::match_secondary_expression(Vector<TokenType> forbidden) const
+{
+ auto type = m_parser_state.m_current_token.type();
+ if (forbidden.contains_slow(type))
+ return false;
+ return type == TokenType::Plus
+ || type == TokenType::PlusEquals
+ || type == TokenType::Minus
+ || type == TokenType::MinusEquals
+ || type == TokenType::Asterisk
+ || type == TokenType::AsteriskEquals
+ || type == TokenType::Slash
+ || type == TokenType::SlashEquals
+ || type == TokenType::Percent
+ || type == TokenType::PercentEquals
+ || type == TokenType::DoubleAsterisk
+ || type == TokenType::DoubleAsteriskEquals
+ || type == TokenType::Equals
+ || type == TokenType::EqualsEqualsEquals
+ || type == TokenType::ExclamationMarkEqualsEquals
+ || type == TokenType::EqualsEquals
+ || type == TokenType::ExclamationMarkEquals
+ || type == TokenType::GreaterThan
+ || type == TokenType::GreaterThanEquals
+ || type == TokenType::LessThan
+ || type == TokenType::LessThanEquals
+ || type == TokenType::ParenOpen
+ || type == TokenType::Period
+ || type == TokenType::BracketOpen
+ || type == TokenType::PlusPlus
+ || type == TokenType::MinusMinus
+ || type == TokenType::In
+ || type == TokenType::Instanceof
+ || type == TokenType::QuestionMark
+ || type == TokenType::Ampersand
+ || type == TokenType::AmpersandEquals
+ || type == TokenType::Pipe
+ || type == TokenType::PipeEquals
+ || type == TokenType::Caret
+ || type == TokenType::CaretEquals
+ || type == TokenType::ShiftLeft
+ || type == TokenType::ShiftLeftEquals
+ || type == TokenType::ShiftRight
+ || type == TokenType::ShiftRightEquals
+ || type == TokenType::UnsignedShiftRight
+ || type == TokenType::UnsignedShiftRightEquals
+ || type == TokenType::DoubleAmpersand
+ || type == TokenType::DoubleAmpersandEquals
+ || type == TokenType::DoublePipe
+ || type == TokenType::DoublePipeEquals
+ || type == TokenType::DoubleQuestionMark
+ || type == TokenType::DoubleQuestionMarkEquals;
+}
+
+bool Parser::match_statement() const
+{
+ auto type = m_parser_state.m_current_token.type();
+ return match_expression()
+ || type == TokenType::Return
+ || type == TokenType::Do
+ || type == TokenType::If
+ || type == TokenType::Throw
+ || type == TokenType::Try
+ || type == TokenType::While
+ || type == TokenType::With
+ || type == TokenType::For
+ || type == TokenType::CurlyOpen
+ || type == TokenType::Switch
+ || type == TokenType::Break
+ || type == TokenType::Continue
+ || type == TokenType::Var
+ || type == TokenType::Debugger
+ || type == TokenType::Semicolon;
+}
+
+bool Parser::match_declaration() const
+{
+ auto type = m_parser_state.m_current_token.type();
+ return type == TokenType::Function
+ || type == TokenType::Class
+ || type == TokenType::Const
+ || type == TokenType::Let;
+}
+
+bool Parser::match_variable_declaration() const
+{
+ auto type = m_parser_state.m_current_token.type();
+ return type == TokenType::Var
+ || type == TokenType::Let
+ || type == TokenType::Const;
+}
+
+bool Parser::match_identifier_name() const
+{
+ return m_parser_state.m_current_token.is_identifier_name();
+}
+
+bool Parser::match_property_key() const
+{
+ auto type = m_parser_state.m_current_token.type();
+ return match_identifier_name()
+ || type == TokenType::BracketOpen
+ || type == TokenType::StringLiteral
+ || type == TokenType::NumericLiteral
+ || type == TokenType::BigIntLiteral;
+}
+
+bool Parser::done() const
+{
+ return match(TokenType::Eof);
+}
+
+Token Parser::consume()
+{
+ auto old_token = m_parser_state.m_current_token;
+ m_parser_state.m_current_token = m_parser_state.m_lexer.next();
+ return old_token;
+}
+
+void Parser::consume_or_insert_semicolon()
+{
+ // Semicolon was found and will be consumed
+ if (match(TokenType::Semicolon)) {
+ consume();
+ return;
+ }
+ // Insert semicolon if...
+ // ...token is preceded by one or more newlines
+ if (m_parser_state.m_current_token.trivia_contains_line_terminator())
+ return;
+ // ...token is a closing curly brace
+ if (match(TokenType::CurlyClose))
+ return;
+ // ...token is eof
+ if (match(TokenType::Eof))
+ return;
+
+ // No rule for semicolon insertion applies -> syntax error
+ expected("Semicolon");
+}
+
+Token Parser::consume(TokenType expected_type)
+{
+ if (!match(expected_type)) {
+ expected(Token::name(expected_type));
+ }
+ return consume();
+}
+
+Token Parser::consume_and_validate_numeric_literal()
+{
+ auto is_unprefixed_octal_number = [](const StringView& value) {
+ return value.length() > 1 && value[0] == '0' && isdigit(value[1]);
+ };
+ auto literal_start = position();
+ auto token = consume(TokenType::NumericLiteral);
+ if (m_parser_state.m_strict_mode && is_unprefixed_octal_number(token.value()))
+ syntax_error("Unprefixed octal number not allowed in strict mode", literal_start);
+ if (match_identifier_name() && m_parser_state.m_current_token.trivia().is_empty())
+ syntax_error("Numeric literal must not be immediately followed by identifier");
+ return token;
+}
+
+void Parser::expected(const char* what)
+{
+ auto message = m_parser_state.m_current_token.message();
+ if (message.is_empty())
+ message = String::formatted("Unexpected token {}. Expected {}", m_parser_state.m_current_token.name(), what);
+ syntax_error(message);
+}
+
+Position Parser::position() const
+{
+ return {
+ m_parser_state.m_current_token.line_number(),
+ m_parser_state.m_current_token.line_column()
+ };
+}
+
+void Parser::syntax_error(const String& message, Optional<Position> position)
+{
+ if (!position.has_value())
+ position = this->position();
+ m_parser_state.m_errors.append({ message, position });
+}
+
+void Parser::save_state()
+{
+ m_saved_state.append(m_parser_state);
+}
+
+void Parser::load_state()
+{
+ ASSERT(!m_saved_state.is_empty());
+ m_parser_state = m_saved_state.take_last();
+}
+
+void Parser::discard_saved_state()
+{
+ m_saved_state.take_last();
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h
new file mode 100644
index 0000000000..6d6c4a57a6
--- /dev/null
+++ b/Userland/Libraries/LibJS/Parser.h
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashTable.h>
+#include <AK/NonnullRefPtr.h>
+#include <AK/StringBuilder.h>
+#include <LibJS/AST.h>
+#include <LibJS/Lexer.h>
+#include <LibJS/SourceRange.h>
+#include <stdio.h>
+
+namespace JS {
+
+enum class Associativity {
+ Left,
+ Right
+};
+
+struct FunctionNodeParseOptions {
+ enum {
+ CheckForFunctionAndName = 1 << 0,
+ AllowSuperPropertyLookup = 1 << 1,
+ AllowSuperConstructorCall = 1 << 2,
+ IsGetterFunction = 1 << 3,
+ IsSetterFunction = 1 << 4,
+ IsArrowFunction = 1 << 5,
+ };
+};
+
+class Parser {
+public:
+ explicit Parser(Lexer lexer);
+
+ NonnullRefPtr<Program> parse_program();
+
+ template<typename FunctionNodeType>
+ NonnullRefPtr<FunctionNodeType> parse_function_node(u8 parse_options = FunctionNodeParseOptions::CheckForFunctionAndName);
+ Vector<FunctionNode::Parameter> parse_function_parameters(int& function_length, u8 parse_options = 0);
+
+ NonnullRefPtr<Declaration> parse_declaration();
+ NonnullRefPtr<Statement> parse_statement();
+ NonnullRefPtr<BlockStatement> parse_block_statement();
+ NonnullRefPtr<BlockStatement> parse_block_statement(bool& is_strict);
+ NonnullRefPtr<ReturnStatement> parse_return_statement();
+ NonnullRefPtr<VariableDeclaration> parse_variable_declaration(bool for_loop_variable_declaration = false);
+ NonnullRefPtr<Statement> parse_for_statement();
+ NonnullRefPtr<Statement> parse_for_in_of_statement(NonnullRefPtr<ASTNode> lhs);
+ NonnullRefPtr<IfStatement> parse_if_statement();
+ NonnullRefPtr<ThrowStatement> parse_throw_statement();
+ NonnullRefPtr<TryStatement> parse_try_statement();
+ NonnullRefPtr<CatchClause> parse_catch_clause();
+ NonnullRefPtr<SwitchStatement> parse_switch_statement();
+ NonnullRefPtr<SwitchCase> parse_switch_case();
+ NonnullRefPtr<BreakStatement> parse_break_statement();
+ NonnullRefPtr<ContinueStatement> parse_continue_statement();
+ NonnullRefPtr<DoWhileStatement> parse_do_while_statement();
+ NonnullRefPtr<WhileStatement> parse_while_statement();
+ NonnullRefPtr<WithStatement> parse_with_statement();
+ NonnullRefPtr<DebuggerStatement> parse_debugger_statement();
+ NonnullRefPtr<ConditionalExpression> parse_conditional_expression(NonnullRefPtr<Expression> test);
+ NonnullRefPtr<Expression> parse_expression(int min_precedence, Associativity associate = Associativity::Right, Vector<TokenType> forbidden = {});
+ NonnullRefPtr<Expression> parse_primary_expression();
+ NonnullRefPtr<Expression> parse_unary_prefixed_expression();
+ NonnullRefPtr<RegExpLiteral> parse_regexp_literal();
+ NonnullRefPtr<ObjectExpression> parse_object_expression();
+ NonnullRefPtr<ArrayExpression> parse_array_expression();
+ NonnullRefPtr<StringLiteral> parse_string_literal(Token token, bool in_template_literal = false);
+ NonnullRefPtr<TemplateLiteral> parse_template_literal(bool is_tagged);
+ NonnullRefPtr<Expression> parse_secondary_expression(NonnullRefPtr<Expression>, int min_precedence, Associativity associate = Associativity::Right);
+ NonnullRefPtr<CallExpression> parse_call_expression(NonnullRefPtr<Expression>);
+ NonnullRefPtr<NewExpression> parse_new_expression();
+ NonnullRefPtr<ClassDeclaration> parse_class_declaration();
+ NonnullRefPtr<ClassExpression> parse_class_expression(bool expect_class_name);
+ NonnullRefPtr<Expression> parse_property_key();
+ NonnullRefPtr<AssignmentExpression> parse_assignment_expression(AssignmentOp, NonnullRefPtr<Expression> lhs, int min_precedence, Associativity);
+
+ RefPtr<FunctionExpression> try_parse_arrow_function_expression(bool expect_parens);
+ RefPtr<Statement> try_parse_labelled_statement();
+ RefPtr<MetaProperty> try_parse_new_target_expression();
+
+ struct Error {
+ String message;
+ Optional<Position> position;
+
+ String to_string() const
+ {
+ if (!position.has_value())
+ return message;
+ return String::formatted("{} (line: {}, column: {})", message, position.value().line, position.value().column);
+ }
+
+ String source_location_hint(const StringView& source, const char spacer = ' ', const char indicator = '^') const
+ {
+ if (!position.has_value())
+ return {};
+ // We need to modify the source to match what the lexer considers one line - normalizing
+ // line terminators to \n is easier than splitting using all different LT characters.
+ String source_string { source };
+ source_string.replace("\r\n", "\n");
+ source_string.replace("\r", "\n");
+ source_string.replace(LINE_SEPARATOR, "\n");
+ source_string.replace(PARAGRAPH_SEPARATOR, "\n");
+ StringBuilder builder;
+ builder.append(source_string.split_view('\n', true)[position.value().line - 1]);
+ builder.append('\n');
+ for (size_t i = 0; i < position.value().column - 1; ++i)
+ builder.append(spacer);
+ builder.append(indicator);
+ return builder.build();
+ }
+ };
+
+ bool has_errors() const { return m_parser_state.m_errors.size(); }
+ const Vector<Error>& errors() const { return m_parser_state.m_errors; }
+ void print_errors() const
+ {
+ for (auto& error : m_parser_state.m_errors) {
+ auto hint = error.source_location_hint(m_parser_state.m_lexer.source());
+ if (!hint.is_empty())
+ warnln("{}", hint);
+ warnln("SyntaxError: {}", error.to_string());
+ }
+ }
+
+private:
+ friend class ScopePusher;
+
+ Associativity operator_associativity(TokenType) const;
+ bool match_expression() const;
+ bool match_unary_prefixed_expression() const;
+ bool match_secondary_expression(Vector<TokenType> forbidden = {}) const;
+ bool match_statement() const;
+ bool match_declaration() const;
+ bool match_variable_declaration() const;
+ bool match_identifier_name() const;
+ bool match_property_key() const;
+ bool match(TokenType type) const;
+ bool done() const;
+ void expected(const char* what);
+ void syntax_error(const String& message, Optional<Position> = {});
+ Token consume();
+ Token consume(TokenType type);
+ Token consume_and_validate_numeric_literal();
+ void consume_or_insert_semicolon();
+ void save_state();
+ void load_state();
+ void discard_saved_state();
+ Position position() const;
+
+ struct RulePosition {
+ AK_MAKE_NONCOPYABLE(RulePosition);
+ AK_MAKE_NONMOVABLE(RulePosition);
+
+ public:
+ RulePosition(Parser& parser, Position position)
+ : m_parser(parser)
+ , m_position(position)
+ {
+ m_parser.m_rule_starts.append(position);
+ }
+
+ ~RulePosition()
+ {
+ auto last = m_parser.m_rule_starts.take_last();
+ ASSERT(last.line == m_position.line);
+ ASSERT(last.column == m_position.column);
+ }
+
+ const Position& position() const { return m_position; }
+
+ private:
+ Parser& m_parser;
+ Position m_position;
+ };
+
+ [[nodiscard]] RulePosition push_start() { return { *this, position() }; }
+
+ struct ParserState {
+ Lexer m_lexer;
+ Token m_current_token;
+ Vector<Error> m_errors;
+ Vector<NonnullRefPtrVector<VariableDeclaration>> m_var_scopes;
+ Vector<NonnullRefPtrVector<VariableDeclaration>> m_let_scopes;
+ Vector<NonnullRefPtrVector<FunctionDeclaration>> m_function_scopes;
+ HashTable<StringView> m_labels_in_scope;
+ bool m_strict_mode { false };
+ bool m_allow_super_property_lookup { false };
+ bool m_allow_super_constructor_call { false };
+ bool m_in_function_context { false };
+ bool m_in_arrow_function_context { false };
+ bool m_in_break_context { false };
+ bool m_in_continue_context { false };
+ bool m_string_legacy_octal_escape_sequence_in_scope { false };
+
+ explicit ParserState(Lexer);
+ };
+
+ Vector<Position> m_rule_starts;
+ ParserState m_parser_state;
+ Vector<ParserState> m_saved_state;
+};
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Accessor.h b/Userland/Libraries/LibJS/Runtime/Accessor.h
new file mode 100644
index 0000000000..ce2d9efb57
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Accessor.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Function.h>
+#include <LibJS/Runtime/VM.h>
+
+namespace JS {
+
+class Accessor final : public Cell {
+public:
+ static Accessor* create(VM& vm, Function* getter, Function* setter)
+ {
+ return vm.heap().allocate_without_global_object<Accessor>(getter, setter);
+ }
+
+ Accessor(Function* getter, Function* setter)
+ : m_getter(getter)
+ , m_setter(setter)
+ {
+ }
+
+ Function* getter() const { return m_getter; }
+ void set_getter(Function* getter) { m_getter = getter; }
+
+ Function* setter() const { return m_setter; }
+ void set_setter(Function* setter) { m_setter = setter; }
+
+ Value call_getter(Value this_value)
+ {
+ if (!m_getter)
+ return js_undefined();
+ return vm().call(*m_getter, this_value);
+ }
+
+ void call_setter(Value this_value, Value setter_value)
+ {
+ if (!m_setter)
+ return;
+ // FIXME: It might be nice if we had a way to communicate to our caller if an exception happened after this.
+ [[maybe_unused]] auto rc = vm().call(*m_setter, this_value, setter_value);
+ }
+
+ void visit_edges(Cell::Visitor& visitor) override
+ {
+ visitor.visit(m_getter);
+ visitor.visit(m_setter);
+ }
+
+private:
+ const char* class_name() const override { return "Accessor"; };
+
+ Function* m_getter { nullptr };
+ Function* m_setter { nullptr };
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Array.cpp b/Userland/Libraries/LibJS/Runtime/Array.cpp
new file mode 100644
index 0000000000..6e1f5f5300
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Array.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/ArrayPrototype.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+Array* Array::create(GlobalObject& global_object)
+{
+ return global_object.heap().allocate<Array>(global_object, *global_object.array_prototype());
+}
+
+Array::Array(Object& prototype)
+ : Object(prototype)
+{
+ auto& vm = this->vm();
+ define_native_property(vm.names.length, length_getter, length_setter, Attribute::Writable);
+}
+
+Array::~Array()
+{
+}
+
+Array* Array::typed_this(VM& vm, GlobalObject& global_object)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ if (!this_object->is_array()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAn, "Array");
+ return nullptr;
+ }
+ return static_cast<Array*>(this_object);
+}
+
+JS_DEFINE_NATIVE_GETTER(Array::length_getter)
+{
+ auto* array = typed_this(vm, global_object);
+ if (!array)
+ return {};
+ return Value(static_cast<i32>(array->indexed_properties().array_like_size()));
+}
+
+JS_DEFINE_NATIVE_SETTER(Array::length_setter)
+{
+ auto* array = typed_this(vm, global_object);
+ if (!array)
+ return;
+ auto length = value.to_number(global_object);
+ if (vm.exception())
+ return;
+ if (length.is_nan() || length.is_infinity() || length.as_double() < 0) {
+ vm.throw_exception<RangeError>(global_object, ErrorType::InvalidLength, "array");
+ return;
+ }
+ array->indexed_properties().set_array_like_size(length.as_double());
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Array.h b/Userland/Libraries/LibJS/Runtime/Array.h
new file mode 100644
index 0000000000..2ee4727c35
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Array.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class Array final : public Object {
+ JS_OBJECT(Array, Object);
+
+public:
+ static Array* create(GlobalObject&);
+
+ explicit Array(Object& prototype);
+ virtual ~Array() override;
+
+ static Array* typed_this(VM&, GlobalObject&);
+
+private:
+ virtual bool is_array() const override { return true; }
+
+ JS_DECLARE_NATIVE_GETTER(length_getter);
+ JS_DECLARE_NATIVE_SETTER(length_setter);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp
new file mode 100644
index 0000000000..007e61c0fa
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/ArrayBuffer.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+ArrayBuffer* ArrayBuffer::create(GlobalObject& global_object, size_t byte_size)
+{
+ return global_object.heap().allocate<ArrayBuffer>(global_object, byte_size, *global_object.array_buffer_prototype());
+}
+
+ArrayBuffer::ArrayBuffer(size_t byte_size, Object& prototype)
+ : Object(prototype)
+ , m_buffer(ByteBuffer::create_zeroed(byte_size))
+{
+}
+
+ArrayBuffer::~ArrayBuffer()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h
new file mode 100644
index 0000000000..d2724cd96e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class ArrayBuffer : public Object {
+ JS_OBJECT(ArrayBuffer, Object);
+
+public:
+ static ArrayBuffer* create(GlobalObject&, size_t);
+
+ ArrayBuffer(size_t, Object& prototype);
+ virtual ~ArrayBuffer() override;
+
+ size_t byte_length() const { return m_buffer.size(); }
+ ByteBuffer& buffer() { return m_buffer; }
+ const ByteBuffer& buffer() const { return m_buffer; }
+
+private:
+ ByteBuffer m_buffer;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.cpp
new file mode 100644
index 0000000000..b869dcd5f3
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/ArrayBuffer.h>
+#include <LibJS/Runtime/ArrayBufferConstructor.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/TypedArray.h>
+
+namespace JS {
+
+ArrayBufferConstructor::ArrayBufferConstructor(GlobalObject& global_object)
+ : NativeFunction(vm().names.ArrayBuffer, *global_object.function_prototype())
+{
+}
+
+void ArrayBufferConstructor::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_property(vm.names.prototype, global_object.array_buffer_prototype(), 0);
+ define_property(vm.names.length, Value(1), Attribute::Configurable);
+ define_native_function(vm.names.isView, is_view, 1, attr);
+}
+
+ArrayBufferConstructor::~ArrayBufferConstructor()
+{
+}
+
+Value ArrayBufferConstructor::call()
+{
+ auto& vm = this->vm();
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ConstructorWithoutNew, vm.names.ArrayBuffer);
+ return {};
+}
+
+Value ArrayBufferConstructor::construct(Function&)
+{
+ auto& vm = this->vm();
+ auto byte_length = vm.argument(0).to_index(global_object());
+ if (vm.exception()) {
+ // Re-throw more specific RangeError
+ vm.clear_exception();
+ vm.throw_exception<RangeError>(global_object(), ErrorType::InvalidLength, "array buffer");
+ return {};
+ }
+ return ArrayBuffer::create(global_object(), byte_length);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayBufferConstructor::is_view)
+{
+ auto arg = vm.argument(0);
+ if (!arg.is_object())
+ return Value(false);
+ if (arg.as_object().is_typed_array())
+ return Value(true);
+ // FIXME: Check for DataView as well
+ return Value(false);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.h b/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.h
new file mode 100644
index 0000000000..455fa7e7e2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class ArrayBufferConstructor final : public NativeFunction {
+ JS_OBJECT(ArrayBufferConstructor, NativeFunction);
+
+public:
+ explicit ArrayBufferConstructor(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~ArrayBufferConstructor() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+
+ JS_DECLARE_NATIVE_FUNCTION(is_view);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp
new file mode 100644
index 0000000000..846acbd562
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibJS/Runtime/ArrayBuffer.h>
+#include <LibJS/Runtime/ArrayBufferPrototype.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+ArrayBufferPrototype::ArrayBufferPrototype(GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void ArrayBufferPrototype::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_function(vm.names.slice, slice, 2, attr);
+ // FIXME: This should be an accessor property
+ define_native_property(vm.names.byteLength, byte_length_getter, {}, Attribute::Configurable);
+
+ define_property(vm.well_known_symbol_to_string_tag(), js_string(vm.heap(), "ArrayBuffer"), Attribute::Configurable);
+}
+
+ArrayBufferPrototype::~ArrayBufferPrototype()
+{
+}
+
+static ArrayBuffer* array_buffer_object_from(VM& vm, GlobalObject& global_object)
+{
+ // ArrayBuffer.prototype.* deliberately don't coerce |this| value to object.
+ auto this_value = vm.this_value(global_object);
+ if (!this_value.is_object())
+ return nullptr;
+ auto& this_object = this_value.as_object();
+ if (!is<ArrayBuffer>(this_object)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAn, "ArrayBuffer");
+ return nullptr;
+ }
+ return static_cast<ArrayBuffer*>(&this_object);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::slice)
+{
+ auto array_buffer_object = array_buffer_object_from(vm, global_object);
+ if (!array_buffer_object)
+ return {};
+ TODO();
+}
+
+JS_DEFINE_NATIVE_GETTER(ArrayBufferPrototype::byte_length_getter)
+{
+ auto array_buffer_object = array_buffer_object_from(vm, global_object);
+ if (!array_buffer_object)
+ return {};
+ // FIXME: Check for shared buffer
+ // FIXME: Check for detached buffer
+ return Value((double)array_buffer_object->byte_length());
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.h b/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.h
new file mode 100644
index 0000000000..96ed852830
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class ArrayBufferPrototype final : public Object {
+ JS_OBJECT(ArrayBufferPrototype, Object);
+
+public:
+ explicit ArrayBufferPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~ArrayBufferPrototype() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(slice);
+ JS_DECLARE_NATIVE_GETTER(byte_length_getter);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp
new file mode 100644
index 0000000000..153e2a4fe2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/ArrayConstructor.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/IteratorOperations.h>
+#include <LibJS/Runtime/Shape.h>
+
+namespace JS {
+
+ArrayConstructor::ArrayConstructor(GlobalObject& global_object)
+ : NativeFunction(vm().names.Array, *global_object.function_prototype())
+{
+}
+
+ArrayConstructor::~ArrayConstructor()
+{
+}
+
+void ArrayConstructor::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+
+ define_property(vm.names.prototype, global_object.array_prototype(), 0);
+ define_property(vm.names.length, Value(1), Attribute::Configurable);
+
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_function(vm.names.from, from, 1, attr);
+ define_native_function(vm.names.isArray, is_array, 1, attr);
+ define_native_function(vm.names.of, of, 0, attr);
+}
+
+Value ArrayConstructor::call()
+{
+ if (vm().argument_count() <= 0)
+ return Array::create(global_object());
+
+ if (vm().argument_count() == 1 && vm().argument(0).is_number()) {
+ auto array_length_value = vm().argument(0);
+ if (!array_length_value.is_integer() || array_length_value.as_i32() < 0) {
+ vm().throw_exception<RangeError>(global_object(), ErrorType::InvalidLength, "array");
+ return {};
+ }
+ auto* array = Array::create(global_object());
+ array->indexed_properties().set_array_like_size(array_length_value.as_i32());
+ return array;
+ }
+
+ auto* array = Array::create(global_object());
+ for (size_t i = 0; i < vm().argument_count(); ++i)
+ array->indexed_properties().append(vm().argument(i));
+ return array;
+}
+
+Value ArrayConstructor::construct(Function&)
+{
+ return call();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from)
+{
+ auto value = vm.argument(0);
+ auto object = value.to_object(global_object);
+ if (!object)
+ return {};
+
+ auto* array = Array::create(global_object);
+
+ // Array.from() lets you create Arrays from:
+ if (auto size = object->indexed_properties().array_like_size()) {
+ // * array-like objects (objects with a length property and indexed elements)
+ MarkedValueList elements(vm.heap());
+ elements.ensure_capacity(size);
+ for (size_t i = 0; i < size; ++i) {
+ elements.append(object->get(i));
+ if (vm.exception())
+ return {};
+ }
+ array->set_indexed_property_elements(move(elements));
+ } else {
+ // * iterable objects
+ get_iterator_values(global_object, value, [&](Value element) {
+ if (vm.exception())
+ return IterationDecision::Break;
+ array->indexed_properties().append(element);
+ return IterationDecision::Continue;
+ });
+ if (vm.exception())
+ return {};
+ }
+
+ // FIXME: if interpreter.argument_count() >= 2: mapFn
+ // FIXME: if interpreter.argument_count() >= 3: thisArg
+
+ return array;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::is_array)
+{
+ auto value = vm.argument(0);
+ return Value(value.is_array());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::of)
+{
+ auto* array = Array::create(global_object);
+ for (size_t i = 0; i < vm.argument_count(); ++i)
+ array->indexed_properties().append(vm.argument(i));
+ return array;
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayConstructor.h b/Userland/Libraries/LibJS/Runtime/ArrayConstructor.h
new file mode 100644
index 0000000000..5c85dfc04b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ArrayConstructor.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class ArrayConstructor final : public NativeFunction {
+ JS_OBJECT(ArrayConstructor, NativeFunction);
+
+public:
+ explicit ArrayConstructor(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~ArrayConstructor() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+
+ JS_DECLARE_NATIVE_FUNCTION(from);
+ JS_DECLARE_NATIVE_FUNCTION(is_array);
+ JS_DECLARE_NATIVE_FUNCTION(of);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayIterator.cpp b/Userland/Libraries/LibJS/Runtime/ArrayIterator.cpp
new file mode 100644
index 0000000000..338ec0a0bc
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ArrayIterator.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/ArrayIterator.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+ArrayIterator* ArrayIterator::create(GlobalObject& global_object, Value array, Object::PropertyKind iteration_kind)
+{
+ return global_object.heap().allocate<ArrayIterator>(global_object, *global_object.array_iterator_prototype(), array, iteration_kind);
+}
+
+ArrayIterator::ArrayIterator(Object& prototype, Value array, Object::PropertyKind iteration_kind)
+ : Object(prototype)
+ , m_array(array)
+ , m_iteration_kind(iteration_kind)
+{
+}
+
+ArrayIterator::~ArrayIterator()
+{
+}
+
+void ArrayIterator::visit_edges(Cell::Visitor& visitor)
+{
+ Base::visit_edges(visitor);
+ visitor.visit(m_array);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayIterator.h b/Userland/Libraries/LibJS/Runtime/ArrayIterator.h
new file mode 100644
index 0000000000..30ec25b70f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ArrayIterator.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class ArrayIterator final : public Object {
+ JS_OBJECT(ArrayIterator, Object);
+
+public:
+ static ArrayIterator* create(GlobalObject&, Value array, Object::PropertyKind iteration_kind);
+
+ explicit ArrayIterator(Object& prototype, Value array, Object::PropertyKind iteration_kind);
+ virtual ~ArrayIterator() override;
+
+ Value array() const { return m_array; }
+ Object::PropertyKind iteration_kind() const { return m_iteration_kind; }
+ size_t index() const { return m_index; }
+
+private:
+ friend class ArrayIteratorPrototype;
+
+ virtual void visit_edges(Cell::Visitor&) override;
+
+ Value m_array;
+ Object::PropertyKind m_iteration_kind;
+ size_t m_index { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp
new file mode 100644
index 0000000000..c3cbd179b0
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/ArrayIterator.h>
+#include <LibJS/Runtime/ArrayIteratorPrototype.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/IteratorOperations.h>
+
+namespace JS {
+
+ArrayIteratorPrototype::ArrayIteratorPrototype(GlobalObject& global_object)
+ : Object(*global_object.iterator_prototype())
+{
+}
+
+void ArrayIteratorPrototype::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+
+ define_native_function(vm.names.next, next, 0, Attribute::Configurable | Attribute::Writable);
+ define_property(global_object.vm().well_known_symbol_to_string_tag(), js_string(global_object.heap(), "Array Iterator"), Attribute::Configurable);
+}
+
+ArrayIteratorPrototype::~ArrayIteratorPrototype()
+{
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayIteratorPrototype::next)
+{
+ auto this_value = vm.this_value(global_object);
+ if (!this_value.is_object() || !is<ArrayIterator>(this_value.as_object())) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAn, "Array Iterator");
+ return {};
+ }
+ auto& this_object = this_value.as_object();
+ auto& iterator = static_cast<ArrayIterator&>(this_object);
+ auto target_array = iterator.array();
+ if (target_array.is_undefined())
+ return create_iterator_result_object(global_object, js_undefined(), true);
+ ASSERT(target_array.is_object());
+ auto& array = target_array.as_object();
+
+ auto index = iterator.index();
+ auto iteration_kind = iterator.iteration_kind();
+ // FIXME: Typed array check
+ auto length = array.indexed_properties().array_like_size();
+
+ if (index >= length) {
+ iterator.m_array = js_undefined();
+ return create_iterator_result_object(global_object, js_undefined(), true);
+ }
+
+ iterator.m_index++;
+ if (iteration_kind == Object::PropertyKind::Key)
+ return create_iterator_result_object(global_object, Value(static_cast<i32>(index)), false);
+
+ auto value = array.get(index);
+ if (vm.exception())
+ return {};
+ if (iteration_kind == Object::PropertyKind::Value)
+ return create_iterator_result_object(global_object, value, false);
+
+ auto* entry_array = Array::create(global_object);
+ entry_array->define_property(0, Value(static_cast<i32>(index)));
+ entry_array->define_property(1, value);
+ return create_iterator_result_object(global_object, entry_array, false);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.h b/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.h
new file mode 100644
index 0000000000..0e79a55ca5
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class ArrayIteratorPrototype final : public Object {
+ JS_OBJECT(ArrayIteratorPrototype, Object)
+
+public:
+ ArrayIteratorPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~ArrayIteratorPrototype() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(next);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp
new file mode 100644
index 0000000000..8b155e9a95
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp
@@ -0,0 +1,1037 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * Copyright (c) 2020, Marcin Gasperowicz <xnooga@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <AK/HashTable.h>
+#include <AK/ScopeGuard.h>
+#include <AK/StringBuilder.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/ArrayIterator.h>
+#include <LibJS/Runtime/ArrayPrototype.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/Function.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/ObjectPrototype.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+static HashTable<Object*> s_array_join_seen_objects;
+
+ArrayPrototype::ArrayPrototype(GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void ArrayPrototype::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+
+ define_native_function(vm.names.filter, filter, 1, attr);
+ define_native_function(vm.names.forEach, for_each, 1, attr);
+ define_native_function(vm.names.map, map, 1, attr);
+ define_native_function(vm.names.pop, pop, 0, attr);
+ define_native_function(vm.names.push, push, 1, attr);
+ define_native_function(vm.names.shift, shift, 0, attr);
+ define_native_function(vm.names.toString, to_string, 0, attr);
+ define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr);
+ define_native_function(vm.names.unshift, unshift, 1, attr);
+ define_native_function(vm.names.join, join, 1, attr);
+ define_native_function(vm.names.concat, concat, 1, attr);
+ define_native_function(vm.names.slice, slice, 2, attr);
+ define_native_function(vm.names.indexOf, index_of, 1, attr);
+ define_native_function(vm.names.reduce, reduce, 1, attr);
+ define_native_function(vm.names.reduceRight, reduce_right, 1, attr);
+ define_native_function(vm.names.reverse, reverse, 0, attr);
+ define_native_function(vm.names.sort, sort, 1, attr);
+ define_native_function(vm.names.lastIndexOf, last_index_of, 1, attr);
+ define_native_function(vm.names.includes, includes, 1, attr);
+ define_native_function(vm.names.find, find, 1, attr);
+ define_native_function(vm.names.findIndex, find_index, 1, attr);
+ define_native_function(vm.names.some, some, 1, attr);
+ define_native_function(vm.names.every, every, 1, attr);
+ define_native_function(vm.names.splice, splice, 2, attr);
+ define_native_function(vm.names.fill, fill, 1, attr);
+ define_native_function(vm.names.values, values, 0, attr);
+ define_property(vm.names.length, Value(0), Attribute::Configurable);
+
+ // Use define_property here instead of define_native_function so that
+ // Object.is(Array.prototype[Symbol.iterator], Array.prototype.values)
+ // evaluates to true
+ define_property(vm.well_known_symbol_iterator(), get(vm.names.values), attr);
+}
+
+ArrayPrototype::~ArrayPrototype()
+{
+}
+
+static Function* callback_from_args(GlobalObject& global_object, const String& name)
+{
+ auto& vm = global_object.vm();
+ if (vm.argument_count() < 1) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ArrayPrototypeOneArg, name);
+ return nullptr;
+ }
+ auto callback = vm.argument(0);
+ if (!callback.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback.to_string_without_side_effects());
+ return nullptr;
+ }
+ return &callback.as_function();
+}
+
+static void for_each_item(VM& vm, GlobalObject& global_object, const String& name, AK::Function<IterationDecision(size_t index, Value value, Value callback_result)> callback, bool skip_empty = true)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return;
+
+ auto initial_length = length_of_array_like(global_object, *this_object);
+ if (vm.exception())
+ return;
+
+ auto* callback_function = callback_from_args(global_object, name);
+ if (!callback_function)
+ return;
+
+ auto this_value = vm.argument(1);
+
+ for (size_t i = 0; i < initial_length; ++i) {
+ auto value = this_object->get(i);
+ if (vm.exception())
+ return;
+ if (value.is_empty()) {
+ if (skip_empty)
+ continue;
+ value = js_undefined();
+ }
+
+ auto callback_result = vm.call(*callback_function, this_value, value, Value((i32)i), this_object);
+ if (vm.exception())
+ return;
+
+ if (callback(i, value, callback_result) == IterationDecision::Break)
+ break;
+ }
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::filter)
+{
+ auto* new_array = Array::create(global_object);
+ for_each_item(vm, global_object, "filter", [&](auto, auto value, auto callback_result) {
+ if (callback_result.to_boolean())
+ new_array->indexed_properties().append(value);
+ return IterationDecision::Continue;
+ });
+ return Value(new_array);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::for_each)
+{
+ for_each_item(vm, global_object, "forEach", [](auto, auto, auto) {
+ return IterationDecision::Continue;
+ });
+ return js_undefined();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::map)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ auto initial_length = length_of_array_like(global_object, *this_object);
+ if (vm.exception())
+ return {};
+ auto* new_array = Array::create(global_object);
+ new_array->indexed_properties().set_array_like_size(initial_length);
+ for_each_item(vm, global_object, "map", [&](auto index, auto, auto callback_result) {
+ if (vm.exception())
+ return IterationDecision::Break;
+ new_array->define_property(index, callback_result);
+ return IterationDecision::Continue;
+ });
+ return Value(new_array);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::push)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ if (this_object->is_array()) {
+ auto* array = static_cast<Array*>(this_object);
+ for (size_t i = 0; i < vm.argument_count(); ++i)
+ array->indexed_properties().append(vm.argument(i));
+ return Value(static_cast<i32>(array->indexed_properties().array_like_size()));
+ }
+ auto length = length_of_array_like(global_object, *this_object);
+ if (vm.exception())
+ return {};
+ auto argument_count = vm.argument_count();
+ auto new_length = length + argument_count;
+ if (new_length > MAX_ARRAY_LIKE_INDEX) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ArrayMaxSize);
+ return {};
+ }
+ for (size_t i = 0; i < argument_count; ++i) {
+ this_object->put(length + i, vm.argument(i));
+ if (vm.exception())
+ return {};
+ }
+ auto new_length_value = Value((i32)new_length);
+ this_object->put(vm.names.length, new_length_value);
+ if (vm.exception())
+ return {};
+ return new_length_value;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::unshift)
+{
+ auto* array = Array::typed_this(vm, global_object);
+ if (!array)
+ return {};
+ for (size_t i = 0; i < vm.argument_count(); ++i)
+ array->indexed_properties().insert(i, vm.argument(i));
+ return Value(static_cast<i32>(array->indexed_properties().array_like_size()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::pop)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ if (this_object->is_array()) {
+ auto* array = static_cast<Array*>(this_object);
+ if (array->indexed_properties().is_empty())
+ return js_undefined();
+ return array->indexed_properties().take_last(array).value.value_or(js_undefined());
+ }
+ auto length = length_of_array_like(global_object, *this_object);
+ if (vm.exception())
+ return {};
+ if (length == 0) {
+ this_object->put(vm.names.length, Value(0));
+ return js_undefined();
+ }
+ auto index = length - 1;
+ auto element = this_object->get(index).value_or(js_undefined());
+ if (vm.exception())
+ return {};
+ this_object->delete_property(index);
+ if (vm.exception())
+ return {};
+ this_object->put(vm.names.length, Value((i32)index));
+ if (vm.exception())
+ return {};
+ return element;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::shift)
+{
+ auto* array = Array::typed_this(vm, global_object);
+ if (!array)
+ return {};
+ if (array->indexed_properties().is_empty())
+ return js_undefined();
+ auto result = array->indexed_properties().take_first(array);
+ if (vm.exception())
+ return {};
+ return result.value.value_or(js_undefined());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_string)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ auto join_function = this_object->get(vm.names.join);
+ if (vm.exception())
+ return {};
+ if (!join_function.is_function())
+ return ObjectPrototype::to_string(vm, global_object);
+ return vm.call(join_function.as_function(), this_object);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_locale_string)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+
+ if (s_array_join_seen_objects.contains(this_object))
+ return js_string(vm, "");
+ s_array_join_seen_objects.set(this_object);
+ ArmedScopeGuard unsee_object_guard = [&] {
+ s_array_join_seen_objects.remove(this_object);
+ };
+
+ auto length = length_of_array_like(global_object, *this_object);
+ if (vm.exception())
+ return {};
+
+ String separator = ","; // NOTE: This is implementation-specific.
+ StringBuilder builder;
+ for (size_t i = 0; i < length; ++i) {
+ if (i > 0)
+ builder.append(separator);
+ auto value = this_object->get(i).value_or(js_undefined());
+ if (vm.exception())
+ return {};
+ if (value.is_nullish())
+ continue;
+ auto* value_object = value.to_object(global_object);
+ if (!value_object)
+ return {};
+ auto locale_string_result = value_object->invoke("toLocaleString");
+ if (vm.exception())
+ return {};
+ auto string = locale_string_result.to_string(global_object);
+ if (vm.exception())
+ return {};
+ builder.append(string);
+ }
+ return js_string(vm, builder.to_string());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::join)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+
+ // This is not part of the spec, but all major engines do some kind of circular reference checks.
+ // FWIW: engine262, a "100% spec compliant" ECMA-262 impl, aborts with "too much recursion".
+ // Same applies to Array.prototype.toLocaleString().
+ if (s_array_join_seen_objects.contains(this_object))
+ return js_string(vm, "");
+ s_array_join_seen_objects.set(this_object);
+ ArmedScopeGuard unsee_object_guard = [&] {
+ s_array_join_seen_objects.remove(this_object);
+ };
+
+ auto length = length_of_array_like(global_object, *this_object);
+ if (vm.exception())
+ return {};
+ String separator = ",";
+ if (!vm.argument(0).is_undefined()) {
+ separator = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+ }
+ StringBuilder builder;
+ for (size_t i = 0; i < length; ++i) {
+ if (i > 0)
+ builder.append(separator);
+ auto value = this_object->get(i).value_or(js_undefined());
+ if (vm.exception())
+ return {};
+ if (value.is_nullish())
+ continue;
+ auto string = value.to_string(global_object);
+ if (vm.exception())
+ return {};
+ builder.append(string);
+ }
+
+ return js_string(vm, builder.to_string());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::concat)
+{
+ auto* array = Array::typed_this(vm, global_object);
+ if (!array)
+ return {};
+
+ auto* new_array = Array::create(global_object);
+ new_array->indexed_properties().append_all(array, array->indexed_properties());
+ if (vm.exception())
+ return {};
+
+ for (size_t i = 0; i < vm.argument_count(); ++i) {
+ auto argument = vm.argument(i);
+ if (argument.is_array()) {
+ auto& argument_object = argument.as_object();
+ new_array->indexed_properties().append_all(&argument_object, argument_object.indexed_properties());
+ if (vm.exception())
+ return {};
+ } else {
+ new_array->indexed_properties().append(argument);
+ }
+ }
+
+ return Value(new_array);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::slice)
+{
+ auto* array = Array::typed_this(vm, global_object);
+ if (!array)
+ return {};
+
+ auto* new_array = Array::create(global_object);
+ if (vm.argument_count() == 0) {
+ new_array->indexed_properties().append_all(array, array->indexed_properties());
+ if (vm.exception())
+ return {};
+ return new_array;
+ }
+
+ ssize_t array_size = static_cast<ssize_t>(array->indexed_properties().array_like_size());
+ auto start_slice = vm.argument(0).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ auto end_slice = array_size;
+
+ if (start_slice > array_size)
+ return new_array;
+
+ if (start_slice < 0)
+ start_slice = end_slice + start_slice;
+
+ if (vm.argument_count() >= 2) {
+ end_slice = vm.argument(1).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ if (end_slice < 0)
+ end_slice = array_size + end_slice;
+ else if (end_slice > array_size)
+ end_slice = array_size;
+ }
+
+ for (ssize_t i = start_slice; i < end_slice; ++i) {
+ new_array->indexed_properties().append(array->get(i));
+ if (vm.exception())
+ return {};
+ }
+
+ return new_array;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::index_of)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ i32 length = length_of_array_like(global_object, *this_object);
+ if (vm.exception())
+ return {};
+ if (length == 0)
+ return Value(-1);
+ i32 from_index = 0;
+ if (vm.argument_count() >= 2) {
+ from_index = vm.argument(1).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ if (from_index >= length)
+ return Value(-1);
+ if (from_index < 0)
+ from_index = max(length + from_index, 0);
+ }
+ auto search_element = vm.argument(0);
+ for (i32 i = from_index; i < length; ++i) {
+ auto element = this_object->get(i);
+ if (vm.exception())
+ return {};
+ if (strict_eq(element, search_element))
+ return Value(i);
+ }
+ return Value(-1);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::reduce)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+
+ auto initial_length = length_of_array_like(global_object, *this_object);
+ if (vm.exception())
+ return {};
+
+ auto* callback_function = callback_from_args(global_object, "reduce");
+ if (!callback_function)
+ return {};
+
+ size_t start = 0;
+
+ auto accumulator = js_undefined();
+ if (vm.argument_count() > 1) {
+ accumulator = vm.argument(1);
+ } else {
+ bool start_found = false;
+ while (!start_found && start < initial_length) {
+ auto value = this_object->get(start);
+ if (vm.exception())
+ return {};
+ start_found = !value.is_empty();
+ if (start_found)
+ accumulator = value;
+ start += 1;
+ }
+ if (!start_found) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ReduceNoInitial);
+ return {};
+ }
+ }
+
+ auto this_value = js_undefined();
+
+ for (size_t i = start; i < initial_length; ++i) {
+ auto value = this_object->get(i);
+ if (vm.exception())
+ return {};
+ if (value.is_empty())
+ continue;
+
+ accumulator = vm.call(*callback_function, this_value, accumulator, value, Value((i32)i), this_object);
+ if (vm.exception())
+ return {};
+ }
+
+ return accumulator;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::reduce_right)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+
+ auto initial_length = length_of_array_like(global_object, *this_object);
+ if (vm.exception())
+ return {};
+
+ auto* callback_function = callback_from_args(global_object, "reduceRight");
+ if (!callback_function)
+ return {};
+
+ int start = initial_length - 1;
+
+ auto accumulator = js_undefined();
+ if (vm.argument_count() > 1) {
+ accumulator = vm.argument(1);
+ } else {
+ bool start_found = false;
+ while (!start_found && start >= 0) {
+ auto value = this_object->get(start);
+ if (vm.exception())
+ return {};
+ start_found = !value.is_empty();
+ if (start_found)
+ accumulator = value;
+ start -= 1;
+ }
+ if (!start_found) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ReduceNoInitial);
+ return {};
+ }
+ }
+
+ auto this_value = js_undefined();
+
+ for (int i = start; i >= 0; --i) {
+ auto value = this_object->get(i);
+ if (vm.exception())
+ return {};
+ if (value.is_empty())
+ continue;
+
+ accumulator = vm.call(*callback_function, this_value, accumulator, value, Value((i32)i), this_object);
+ if (vm.exception())
+ return {};
+ }
+
+ return accumulator;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::reverse)
+{
+ auto* array = Array::typed_this(vm, global_object);
+ if (!array)
+ return {};
+
+ if (array->indexed_properties().is_empty())
+ return array;
+
+ MarkedValueList array_reverse(vm.heap());
+ auto size = array->indexed_properties().array_like_size();
+ array_reverse.ensure_capacity(size);
+
+ for (ssize_t i = size - 1; i >= 0; --i) {
+ array_reverse.append(array->get(i));
+ if (vm.exception())
+ return {};
+ }
+
+ array->set_indexed_property_elements(move(array_reverse));
+
+ return array;
+}
+
+static void array_merge_sort(VM& vm, GlobalObject& global_object, Function* compare_func, MarkedValueList& arr_to_sort)
+{
+ // FIXME: it would probably be better to switch to insertion sort for small arrays for
+ // better performance
+ if (arr_to_sort.size() <= 1)
+ return;
+
+ MarkedValueList left(vm.heap());
+ MarkedValueList right(vm.heap());
+
+ left.ensure_capacity(arr_to_sort.size() / 2);
+ right.ensure_capacity(arr_to_sort.size() / 2 + (arr_to_sort.size() & 1));
+
+ for (size_t i = 0; i < arr_to_sort.size(); ++i) {
+ if (i < arr_to_sort.size() / 2) {
+ left.append(arr_to_sort[i]);
+ } else {
+ right.append(arr_to_sort[i]);
+ }
+ }
+
+ array_merge_sort(vm, global_object, compare_func, left);
+ if (vm.exception())
+ return;
+ array_merge_sort(vm, global_object, compare_func, right);
+ if (vm.exception())
+ return;
+
+ arr_to_sort.clear();
+
+ size_t left_index = 0, right_index = 0;
+
+ while (left_index < left.size() && right_index < right.size()) {
+ auto x = left[left_index];
+ auto y = right[right_index];
+
+ double comparison_result;
+
+ if (x.is_undefined() && y.is_undefined()) {
+ comparison_result = 0;
+ } else if (x.is_undefined()) {
+ comparison_result = 1;
+ } else if (y.is_undefined()) {
+ comparison_result = -1;
+ } else if (compare_func) {
+ auto call_result = vm.call(*compare_func, js_undefined(), left[left_index], right[right_index]);
+ if (vm.exception())
+ return;
+
+ if (call_result.is_nan()) {
+ comparison_result = 0;
+ } else {
+ comparison_result = call_result.to_double(global_object);
+ if (vm.exception())
+ return;
+ }
+ } else {
+ // FIXME: It would probably be much better to be smarter about this and implement
+ // the Abstract Relational Comparison in line once iterating over code points, rather
+ // than calling it twice after creating two primitive strings.
+
+ auto x_string = x.to_primitive_string(global_object);
+ if (vm.exception())
+ return;
+ auto y_string = y.to_primitive_string(global_object);
+ if (vm.exception())
+ return;
+
+ auto x_string_value = Value(x_string);
+ auto y_string_value = Value(y_string);
+
+ // Because they are called with primitive strings, these abstract_relation calls
+ // should never result in a VM exception.
+ auto x_lt_y_relation = abstract_relation(global_object, true, x_string_value, y_string_value);
+ ASSERT(x_lt_y_relation != TriState::Unknown);
+ auto y_lt_x_relation = abstract_relation(global_object, true, y_string_value, x_string_value);
+ ASSERT(y_lt_x_relation != TriState::Unknown);
+
+ if (x_lt_y_relation == TriState::True) {
+ comparison_result = -1;
+ } else if (y_lt_x_relation == TriState::True) {
+ comparison_result = 1;
+ } else {
+ comparison_result = 0;
+ }
+ }
+
+ if (comparison_result <= 0) {
+ arr_to_sort.append(left[left_index]);
+ left_index++;
+ } else {
+ arr_to_sort.append(right[right_index]);
+ right_index++;
+ }
+ }
+
+ while (left_index < left.size()) {
+ arr_to_sort.append(left[left_index]);
+ left_index++;
+ }
+
+ while (right_index < right.size()) {
+ arr_to_sort.append(right[right_index]);
+ right_index++;
+ }
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::sort)
+{
+ auto* array = vm.this_value(global_object).to_object(global_object);
+ if (vm.exception())
+ return {};
+
+ auto callback = vm.argument(0);
+ if (!callback.is_undefined() && !callback.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback.to_string_without_side_effects());
+ return {};
+ }
+
+ auto original_length = length_of_array_like(global_object, *array);
+ if (vm.exception())
+ return {};
+
+ MarkedValueList values_to_sort(vm.heap());
+
+ for (size_t i = 0; i < original_length; ++i) {
+ auto element_val = array->get(i);
+ if (vm.exception())
+ return {};
+
+ if (!element_val.is_empty())
+ values_to_sort.append(element_val);
+ }
+
+ // Perform sorting by merge sort. This isn't as efficient compared to quick sort, but
+ // quicksort can't be used in all cases because the spec requires Array.prototype.sort()
+ // to be stable. FIXME: when initially scanning through the array, maintain a flag
+ // for if an unstable sort would be indistinguishable from a stable sort (such as just
+ // just strings or numbers), and in that case use quick sort instead for better performance.
+ array_merge_sort(vm, global_object, callback.is_undefined() ? nullptr : &callback.as_function(), values_to_sort);
+ if (vm.exception())
+ return {};
+
+ for (size_t i = 0; i < values_to_sort.size(); ++i) {
+ array->put(i, values_to_sort[i]);
+ if (vm.exception())
+ return {};
+ }
+
+ // The empty parts of the array are always sorted to the end, regardless of the
+ // compare function. FIXME: For performance, a similar process could be used
+ // for undefined, which are sorted to right before the empty values.
+ for (size_t i = values_to_sort.size(); i < original_length; ++i) {
+ array->delete_property(i);
+ if (vm.exception())
+ return {};
+ }
+
+ return array;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::last_index_of)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ i32 length = length_of_array_like(global_object, *this_object);
+ if (vm.exception())
+ return {};
+ if (length == 0)
+ return Value(-1);
+ i32 from_index = length - 1;
+ if (vm.argument_count() >= 2) {
+ from_index = vm.argument(1).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ if (from_index >= 0)
+ from_index = min(from_index, length - 1);
+ else
+ from_index = length + from_index;
+ }
+ auto search_element = vm.argument(0);
+ for (i32 i = from_index; i >= 0; --i) {
+ auto element = this_object->get(i);
+ if (vm.exception())
+ return {};
+ if (strict_eq(element, search_element))
+ return Value(i);
+ }
+ return Value(-1);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::includes)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ i32 length = length_of_array_like(global_object, *this_object);
+ if (vm.exception())
+ return {};
+ if (length == 0)
+ return Value(false);
+ i32 from_index = 0;
+ if (vm.argument_count() >= 2) {
+ from_index = vm.argument(1).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ if (from_index >= length)
+ return Value(false);
+ if (from_index < 0)
+ from_index = max(length + from_index, 0);
+ }
+ auto value_to_find = vm.argument(0);
+ for (i32 i = from_index; i < length; ++i) {
+ auto element = this_object->get(i).value_or(js_undefined());
+ if (vm.exception())
+ return {};
+ if (same_value_zero(element, value_to_find))
+ return Value(true);
+ }
+ return Value(false);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find)
+{
+ auto result = js_undefined();
+ for_each_item(
+ vm, global_object, "find", [&](auto, auto value, auto callback_result) {
+ if (callback_result.to_boolean()) {
+ result = value;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ },
+ false);
+ return result;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find_index)
+{
+ auto result_index = -1;
+ for_each_item(
+ vm, global_object, "findIndex", [&](auto index, auto, auto callback_result) {
+ if (callback_result.to_boolean()) {
+ result_index = index;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ },
+ false);
+ return Value(result_index);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::some)
+{
+ auto result = false;
+ for_each_item(vm, global_object, "some", [&](auto, auto, auto callback_result) {
+ if (callback_result.to_boolean()) {
+ result = true;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+ return Value(result);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::every)
+{
+ auto result = true;
+ for_each_item(vm, global_object, "every", [&](auto, auto, auto callback_result) {
+ if (!callback_result.to_boolean()) {
+ result = false;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+ return Value(result);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::splice)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+
+ auto initial_length = length_of_array_like(global_object, *this_object);
+ if (vm.exception())
+ return {};
+
+ auto relative_start = vm.argument(0).to_i32(global_object);
+ if (vm.exception())
+ return {};
+
+ size_t actual_start;
+
+ if (relative_start < 0)
+ actual_start = max((ssize_t)initial_length + relative_start, (ssize_t)0);
+ else
+ actual_start = min((size_t)relative_start, initial_length);
+
+ size_t insert_count = 0;
+ size_t actual_delete_count = 0;
+
+ if (vm.argument_count() == 1) {
+ actual_delete_count = initial_length - actual_start;
+ } else if (vm.argument_count() >= 2) {
+ insert_count = vm.argument_count() - 2;
+ i32 delete_count = vm.argument(1).to_i32(global_object);
+ if (vm.exception())
+ return {};
+
+ actual_delete_count = min((size_t)max(delete_count, 0), initial_length - actual_start);
+ }
+
+ size_t new_length = initial_length + insert_count - actual_delete_count;
+
+ if (new_length > MAX_ARRAY_LIKE_INDEX) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ArrayMaxSize);
+ return {};
+ }
+
+ auto removed_elements = Array::create(global_object);
+
+ for (size_t i = 0; i < actual_delete_count; ++i) {
+ auto value = this_object->get(actual_start + i);
+ if (vm.exception())
+ return {};
+
+ removed_elements->indexed_properties().append(value);
+ }
+
+ if (insert_count < actual_delete_count) {
+ for (size_t i = actual_start; i < initial_length - actual_delete_count; ++i) {
+ auto from = this_object->get(i + actual_delete_count);
+ if (vm.exception())
+ return {};
+
+ auto to = i + insert_count;
+
+ if (!from.is_empty()) {
+ this_object->put(to, from);
+ } else {
+ this_object->delete_property(to);
+ }
+ if (vm.exception())
+ return {};
+ }
+
+ for (size_t i = initial_length; i > new_length; --i) {
+ this_object->delete_property(i - 1);
+ if (vm.exception())
+ return {};
+ }
+ } else if (insert_count > actual_delete_count) {
+ for (size_t i = initial_length - actual_delete_count; i > actual_start; --i) {
+ auto from = this_object->get(i + actual_delete_count - 1);
+ if (vm.exception())
+ return {};
+
+ auto to = i + insert_count - 1;
+
+ if (!from.is_empty()) {
+ this_object->put(to, from);
+ } else {
+ this_object->delete_property(to);
+ }
+ if (vm.exception())
+ return {};
+ }
+ }
+
+ for (size_t i = 0; i < insert_count; ++i) {
+ this_object->put(actual_start + i, vm.argument(i + 2));
+ if (vm.exception())
+ return {};
+ }
+
+ this_object->put(vm.names.length, Value((i32)new_length));
+ if (vm.exception())
+ return {};
+
+ return removed_elements;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::fill)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+
+ ssize_t length = length_of_array_like(global_object, *this_object);
+ if (vm.exception())
+ return {};
+
+ ssize_t relative_start = 0;
+ ssize_t relative_end = length;
+
+ if (vm.argument_count() >= 2) {
+ relative_start = vm.argument(1).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ }
+
+ if (vm.argument_count() >= 3) {
+ relative_end = vm.argument(2).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ }
+
+ size_t from, to;
+
+ if (relative_start < 0)
+ from = max(length + relative_start, 0L);
+ else
+ from = min(relative_start, length);
+
+ if (relative_end < 0)
+ to = max(length + relative_end, 0L);
+ else
+ to = min(relative_end, length);
+
+ for (size_t i = from; i < to; i++) {
+ this_object->put(i, vm.argument(0));
+ if (vm.exception())
+ return {};
+ }
+
+ return this_object;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::values)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+
+ return ArrayIterator::create(global_object, this_object, Object::PropertyKind::Value);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h
new file mode 100644
index 0000000000..bbdb82903e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class ArrayPrototype final : public Object {
+ JS_OBJECT(ArrayPrototype, Object);
+
+public:
+ ArrayPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~ArrayPrototype() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(filter);
+ JS_DECLARE_NATIVE_FUNCTION(for_each);
+ JS_DECLARE_NATIVE_FUNCTION(map);
+ JS_DECLARE_NATIVE_FUNCTION(pop);
+ JS_DECLARE_NATIVE_FUNCTION(push);
+ JS_DECLARE_NATIVE_FUNCTION(shift);
+ JS_DECLARE_NATIVE_FUNCTION(to_string);
+ JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
+ JS_DECLARE_NATIVE_FUNCTION(unshift);
+ JS_DECLARE_NATIVE_FUNCTION(join);
+ JS_DECLARE_NATIVE_FUNCTION(concat);
+ JS_DECLARE_NATIVE_FUNCTION(slice);
+ JS_DECLARE_NATIVE_FUNCTION(index_of);
+ JS_DECLARE_NATIVE_FUNCTION(reduce);
+ JS_DECLARE_NATIVE_FUNCTION(reduce_right);
+ JS_DECLARE_NATIVE_FUNCTION(reverse);
+ JS_DECLARE_NATIVE_FUNCTION(sort);
+ JS_DECLARE_NATIVE_FUNCTION(last_index_of);
+ JS_DECLARE_NATIVE_FUNCTION(includes);
+ JS_DECLARE_NATIVE_FUNCTION(find);
+ JS_DECLARE_NATIVE_FUNCTION(find_index);
+ JS_DECLARE_NATIVE_FUNCTION(some);
+ JS_DECLARE_NATIVE_FUNCTION(every);
+ JS_DECLARE_NATIVE_FUNCTION(splice);
+ JS_DECLARE_NATIVE_FUNCTION(fill);
+ JS_DECLARE_NATIVE_FUNCTION(values);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BigInt.cpp b/Userland/Libraries/LibJS/Runtime/BigInt.cpp
new file mode 100644
index 0000000000..59a0951ff7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BigInt.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCrypto/BigInt/SignedBigInteger.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/BigInt.h>
+
+namespace JS {
+
+BigInt::BigInt(Crypto::SignedBigInteger big_integer)
+ : m_big_integer(move(big_integer))
+{
+ ASSERT(!m_big_integer.is_invalid());
+}
+
+BigInt::~BigInt()
+{
+}
+
+BigInt* js_bigint(Heap& heap, Crypto::SignedBigInteger big_integer)
+{
+ return heap.allocate_without_global_object<BigInt>(move(big_integer));
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BigInt.h b/Userland/Libraries/LibJS/Runtime/BigInt.h
new file mode 100644
index 0000000000..09a700c4b0
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BigInt.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibCrypto/BigInt/SignedBigInteger.h>
+#include <LibJS/Runtime/Cell.h>
+
+namespace JS {
+
+class BigInt final : public Cell {
+public:
+ BigInt(Crypto::SignedBigInteger);
+ virtual ~BigInt();
+
+ const Crypto::SignedBigInteger& big_integer() const { return m_big_integer; }
+ const String to_string() const { return String::formatted("{}n", m_big_integer.to_base10()); }
+
+private:
+ virtual const char* class_name() const override { return "BigInt"; }
+
+ Crypto::SignedBigInteger m_big_integer;
+};
+
+BigInt* js_bigint(Heap&, Crypto::SignedBigInteger);
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BigIntConstructor.cpp b/Userland/Libraries/LibJS/Runtime/BigIntConstructor.cpp
new file mode 100644
index 0000000000..5eec127b63
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BigIntConstructor.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <LibCrypto/BigInt/SignedBigInteger.h>
+#include <LibJS/Runtime/BigIntConstructor.h>
+#include <LibJS/Runtime/BigIntObject.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/VM.h>
+
+namespace JS {
+
+BigIntConstructor::BigIntConstructor(GlobalObject& global_object)
+ : NativeFunction(vm().names.BigInt, *global_object.function_prototype())
+{
+}
+
+void BigIntConstructor::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+ define_property(vm.names.prototype, global_object.bigint_prototype(), 0);
+ define_property(vm.names.length, Value(1), Attribute::Configurable);
+
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_function(vm.names.asIntN, as_int_n, 2, attr);
+ define_native_function(vm.names.asUintN, as_uint_n, 2, attr);
+}
+
+BigIntConstructor::~BigIntConstructor()
+{
+}
+
+Value BigIntConstructor::call()
+{
+ auto primitive = vm().argument(0).to_primitive(Value::PreferredType::Number);
+ if (vm().exception())
+ return {};
+ if (primitive.is_number()) {
+ if (!primitive.is_integer()) {
+ vm().throw_exception<RangeError>(global_object(), ErrorType::BigIntIntArgument);
+ return {};
+ }
+ return js_bigint(heap(), Crypto::SignedBigInteger { primitive.as_i32() });
+ }
+ auto* bigint = vm().argument(0).to_bigint(global_object());
+ if (vm().exception())
+ return {};
+ return bigint;
+}
+
+Value BigIntConstructor::construct(Function&)
+{
+ vm().throw_exception<TypeError>(global_object(), ErrorType::NotAConstructor, "BigInt");
+ return {};
+}
+
+JS_DEFINE_NATIVE_FUNCTION(BigIntConstructor::as_int_n)
+{
+ TODO();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(BigIntConstructor::as_uint_n)
+{
+ TODO();
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BigIntConstructor.h b/Userland/Libraries/LibJS/Runtime/BigIntConstructor.h
new file mode 100644
index 0000000000..5a4fc6bef7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BigIntConstructor.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class BigIntConstructor final : public NativeFunction {
+ JS_OBJECT(BigIntConstructor, NativeFunction);
+
+public:
+ explicit BigIntConstructor(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~BigIntConstructor() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+
+ JS_DECLARE_NATIVE_FUNCTION(as_int_n);
+ JS_DECLARE_NATIVE_FUNCTION(as_uint_n);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BigIntObject.cpp b/Userland/Libraries/LibJS/Runtime/BigIntObject.cpp
new file mode 100644
index 0000000000..eea70af64e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BigIntObject.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/BigIntObject.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+BigIntObject* BigIntObject::create(GlobalObject& global_object, BigInt& bigint)
+{
+ return global_object.heap().allocate<BigIntObject>(global_object, bigint, *global_object.bigint_prototype());
+}
+
+BigIntObject::BigIntObject(BigInt& bigint, Object& prototype)
+ : Object(prototype)
+ , m_bigint(bigint)
+{
+}
+
+BigIntObject::~BigIntObject()
+{
+}
+
+void BigIntObject::visit_edges(Cell::Visitor& visitor)
+{
+ Object::visit_edges(visitor);
+ visitor.visit(&m_bigint);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BigIntObject.h b/Userland/Libraries/LibJS/Runtime/BigIntObject.h
new file mode 100644
index 0000000000..f58183e4c3
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BigIntObject.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/BigInt.h>
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class BigIntObject final : public Object {
+ JS_OBJECT(BigIntObject, Object);
+
+public:
+ static BigIntObject* create(GlobalObject&, BigInt&);
+
+ BigIntObject(BigInt&, Object& prototype);
+ virtual ~BigIntObject();
+
+ const BigInt& bigint() const { return m_bigint; }
+ virtual Value value_of() const override
+ {
+ return Value(&m_bigint);
+ }
+
+private:
+ virtual void visit_edges(Visitor&) override;
+
+ BigInt& m_bigint;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp b/Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp
new file mode 100644
index 0000000000..84b6617fe5
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibJS/Runtime/BigIntObject.h>
+#include <LibJS/Runtime/BigIntPrototype.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+BigIntPrototype::BigIntPrototype(GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void BigIntPrototype::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_function(vm.names.toString, to_string, 0, attr);
+ define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr);
+ define_native_function(vm.names.valueOf, value_of, 0, attr);
+
+ define_property(vm.well_known_symbol_to_string_tag(), js_string(global_object.heap(), "BigInt"), Attribute::Configurable);
+}
+
+BigIntPrototype::~BigIntPrototype()
+{
+}
+
+static BigIntObject* bigint_object_from(VM& vm, GlobalObject& global_object)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return nullptr;
+ if (!is<BigIntObject>(this_object)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "BigInt");
+ return nullptr;
+ }
+ return static_cast<BigIntObject*>(this_object);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(BigIntPrototype::to_string)
+{
+ auto* bigint_object = bigint_object_from(vm, global_object);
+ if (!bigint_object)
+ return {};
+ return js_string(vm, bigint_object->bigint().big_integer().to_base10());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(BigIntPrototype::to_locale_string)
+{
+ return to_string(vm, global_object);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(BigIntPrototype::value_of)
+{
+ auto* bigint_object = bigint_object_from(vm, global_object);
+ if (!bigint_object)
+ return {};
+ return bigint_object->value_of();
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BigIntPrototype.h b/Userland/Libraries/LibJS/Runtime/BigIntPrototype.h
new file mode 100644
index 0000000000..c0e54a7c2a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BigIntPrototype.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class BigIntPrototype final : public Object {
+ JS_OBJECT(BigIntPrototype, Object);
+
+public:
+ explicit BigIntPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~BigIntPrototype() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(to_string);
+ JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
+ JS_DECLARE_NATIVE_FUNCTION(value_of);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BooleanConstructor.cpp b/Userland/Libraries/LibJS/Runtime/BooleanConstructor.cpp
new file mode 100644
index 0000000000..33e71c238d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BooleanConstructor.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/BooleanConstructor.h>
+#include <LibJS/Runtime/BooleanObject.h>
+#include <LibJS/Runtime/BooleanPrototype.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+BooleanConstructor::BooleanConstructor(GlobalObject& global_object)
+ : NativeFunction(vm().names.Boolean, *global_object.function_prototype())
+{
+}
+
+void BooleanConstructor::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+ define_property(vm.names.prototype, Value(global_object.boolean_prototype()), 0);
+ define_property(vm.names.length, Value(1), Attribute::Configurable);
+}
+
+BooleanConstructor::~BooleanConstructor()
+{
+}
+
+Value BooleanConstructor::call()
+{
+ return Value(vm().argument(0).to_boolean());
+}
+
+Value BooleanConstructor::construct(Function&)
+{
+ return BooleanObject::create(global_object(), vm().argument(0).to_boolean());
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BooleanConstructor.h b/Userland/Libraries/LibJS/Runtime/BooleanConstructor.h
new file mode 100644
index 0000000000..1c68814b21
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BooleanConstructor.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class BooleanConstructor final : public NativeFunction {
+ JS_OBJECT(BooleanConstructor, NativeFunction);
+
+public:
+ explicit BooleanConstructor(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~BooleanConstructor() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BooleanObject.cpp b/Userland/Libraries/LibJS/Runtime/BooleanObject.cpp
new file mode 100644
index 0000000000..b6a4cef400
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BooleanObject.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/BooleanObject.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+BooleanObject* BooleanObject::create(GlobalObject& global_object, bool value)
+{
+ return global_object.heap().allocate<BooleanObject>(global_object, value, *global_object.boolean_prototype());
+}
+
+BooleanObject::BooleanObject(bool value, Object& prototype)
+ : Object(prototype)
+ , m_value(value)
+{
+}
+
+BooleanObject::~BooleanObject()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BooleanObject.h b/Userland/Libraries/LibJS/Runtime/BooleanObject.h
new file mode 100644
index 0000000000..4b9775205a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BooleanObject.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+class BooleanObject : public Object {
+ JS_OBJECT(BooleanObject, Object);
+
+public:
+ static BooleanObject* create(GlobalObject&, bool);
+
+ BooleanObject(bool, Object& prototype);
+ virtual ~BooleanObject() override;
+
+ virtual Value value_of() const override
+ {
+ return Value(m_value);
+ }
+
+private:
+ bool m_value { false };
+};
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BooleanPrototype.cpp b/Userland/Libraries/LibJS/Runtime/BooleanPrototype.cpp
new file mode 100644
index 0000000000..dd69ff57dc
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BooleanPrototype.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibJS/Runtime/BooleanPrototype.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+BooleanPrototype::BooleanPrototype(GlobalObject& global_object)
+ : BooleanObject(false, *global_object.object_prototype())
+{
+}
+
+void BooleanPrototype::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ BooleanObject::initialize(global_object);
+ define_native_function(vm.names.toString, to_string, 0, Attribute::Writable | Attribute::Configurable);
+ define_native_function(vm.names.valueOf, value_of, 0, Attribute::Writable | Attribute::Configurable);
+}
+
+BooleanPrototype::~BooleanPrototype()
+{
+}
+
+JS_DEFINE_NATIVE_FUNCTION(BooleanPrototype::to_string)
+{
+ auto this_value = vm.this_value(global_object);
+ if (this_value.is_boolean())
+ return js_string(vm, this_value.as_bool() ? "true" : "false");
+ if (!this_value.is_object() || !is<BooleanObject>(this_value.as_object())) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Boolean");
+ return {};
+ }
+
+ bool bool_value = static_cast<const BooleanObject&>(this_value.as_object()).value_of().as_bool();
+ return js_string(vm, bool_value ? "true" : "false");
+}
+
+JS_DEFINE_NATIVE_FUNCTION(BooleanPrototype::value_of)
+{
+ auto this_value = vm.this_value(global_object);
+ if (this_value.is_boolean())
+ return this_value;
+ if (!this_value.is_object() || !is<BooleanObject>(this_value.as_object())) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Boolean");
+ return {};
+ }
+
+ return static_cast<const BooleanObject&>(this_value.as_object()).value_of();
+}
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BooleanPrototype.h b/Userland/Libraries/LibJS/Runtime/BooleanPrototype.h
new file mode 100644
index 0000000000..10b46f4efb
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BooleanPrototype.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/BooleanObject.h>
+
+namespace JS {
+
+class BooleanPrototype final : public BooleanObject {
+ JS_OBJECT(BooleanPrototype, BooleanObject);
+
+public:
+ explicit BooleanPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~BooleanPrototype() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(to_string);
+ JS_DECLARE_NATIVE_FUNCTION(value_of);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp b/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp
new file mode 100644
index 0000000000..891eebc57b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/BoundFunction.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+BoundFunction::BoundFunction(GlobalObject& global_object, Function& target_function, Value bound_this, Vector<Value> arguments, i32 length, Object* constructor_prototype)
+ : Function::Function(*global_object.function_prototype(), bound_this, move(arguments))
+ , m_target_function(&target_function)
+ , m_constructor_prototype(constructor_prototype)
+ , m_name(String::formatted("bound {}", target_function.name()))
+ , m_length(length)
+{
+}
+
+void BoundFunction::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Function::initialize(global_object);
+ define_property(vm.names.length, Value(m_length), Attribute::Configurable);
+}
+
+BoundFunction::~BoundFunction()
+{
+}
+
+Value BoundFunction::call()
+{
+ return m_target_function->call();
+}
+
+Value BoundFunction::construct(Function& new_target)
+{
+ if (auto this_value = vm().this_value(global_object()); m_constructor_prototype && this_value.is_object()) {
+ this_value.as_object().set_prototype(m_constructor_prototype);
+ if (vm().exception())
+ return {};
+ }
+ return m_target_function->construct(new_target);
+}
+
+LexicalEnvironment* BoundFunction::create_environment()
+{
+ return m_target_function->create_environment();
+}
+
+void BoundFunction::visit_edges(Visitor& visitor)
+{
+ Function::visit_edges(visitor);
+ visitor.visit(m_target_function);
+ visitor.visit(m_constructor_prototype);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/BoundFunction.h b/Userland/Libraries/LibJS/Runtime/BoundFunction.h
new file mode 100644
index 0000000000..8d4c3a443c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/BoundFunction.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Function.h>
+
+namespace JS {
+
+class BoundFunction final : public Function {
+ JS_OBJECT(BoundFunction, Function);
+
+public:
+ BoundFunction(GlobalObject&, Function& target_function, Value bound_this, Vector<Value> arguments, i32 length, Object* constructor_prototype);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~BoundFunction();
+
+ virtual Value call() override;
+
+ virtual Value construct(Function& new_target) override;
+
+ virtual LexicalEnvironment* create_environment() override;
+
+ virtual void visit_edges(Visitor&) override;
+
+ virtual const FlyString& name() const override
+ {
+ return m_name;
+ }
+
+ Function& target_function() const
+ {
+ return *m_target_function;
+ }
+
+ virtual bool is_strict_mode() const override { return m_target_function->is_strict_mode(); }
+
+private:
+ Function* m_target_function = nullptr;
+ Object* m_constructor_prototype = nullptr;
+ FlyString m_name;
+ i32 m_length { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Cell.cpp b/Userland/Libraries/LibJS/Runtime/Cell.cpp
new file mode 100644
index 0000000000..22a9979a35
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Cell.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Heap/HeapBlock.h>
+#include <LibJS/Runtime/Cell.h>
+#include <LibJS/Runtime/Object.h>
+#include <LibJS/Runtime/PrimitiveString.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+void Cell::Visitor::visit(Cell* cell)
+{
+ if (cell)
+ visit_impl(cell);
+}
+
+void Cell::Visitor::visit(Value value)
+{
+ if (value.is_cell())
+ visit_impl(value.as_cell());
+}
+
+Heap& Cell::heap() const
+{
+ return HeapBlock::from_cell(this)->heap();
+}
+
+VM& Cell::vm() const
+{
+ return heap().vm();
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Cell.h b/Userland/Libraries/LibJS/Runtime/Cell.h
new file mode 100644
index 0000000000..df4a19f19a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Cell.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Format.h>
+#include <AK/Forward.h>
+#include <AK/Noncopyable.h>
+#include <AK/String.h>
+#include <AK/TypeCasts.h>
+#include <LibJS/Forward.h>
+
+namespace JS {
+
+class Cell {
+ AK_MAKE_NONCOPYABLE(Cell);
+ AK_MAKE_NONMOVABLE(Cell);
+
+public:
+ virtual void initialize(GlobalObject&) { }
+ virtual ~Cell() { }
+
+ bool is_marked() const { return m_mark; }
+ void set_marked(bool b) { m_mark = b; }
+
+ bool is_live() const { return m_live; }
+ void set_live(bool b) { m_live = b; }
+
+ virtual const char* class_name() const = 0;
+
+ class Visitor {
+ public:
+ void visit(Cell*);
+ void visit(Value);
+
+ protected:
+ virtual void visit_impl(Cell*) = 0;
+ };
+
+ virtual void visit_edges(Visitor&) { }
+
+ Heap& heap() const;
+ VM& vm() const;
+
+protected:
+ Cell() { }
+
+private:
+ bool m_mark { false };
+ bool m_live { true };
+};
+
+}
+
+template<>
+struct AK::Formatter<JS::Cell> : AK::Formatter<FormatString> {
+ void format(FormatBuilder& builder, const JS::Cell* cell)
+ {
+ if (!cell)
+ Formatter<FormatString>::format(builder, "Cell{nullptr}");
+ else
+ Formatter<FormatString>::format(builder, "{}({})", cell->class_name(), cell);
+ }
+};
diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h
new file mode 100644
index 0000000000..2d1d4d1d2e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <LibJS/Forward.h>
+
+namespace JS {
+
+#define ENUMERATE_STANDARD_PROPERTY_NAMES(P) \
+ P(BYTES_PER_ELEMENT) \
+ P(BigInt) \
+ P(Boolean) \
+ P(E) \
+ P(EPSILON) \
+ P(Infinity) \
+ P(JSON) \
+ P(LN10) \
+ P(LN2) \
+ P(LOG10E) \
+ P(LOG2E) \
+ P(MAX_SAFE_INTEGER) \
+ P(MIN_SAFE_INTEGER) \
+ P(Math) \
+ P(NEGATIVE_INFINITY) \
+ P(NaN) \
+ P(Number) \
+ P(PI) \
+ P(POSITIVE_INFINITY) \
+ P(Proxy) \
+ P(Reflect) \
+ P(RegExp) \
+ P(SQRT1_2) \
+ P(SQRT2) \
+ P(String) \
+ P(Symbol) \
+ P(UTC) \
+ P(abs) \
+ P(acos) \
+ P(acosh) \
+ P(apply) \
+ P(arguments) \
+ P(asIntN) \
+ P(asUintN) \
+ P(asin) \
+ P(asinh) \
+ P(atan) \
+ P(atan2) \
+ P(atanh) \
+ P(bind) \
+ P(byteLength) \
+ P(call) \
+ P(cbrt) \
+ P(ceil) \
+ P(charAt) \
+ P(charCodeAt) \
+ P(clear) \
+ P(clz32) \
+ P(concat) \
+ P(configurable) \
+ P(console) \
+ P(construct) \
+ P(constructor) \
+ P(cos) \
+ P(cosh) \
+ P(count) \
+ P(countReset) \
+ P(debug) \
+ P(defineProperty) \
+ P(deleteProperty) \
+ P(description) \
+ P(done) \
+ P(dotAll) \
+ P(endsWith) \
+ P(entries) \
+ P(enumerable) \
+ P(error) \
+ P(every) \
+ P(exec) \
+ P(exp) \
+ P(expm1) \
+ P(fill) \
+ P(filter) \
+ P(find) \
+ P(findIndex) \
+ P(flags) \
+ P(floor) \
+ P(forEach) \
+ P(from) \
+ P(fromCharCode) \
+ P(fround) \
+ P(gc) \
+ P(get) \
+ P(getDate) \
+ P(getDay) \
+ P(getFullYear) \
+ P(getHours) \
+ P(getMilliseconds) \
+ P(getMinutes) \
+ P(getMonth) \
+ P(getOwnPropertyDescriptor) \
+ P(getOwnPropertyNames) \
+ P(getPrototypeOf) \
+ P(getSeconds) \
+ P(getTime) \
+ P(getUTCDate) \
+ P(getUTCDay) \
+ P(getUTCFullYear) \
+ P(getUTCHours) \
+ P(getUTCMilliseconds) \
+ P(getUTCMinutes) \
+ P(getUTCMonth) \
+ P(getUTCSeconds) \
+ P(global) \
+ P(globalThis) \
+ P(groups) \
+ P(has) \
+ P(hasOwnProperty) \
+ P(hypot) \
+ P(ignoreCase) \
+ P(imul) \
+ P(includes) \
+ P(index) \
+ P(indexOf) \
+ P(info) \
+ P(input) \
+ P(is) \
+ P(isArray) \
+ P(isExtensible) \
+ P(isFinite) \
+ P(isInteger) \
+ P(isNaN) \
+ P(isPrototypeOf) \
+ P(isSafeInteger) \
+ P(isView) \
+ P(join) \
+ P(keyFor) \
+ P(keys) \
+ P(lastIndex) \
+ P(lastIndexOf) \
+ P(length) \
+ P(log) \
+ P(log1p) \
+ P(log2) \
+ P(log10) \
+ P(map) \
+ P(max) \
+ P(message) \
+ P(min) \
+ P(multiline) \
+ P(name) \
+ P(next) \
+ P(now) \
+ P(of) \
+ P(ownKeys) \
+ P(padEnd) \
+ P(padStart) \
+ P(parse) \
+ P(parseFloat) \
+ P(parseInt) \
+ P(pop) \
+ P(pow) \
+ P(preventExtensions) \
+ P(propertyIsEnumerable) \
+ P(prototype) \
+ P(push) \
+ P(random) \
+ P(raw) \
+ P(reduce) \
+ P(reduceRight) \
+ P(repeat) \
+ P(reverse) \
+ P(round) \
+ P(set) \
+ P(setPrototypeOf) \
+ P(shift) \
+ P(sign) \
+ P(sin) \
+ P(sinh) \
+ P(slice) \
+ P(some) \
+ P(sort) \
+ P(source) \
+ P(splice) \
+ P(sqrt) \
+ P(startsWith) \
+ P(sticky) \
+ P(stringify) \
+ P(substr) \
+ P(substring) \
+ P(tan) \
+ P(tanh) \
+ P(test) \
+ P(toDateString) \
+ P(toISOString) \
+ P(toJSON) \
+ P(toLocaleDateString) \
+ P(toLocaleString) \
+ P(toLocaleTimeString) \
+ P(toLowerCase) \
+ P(toString) \
+ P(toTimeString) \
+ P(toUpperCase) \
+ P(trace) \
+ P(trim) \
+ P(trimEnd) \
+ P(trimStart) \
+ P(trunc) \
+ P(undefined) \
+ P(unicode) \
+ P(unshift) \
+ P(value) \
+ P(valueOf) \
+ P(values) \
+ P(warn) \
+ P(writable)
+
+struct CommonPropertyNames {
+ FlyString for_ { "for" };
+#define __ENUMERATE(x) FlyString x { #x };
+ ENUMERATE_STANDARD_PROPERTY_NAMES(__ENUMERATE)
+#undef __ENUMERATE
+#define __JS_ENUMERATE(x, a, b, c, t) FlyString x { #x };
+ JS_ENUMERATE_BUILTIN_TYPES
+#undef __JS_ENUMERATE
+#define __JS_ENUMERATE(x, a) FlyString x { #x };
+ JS_ENUMERATE_WELL_KNOWN_SYMBOLS
+#undef __JS_ENUMERATE
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ConsoleObject.cpp b/Userland/Libraries/LibJS/Runtime/ConsoleObject.cpp
new file mode 100644
index 0000000000..f87b29c0a1
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ConsoleObject.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * Copyright (c) 2020, Emanuele Torre <torreemanuele6@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/FlyString.h>
+#include <AK/Function.h>
+#include <LibJS/Console.h>
+#include <LibJS/Runtime/ConsoleObject.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+ConsoleObject::ConsoleObject(GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void ConsoleObject::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+ define_native_function(vm.names.log, log);
+ define_native_function(vm.names.debug, debug);
+ define_native_function(vm.names.info, info);
+ define_native_function(vm.names.warn, warn);
+ define_native_function(vm.names.error, error);
+ define_native_function(vm.names.trace, trace);
+ define_native_function(vm.names.count, count);
+ define_native_function(vm.names.countReset, count_reset);
+ define_native_function(vm.names.clear, clear);
+}
+
+ConsoleObject::~ConsoleObject()
+{
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::log)
+{
+ return global_object.console().log();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::debug)
+{
+ return global_object.console().debug();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::info)
+{
+ return global_object.console().info();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::warn)
+{
+ return global_object.console().warn();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::error)
+{
+ return global_object.console().error();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::trace)
+{
+ return global_object.console().trace();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::count)
+{
+ return global_object.console().count();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::count_reset)
+{
+ return global_object.console().count_reset();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::clear)
+{
+ return global_object.console().clear();
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ConsoleObject.h b/Userland/Libraries/LibJS/Runtime/ConsoleObject.h
new file mode 100644
index 0000000000..07848aa44f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ConsoleObject.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class ConsoleObject final : public Object {
+ JS_OBJECT(ConsoleObject, Object);
+
+public:
+ explicit ConsoleObject(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~ConsoleObject() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(log);
+ JS_DECLARE_NATIVE_FUNCTION(debug);
+ JS_DECLARE_NATIVE_FUNCTION(info);
+ JS_DECLARE_NATIVE_FUNCTION(warn);
+ JS_DECLARE_NATIVE_FUNCTION(error);
+ JS_DECLARE_NATIVE_FUNCTION(trace);
+ JS_DECLARE_NATIVE_FUNCTION(count);
+ JS_DECLARE_NATIVE_FUNCTION(count_reset);
+ JS_DECLARE_NATIVE_FUNCTION(clear);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Date.cpp b/Userland/Libraries/LibJS/Runtime/Date.cpp
new file mode 100644
index 0000000000..ced1a58009
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Date.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibCore/DateTime.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/Date.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+Date* Date::create(GlobalObject& global_object, Core::DateTime datetime, u16 milliseconds)
+{
+ return global_object.heap().allocate<Date>(global_object, datetime, milliseconds, *global_object.date_prototype());
+}
+
+Date::Date(Core::DateTime datetime, u16 milliseconds, Object& prototype)
+ : Object(prototype)
+ , m_datetime(datetime)
+ , m_milliseconds(milliseconds)
+{
+}
+
+Date::~Date()
+{
+}
+
+tm Date::to_utc_tm() const
+{
+ time_t timestamp = m_datetime.timestamp();
+ struct tm tm;
+ gmtime_r(&timestamp, &tm);
+ return tm;
+}
+
+int Date::utc_date() const
+{
+ return to_utc_tm().tm_mday;
+}
+
+int Date::utc_day() const
+{
+ return to_utc_tm().tm_wday;
+}
+
+int Date::utc_full_year() const
+{
+ return to_utc_tm().tm_year + 1900;
+}
+
+int Date::utc_hours() const
+{
+ return to_utc_tm().tm_hour;
+}
+
+int Date::utc_minutes() const
+{
+ return to_utc_tm().tm_min;
+}
+
+int Date::utc_month() const
+{
+ return to_utc_tm().tm_mon;
+}
+
+int Date::utc_seconds() const
+{
+ return to_utc_tm().tm_sec;
+}
+
+String Date::iso_date_string() const
+{
+ auto tm = to_utc_tm();
+ int year = tm.tm_year + 1900;
+ int month = tm.tm_mon + 1;
+
+ StringBuilder builder;
+ if (year < 0)
+ builder.appendf("-%06d", -year);
+ else if (year > 9999)
+ builder.appendf("+%06d", year);
+ else
+ builder.appendf("%04d", year);
+ builder.append('-');
+ builder.appendf("%02d", month);
+ builder.append('-');
+ builder.appendf("%02d", tm.tm_mday);
+ builder.append('T');
+ builder.appendf("%02d", tm.tm_hour);
+ builder.append(':');
+ builder.appendf("%02d", tm.tm_min);
+ builder.append(':');
+ builder.appendf("%02d", tm.tm_sec);
+ builder.append('.');
+ builder.appendf("%03d", m_milliseconds);
+ builder.append('Z');
+
+ return builder.build();
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Date.h b/Userland/Libraries/LibJS/Runtime/Date.h
new file mode 100644
index 0000000000..368cc91bc2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Date.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibCore/DateTime.h>
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class Date final : public Object {
+ JS_OBJECT(Date, Object);
+
+public:
+ static Date* create(GlobalObject&, Core::DateTime, u16 milliseconds);
+
+ Date(Core::DateTime datetime, u16 milliseconds, Object& prototype);
+ virtual ~Date() override;
+
+ Core::DateTime& datetime() { return m_datetime; }
+ const Core::DateTime& datetime() const { return m_datetime; }
+
+ int date() const { return datetime().day(); }
+ int day() const { return datetime().weekday(); }
+ int full_year() const { return datetime().year(); }
+ int hours() const { return datetime().hour(); }
+ u16 milliseconds() const { return m_milliseconds; }
+ int minutes() const { return datetime().minute(); }
+ int month() const { return datetime().month() - 1; }
+ int seconds() const { return datetime().second(); }
+ double time() const { return datetime().timestamp() * 1000.0 + milliseconds(); }
+ int year() const { return datetime().day(); }
+
+ int utc_date() const;
+ int utc_day() const;
+ int utc_full_year() const;
+ int utc_hours() const;
+ int utc_milliseconds() const { return milliseconds(); }
+ int utc_minutes() const;
+ int utc_month() const;
+ int utc_seconds() const;
+
+ String date_string() const { return m_datetime.to_string("%a %b %d %Y"); }
+ // FIXME: Deal with timezones once SerenityOS has a working tzset(3)
+ String time_string() const { return m_datetime.to_string("%T GMT+0000 (UTC)"); }
+ String string() const
+ {
+ return String::formatted("{} {}", date_string(), time_string());
+ }
+
+ String iso_date_string() const;
+
+ // FIXME: One day, implement real locale support. Until then, everyone gets what the Clock MenuApplet displays.
+ String locale_date_string() const { return m_datetime.to_string("%Y-%m-%d"); }
+ String locale_string() const { return m_datetime.to_string(); }
+ String locale_time_string() const { return m_datetime.to_string("%H:%M:%S"); }
+
+ virtual Value value_of() const override
+ {
+ return Value(static_cast<double>(m_datetime.timestamp() * 1000 + m_milliseconds));
+ }
+
+private:
+ tm to_utc_tm() const;
+
+ Core::DateTime m_datetime;
+ u16 m_milliseconds;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp b/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp
new file mode 100644
index 0000000000..37b4d87865
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * Copyright (c) 2020, Nico Weber <thakis@chromium.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/GenericLexer.h>
+#include <LibCore/DateTime.h>
+#include <LibJS/Runtime/Date.h>
+#include <LibJS/Runtime/DateConstructor.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/VM.h>
+#include <ctype.h>
+#include <sys/time.h>
+#include <time.h>
+
+namespace JS {
+
+static Value parse_simplified_iso8601(const String& iso_8601)
+{
+ // Date.parse() is allowed to accept many formats. We strictly only accept things matching
+ // http://www.ecma-international.org/ecma-262/#sec-date-time-string-format
+ GenericLexer lexer(iso_8601);
+ auto lex_n_digits = [&](size_t n, int& out) {
+ if (lexer.tell_remaining() < n)
+ return false;
+ int r = 0;
+ for (size_t i = 0; i < n; ++i) {
+ char ch = lexer.consume();
+ if (!isdigit(ch))
+ return false;
+ r = 10 * r + ch - '0';
+ }
+ out = r;
+ return true;
+ };
+
+ int year = -1, month = -1, day = -1;
+ int hours = -1, minutes = -1, seconds = -1, milliseconds = -1;
+ char timezone = -1;
+ int timezone_hours = -1, timezone_minutes = -1;
+ auto lex_year = [&]() {
+ if (lexer.consume_specific('+'))
+ return lex_n_digits(6, year);
+ if (lexer.consume_specific('-')) {
+ int absolute_year;
+ if (!lex_n_digits(6, absolute_year))
+ return false;
+ year = -absolute_year;
+ return true;
+ }
+ return lex_n_digits(4, year);
+ };
+ auto lex_month = [&]() { return lex_n_digits(2, month) && month >= 1 && month <= 12; };
+ auto lex_day = [&]() { return lex_n_digits(2, day) && day >= 1 && day <= 31; };
+ auto lex_date = [&]() { return lex_year() && (!lexer.consume_specific('-') || (lex_month() && (!lexer.consume_specific('-') || lex_day()))); };
+
+ auto lex_hours_minutes = [&](int& out_h, int& out_m) {
+ int h, m;
+ if (lex_n_digits(2, h) && h >= 0 && h <= 24 && lexer.consume_specific(':') && lex_n_digits(2, m) && m >= 0 && m <= 59) {
+ out_h = h;
+ out_m = m;
+ return true;
+ }
+ return false;
+ };
+ auto lex_seconds = [&]() { return lex_n_digits(2, seconds) && seconds >= 0 && seconds <= 59; };
+ auto lex_milliseconds = [&]() { return lex_n_digits(3, milliseconds); };
+ auto lex_seconds_milliseconds = [&]() { return lex_seconds() && (!lexer.consume_specific('.') || lex_milliseconds()); };
+ auto lex_timezone = [&]() {
+ if (lexer.consume_specific('+')) {
+ timezone = '+';
+ return lex_hours_minutes(timezone_hours, timezone_minutes);
+ }
+ if (lexer.consume_specific('-')) {
+ timezone = '-';
+ return lex_hours_minutes(timezone_hours, timezone_minutes);
+ }
+ if (lexer.consume_specific('Z'))
+ timezone = 'Z';
+ return true;
+ };
+ auto lex_time = [&]() { return lex_hours_minutes(hours, minutes) && (!lexer.consume_specific(':') || lex_seconds_milliseconds()) && lex_timezone(); };
+
+ if (!lex_date() || (lexer.consume_specific('T') && !lex_time()) || !lexer.is_eof()) {
+ return js_nan();
+ }
+
+ // We parsed a valid date simplified ISO 8601 string. Values not present in the string are -1.
+ ASSERT(year != -1); // A valid date string always has at least a year.
+ struct tm tm = {};
+ tm.tm_year = year - 1900;
+ tm.tm_mon = month == -1 ? 0 : month - 1;
+ tm.tm_mday = day == -1 ? 1 : day;
+ tm.tm_hour = hours == -1 ? 0 : hours;
+ tm.tm_min = minutes == -1 ? 0 : minutes;
+ tm.tm_sec = seconds == -1 ? 0 : seconds;
+
+ // http://www.ecma-international.org/ecma-262/#sec-date.parse:
+ // "When the UTC offset representation is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as a local time."
+ time_t timestamp;
+ if (timezone != -1 || hours == -1)
+ timestamp = timegm(&tm);
+ else
+ timestamp = mktime(&tm);
+
+ if (timezone == '-')
+ timestamp += (timezone_hours * 60 + timezone_minutes) * 60;
+ else if (timezone == '+')
+ timestamp -= (timezone_hours * 60 + timezone_minutes) * 60;
+
+ // FIXME: reject timestamp if resulting value wouldn't fit in a double
+
+ if (milliseconds == -1)
+ milliseconds = 0;
+ return Value(1000.0 * timestamp + milliseconds);
+}
+
+DateConstructor::DateConstructor(GlobalObject& global_object)
+ : NativeFunction(vm().names.Date, *global_object.function_prototype())
+{
+}
+
+void DateConstructor::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+ define_property(vm.names.prototype, global_object.date_prototype(), 0);
+ define_property(vm.names.length, Value(7), Attribute::Configurable);
+
+ define_native_function(vm.names.now, now, 0, Attribute::Writable | Attribute::Configurable);
+ define_native_function(vm.names.parse, parse, 1, Attribute::Writable | Attribute::Configurable);
+ define_native_function(vm.names.UTC, utc, 1, Attribute::Writable | Attribute::Configurable);
+}
+
+DateConstructor::~DateConstructor()
+{
+}
+
+Value DateConstructor::call()
+{
+ auto date = construct(*this);
+ if (!date.is_object())
+ return {};
+ return js_string(heap(), static_cast<Date&>(date.as_object()).string());
+}
+
+Value DateConstructor::construct(Function&)
+{
+ if (vm().argument_count() == 0) {
+ struct timeval tv;
+ gettimeofday(&tv, nullptr);
+ auto datetime = Core::DateTime::now();
+ auto milliseconds = static_cast<u16>(tv.tv_usec / 1000);
+ return Date::create(global_object(), datetime, milliseconds);
+ }
+ if (vm().argument_count() == 1) {
+ auto value = vm().argument(0);
+ if (value.is_string())
+ value = parse_simplified_iso8601(value.as_string().string());
+ // A timestamp since the epoch, in UTC.
+ // FIXME: Date() probably should use a double as internal representation, so that NaN arguments and larger offsets are handled correctly.
+ double value_as_double = value.to_double(global_object());
+ auto datetime = Core::DateTime::from_timestamp(static_cast<time_t>(value_as_double / 1000));
+ auto milliseconds = static_cast<u16>(fmod(value_as_double, 1000));
+ return Date::create(global_object(), datetime, milliseconds);
+ }
+ // A date/time in components, in local time.
+ // FIXME: This doesn't construct an "Invalid Date" object if one of the parameters is NaN.
+ auto arg_or = [this](size_t i, i32 fallback) { return vm().argument_count() > i ? vm().argument(i).to_i32(global_object()) : fallback; };
+ int year = vm().argument(0).to_i32(global_object());
+ int month_index = vm().argument(1).to_i32(global_object());
+ int day = arg_or(2, 1);
+ int hours = arg_or(3, 0);
+ int minutes = arg_or(4, 0);
+ int seconds = arg_or(5, 0);
+ int milliseconds = arg_or(6, 0);
+
+ seconds += milliseconds / 1000;
+ milliseconds %= 1000;
+ if (milliseconds < 0) {
+ seconds -= 1;
+ milliseconds += 1000;
+ }
+
+ if (year >= 0 && year <= 99)
+ year += 1900;
+ int month = month_index + 1;
+ auto datetime = Core::DateTime::create(year, month, day, hours, minutes, seconds);
+ return Date::create(global_object(), datetime, milliseconds);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DateConstructor::now)
+{
+ struct timeval tv;
+ gettimeofday(&tv, nullptr);
+ return Value(tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DateConstructor::parse)
+{
+ if (!vm.argument_count())
+ return js_nan();
+
+ auto iso_8601 = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return js_nan();
+
+ return parse_simplified_iso8601(iso_8601);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DateConstructor::utc)
+{
+ auto arg_or = [&vm, &global_object](size_t i, i32 fallback) { return vm.argument_count() > i ? vm.argument(i).to_i32(global_object) : fallback; };
+ int year = vm.argument(0).to_i32(global_object);
+ if (year >= 0 && year <= 99)
+ year += 1900;
+
+ struct tm tm = {};
+ tm.tm_year = year - 1900;
+ tm.tm_mon = arg_or(1, 0); // 0-based in both tm and JavaScript
+ tm.tm_mday = arg_or(2, 1);
+ tm.tm_hour = arg_or(3, 0);
+ tm.tm_min = arg_or(4, 0);
+ tm.tm_sec = arg_or(5, 0);
+ // timegm() doesn't read tm.tm_wday and tm.tm_yday, no need to fill them in.
+
+ int milliseconds = arg_or(6, 0);
+ return Value(1000.0 * timegm(&tm) + milliseconds);
+}
+}
diff --git a/Userland/Libraries/LibJS/Runtime/DateConstructor.h b/Userland/Libraries/LibJS/Runtime/DateConstructor.h
new file mode 100644
index 0000000000..cc16ee8e4a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/DateConstructor.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class DateConstructor final : public NativeFunction {
+ JS_OBJECT(DateConstructor, NativeFunction);
+
+public:
+ explicit DateConstructor(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~DateConstructor() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+
+ JS_DECLARE_NATIVE_FUNCTION(now);
+ JS_DECLARE_NATIVE_FUNCTION(parse);
+ JS_DECLARE_NATIVE_FUNCTION(utc);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp b/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp
new file mode 100644
index 0000000000..72fea9ca25
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <AK/String.h>
+#include <LibCore/DateTime.h>
+#include <LibJS/Runtime/Date.h>
+#include <LibJS/Runtime/DatePrototype.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+static Date* typed_this(VM& vm, GlobalObject& global_object)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return nullptr;
+ if (!is<Date>(this_object)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Date");
+ return nullptr;
+ }
+ return static_cast<Date*>(this_object);
+}
+
+DatePrototype::DatePrototype(GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void DatePrototype::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_function(vm.names.getDate, get_date, 0, attr);
+ define_native_function(vm.names.getDay, get_day, 0, attr);
+ define_native_function(vm.names.getFullYear, get_full_year, 0, attr);
+ define_native_function(vm.names.getHours, get_hours, 0, attr);
+ define_native_function(vm.names.getMilliseconds, get_milliseconds, 0, attr);
+ define_native_function(vm.names.getMinutes, get_minutes, 0, attr);
+ define_native_function(vm.names.getMonth, get_month, 0, attr);
+ define_native_function(vm.names.getSeconds, get_seconds, 0, attr);
+ define_native_function(vm.names.getTime, get_time, 0, attr);
+ define_native_function(vm.names.getUTCDate, get_utc_date, 0, attr);
+ define_native_function(vm.names.getUTCDay, get_utc_day, 0, attr);
+ define_native_function(vm.names.getUTCFullYear, get_utc_full_year, 0, attr);
+ define_native_function(vm.names.getUTCHours, get_utc_hours, 0, attr);
+ define_native_function(vm.names.getUTCMilliseconds, get_utc_milliseconds, 0, attr);
+ define_native_function(vm.names.getUTCMinutes, get_utc_minutes, 0, attr);
+ define_native_function(vm.names.getUTCMonth, get_utc_month, 0, attr);
+ define_native_function(vm.names.getUTCSeconds, get_utc_seconds, 0, attr);
+ define_native_function(vm.names.toDateString, to_date_string, 0, attr);
+ define_native_function(vm.names.toISOString, to_iso_string, 0, attr);
+ define_native_function(vm.names.toLocaleDateString, to_locale_date_string, 0, attr);
+ define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr);
+ define_native_function(vm.names.toLocaleTimeString, to_locale_time_string, 0, attr);
+ define_native_function(vm.names.toTimeString, to_time_string, 0, attr);
+ define_native_function(vm.names.toString, to_string, 0, attr);
+
+ // Aliases.
+ define_native_function(vm.names.valueOf, get_time, 0, attr);
+ // toJSON() isn't quite an alias for toISOString():
+ // - it returns null instead of throwing RangeError
+ // - its .length is 1, not 0
+ // - it can be transferred to other prototypes
+}
+
+DatePrototype::~DatePrototype()
+{
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_date)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->date()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_day)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->day()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_full_year)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->full_year()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_hours)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->hours()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_milliseconds)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->milliseconds()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_minutes)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->minutes()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_month)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->month()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_seconds)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->seconds()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_time)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(this_object->time());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_date)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->utc_date()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_day)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->utc_day()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_full_year)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->utc_full_year()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_hours)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->utc_hours()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_milliseconds)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->utc_milliseconds()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_month)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->utc_month()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_minutes)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->utc_minutes()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_seconds)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return Value(static_cast<double>(this_object->utc_seconds()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_date_string)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ auto string = this_object->date_string();
+ return js_string(vm, move(string));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_iso_string)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ auto string = this_object->iso_date_string();
+ return js_string(vm, move(string));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_locale_date_string)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ // FIXME: Optional locales, options params.
+ auto string = this_object->locale_date_string();
+ return js_string(vm, move(string));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_locale_string)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ // FIXME: Optional locales, options params.
+ auto string = this_object->locale_string();
+ return js_string(vm, move(string));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_locale_time_string)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ // FIXME: Optional locales, options params.
+ auto string = this_object->locale_time_string();
+ return js_string(vm, move(string));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_time_string)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ auto string = this_object->time_string();
+ return js_string(vm, move(string));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_string)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ auto string = this_object->string();
+ return js_string(vm, move(string));
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/DatePrototype.h b/Userland/Libraries/LibJS/Runtime/DatePrototype.h
new file mode 100644
index 0000000000..5c8c8bebd4
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/DatePrototype.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class DatePrototype final : public Object {
+ JS_OBJECT(DatePrototype, Object);
+
+public:
+ explicit DatePrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~DatePrototype() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(get_date);
+ JS_DECLARE_NATIVE_FUNCTION(get_day);
+ JS_DECLARE_NATIVE_FUNCTION(get_full_year);
+ JS_DECLARE_NATIVE_FUNCTION(get_hours);
+ JS_DECLARE_NATIVE_FUNCTION(get_milliseconds);
+ JS_DECLARE_NATIVE_FUNCTION(get_minutes);
+ JS_DECLARE_NATIVE_FUNCTION(get_month);
+ JS_DECLARE_NATIVE_FUNCTION(get_seconds);
+ JS_DECLARE_NATIVE_FUNCTION(get_time);
+ JS_DECLARE_NATIVE_FUNCTION(get_utc_date);
+ JS_DECLARE_NATIVE_FUNCTION(get_utc_day);
+ JS_DECLARE_NATIVE_FUNCTION(get_utc_full_year);
+ JS_DECLARE_NATIVE_FUNCTION(get_utc_hours);
+ JS_DECLARE_NATIVE_FUNCTION(get_utc_milliseconds);
+ JS_DECLARE_NATIVE_FUNCTION(get_utc_minutes);
+ JS_DECLARE_NATIVE_FUNCTION(get_utc_month);
+ JS_DECLARE_NATIVE_FUNCTION(get_utc_seconds);
+ JS_DECLARE_NATIVE_FUNCTION(to_date_string);
+ JS_DECLARE_NATIVE_FUNCTION(to_iso_string);
+ JS_DECLARE_NATIVE_FUNCTION(to_locale_date_string);
+ JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
+ JS_DECLARE_NATIVE_FUNCTION(to_locale_time_string);
+ JS_DECLARE_NATIVE_FUNCTION(to_time_string);
+ JS_DECLARE_NATIVE_FUNCTION(to_string);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Error.cpp b/Userland/Libraries/LibJS/Runtime/Error.cpp
new file mode 100644
index 0000000000..7f30ab872c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Error.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+Error* Error::create(GlobalObject& global_object, const FlyString& name, const String& message)
+{
+ return global_object.heap().allocate<Error>(global_object, name, message, *global_object.error_prototype());
+}
+
+Error::Error(const FlyString& name, const String& message, Object& prototype)
+ : Object(prototype)
+ , m_name(name)
+ , m_message(message)
+{
+}
+
+Error::~Error()
+{
+}
+
+#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
+ ClassName* ClassName::create(GlobalObject& global_object, const String& message) \
+ { \
+ return global_object.heap().allocate<ClassName>(global_object, message, *global_object.snake_name##_prototype()); \
+ } \
+ ClassName::ClassName(const String& message, Object& prototype) \
+ : Error(vm().names.ClassName, message, prototype) \
+ { \
+ } \
+ ClassName::~ClassName() { }
+
+JS_ENUMERATE_ERROR_SUBCLASSES
+#undef __JS_ENUMERATE
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Error.h b/Userland/Libraries/LibJS/Runtime/Error.h
new file mode 100644
index 0000000000..3605825f1d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Error.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class Error : public Object {
+ JS_OBJECT(Error, Object);
+
+public:
+ static Error* create(GlobalObject&, const FlyString& name, const String& message);
+
+ Error(const FlyString& name, const String& message, Object& prototype);
+ virtual ~Error() override;
+
+ const FlyString& name() const { return m_name; }
+ const String& message() const { return m_message; }
+
+ void set_name(const FlyString& name) { m_name = name; }
+
+private:
+ FlyString m_name;
+ String m_message;
+};
+
+#define DECLARE_ERROR_SUBCLASS(ClassName, snake_name, PrototypeName, ConstructorName) \
+ class ClassName final : public Error { \
+ JS_OBJECT(ClassName, Error); \
+ \
+ public: \
+ static ClassName* create(GlobalObject&, const String& message); \
+ \
+ ClassName(const String& message, Object& prototype); \
+ virtual ~ClassName() override; \
+ };
+
+#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
+ DECLARE_ERROR_SUBCLASS(ClassName, snake_name, PrototypeName, ConstructorName)
+JS_ENUMERATE_ERROR_SUBCLASSES
+#undef __JS_ENUMERATE
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp
new file mode 100644
index 0000000000..549c74855c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/ErrorConstructor.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+ErrorConstructor::ErrorConstructor(GlobalObject& global_object)
+ : NativeFunction(vm().names.Error, *global_object.function_prototype())
+{
+}
+
+void ErrorConstructor::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+ define_property(vm.names.prototype, global_object.error_prototype(), 0);
+ define_property(vm.names.length, Value(1), Attribute::Configurable);
+}
+
+ErrorConstructor::~ErrorConstructor()
+{
+}
+
+Value ErrorConstructor::call()
+{
+ return construct(*this);
+}
+
+Value ErrorConstructor::construct(Function&)
+{
+ auto& vm = this->vm();
+ String message = "";
+ if (!vm.call_frame().arguments.is_empty() && !vm.call_frame().arguments[0].is_undefined()) {
+ message = vm.call_frame().arguments[0].to_string(global_object());
+ if (vm.exception())
+ return {};
+ }
+ return Error::create(global_object(), vm.names.Error, message);
+}
+
+#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
+ ConstructorName::ConstructorName(GlobalObject& global_object) \
+ : NativeFunction(*global_object.function_prototype()) \
+ { \
+ } \
+ void ConstructorName::initialize(GlobalObject& global_object) \
+ { \
+ auto& vm = this->vm(); \
+ NativeFunction::initialize(global_object); \
+ define_property(vm.names.prototype, global_object.snake_name##_prototype(), 0); \
+ define_property(vm.names.length, Value(1), Attribute::Configurable); \
+ } \
+ ConstructorName::~ConstructorName() { } \
+ Value ConstructorName::call() \
+ { \
+ return construct(*this); \
+ } \
+ Value ConstructorName::construct(Function&) \
+ { \
+ String message = ""; \
+ if (!vm().call_frame().arguments.is_empty() && !vm().call_frame().arguments[0].is_undefined()) { \
+ message = vm().call_frame().arguments[0].to_string(global_object()); \
+ if (vm().exception()) \
+ return {}; \
+ } \
+ return ClassName::create(global_object(), message); \
+ }
+
+JS_ENUMERATE_ERROR_SUBCLASSES
+#undef __JS_ENUMERATE
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ErrorConstructor.h b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.h
new file mode 100644
index 0000000000..2626623aaa
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class ErrorConstructor final : public NativeFunction {
+ JS_OBJECT(ErrorConstructor, NativeFunction);
+
+public:
+ explicit ErrorConstructor(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~ErrorConstructor() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+};
+
+#define DECLARE_ERROR_SUBCLASS_CONSTRUCTOR(ClassName, snake_name, PrototypeName, ConstructorName) \
+ class ConstructorName final : public NativeFunction { \
+ JS_OBJECT(ConstructorName, NativeFunction); \
+ \
+ public: \
+ explicit ConstructorName(GlobalObject&); \
+ virtual void initialize(GlobalObject&) override; \
+ virtual ~ConstructorName() override; \
+ virtual Value call() override; \
+ virtual Value construct(Function& new_target) override; \
+ \
+ private: \
+ virtual bool has_constructor() const override { return true; } \
+ };
+
+#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
+ DECLARE_ERROR_SUBCLASS_CONSTRUCTOR(ClassName, snake_name, PrototypeName, ConstructorName)
+JS_ENUMERATE_ERROR_SUBCLASSES
+#undef __JS_ENUMERATE
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ErrorPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ErrorPrototype.cpp
new file mode 100644
index 0000000000..68affb5745
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ErrorPrototype.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/ErrorPrototype.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/PrimitiveString.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+ErrorPrototype::ErrorPrototype(GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void ErrorPrototype::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_property(vm.names.name, name_getter, name_setter, attr);
+ define_native_property(vm.names.message, message_getter, {}, attr);
+ define_native_function(vm.names.toString, to_string, 0, attr);
+}
+
+ErrorPrototype::~ErrorPrototype()
+{
+}
+
+JS_DEFINE_NATIVE_GETTER(ErrorPrototype::name_getter)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ if (!is<Error>(this_object)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAn, "Error");
+ return {};
+ }
+ return js_string(vm, static_cast<const Error*>(this_object)->name());
+}
+
+JS_DEFINE_NATIVE_SETTER(ErrorPrototype::name_setter)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return;
+ if (!is<Error>(this_object)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAn, "Error");
+ return;
+ }
+ auto name = value.to_string(global_object);
+ if (vm.exception())
+ return;
+ static_cast<Error*>(this_object)->set_name(name);
+}
+
+JS_DEFINE_NATIVE_GETTER(ErrorPrototype::message_getter)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ if (!is<Error>(this_object)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAn, "Error");
+ return {};
+ }
+ return js_string(vm, static_cast<const Error*>(this_object)->message());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ErrorPrototype::to_string)
+{
+ if (!vm.this_value(global_object).is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, vm.this_value(global_object).to_string_without_side_effects());
+ return {};
+ }
+ auto& this_object = vm.this_value(global_object).as_object();
+
+ String name = "Error";
+ auto name_property = this_object.get(vm.names.name);
+ if (vm.exception())
+ return {};
+ if (!name_property.is_empty() && !name_property.is_undefined()) {
+ name = name_property.to_string(global_object);
+ if (vm.exception())
+ return {};
+ }
+
+ String message = "";
+ auto message_property = this_object.get(vm.names.message);
+ if (vm.exception())
+ return {};
+ if (!message_property.is_empty() && !message_property.is_undefined()) {
+ message = message_property.to_string(global_object);
+ if (vm.exception())
+ return {};
+ }
+
+ if (name.length() == 0)
+ return js_string(vm, message);
+ if (message.length() == 0)
+ return js_string(vm, name);
+ return js_string(vm, String::formatted("{}: {}", name, message));
+}
+
+#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
+ PrototypeName::PrototypeName(GlobalObject& global_object) \
+ : Object(*global_object.error_prototype()) \
+ { \
+ } \
+ PrototypeName::~PrototypeName() { }
+
+JS_ENUMERATE_ERROR_SUBCLASSES
+#undef __JS_ENUMERATE
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ErrorPrototype.h b/Userland/Libraries/LibJS/Runtime/ErrorPrototype.h
new file mode 100644
index 0000000000..3b461942ab
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ErrorPrototype.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Error.h>
+
+namespace JS {
+
+class ErrorPrototype final : public Object {
+ JS_OBJECT(ErrorPrototype, Object);
+
+public:
+ explicit ErrorPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~ErrorPrototype() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(to_string);
+
+ JS_DECLARE_NATIVE_GETTER(name_getter);
+ JS_DECLARE_NATIVE_SETTER(name_setter);
+
+ JS_DECLARE_NATIVE_GETTER(message_getter);
+};
+
+#define DECLARE_ERROR_SUBCLASS_PROTOTYPE(ClassName, snake_name, PrototypeName, ConstructorName) \
+ class PrototypeName final : public Object { \
+ JS_OBJECT(PrototypeName, Object); \
+ \
+ public: \
+ explicit PrototypeName(GlobalObject&); \
+ virtual void initialize(GlobalObject&) override { } \
+ virtual ~PrototypeName() override; \
+ };
+
+#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
+ DECLARE_ERROR_SUBCLASS_PROTOTYPE(ClassName, snake_name, PrototypeName, ConstructorName)
+JS_ENUMERATE_ERROR_SUBCLASSES
+#undef __JS_ENUMERATE
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.cpp b/Userland/Libraries/LibJS/Runtime/ErrorTypes.cpp
new file mode 100644
index 0000000000..f71172b68a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/ErrorTypes.h>
+
+namespace JS {
+
+#define __ENUMERATE_JS_ERROR(name, message) \
+ const ErrorType ErrorType::name = ErrorType(message);
+JS_ENUMERATE_ERROR_TYPES(__ENUMERATE_JS_ERROR)
+#undef __ENUMERATE_JS_ERROR
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h
new file mode 100644
index 0000000000..e406d4377f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#define JS_ENUMERATE_ERROR_TYPES(M) \
+ M(ArrayMaxSize, "Maximum array size exceeded") \
+ M(ArrayPrototypeOneArg, "Array.prototype.{}() requires at least one argument") \
+ M(AccessorBadField, "Accessor descriptor's '{}' field must be a function or undefined") \
+ M(AccessorValueOrWritable, "Accessor property descriptor cannot specify a value or writable key") \
+ M(BigIntBadOperator, "Cannot use {} operator with BigInt") \
+ M(BigIntBadOperatorOtherType, "Cannot use {} operator with BigInt and other type") \
+ M(BigIntIntArgument, "BigInt argument must be an integer") \
+ M(BigIntInvalidValue, "Invalid value for BigInt: {}") \
+ M(ClassConstructorWithoutNew, "Class constructor {} must be called with 'new'") \
+ M(ClassIsAbstract, "Abstract class {} cannot be constructed directly") \
+ M(ClassDoesNotExtendAConstructorOrNull, "Class extends value {} is not a constructor or null") \
+ M(ConstructorWithoutNew, "{} constructor must be called with 'new'") \
+ M(Convert, "Cannot convert {} to {}") \
+ M(ConvertUndefinedToObject, "Cannot convert undefined to object") \
+ M(DescChangeNonConfigurable, "Cannot change attributes of non-configurable property '{}'") \
+ M(FunctionArgsNotObject, "Argument array must be an object") \
+ M(InOperatorWithObject, "'in' operator must be used on an object") \
+ M(InstanceOfOperatorBadPrototype, "'prototype' property of {} is not an object") \
+ M(InvalidAssignToConst, "Invalid assignment to const variable") \
+ M(InvalidIndex, "Index must be a positive integer") \
+ M(InvalidLeftHandAssignment, "Invalid left-hand side in assignment") \
+ M(InvalidLength, "Invalid {} length") \
+ M(InvalidRadix, "Radix must be an integer no less than 2, and no greater than 36") \
+ M(IsNotA, "{} is not a {}") \
+ M(IsNotAEvaluatedFrom, "{} is not a {} (evaluated from '{}')") \
+ M(IterableNextBadReturn, "iterator.next() returned a non-object value") \
+ M(IterableNextNotAFunction, "'next' property on returned object from Symbol.iterator method is " \
+ "not a function") \
+ M(JsonBigInt, "Cannot serialize BigInt value to JSON") \
+ M(JsonCircular, "Cannot stringify circular object") \
+ M(JsonMalformed, "Malformed JSON string") \
+ M(NotA, "Not a {} object") \
+ M(NotAConstructor, "{} is not a constructor") \
+ M(NotAFunction, "{} is not a function") \
+ M(NotAFunctionNoParam, "Not a function") \
+ M(NotAn, "Not an {} object") \
+ M(NotAnObject, "{} is not an object") \
+ M(NotASymbol, "{} is not a symbol") \
+ M(NotIterable, "{} is not iterable") \
+ M(NonExtensibleDefine, "Cannot define property {} on non-extensible object") \
+ M(NumberIncompatibleThis, "Number.prototype.{} method called with incompatible this target") \
+ M(ObjectDefinePropertyReturnedFalse, "Object's [[DefineProperty]] method returned false") \
+ M(ObjectSetPrototypeOfReturnedFalse, "Object's [[SetPrototypeOf]] method returned false") \
+ M(ObjectSetPrototypeOfTwoArgs, "Object.setPrototypeOf requires at least two arguments") \
+ M(ObjectPreventExtensionsReturnedFalse, "Object's [[PreventExtensions]] method returned false") \
+ M(ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, \
+ "Object prototype must not be {} on a super property access") \
+ M(ObjectPrototypeWrongType, "Prototype must be an object or null") \
+ M(ProxyConstructBadReturnType, "Proxy handler's construct trap violates invariant: must return " \
+ "an object") \
+ M(ProxyConstructorBadType, "Expected {} argument of Proxy constructor to be object, got {}") \
+ M(ProxyDefinePropExistingConfigurable, "Proxy handler's defineProperty trap violates " \
+ "invariant: a property cannot be defined as non-configurable if it already exists on the " \
+ "target object as a configurable property") \
+ M(ProxyDefinePropIncompatibleDescriptor, "Proxy handler's defineProperty trap violates " \
+ "invariant: the new descriptor is not compatible with the existing descriptor of the " \
+ "property on the target") \
+ M(ProxyDefinePropNonConfigurableNonExisting, "Proxy handler's defineProperty trap " \
+ "violates invariant: a property cannot be defined as non-configurable if it does not " \
+ "already exist on the target object") \
+ M(ProxyDefinePropNonExtensible, "Proxy handler's defineProperty trap violates invariant: " \
+ "a property cannot be reported as being defined if the property does not exist on " \
+ "the target and the target is non-extensible") \
+ M(ProxyDeleteNonConfigurable, "Proxy handler's deleteProperty trap violates invariant: " \
+ "cannot report a non-configurable own property of the target as deleted") \
+ M(ProxyGetImmutableDataProperty, "Proxy handler's get trap violates invariant: the " \
+ "returned value must match the value on the target if the property exists on the " \
+ "target as a non-writable, non-configurable own data property") \
+ M(ProxyGetNonConfigurableAccessor, "Proxy handler's get trap violates invariant: the " \
+ "returned value must be undefined if the property exists on the target as a " \
+ "non-configurable accessor property with an undefined get attribute") \
+ M(ProxyGetOwnDescriptorExistingConfigurable, "Proxy handler's getOwnPropertyDescriptor " \
+ "trap violates invariant: a property cannot be defined as non-configurable if it " \
+ "already exists on the target object as a configurable property") \
+ M(ProxyGetOwnDescriptorInvalidDescriptor, "Proxy handler's getOwnPropertyDescriptor trap " \
+ "violates invariant: invalid property descriptor for existing property on the target") \
+ M(ProxyGetOwnDescriptorInvalidNonConfig, "Proxy handler's getOwnPropertyDescriptor trap " \
+ "violates invariant: cannot report target's property as non-configurable if the " \
+ "property does not exist, or if it is configurable") \
+ M(ProxyGetOwnDescriptorNonConfigurable, "Proxy handler's getOwnPropertyDescriptor trap " \
+ "violates invariant: cannot return undefined for a property on the target which is " \
+ "a non-configurable property") \
+ M(ProxyGetOwnDescriptorReturn, "Proxy handler's getOwnPropertyDescriptor trap violates " \
+ "invariant: must return an object or undefined") \
+ M(ProxyGetOwnDescriptorUndefReturn, "Proxy handler's getOwnPropertyDescriptor trap " \
+ "violates invariant: cannot report a property as being undefined if it exists as an " \
+ "own property of the target and the target is non-extensible") \
+ M(ProxyGetPrototypeOfNonExtensible, "Proxy handler's getPrototypeOf trap violates " \
+ "invariant: cannot return a different prototype object for a non-extensible target") \
+ M(ProxyGetPrototypeOfReturn, "Proxy handler's getPrototypeOf trap violates invariant: " \
+ "must return an object or null") \
+ M(ProxyHasExistingNonConfigurable, "Proxy handler's has trap violates invariant: a " \
+ "property cannot be reported as non-existent if it exists on the target as a " \
+ "non-configurable property") \
+ M(ProxyHasExistingNonExtensible, "Proxy handler's has trap violates invariant: a property " \
+ "cannot be reported as non-existent if it exists on the target and the target is " \
+ "non-extensible") \
+ M(ProxyInvalidTrap, "Proxy handler's {} trap wasn't undefined, null, or callable") \
+ M(ProxyIsExtensibleReturn, "Proxy handler's isExtensible trap violates invariant: " \
+ "return value must match the target's extensibility") \
+ M(ProxyPreventExtensionsReturn, "Proxy handler's preventExtensions trap violates " \
+ "invariant: cannot return true if the target object is extensible") \
+ M(ProxyRevoked, "An operation was performed on a revoked Proxy object") \
+ M(ProxySetImmutableDataProperty, "Proxy handler's set trap violates invariant: cannot " \
+ "return true for a property on the target which is a non-configurable, non-writable " \
+ "own data property") \
+ M(ProxySetNonConfigurableAccessor, "Proxy handler's set trap violates invariant: cannot " \
+ "return true for a property on the target which is a non-configurable own accessor " \
+ "property with an undefined set attribute") \
+ M(ProxySetPrototypeOfNonExtensible, "Proxy handler's setPrototypeOf trap violates " \
+ "invariant: the argument must match the prototype of the target if the " \
+ "target is non-extensible") \
+ M(ProxyTwoArguments, "Proxy constructor requires at least two arguments") \
+ M(ReduceNoInitial, "Reduce of empty array with no initial value") \
+ M(ReferencePrimitiveAssignment, "Cannot assign property {} to primitive value") \
+ M(ReferenceUnresolvable, "Unresolvable reference") \
+ M(ReflectArgumentMustBeAFunction, "First argument of Reflect.{}() must be a function") \
+ M(ReflectArgumentMustBeAnObject, "First argument of Reflect.{}() must be an object") \
+ M(ReflectBadArgumentsList, "Arguments list must be an object") \
+ M(ReflectBadNewTarget, "Optional third argument of Reflect.construct() must be a constructor") \
+ M(ReflectBadDescriptorArgument, "Descriptor argument is not an object") \
+ M(RegExpCompileError, "RegExp compile error: {}") \
+ M(RegExpObjectBadFlag, "Invalid RegExp flag '{}'") \
+ M(RegExpObjectRepeatedFlag, "Repeated RegExp flag '{}'") \
+ M(StringRawCannotConvert, "Cannot convert property 'raw' to object from {}") \
+ M(StringRepeatCountMustBe, "repeat count must be a {} number") \
+ M(ThisHasNotBeenInitialized, "|this| has not been initialized") \
+ M(ThisIsAlreadyInitialized, "|this| is already initialized") \
+ M(ToObjectNullOrUndef, "ToObject on null or undefined") \
+ M(TypedArrayInvalidBufferLength, "Invalid buffer length for {}: must be a multiple of {}, got {}") \
+ M(TypedArrayInvalidByteOffset, "Invalid byte offset for {}: must be a multiple of {}, got {}") \
+ M(TypedArrayOutOfRangeByteOffset, "Typed array byte offset {} is out of range for buffer with length {}") \
+ M(TypedArrayOutOfRangeByteOffsetOrLength, "Typed array range {}:{} is out of range for buffer with length {}") \
+ M(UnknownIdentifier, "'{}' is not defined") \
+ /* LibWeb bindings */ \
+ M(NotAByteString, "Argument to {}() must be a byte string") \
+ M(BadArgCountOne, "{}() needs one argument") \
+ M(BadArgCountAtLeastOne, "{}() needs at least one argument") \
+ M(BadArgCountMany, "{}() needs {} arguments")
+
+namespace JS {
+
+class ErrorType {
+public:
+#define __ENUMERATE_JS_ERROR(name, message) \
+ static const ErrorType name;
+ JS_ENUMERATE_ERROR_TYPES(__ENUMERATE_JS_ERROR)
+#undef __ENUMERATE_JS_ERROR
+
+ const char* message() const
+ {
+ return m_message;
+ }
+
+private:
+ explicit ErrorType(const char* message)
+ : m_message(message)
+ {
+ }
+
+ const char* m_message;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Exception.cpp b/Userland/Libraries/LibJS/Runtime/Exception.cpp
new file mode 100644
index 0000000000..08c6f6bde9
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Exception.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <LibJS/AST.h>
+#include <LibJS/Runtime/Exception.h>
+#include <LibJS/Runtime/VM.h>
+
+namespace JS {
+
+Exception::Exception(Value value)
+ : m_value(value)
+{
+ auto& call_stack = vm().call_stack();
+ for (ssize_t i = call_stack.size() - 1; i >= 0; --i) {
+ String function_name = call_stack[i]->function_name;
+ if (function_name.is_empty())
+ function_name = "<anonymous>";
+ m_trace.append(function_name);
+ }
+
+ auto& node_stack = vm().node_stack();
+ for (ssize_t i = node_stack.size() - 1; i >= 0; --i) {
+ auto* node = node_stack[i];
+ ASSERT(node);
+ m_source_ranges.append(node->source_range());
+ }
+}
+
+Exception::~Exception()
+{
+}
+
+void Exception::visit_edges(Visitor& visitor)
+{
+ Cell::visit_edges(visitor);
+ visitor.visit(m_value);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Exception.h b/Userland/Libraries/LibJS/Runtime/Exception.h
new file mode 100644
index 0000000000..2d26447c89
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Exception.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Vector.h>
+#include <LibJS/Runtime/Cell.h>
+#include <LibJS/Runtime/Value.h>
+#include <LibJS/SourceRange.h>
+
+namespace JS {
+
+class Exception : public Cell {
+public:
+ explicit Exception(Value);
+ virtual ~Exception() override;
+
+ Value value() const { return m_value; }
+ const Vector<String>& trace() const { return m_trace; }
+ const Vector<SourceRange>& source_ranges() const { return m_source_ranges; }
+
+private:
+ virtual const char* class_name() const override { return "Exception"; }
+ virtual void visit_edges(Visitor&) override;
+
+ Value m_value;
+ Vector<String> m_trace;
+ Vector<SourceRange> m_source_ranges;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Function.cpp b/Userland/Libraries/LibJS/Runtime/Function.cpp
new file mode 100644
index 0000000000..43b7d6e7e1
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Function.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Interpreter.h>
+#include <LibJS/Runtime/BoundFunction.h>
+#include <LibJS/Runtime/Function.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+Function::Function(Object& prototype)
+ : Function(prototype, {}, {})
+{
+}
+
+Function::Function(Object& prototype, Value bound_this, Vector<Value> bound_arguments)
+ : Object(prototype)
+ , m_bound_this(bound_this)
+ , m_bound_arguments(move(bound_arguments))
+{
+}
+
+Function::~Function()
+{
+}
+
+BoundFunction* Function::bind(Value bound_this_value, Vector<Value> arguments)
+{
+ auto& vm = this->vm();
+ Function& target_function = is<BoundFunction>(*this) ? static_cast<BoundFunction&>(*this).target_function() : *this;
+
+ auto bound_this_object = [&vm, bound_this_value, this]() -> Value {
+ if (!m_bound_this.is_empty())
+ return m_bound_this;
+ switch (bound_this_value.type()) {
+ case Value::Type::Undefined:
+ case Value::Type::Null:
+ if (vm.in_strict_mode())
+ return bound_this_value;
+ return &global_object();
+ default:
+ return bound_this_value.to_object(global_object());
+ }
+ }();
+
+ i32 computed_length = 0;
+ auto length_property = get(vm.names.length);
+ if (vm.exception())
+ return nullptr;
+ if (length_property.is_number())
+ computed_length = max(0, length_property.as_i32() - static_cast<i32>(arguments.size()));
+
+ Object* constructor_prototype = nullptr;
+ auto prototype_property = target_function.get(vm.names.prototype);
+ if (vm.exception())
+ return nullptr;
+ if (prototype_property.is_object())
+ constructor_prototype = &prototype_property.as_object();
+
+ auto all_bound_arguments = bound_arguments();
+ all_bound_arguments.append(move(arguments));
+
+ return heap().allocate<BoundFunction>(global_object(), global_object(), target_function, bound_this_object, move(all_bound_arguments), computed_length, constructor_prototype);
+}
+
+void Function::visit_edges(Visitor& visitor)
+{
+ Object::visit_edges(visitor);
+
+ visitor.visit(m_bound_this);
+
+ for (auto argument : m_bound_arguments)
+ visitor.visit(argument);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Function.h b/Userland/Libraries/LibJS/Runtime/Function.h
new file mode 100644
index 0000000000..d54f059ec2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Function.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class Function : public Object {
+ JS_OBJECT(Function, Object);
+
+public:
+ enum class ConstructorKind {
+ Base,
+ Derived,
+ };
+
+ virtual ~Function();
+ virtual void initialize(GlobalObject&) override { }
+
+ virtual Value call() = 0;
+ virtual Value construct(Function& new_target) = 0;
+ virtual const FlyString& name() const = 0;
+ virtual LexicalEnvironment* create_environment() = 0;
+
+ virtual void visit_edges(Visitor&) override;
+
+ BoundFunction* bind(Value bound_this_value, Vector<Value> arguments);
+
+ Value bound_this() const { return m_bound_this; }
+
+ const Vector<Value>& bound_arguments() const { return m_bound_arguments; }
+
+ Value home_object() const { return m_home_object; }
+ void set_home_object(Value home_object) { m_home_object = home_object; }
+
+ ConstructorKind constructor_kind() const { return m_constructor_kind; };
+ void set_constructor_kind(ConstructorKind constructor_kind) { m_constructor_kind = constructor_kind; }
+
+ virtual bool is_strict_mode() const { return false; }
+
+protected:
+ explicit Function(Object& prototype);
+ Function(Object& prototype, Value bound_this, Vector<Value> bound_arguments);
+
+private:
+ virtual bool is_function() const override { return true; }
+ Value m_bound_this;
+ Vector<Value> m_bound_arguments;
+ Value m_home_object;
+ ConstructorKind m_constructor_kind = ConstructorKind::Base;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp
new file mode 100644
index 0000000000..b8664907f2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibJS/AST.h>
+#include <LibJS/Interpreter.h>
+#include <LibJS/Parser.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/FunctionConstructor.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/ScriptFunction.h>
+
+namespace JS {
+
+FunctionConstructor::FunctionConstructor(GlobalObject& global_object)
+ : NativeFunction(vm().names.Function, *global_object.function_prototype())
+{
+}
+
+void FunctionConstructor::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+ define_property(vm.names.prototype, global_object.function_prototype(), 0);
+ define_property(vm.names.length, Value(1), Attribute::Configurable);
+}
+
+FunctionConstructor::~FunctionConstructor()
+{
+}
+
+Value FunctionConstructor::call()
+{
+ return construct(*this);
+}
+
+Value FunctionConstructor::construct(Function&)
+{
+ auto& vm = this->vm();
+ String parameters_source = "";
+ String body_source = "";
+ if (vm.argument_count() == 1) {
+ body_source = vm.argument(0).to_string(global_object());
+ if (vm.exception())
+ return {};
+ }
+ if (vm.argument_count() > 1) {
+ Vector<String> parameters;
+ for (size_t i = 0; i < vm.argument_count() - 1; ++i) {
+ parameters.append(vm.argument(i).to_string(global_object()));
+ if (vm.exception())
+ return {};
+ }
+ StringBuilder parameters_builder;
+ parameters_builder.join(',', parameters);
+ parameters_source = parameters_builder.build();
+ body_source = vm.argument(vm.argument_count() - 1).to_string(global_object());
+ if (vm.exception())
+ return {};
+ }
+ auto source = String::formatted("function anonymous({}\n) {{\n{}\n}}", parameters_source, body_source);
+ auto parser = Parser(Lexer(source));
+ auto function_expression = parser.parse_function_node<FunctionExpression>();
+ if (parser.has_errors()) {
+ auto error = parser.errors()[0];
+ vm.throw_exception<SyntaxError>(global_object(), error.to_string());
+ return {};
+ }
+
+ OwnPtr<Interpreter> local_interpreter;
+ Interpreter* interpreter = vm.interpreter_if_exists();
+
+ if (!interpreter) {
+ local_interpreter = Interpreter::create_with_existing_global_object(global_object());
+ interpreter = local_interpreter.ptr();
+ }
+
+ VM::InterpreterExecutionScope scope(*interpreter);
+ return function_expression->execute(*interpreter, global_object());
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/FunctionConstructor.h b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.h
new file mode 100644
index 0000000000..70405d43e4
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class FunctionConstructor final : public NativeFunction {
+ JS_OBJECT(FunctionConstructor, NativeFunction);
+
+public:
+ explicit FunctionConstructor(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~FunctionConstructor() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp b/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp
new file mode 100644
index 0000000000..972c5021ea
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <AK/StringBuilder.h>
+#include <LibJS/AST.h>
+#include <LibJS/Interpreter.h>
+#include <LibJS/Runtime/BoundFunction.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/Function.h>
+#include <LibJS/Runtime/FunctionPrototype.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/MarkedValueList.h>
+#include <LibJS/Runtime/NativeFunction.h>
+#include <LibJS/Runtime/ScriptFunction.h>
+
+namespace JS {
+
+FunctionPrototype::FunctionPrototype(GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void FunctionPrototype::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_function(vm.names.apply, apply, 2, attr);
+ define_native_function(vm.names.bind, bind, 1, attr);
+ define_native_function(vm.names.call, call, 1, attr);
+ define_native_function(vm.names.toString, to_string, 0, attr);
+ define_native_function(vm.well_known_symbol_has_instance(), symbol_has_instance, 1, 0);
+ define_property(vm.names.length, Value(0), Attribute::Configurable);
+ define_property(vm.names.name, js_string(heap(), ""), Attribute::Configurable);
+}
+
+FunctionPrototype::~FunctionPrototype()
+{
+}
+
+JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::apply)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ if (!this_object->is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Function");
+ return {};
+ }
+ auto& function = static_cast<Function&>(*this_object);
+ auto this_arg = vm.argument(0);
+ auto arg_array = vm.argument(1);
+ if (arg_array.is_nullish())
+ return vm.call(function, this_arg);
+ if (!arg_array.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::FunctionArgsNotObject);
+ return {};
+ }
+ auto length = length_of_array_like(global_object, arg_array.as_object());
+ if (vm.exception())
+ return {};
+ MarkedValueList arguments(vm.heap());
+ for (size_t i = 0; i < length; ++i) {
+ auto element = arg_array.as_object().get(i);
+ if (vm.exception())
+ return {};
+ arguments.append(element.value_or(js_undefined()));
+ }
+ return vm.call(function, this_arg, move(arguments));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::bind)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ if (!this_object->is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Function");
+ return {};
+ }
+ auto& this_function = static_cast<Function&>(*this_object);
+ auto bound_this_arg = vm.argument(0);
+
+ Vector<Value> arguments;
+ if (vm.argument_count() > 1) {
+ arguments = vm.call_frame().arguments;
+ arguments.remove(0);
+ }
+
+ return this_function.bind(bound_this_arg, move(arguments));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::call)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ if (!this_object->is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Function");
+ return {};
+ }
+ auto& function = static_cast<Function&>(*this_object);
+ auto this_arg = vm.argument(0);
+ MarkedValueList arguments(vm.heap());
+ if (vm.argument_count() > 1) {
+ for (size_t i = 1; i < vm.argument_count(); ++i)
+ arguments.append(vm.argument(i));
+ }
+ return vm.call(function, this_arg, move(arguments));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::to_string)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ if (!this_object->is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Function");
+ return {};
+ }
+ String function_name = static_cast<Function*>(this_object)->name();
+ String function_parameters = "";
+ String function_body;
+
+ if (is<NativeFunction>(this_object) || is<BoundFunction>(this_object)) {
+ function_body = String::formatted(" [{}]", this_object->class_name());
+ } else {
+ StringBuilder parameters_builder;
+ auto first = true;
+ for (auto& parameter : static_cast<ScriptFunction*>(this_object)->parameters()) {
+ if (!first)
+ parameters_builder.append(", ");
+ first = false;
+ parameters_builder.append(parameter.name);
+ if (parameter.default_value) {
+ // FIXME: See note below
+ parameters_builder.append(" = TODO");
+ }
+ }
+ function_parameters = parameters_builder.build();
+ // FIXME: ASTNodes should be able to dump themselves to source strings - something like this:
+ // auto& body = static_cast<ScriptFunction*>(this_object)->body();
+ // function_body = body.to_source();
+ function_body = " ???";
+ }
+
+ auto function_source = String::formatted(
+ "function {}({}) {{\n{}\n}}",
+ function_name.is_null() ? "" : function_name, function_parameters, function_body);
+ return js_string(vm, function_source);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::symbol_has_instance)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ if (!this_object->is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Function");
+ return {};
+ }
+ return ordinary_has_instance(global_object, vm.argument(0), this_object);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/FunctionPrototype.h b/Userland/Libraries/LibJS/Runtime/FunctionPrototype.h
new file mode 100644
index 0000000000..2cb74504ad
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/FunctionPrototype.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class FunctionPrototype final : public Object {
+ JS_OBJECT(FunctionPrototype, Object);
+
+public:
+ explicit FunctionPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~FunctionPrototype() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(apply);
+ JS_DECLARE_NATIVE_FUNCTION(bind);
+ JS_DECLARE_NATIVE_FUNCTION(call);
+ JS_DECLARE_NATIVE_FUNCTION(to_string);
+ JS_DECLARE_NATIVE_FUNCTION(symbol_has_instance);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
new file mode 100644
index 0000000000..37135f907c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Utf8View.h>
+#include <LibJS/Console.h>
+#include <LibJS/Heap/DeferGC.h>
+#include <LibJS/Runtime/ArrayBufferConstructor.h>
+#include <LibJS/Runtime/ArrayBufferPrototype.h>
+#include <LibJS/Runtime/ArrayConstructor.h>
+#include <LibJS/Runtime/ArrayIteratorPrototype.h>
+#include <LibJS/Runtime/ArrayPrototype.h>
+#include <LibJS/Runtime/BigIntConstructor.h>
+#include <LibJS/Runtime/BigIntPrototype.h>
+#include <LibJS/Runtime/BooleanConstructor.h>
+#include <LibJS/Runtime/BooleanPrototype.h>
+#include <LibJS/Runtime/ConsoleObject.h>
+#include <LibJS/Runtime/DateConstructor.h>
+#include <LibJS/Runtime/DatePrototype.h>
+#include <LibJS/Runtime/ErrorConstructor.h>
+#include <LibJS/Runtime/ErrorPrototype.h>
+#include <LibJS/Runtime/FunctionConstructor.h>
+#include <LibJS/Runtime/FunctionPrototype.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/IteratorPrototype.h>
+#include <LibJS/Runtime/JSONObject.h>
+#include <LibJS/Runtime/MathObject.h>
+#include <LibJS/Runtime/NativeFunction.h>
+#include <LibJS/Runtime/NumberConstructor.h>
+#include <LibJS/Runtime/NumberPrototype.h>
+#include <LibJS/Runtime/Object.h>
+#include <LibJS/Runtime/ObjectConstructor.h>
+#include <LibJS/Runtime/ObjectPrototype.h>
+#include <LibJS/Runtime/ProxyConstructor.h>
+#include <LibJS/Runtime/ReflectObject.h>
+#include <LibJS/Runtime/RegExpConstructor.h>
+#include <LibJS/Runtime/RegExpPrototype.h>
+#include <LibJS/Runtime/Shape.h>
+#include <LibJS/Runtime/StringConstructor.h>
+#include <LibJS/Runtime/StringIteratorPrototype.h>
+#include <LibJS/Runtime/StringPrototype.h>
+#include <LibJS/Runtime/SymbolConstructor.h>
+#include <LibJS/Runtime/SymbolPrototype.h>
+#include <LibJS/Runtime/TypedArray.h>
+#include <LibJS/Runtime/TypedArrayConstructor.h>
+#include <LibJS/Runtime/TypedArrayPrototype.h>
+#include <LibJS/Runtime/Value.h>
+#include <ctype.h>
+
+namespace JS {
+
+GlobalObject::GlobalObject()
+ : ScopeObject(GlobalObjectTag::Tag)
+ , m_console(make<Console>(*this))
+{
+}
+
+void GlobalObject::initialize()
+{
+ auto& vm = this->vm();
+
+ ensure_shape_is_unique();
+
+ // These are done first since other prototypes depend on their presence.
+ m_empty_object_shape = heap().allocate_without_global_object<Shape>(*this);
+ m_object_prototype = heap().allocate_without_global_object<ObjectPrototype>(*this);
+ m_function_prototype = heap().allocate_without_global_object<FunctionPrototype>(*this);
+
+ m_new_object_shape = vm.heap().allocate_without_global_object<Shape>(*this);
+ m_new_object_shape->set_prototype_without_transition(m_object_prototype);
+
+ m_new_script_function_prototype_object_shape = vm.heap().allocate_without_global_object<Shape>(*this);
+ m_new_script_function_prototype_object_shape->set_prototype_without_transition(m_object_prototype);
+ m_new_script_function_prototype_object_shape->add_property_without_transition(vm.names.constructor, Attribute::Writable | Attribute::Configurable);
+
+ static_cast<FunctionPrototype*>(m_function_prototype)->initialize(*this);
+ static_cast<ObjectPrototype*>(m_object_prototype)->initialize(*this);
+
+ set_prototype(m_object_prototype);
+
+#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
+ if (!m_##snake_name##_prototype) \
+ m_##snake_name##_prototype = heap().allocate<PrototypeName>(*this, *this);
+ JS_ENUMERATE_BUILTIN_TYPES
+#undef __JS_ENUMERATE
+
+#define __JS_ENUMERATE(ClassName, snake_name) \
+ if (!m_##snake_name##_prototype) \
+ m_##snake_name##_prototype = heap().allocate<ClassName##Prototype>(*this, *this);
+ JS_ENUMERATE_ITERATOR_PROTOTYPES
+#undef __JS_ENUMERATE
+
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_function(vm.names.gc, gc, 0, attr);
+ define_native_function(vm.names.isNaN, is_nan, 1, attr);
+ define_native_function(vm.names.isFinite, is_finite, 1, attr);
+ define_native_function(vm.names.parseFloat, parse_float, 1, attr);
+ define_native_function(vm.names.parseInt, parse_int, 1, attr);
+
+ define_property(vm.names.NaN, js_nan(), 0);
+ define_property(vm.names.Infinity, js_infinity(), 0);
+ define_property(vm.names.undefined, js_undefined(), 0);
+
+ define_property(vm.names.globalThis, this, attr);
+ define_property(vm.names.console, heap().allocate<ConsoleObject>(*this, *this), attr);
+ define_property(vm.names.Math, heap().allocate<MathObject>(*this, *this), attr);
+ define_property(vm.names.JSON, heap().allocate<JSONObject>(*this, *this), attr);
+ define_property(vm.names.Reflect, heap().allocate<ReflectObject>(*this, *this), attr);
+
+ add_constructor(vm.names.Array, m_array_constructor, m_array_prototype);
+ add_constructor(vm.names.ArrayBuffer, m_array_buffer_constructor, m_array_buffer_prototype);
+ add_constructor(vm.names.BigInt, m_bigint_constructor, m_bigint_prototype);
+ add_constructor(vm.names.Boolean, m_boolean_constructor, m_boolean_prototype);
+ add_constructor(vm.names.Date, m_date_constructor, m_date_prototype);
+ add_constructor(vm.names.Error, m_error_constructor, m_error_prototype);
+ add_constructor(vm.names.Function, m_function_constructor, m_function_prototype);
+ add_constructor(vm.names.Number, m_number_constructor, m_number_prototype);
+ add_constructor(vm.names.Object, m_object_constructor, m_object_prototype);
+ add_constructor(vm.names.Proxy, m_proxy_constructor, nullptr);
+ add_constructor(vm.names.RegExp, m_regexp_constructor, m_regexp_prototype);
+ add_constructor(vm.names.String, m_string_constructor, m_string_prototype);
+ add_constructor(vm.names.Symbol, m_symbol_constructor, m_symbol_prototype);
+
+ initialize_constructor(vm.names.TypedArray, m_typed_array_constructor, m_typed_array_prototype);
+
+#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
+ add_constructor(vm.names.ClassName, m_##snake_name##_constructor, m_##snake_name##_prototype);
+ JS_ENUMERATE_ERROR_SUBCLASSES
+ JS_ENUMERATE_TYPED_ARRAYS
+#undef __JS_ENUMERATE
+}
+
+GlobalObject::~GlobalObject()
+{
+}
+
+void GlobalObject::visit_edges(Visitor& visitor)
+{
+ Base::visit_edges(visitor);
+
+ visitor.visit(m_empty_object_shape);
+ visitor.visit(m_new_object_shape);
+ visitor.visit(m_new_script_function_prototype_object_shape);
+ visitor.visit(m_proxy_constructor);
+
+#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
+ visitor.visit(m_##snake_name##_constructor);
+ JS_ENUMERATE_ERROR_SUBCLASSES
+#undef __JS_ENUMERATE
+
+#define __JS_ENUMERATE(ClassName, snake_name) \
+ visitor.visit(m_##snake_name##_prototype);
+ JS_ENUMERATE_ITERATOR_PROTOTYPES
+#undef __JS_ENUMERATE
+}
+
+JS_DEFINE_NATIVE_FUNCTION(GlobalObject::gc)
+{
+ dbgln("Forced garbage collection requested!");
+ vm.heap().collect_garbage();
+ return js_undefined();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(GlobalObject::is_nan)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ return Value(number.is_nan());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(GlobalObject::is_finite)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ return Value(number.is_finite_number());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(GlobalObject::parse_float)
+{
+ if (vm.argument(0).is_number())
+ return vm.argument(0);
+ auto string = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+ for (size_t length = string.length(); length > 0; --length) {
+ // This can't throw, so no exception check is fine.
+ auto number = Value(js_string(vm, string.substring(0, length))).to_number(global_object);
+ if (!number.is_nan())
+ return number;
+ }
+ return js_nan();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(GlobalObject::parse_int)
+{
+ // 18.2.5 parseInt ( string, radix )
+ auto input_string = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+
+ // FIXME: There's a bunch of unnecessary string copying here.
+ double sign = 1;
+ auto s = input_string.trim_whitespace(TrimMode::Left);
+ if (!s.is_empty() && s[0] == '-')
+ sign = -1;
+ if (!s.is_empty() && (s[0] == '+' || s[0] == '-'))
+ s = s.substring(1, s.length() - 1);
+
+ auto radix = vm.argument(1).to_i32(global_object);
+ if (vm.exception())
+ return {};
+
+ bool strip_prefix = true;
+ if (radix != 0) {
+ if (radix < 2 || radix > 36)
+ return js_nan();
+ if (radix != 16)
+ strip_prefix = false;
+ } else {
+ radix = 10;
+ }
+
+ if (strip_prefix) {
+ if (s.length() >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
+ s = s.substring(2, s.length() - 2);
+ radix = 16;
+ }
+ }
+
+ auto parse_digit = [&](u32 codepoint, i32 radix) -> Optional<i32> {
+ i32 digit = -1;
+
+ if (isdigit(codepoint))
+ digit = codepoint - '0';
+ else if (islower(codepoint))
+ digit = 10 + (codepoint - 'a');
+ else if (isupper(codepoint))
+ digit = 10 + (codepoint - 'A');
+
+ if (digit == -1 || digit >= radix)
+ return {};
+ return digit;
+ };
+
+ bool had_digits = false;
+ double number = 0;
+ for (auto codepoint : Utf8View(s)) {
+ auto digit = parse_digit(codepoint, radix);
+ if (!digit.has_value())
+ break;
+ had_digits = true;
+ number *= radix;
+ number += digit.value();
+ }
+
+ if (!had_digits)
+ return js_nan();
+
+ return Value(sign * number);
+}
+
+Optional<Variable> GlobalObject::get_from_scope(const FlyString& name) const
+{
+ auto value = get(name);
+ if (value.is_empty())
+ return {};
+ return Variable { value, DeclarationKind::Var };
+}
+
+void GlobalObject::put_to_scope(const FlyString& name, Variable variable)
+{
+ put(name, variable.value);
+}
+
+bool GlobalObject::has_this_binding() const
+{
+ return true;
+}
+
+Value GlobalObject::get_this_binding(GlobalObject&) const
+{
+ return Value(this);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.h b/Userland/Libraries/LibJS/Runtime/GlobalObject.h
new file mode 100644
index 0000000000..d3b7041e43
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/ScopeObject.h>
+#include <LibJS/Runtime/VM.h>
+
+namespace JS {
+
+class GlobalObject : public ScopeObject {
+ JS_OBJECT(GlobalObject, ScopeObject);
+
+public:
+ explicit GlobalObject();
+ virtual void initialize();
+
+ virtual ~GlobalObject() override;
+
+ virtual Optional<Variable> get_from_scope(const FlyString&) const override;
+ virtual void put_to_scope(const FlyString&, Variable) override;
+ virtual bool has_this_binding() const override;
+ virtual Value get_this_binding(GlobalObject&) const override;
+
+ Console& console() { return *m_console; }
+
+ Shape* empty_object_shape() { return m_empty_object_shape; }
+
+ Shape* new_object_shape() { return m_new_object_shape; }
+ Shape* new_script_function_prototype_object_shape() { return m_new_script_function_prototype_object_shape; }
+
+ // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype
+ ProxyConstructor* proxy_constructor() { return m_proxy_constructor; }
+
+#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
+ ConstructorName* snake_name##_constructor() { return m_##snake_name##_constructor; } \
+ Object* snake_name##_prototype() { return m_##snake_name##_prototype; }
+ JS_ENUMERATE_BUILTIN_TYPES
+#undef __JS_ENUMERATE
+
+#define __JS_ENUMERATE(ClassName, snake_name) \
+ Object* snake_name##_prototype() { return m_##snake_name##_prototype; }
+ JS_ENUMERATE_ITERATOR_PROTOTYPES
+#undef __JS_ENUMERATE
+
+protected:
+ virtual void visit_edges(Visitor&) override;
+
+ template<typename ConstructorType>
+ void initialize_constructor(const FlyString& property_name, ConstructorType*&, Object* prototype);
+ template<typename ConstructorType>
+ void add_constructor(const FlyString& property_name, ConstructorType*&, Object* prototype);
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(gc);
+ JS_DECLARE_NATIVE_FUNCTION(is_nan);
+ JS_DECLARE_NATIVE_FUNCTION(is_finite);
+ JS_DECLARE_NATIVE_FUNCTION(parse_float);
+ JS_DECLARE_NATIVE_FUNCTION(parse_int);
+
+ NonnullOwnPtr<Console> m_console;
+
+ Shape* m_empty_object_shape { nullptr };
+ Shape* m_new_object_shape { nullptr };
+ Shape* m_new_script_function_prototype_object_shape { nullptr };
+
+ // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype
+ ProxyConstructor* m_proxy_constructor { nullptr };
+
+#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
+ ConstructorName* m_##snake_name##_constructor { nullptr }; \
+ Object* m_##snake_name##_prototype { nullptr };
+ JS_ENUMERATE_BUILTIN_TYPES
+#undef __JS_ENUMERATE
+
+#define __JS_ENUMERATE(ClassName, snake_name) \
+ Object* m_##snake_name##_prototype { nullptr };
+ JS_ENUMERATE_ITERATOR_PROTOTYPES
+#undef __JS_ENUMERATE
+};
+
+template<typename ConstructorType>
+inline void GlobalObject::initialize_constructor(const FlyString& property_name, ConstructorType*& constructor, Object* prototype)
+{
+ auto& vm = this->vm();
+ constructor = heap().allocate<ConstructorType>(*this, *this);
+ constructor->define_property(vm.names.name, js_string(heap(), property_name), Attribute::Configurable);
+ if (vm.exception())
+ return;
+ if (prototype) {
+ prototype->define_property(vm.names.constructor, constructor, Attribute::Writable | Attribute::Configurable);
+ if (vm.exception())
+ return;
+ }
+}
+
+template<typename ConstructorType>
+inline void GlobalObject::add_constructor(const FlyString& property_name, ConstructorType*& constructor, Object* prototype)
+{
+ initialize_constructor(property_name, constructor, prototype);
+ define_property(property_name, constructor, Attribute::Writable | Attribute::Configurable);
+}
+
+inline GlobalObject* Shape::global_object() const
+{
+ return static_cast<GlobalObject*>(m_global_object);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp b/Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp
new file mode 100644
index 0000000000..7dc1b4b9a3
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp
@@ -0,0 +1,396 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/QuickSort.h>
+#include <LibJS/Runtime/Accessor.h>
+#include <LibJS/Runtime/IndexedProperties.h>
+
+namespace JS {
+
+SimpleIndexedPropertyStorage::SimpleIndexedPropertyStorage(Vector<Value>&& initial_values)
+ : m_array_size(initial_values.size())
+ , m_packed_elements(move(initial_values))
+{
+}
+
+bool SimpleIndexedPropertyStorage::has_index(u32 index) const
+{
+ return index < m_array_size && !m_packed_elements[index].is_empty();
+}
+
+Optional<ValueAndAttributes> SimpleIndexedPropertyStorage::get(u32 index) const
+{
+ if (index >= m_array_size)
+ return {};
+ return ValueAndAttributes { m_packed_elements[index], default_attributes };
+}
+
+void SimpleIndexedPropertyStorage::put(u32 index, Value value, PropertyAttributes attributes)
+{
+ ASSERT(attributes == default_attributes);
+ ASSERT(index < SPARSE_ARRAY_THRESHOLD);
+
+ if (index >= m_array_size) {
+ m_array_size = index + 1;
+ if (index >= m_packed_elements.size())
+ m_packed_elements.resize(index + MIN_PACKED_RESIZE_AMOUNT >= SPARSE_ARRAY_THRESHOLD ? SPARSE_ARRAY_THRESHOLD : index + MIN_PACKED_RESIZE_AMOUNT);
+ }
+ m_packed_elements[index] = value;
+}
+
+void SimpleIndexedPropertyStorage::remove(u32 index)
+{
+ if (index < m_array_size)
+ m_packed_elements[index] = {};
+}
+
+void SimpleIndexedPropertyStorage::insert(u32 index, Value value, PropertyAttributes attributes)
+{
+ ASSERT(attributes == default_attributes);
+ ASSERT(index < SPARSE_ARRAY_THRESHOLD);
+ m_array_size++;
+ ASSERT(m_array_size <= SPARSE_ARRAY_THRESHOLD);
+ m_packed_elements.insert(index, value);
+}
+
+ValueAndAttributes SimpleIndexedPropertyStorage::take_first()
+{
+ m_array_size--;
+ return { m_packed_elements.take_first(), default_attributes };
+}
+
+ValueAndAttributes SimpleIndexedPropertyStorage::take_last()
+{
+ m_array_size--;
+ auto last_element = m_packed_elements[m_array_size];
+ m_packed_elements[m_array_size] = {};
+ return { last_element, default_attributes };
+}
+
+void SimpleIndexedPropertyStorage::set_array_like_size(size_t new_size)
+{
+ ASSERT(new_size <= SPARSE_ARRAY_THRESHOLD);
+ m_array_size = new_size;
+ m_packed_elements.resize(new_size);
+}
+
+GenericIndexedPropertyStorage::GenericIndexedPropertyStorage(SimpleIndexedPropertyStorage&& storage)
+{
+ m_array_size = storage.array_like_size();
+ for (auto& element : move(storage.m_packed_elements))
+ m_packed_elements.append({ element, default_attributes });
+}
+
+bool GenericIndexedPropertyStorage::has_index(u32 index) const
+{
+ if (index < SPARSE_ARRAY_THRESHOLD)
+ return index < m_packed_elements.size() && !m_packed_elements[index].value.is_empty();
+ return m_sparse_elements.contains(index);
+}
+
+Optional<ValueAndAttributes> GenericIndexedPropertyStorage::get(u32 index) const
+{
+ if (index >= m_array_size)
+ return {};
+ if (index < SPARSE_ARRAY_THRESHOLD) {
+ if (index >= m_packed_elements.size())
+ return {};
+ return m_packed_elements[index];
+ }
+ return m_sparse_elements.get(index);
+}
+
+void GenericIndexedPropertyStorage::put(u32 index, Value value, PropertyAttributes attributes)
+{
+ if (index >= m_array_size)
+ m_array_size = index + 1;
+ if (index < SPARSE_ARRAY_THRESHOLD) {
+ if (index >= m_packed_elements.size())
+ m_packed_elements.resize(index + MIN_PACKED_RESIZE_AMOUNT >= SPARSE_ARRAY_THRESHOLD ? SPARSE_ARRAY_THRESHOLD : index + MIN_PACKED_RESIZE_AMOUNT);
+ m_packed_elements[index] = { value, attributes };
+ } else {
+ m_sparse_elements.set(index, { value, attributes });
+ }
+}
+
+void GenericIndexedPropertyStorage::remove(u32 index)
+{
+ if (index >= m_array_size)
+ return;
+ if (index + 1 == m_array_size) {
+ take_last();
+ return;
+ }
+ if (index < SPARSE_ARRAY_THRESHOLD) {
+ if (index < m_packed_elements.size())
+ m_packed_elements[index] = {};
+ } else {
+ m_sparse_elements.remove(index);
+ }
+}
+
+void GenericIndexedPropertyStorage::insert(u32 index, Value value, PropertyAttributes attributes)
+{
+ if (index >= m_array_size) {
+ put(index, value, attributes);
+ return;
+ }
+
+ m_array_size++;
+
+ if (!m_sparse_elements.is_empty()) {
+ HashMap<u32, ValueAndAttributes> new_sparse_elements;
+ for (auto& entry : m_sparse_elements)
+ new_sparse_elements.set(entry.key >= index ? entry.key + 1 : entry.key, entry.value);
+ m_sparse_elements = move(new_sparse_elements);
+ }
+
+ if (index < SPARSE_ARRAY_THRESHOLD) {
+ m_packed_elements.insert(index, { value, attributes });
+ } else {
+ m_sparse_elements.set(index, { value, attributes });
+ }
+}
+
+ValueAndAttributes GenericIndexedPropertyStorage::take_first()
+{
+ ASSERT(m_array_size > 0);
+ m_array_size--;
+
+ if (!m_sparse_elements.is_empty()) {
+ HashMap<u32, ValueAndAttributes> new_sparse_elements;
+ for (auto& entry : m_sparse_elements)
+ new_sparse_elements.set(entry.key - 1, entry.value);
+ m_sparse_elements = move(new_sparse_elements);
+ }
+
+ return m_packed_elements.take_first();
+}
+
+ValueAndAttributes GenericIndexedPropertyStorage::take_last()
+{
+ ASSERT(m_array_size > 0);
+ m_array_size--;
+
+ if (m_array_size <= SPARSE_ARRAY_THRESHOLD) {
+ auto last_element = m_packed_elements[m_array_size];
+ m_packed_elements[m_array_size] = {};
+ return last_element;
+ } else {
+ auto result = m_sparse_elements.get(m_array_size);
+ m_sparse_elements.remove(m_array_size);
+ ASSERT(result.has_value());
+ return result.value();
+ }
+}
+
+void GenericIndexedPropertyStorage::set_array_like_size(size_t new_size)
+{
+ m_array_size = new_size;
+ if (new_size < SPARSE_ARRAY_THRESHOLD) {
+ m_packed_elements.resize(new_size);
+ m_sparse_elements.clear();
+ } else {
+ m_packed_elements.resize(SPARSE_ARRAY_THRESHOLD);
+
+ HashMap<u32, ValueAndAttributes> new_sparse_elements;
+ for (auto& entry : m_sparse_elements) {
+ if (entry.key < new_size)
+ new_sparse_elements.set(entry.key, entry.value);
+ }
+ m_sparse_elements = move(new_sparse_elements);
+ }
+}
+
+IndexedPropertyIterator::IndexedPropertyIterator(const IndexedProperties& indexed_properties, u32 staring_index, bool skip_empty)
+ : m_indexed_properties(indexed_properties)
+ , m_index(staring_index)
+ , m_skip_empty(skip_empty)
+{
+ if (m_skip_empty)
+ skip_empty_indices();
+}
+
+IndexedPropertyIterator& IndexedPropertyIterator::operator++()
+{
+ m_index++;
+
+ if (m_skip_empty)
+ skip_empty_indices();
+
+ return *this;
+}
+
+IndexedPropertyIterator& IndexedPropertyIterator::operator*()
+{
+ return *this;
+}
+
+bool IndexedPropertyIterator::operator!=(const IndexedPropertyIterator& other) const
+{
+ return m_index != other.m_index;
+}
+
+ValueAndAttributes IndexedPropertyIterator::value_and_attributes(Object* this_object, bool evaluate_accessors)
+{
+ if (m_index < m_indexed_properties.array_like_size())
+ return m_indexed_properties.get(this_object, m_index, evaluate_accessors).value_or({});
+ return {};
+}
+
+void IndexedPropertyIterator::skip_empty_indices()
+{
+ auto indices = m_indexed_properties.indices();
+ for (auto i : indices) {
+ if (i < m_index)
+ continue;
+ m_index = i;
+ return;
+ }
+ m_index = m_indexed_properties.array_like_size();
+}
+
+Optional<ValueAndAttributes> IndexedProperties::get(Object* this_object, u32 index, bool evaluate_accessors) const
+{
+ auto result = m_storage->get(index);
+ if (!evaluate_accessors)
+ return result;
+ if (!result.has_value())
+ return {};
+ auto& value = result.value();
+ if (value.value.is_accessor()) {
+ ASSERT(this_object);
+ auto& accessor = value.value.as_accessor();
+ return ValueAndAttributes { accessor.call_getter(this_object), value.attributes };
+ }
+ return result;
+}
+
+void IndexedProperties::put(Object* this_object, u32 index, Value value, PropertyAttributes attributes, bool evaluate_accessors)
+{
+ if (m_storage->is_simple_storage() && (index >= SPARSE_ARRAY_THRESHOLD || attributes != default_attributes))
+ switch_to_generic_storage();
+ if (m_storage->is_simple_storage() || !evaluate_accessors) {
+ m_storage->put(index, value, attributes);
+ return;
+ }
+
+ auto value_here = m_storage->get(index);
+ if (value_here.has_value() && value_here.value().value.is_accessor()) {
+ ASSERT(this_object);
+ value_here.value().value.as_accessor().call_setter(this_object, value);
+ } else {
+ m_storage->put(index, value, attributes);
+ }
+}
+
+bool IndexedProperties::remove(u32 index)
+{
+ auto result = m_storage->get(index);
+ if (!result.has_value())
+ return true;
+ if (!result.value().attributes.is_configurable())
+ return false;
+ m_storage->remove(index);
+ return true;
+}
+
+void IndexedProperties::insert(u32 index, Value value, PropertyAttributes attributes)
+{
+ if (m_storage->is_simple_storage() && (index >= SPARSE_ARRAY_THRESHOLD || attributes != default_attributes || array_like_size() == SPARSE_ARRAY_THRESHOLD))
+ switch_to_generic_storage();
+ m_storage->insert(index, move(value), attributes);
+}
+
+ValueAndAttributes IndexedProperties::take_first(Object* this_object)
+{
+ auto first = m_storage->take_first();
+ if (first.value.is_accessor())
+ return { first.value.as_accessor().call_getter(this_object), first.attributes };
+ return first;
+}
+
+ValueAndAttributes IndexedProperties::take_last(Object* this_object)
+{
+ auto last = m_storage->take_last();
+ if (last.value.is_accessor())
+ return { last.value.as_accessor().call_getter(this_object), last.attributes };
+ return last;
+}
+
+void IndexedProperties::append_all(Object* this_object, const IndexedProperties& properties, bool evaluate_accessors)
+{
+ if (m_storage->is_simple_storage() && !properties.m_storage->is_simple_storage())
+ switch_to_generic_storage();
+
+ for (auto it = properties.begin(false); it != properties.end(); ++it) {
+ const auto& element = it.value_and_attributes(this_object, evaluate_accessors);
+ if (this_object && this_object->vm().exception())
+ return;
+ m_storage->put(m_storage->array_like_size(), element.value, element.attributes);
+ }
+}
+
+void IndexedProperties::set_array_like_size(size_t new_size)
+{
+ if (m_storage->is_simple_storage() && new_size > SPARSE_ARRAY_THRESHOLD)
+ switch_to_generic_storage();
+ m_storage->set_array_like_size(new_size);
+}
+
+Vector<u32> IndexedProperties::indices() const
+{
+ Vector<u32> indices;
+ if (m_storage->is_simple_storage()) {
+ const auto& storage = static_cast<const SimpleIndexedPropertyStorage&>(*m_storage);
+ const auto& elements = storage.elements();
+ indices.ensure_capacity(storage.array_like_size());
+ for (size_t i = 0; i < elements.size(); ++i) {
+ if (!elements.at(i).is_empty())
+ indices.unchecked_append(i);
+ }
+ } else {
+ const auto& storage = static_cast<const GenericIndexedPropertyStorage&>(*m_storage);
+ const auto packed_elements = storage.packed_elements();
+ indices.ensure_capacity(storage.array_like_size());
+ for (size_t i = 0; i < packed_elements.size(); ++i) {
+ if (!packed_elements.at(i).value.is_empty())
+ indices.unchecked_append(i);
+ }
+ auto sparse_elements_keys = storage.sparse_elements().keys();
+ quick_sort(sparse_elements_keys);
+ indices.append(move(sparse_elements_keys));
+ }
+ return indices;
+}
+
+void IndexedProperties::switch_to_generic_storage()
+{
+ auto& storage = static_cast<SimpleIndexedPropertyStorage&>(*m_storage);
+ m_storage = make<GenericIndexedPropertyStorage>(move(storage));
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/IndexedProperties.h b/Userland/Libraries/LibJS/Runtime/IndexedProperties.h
new file mode 100644
index 0000000000..984c3cef4c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/IndexedProperties.h
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullOwnPtr.h>
+#include <LibJS/Runtime/Shape.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+const u32 SPARSE_ARRAY_THRESHOLD = 200;
+const u32 MIN_PACKED_RESIZE_AMOUNT = 20;
+
+struct ValueAndAttributes {
+ Value value;
+ PropertyAttributes attributes { default_attributes };
+};
+
+class IndexedProperties;
+class IndexedPropertyIterator;
+class GenericIndexedPropertyStorage;
+
+class IndexedPropertyStorage {
+public:
+ virtual ~IndexedPropertyStorage() {};
+
+ virtual bool has_index(u32 index) const = 0;
+ virtual Optional<ValueAndAttributes> get(u32 index) const = 0;
+ virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) = 0;
+ virtual void remove(u32 index) = 0;
+
+ virtual void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes) = 0;
+ virtual ValueAndAttributes take_first() = 0;
+ virtual ValueAndAttributes take_last() = 0;
+
+ virtual size_t size() const = 0;
+ virtual size_t array_like_size() const = 0;
+ virtual void set_array_like_size(size_t new_size) = 0;
+
+ virtual bool is_simple_storage() const { return false; }
+};
+
+class SimpleIndexedPropertyStorage final : public IndexedPropertyStorage {
+public:
+ SimpleIndexedPropertyStorage() = default;
+ explicit SimpleIndexedPropertyStorage(Vector<Value>&& initial_values);
+
+ virtual bool has_index(u32 index) const override;
+ virtual Optional<ValueAndAttributes> get(u32 index) const override;
+ virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
+ virtual void remove(u32 index) override;
+
+ virtual void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
+ virtual ValueAndAttributes take_first() override;
+ virtual ValueAndAttributes take_last() override;
+
+ virtual size_t size() const override { return m_packed_elements.size(); }
+ virtual size_t array_like_size() const override { return m_array_size; }
+ virtual void set_array_like_size(size_t new_size) override;
+
+ virtual bool is_simple_storage() const override { return true; }
+ const Vector<Value>& elements() const { return m_packed_elements; }
+
+private:
+ friend GenericIndexedPropertyStorage;
+
+ size_t m_array_size { 0 };
+ Vector<Value> m_packed_elements;
+};
+
+class GenericIndexedPropertyStorage final : public IndexedPropertyStorage {
+public:
+ explicit GenericIndexedPropertyStorage(SimpleIndexedPropertyStorage&&);
+
+ virtual bool has_index(u32 index) const override;
+ virtual Optional<ValueAndAttributes> get(u32 index) const override;
+ virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
+ virtual void remove(u32 index) override;
+
+ virtual void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
+ virtual ValueAndAttributes take_first() override;
+ virtual ValueAndAttributes take_last() override;
+
+ virtual size_t size() const override { return m_packed_elements.size() + m_sparse_elements.size(); }
+ virtual size_t array_like_size() const override { return m_array_size; }
+ virtual void set_array_like_size(size_t new_size) override;
+
+ const Vector<ValueAndAttributes>& packed_elements() const { return m_packed_elements; }
+ const HashMap<u32, ValueAndAttributes>& sparse_elements() const { return m_sparse_elements; }
+
+private:
+ size_t m_array_size { 0 };
+ Vector<ValueAndAttributes> m_packed_elements;
+ HashMap<u32, ValueAndAttributes> m_sparse_elements;
+};
+
+class IndexedPropertyIterator {
+public:
+ IndexedPropertyIterator(const IndexedProperties&, u32 starting_index, bool skip_empty);
+
+ IndexedPropertyIterator& operator++();
+ IndexedPropertyIterator& operator*();
+ bool operator!=(const IndexedPropertyIterator&) const;
+
+ u32 index() const { return m_index; };
+ ValueAndAttributes value_and_attributes(Object* this_object, bool evaluate_accessors = true);
+
+private:
+ void skip_empty_indices();
+
+ const IndexedProperties& m_indexed_properties;
+ u32 m_index;
+ bool m_skip_empty;
+};
+
+class IndexedProperties {
+public:
+ IndexedProperties() = default;
+
+ IndexedProperties(Vector<Value>&& values)
+ : m_storage(make<SimpleIndexedPropertyStorage>(move(values)))
+ {
+ }
+
+ bool has_index(u32 index) const { return m_storage->has_index(index); }
+ Optional<ValueAndAttributes> get(Object* this_object, u32 index, bool evaluate_accessors = true) const;
+ void put(Object* this_object, u32 index, Value value, PropertyAttributes attributes = default_attributes, bool evaluate_accessors = true);
+ bool remove(u32 index);
+
+ void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes);
+ ValueAndAttributes take_first(Object* this_object);
+ ValueAndAttributes take_last(Object* this_object);
+
+ void append(Value value, PropertyAttributes attributes = default_attributes) { put(nullptr, array_like_size(), value, attributes, false); }
+ void append_all(Object* this_object, const IndexedProperties& properties, bool evaluate_accessors = true);
+
+ IndexedPropertyIterator begin(bool skip_empty = true) const { return IndexedPropertyIterator(*this, 0, skip_empty); };
+ IndexedPropertyIterator end() const { return IndexedPropertyIterator(*this, array_like_size(), false); };
+
+ bool is_empty() const { return array_like_size() == 0; }
+ size_t array_like_size() const { return m_storage->array_like_size(); }
+ void set_array_like_size(size_t);
+
+ Vector<u32> indices() const;
+
+ template<typename Callback>
+ void for_each_value(Callback callback)
+ {
+ if (m_storage->is_simple_storage()) {
+ for (auto& value : static_cast<SimpleIndexedPropertyStorage&>(*m_storage).elements())
+ callback(value);
+ } else {
+ for (auto& element : static_cast<const GenericIndexedPropertyStorage&>(*m_storage).packed_elements())
+ callback(element.value);
+ for (auto& element : static_cast<const GenericIndexedPropertyStorage&>(*m_storage).sparse_elements())
+ callback(element.value.value);
+ }
+ }
+
+private:
+ void switch_to_generic_storage();
+
+ NonnullOwnPtr<IndexedPropertyStorage> m_storage { make<SimpleIndexedPropertyStorage>() };
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp b/Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp
new file mode 100644
index 0000000000..ee6a8ed057
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/IteratorOperations.h>
+
+namespace JS {
+
+Object* get_iterator(GlobalObject& global_object, Value value, String hint, Value method)
+{
+ auto& vm = global_object.vm();
+ ASSERT(hint == "sync" || hint == "async");
+ if (method.is_empty()) {
+ if (hint == "async")
+ TODO();
+ auto object = value.to_object(global_object);
+ if (!object)
+ return {};
+ method = object->get(global_object.vm().well_known_symbol_iterator());
+ if (vm.exception())
+ return {};
+ }
+ if (!method.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotIterable, value.to_string_without_side_effects());
+ return nullptr;
+ }
+ auto iterator = vm.call(method.as_function(), value);
+ if (vm.exception())
+ return {};
+ if (!iterator.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotIterable, value.to_string_without_side_effects());
+ return nullptr;
+ }
+ return &iterator.as_object();
+}
+
+Object* iterator_next(Object& iterator, Value value)
+{
+ auto& vm = iterator.vm();
+ auto& global_object = iterator.global_object();
+ auto next_method = iterator.get(vm.names.next);
+ if (vm.exception())
+ return {};
+
+ if (!next_method.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::IterableNextNotAFunction);
+ return nullptr;
+ }
+
+ Value result;
+ if (value.is_empty())
+ result = vm.call(next_method.as_function(), &iterator);
+ else
+ result = vm.call(next_method.as_function(), &iterator, value);
+
+ if (vm.exception())
+ return {};
+ if (!result.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::IterableNextBadReturn);
+ return nullptr;
+ }
+
+ return &result.as_object();
+}
+
+void iterator_close([[maybe_unused]] Object& iterator)
+{
+ TODO();
+}
+
+Value create_iterator_result_object(GlobalObject& global_object, Value value, bool done)
+{
+ auto& vm = global_object.vm();
+ auto* object = Object::create_empty(global_object);
+ object->define_property(vm.names.value, value);
+ object->define_property(vm.names.done, Value(done));
+ return object;
+}
+
+void get_iterator_values(GlobalObject& global_object, Value value, AK::Function<IterationDecision(Value)> callback)
+{
+ auto& vm = global_object.vm();
+
+ auto iterator = get_iterator(global_object, value);
+ if (!iterator)
+ return;
+
+ while (true) {
+ auto next_object = iterator_next(*iterator);
+ if (!next_object)
+ return;
+
+ auto done_property = next_object->get(vm.names.done);
+ if (vm.exception())
+ return;
+
+ if (!done_property.is_empty() && done_property.to_boolean())
+ return;
+
+ auto next_value = next_object->get(vm.names.value);
+ if (vm.exception())
+ return;
+
+ auto result = callback(next_value);
+ if (result == IterationDecision::Break)
+ return;
+ ASSERT(result == IterationDecision::Continue);
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/IteratorOperations.h b/Userland/Libraries/LibJS/Runtime/IteratorOperations.h
new file mode 100644
index 0000000000..db113eec97
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/IteratorOperations.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+// Common iterator operations defined in ECMA262 7.4
+// https://tc39.es/ecma262/#sec-operations-on-iterator-objects
+
+Object* get_iterator(GlobalObject&, Value value, String hint = "sync", Value method = {});
+bool is_iterator_complete(Object& iterator_result);
+Value create_iterator_result_object(GlobalObject&, Value value, bool done);
+
+Object* iterator_next(Object& iterator, Value value = {});
+void iterator_close(Object& iterator);
+
+void get_iterator_values(GlobalObject&, Value value, AK::Function<IterationDecision(Value)> callback);
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/IteratorPrototype.cpp b/Userland/Libraries/LibJS/Runtime/IteratorPrototype.cpp
new file mode 100644
index 0000000000..32eef36315
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/IteratorPrototype.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/IteratorPrototype.h>
+
+namespace JS {
+
+IteratorPrototype::IteratorPrototype(GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void IteratorPrototype::initialize(GlobalObject& global_object)
+{
+ Object::initialize(global_object);
+ define_native_function(global_object.vm().well_known_symbol_iterator(), symbol_iterator, 0, Attribute::Writable | Attribute::Enumerable);
+}
+
+IteratorPrototype::~IteratorPrototype()
+{
+}
+
+JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::symbol_iterator)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ return this_object;
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/IteratorPrototype.h b/Userland/Libraries/LibJS/Runtime/IteratorPrototype.h
new file mode 100644
index 0000000000..72d37b2540
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/IteratorPrototype.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class IteratorPrototype : public Object {
+ JS_OBJECT(IteratorPrototype, Object)
+
+public:
+ IteratorPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~IteratorPrototype() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(symbol_iterator);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/JSONObject.cpp b/Userland/Libraries/LibJS/Runtime/JSONObject.cpp
new file mode 100644
index 0000000000..dddd82bfa0
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/JSONObject.cpp
@@ -0,0 +1,507 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <AK/JsonArray.h>
+#include <AK/JsonObject.h>
+#include <AK/JsonParser.h>
+#include <AK/StringBuilder.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/BigIntObject.h>
+#include <LibJS/Runtime/BooleanObject.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/JSONObject.h>
+#include <LibJS/Runtime/NumberObject.h>
+#include <LibJS/Runtime/Object.h>
+#include <LibJS/Runtime/StringObject.h>
+
+namespace JS {
+
+JSONObject::JSONObject(GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void JSONObject::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_function(vm.names.stringify, stringify, 3, attr);
+ define_native_function(vm.names.parse, parse, 2, attr);
+ define_property(vm.well_known_symbol_to_string_tag(), js_string(global_object.heap(), "JSON"), Attribute::Configurable);
+}
+
+JSONObject::~JSONObject()
+{
+}
+
+String JSONObject::stringify_impl(GlobalObject& global_object, Value value, Value replacer, Value space)
+{
+ auto& vm = global_object.vm();
+ StringifyState state;
+
+ if (replacer.is_object()) {
+ if (replacer.as_object().is_function()) {
+ state.replacer_function = &replacer.as_function();
+ } else if (replacer.is_array()) {
+ auto& replacer_object = replacer.as_object();
+ auto replacer_length = length_of_array_like(global_object, replacer_object);
+ if (vm.exception())
+ return {};
+ Vector<String> list;
+ for (size_t i = 0; i < replacer_length; ++i) {
+ auto replacer_value = replacer_object.get(i);
+ if (vm.exception())
+ return {};
+ String item;
+ if (replacer_value.is_string() || replacer_value.is_number()) {
+ item = replacer_value.to_string(global_object);
+ if (vm.exception())
+ return {};
+ } else if (replacer_value.is_object()) {
+ auto& value_object = replacer_value.as_object();
+ if (is<StringObject>(value_object) || is<NumberObject>(value_object)) {
+ item = value_object.value_of().to_string(global_object);
+ if (vm.exception())
+ return {};
+ }
+ }
+ if (!item.is_null() && !list.contains_slow(item)) {
+ list.append(item);
+ }
+ }
+ state.property_list = list;
+ }
+ }
+
+ if (space.is_object()) {
+ auto& space_obj = space.as_object();
+ if (is<StringObject>(space_obj) || is<NumberObject>(space_obj))
+ space = space_obj.value_of();
+ }
+
+ if (space.is_number()) {
+ StringBuilder gap_builder;
+ auto gap_size = min(10, space.as_i32());
+ for (auto i = 0; i < gap_size; ++i)
+ gap_builder.append(' ');
+ state.gap = gap_builder.to_string();
+ } else if (space.is_string()) {
+ auto string = space.as_string().string();
+ if (string.length() <= 10) {
+ state.gap = string;
+ } else {
+ state.gap = string.substring(0, 10);
+ }
+ } else {
+ state.gap = String::empty();
+ }
+
+ auto* wrapper = Object::create_empty(global_object);
+ wrapper->define_property(String::empty(), value);
+ if (vm.exception())
+ return {};
+ auto result = serialize_json_property(global_object, state, String::empty(), wrapper);
+ if (vm.exception())
+ return {};
+ if (result.is_null())
+ return {};
+
+ return result;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(JSONObject::stringify)
+{
+ if (!vm.argument_count())
+ return js_undefined();
+
+ auto value = vm.argument(0);
+ auto replacer = vm.argument(1);
+ auto space = vm.argument(2);
+
+ auto string = stringify_impl(global_object, value, replacer, space);
+ if (string.is_null())
+ return js_undefined();
+
+ return js_string(vm, string);
+}
+
+String JSONObject::serialize_json_property(GlobalObject& global_object, StringifyState& state, const PropertyName& key, Object* holder)
+{
+ auto& vm = global_object.vm();
+ auto value = holder->get(key);
+ if (vm.exception())
+ return {};
+ if (value.is_object()) {
+ auto to_json = value.as_object().get(vm.names.toJSON);
+ if (vm.exception())
+ return {};
+ if (to_json.is_function()) {
+ value = vm.call(to_json.as_function(), value, js_string(vm, key.to_string()));
+ if (vm.exception())
+ return {};
+ }
+ }
+
+ if (state.replacer_function) {
+ value = vm.call(*state.replacer_function, holder, js_string(vm, key.to_string()), value);
+ if (vm.exception())
+ return {};
+ }
+
+ if (value.is_object()) {
+ auto& value_object = value.as_object();
+ if (is<NumberObject>(value_object) || is<BooleanObject>(value_object) || is<StringObject>(value_object) || is<BigIntObject>(value_object))
+ value = value_object.value_of();
+ }
+
+ if (value.is_null())
+ return "null";
+ if (value.is_boolean())
+ return value.as_bool() ? "true" : "false";
+ if (value.is_string())
+ return quote_json_string(value.as_string().string());
+ if (value.is_number()) {
+ if (value.is_finite_number())
+ return value.to_string(global_object);
+ return "null";
+ }
+ if (value.is_object() && !value.is_function()) {
+ if (value.is_array())
+ return serialize_json_array(global_object, state, static_cast<Array&>(value.as_object()));
+ return serialize_json_object(global_object, state, value.as_object());
+ }
+ if (value.is_bigint())
+ vm.throw_exception<TypeError>(global_object, ErrorType::JsonBigInt);
+ return {};
+}
+
+String JSONObject::serialize_json_object(GlobalObject& global_object, StringifyState& state, Object& object)
+{
+ auto& vm = global_object.vm();
+ if (state.seen_objects.contains(&object)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::JsonCircular);
+ return {};
+ }
+
+ state.seen_objects.set(&object);
+ String previous_indent = state.indent;
+ state.indent = String::formatted("{}{}", state.indent, state.gap);
+ Vector<String> property_strings;
+
+ auto process_property = [&](const PropertyName& key) {
+ auto serialized_property_string = serialize_json_property(global_object, state, key, &object);
+ if (vm.exception())
+ return;
+ if (!serialized_property_string.is_null()) {
+ property_strings.append(String::formatted(
+ "{}:{}{}",
+ quote_json_string(key.to_string()),
+ state.gap.is_empty() ? "" : " ",
+ serialized_property_string));
+ }
+ };
+
+ if (state.property_list.has_value()) {
+ auto property_list = state.property_list.value();
+ for (auto& property : property_list) {
+ process_property(property);
+ if (vm.exception())
+ return {};
+ }
+ } else {
+ for (auto& entry : object.indexed_properties()) {
+ auto value_and_attributes = entry.value_and_attributes(&object);
+ if (!value_and_attributes.attributes.is_enumerable())
+ continue;
+ process_property(entry.index());
+ if (vm.exception())
+ return {};
+ }
+ for (auto& [key, metadata] : object.shape().property_table_ordered()) {
+ if (!metadata.attributes.is_enumerable())
+ continue;
+ process_property(key);
+ if (vm.exception())
+ return {};
+ }
+ }
+ StringBuilder builder;
+ if (property_strings.is_empty()) {
+ builder.append("{}");
+ } else {
+ bool first = true;
+ builder.append('{');
+ if (state.gap.is_empty()) {
+ for (auto& property_string : property_strings) {
+ if (!first)
+ builder.append(',');
+ first = false;
+ builder.append(property_string);
+ }
+ } else {
+ builder.append('\n');
+ builder.append(state.indent);
+ auto separator = String::formatted(",\n{}", state.indent);
+ for (auto& property_string : property_strings) {
+ if (!first)
+ builder.append(separator);
+ first = false;
+ builder.append(property_string);
+ }
+ builder.append('\n');
+ builder.append(previous_indent);
+ }
+ builder.append('}');
+ }
+
+ state.seen_objects.remove(&object);
+ state.indent = previous_indent;
+ return builder.to_string();
+}
+
+String JSONObject::serialize_json_array(GlobalObject& global_object, StringifyState& state, Object& object)
+{
+ auto& vm = global_object.vm();
+ if (state.seen_objects.contains(&object)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::JsonCircular);
+ return {};
+ }
+
+ state.seen_objects.set(&object);
+ String previous_indent = state.indent;
+ state.indent = String::formatted("{}{}", state.indent, state.gap);
+ Vector<String> property_strings;
+
+ auto length = length_of_array_like(global_object, object);
+ if (vm.exception())
+ return {};
+ for (size_t i = 0; i < length; ++i) {
+ if (vm.exception())
+ return {};
+ auto serialized_property_string = serialize_json_property(global_object, state, i, &object);
+ if (vm.exception())
+ return {};
+ if (serialized_property_string.is_null()) {
+ property_strings.append("null");
+ } else {
+ property_strings.append(serialized_property_string);
+ }
+ }
+
+ StringBuilder builder;
+ if (property_strings.is_empty()) {
+ builder.append("[]");
+ } else {
+ if (state.gap.is_empty()) {
+ builder.append('[');
+ bool first = true;
+ for (auto& property_string : property_strings) {
+ if (!first)
+ builder.append(',');
+ first = false;
+ builder.append(property_string);
+ }
+ builder.append(']');
+ } else {
+ builder.append("[\n");
+ builder.append(state.indent);
+ auto separator = String::formatted(",\n{}", state.indent);
+ bool first = true;
+ for (auto& property_string : property_strings) {
+ if (!first)
+ builder.append(separator);
+ first = false;
+ builder.append(property_string);
+ }
+ builder.append('\n');
+ builder.append(previous_indent);
+ builder.append(']');
+ }
+ }
+
+ state.seen_objects.remove(&object);
+ state.indent = previous_indent;
+ return builder.to_string();
+}
+
+String JSONObject::quote_json_string(String string)
+{
+ // FIXME: Handle UTF16
+ StringBuilder builder;
+ builder.append('"');
+ for (auto& ch : string) {
+ switch (ch) {
+ case '\b':
+ builder.append("\\b");
+ break;
+ case '\t':
+ builder.append("\\t");
+ break;
+ case '\n':
+ builder.append("\\n");
+ break;
+ case '\f':
+ builder.append("\\f");
+ break;
+ case '\r':
+ builder.append("\\r");
+ break;
+ case '"':
+ builder.append("\\\"");
+ break;
+ case '\\':
+ builder.append("\\\\");
+ break;
+ default:
+ if (ch < 0x20) {
+ builder.append("\\u%#08x", ch);
+ } else {
+ builder.append(ch);
+ }
+ }
+ }
+ builder.append('"');
+ return builder.to_string();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(JSONObject::parse)
+{
+ if (!vm.argument_count())
+ return js_undefined();
+ auto string = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+ auto reviver = vm.argument(1);
+
+ auto json = JsonValue::from_string(string);
+ if (!json.has_value()) {
+ vm.throw_exception<SyntaxError>(global_object, ErrorType::JsonMalformed);
+ return {};
+ }
+ Value result = parse_json_value(global_object, json.value());
+ if (reviver.is_function()) {
+ auto* holder_object = Object::create_empty(global_object);
+ holder_object->define_property(String::empty(), result);
+ if (vm.exception())
+ return {};
+ return internalize_json_property(global_object, holder_object, String::empty(), reviver.as_function());
+ }
+ return result;
+}
+
+Value JSONObject::parse_json_value(GlobalObject& global_object, const JsonValue& value)
+{
+ if (value.is_object())
+ return Value(parse_json_object(global_object, value.as_object()));
+ if (value.is_array())
+ return Value(parse_json_array(global_object, value.as_array()));
+ if (value.is_null())
+ return js_null();
+#if !defined(KERNEL)
+ if (value.is_double())
+ return Value(value.as_double());
+#endif
+ if (value.is_number())
+ return Value(value.to_i32(0));
+ if (value.is_string())
+ return js_string(global_object.heap(), value.to_string());
+ if (value.is_bool())
+ return Value(static_cast<bool>(value.as_bool()));
+ ASSERT_NOT_REACHED();
+}
+
+Object* JSONObject::parse_json_object(GlobalObject& global_object, const JsonObject& json_object)
+{
+ auto* object = Object::create_empty(global_object);
+ json_object.for_each_member([&](auto& key, auto& value) {
+ object->define_property(key, parse_json_value(global_object, value));
+ });
+ return object;
+}
+
+Array* JSONObject::parse_json_array(GlobalObject& global_object, const JsonArray& json_array)
+{
+ auto* array = Array::create(global_object);
+ size_t index = 0;
+ json_array.for_each([&](auto& value) {
+ array->define_property(index++, parse_json_value(global_object, value));
+ });
+ return array;
+}
+
+Value JSONObject::internalize_json_property(GlobalObject& global_object, Object* holder, const PropertyName& name, Function& reviver)
+{
+ auto& vm = global_object.vm();
+ auto value = holder->get(name);
+ if (vm.exception())
+ return {};
+ if (value.is_object()) {
+ auto& value_object = value.as_object();
+
+ auto process_property = [&](const PropertyName& key) {
+ auto element = internalize_json_property(global_object, &value_object, key, reviver);
+ if (vm.exception())
+ return;
+ if (element.is_undefined()) {
+ value_object.delete_property(key);
+ } else {
+ value_object.define_property(key, element, default_attributes, false);
+ }
+ };
+
+ if (value_object.is_array()) {
+ auto length = length_of_array_like(global_object, value_object);
+ for (size_t i = 0; i < length; ++i) {
+ process_property(i);
+ if (vm.exception())
+ return {};
+ }
+ } else {
+ for (auto& entry : value_object.indexed_properties()) {
+ auto value_and_attributes = entry.value_and_attributes(&value_object);
+ if (!value_and_attributes.attributes.is_enumerable())
+ continue;
+ process_property(entry.index());
+ if (vm.exception())
+ return {};
+ }
+ for (auto& [key, metadata] : value_object.shape().property_table_ordered()) {
+ if (!metadata.attributes.is_enumerable())
+ continue;
+ process_property(key);
+ if (vm.exception())
+ return {};
+ }
+ }
+ }
+
+ return vm.call(reviver, Value(holder), js_string(vm, name.to_string()), value);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/JSONObject.h b/Userland/Libraries/LibJS/Runtime/JSONObject.h
new file mode 100644
index 0000000000..23fdff2473
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/JSONObject.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class JSONObject final : public Object {
+ JS_OBJECT(JSONObject, Object);
+
+public:
+ explicit JSONObject(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~JSONObject() override;
+
+ // The base implementation of stringify is exposed because it is used by
+ // test-js to communicate between the JS tests and the C++ test runner.
+ static String stringify_impl(GlobalObject&, Value value, Value replacer, Value space);
+
+private:
+ struct StringifyState {
+ Function* replacer_function { nullptr };
+ HashTable<Object*> seen_objects;
+ String indent { String::empty() };
+ String gap;
+ Optional<Vector<String>> property_list;
+ };
+
+ // Stringify helpers
+ static String serialize_json_property(GlobalObject&, StringifyState&, const PropertyName& key, Object* holder);
+ static String serialize_json_object(GlobalObject&, StringifyState&, Object&);
+ static String serialize_json_array(GlobalObject&, StringifyState&, Object&);
+ static String quote_json_string(String);
+
+ // Parse helpers
+ static Object* parse_json_object(GlobalObject&, const JsonObject&);
+ static Array* parse_json_array(GlobalObject&, const JsonArray&);
+ static Value parse_json_value(GlobalObject&, const JsonValue&);
+ static Value internalize_json_property(GlobalObject&, Object* holder, const PropertyName& name, Function& reviver);
+
+ JS_DECLARE_NATIVE_FUNCTION(stringify);
+ JS_DECLARE_NATIVE_FUNCTION(parse);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/LexicalEnvironment.cpp b/Userland/Libraries/LibJS/Runtime/LexicalEnvironment.cpp
new file mode 100644
index 0000000000..2275b6402b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/LexicalEnvironment.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Interpreter.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/Function.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/LexicalEnvironment.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+LexicalEnvironment::LexicalEnvironment()
+ : ScopeObject(nullptr)
+{
+}
+
+LexicalEnvironment::LexicalEnvironment(EnvironmentRecordType environment_record_type)
+ : ScopeObject(nullptr)
+ , m_environment_record_type(environment_record_type)
+{
+}
+
+LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope)
+ : ScopeObject(parent_scope)
+ , m_variables(move(variables))
+{
+}
+
+LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope, EnvironmentRecordType environment_record_type)
+ : ScopeObject(parent_scope)
+ , m_environment_record_type(environment_record_type)
+ , m_variables(move(variables))
+{
+}
+
+LexicalEnvironment::~LexicalEnvironment()
+{
+}
+
+void LexicalEnvironment::visit_edges(Visitor& visitor)
+{
+ Cell::visit_edges(visitor);
+ visitor.visit(m_this_value);
+ visitor.visit(m_home_object);
+ visitor.visit(m_new_target);
+ visitor.visit(m_current_function);
+ for (auto& it : m_variables)
+ visitor.visit(it.value.value);
+}
+
+Optional<Variable> LexicalEnvironment::get_from_scope(const FlyString& name) const
+{
+ return m_variables.get(name);
+}
+
+void LexicalEnvironment::put_to_scope(const FlyString& name, Variable variable)
+{
+ m_variables.set(name, variable);
+}
+
+bool LexicalEnvironment::has_super_binding() const
+{
+ return m_environment_record_type == EnvironmentRecordType::Function && this_binding_status() != ThisBindingStatus::Lexical && m_home_object.is_object();
+}
+
+Value LexicalEnvironment::get_super_base()
+{
+ ASSERT(has_super_binding());
+ if (m_home_object.is_object())
+ return m_home_object.as_object().prototype();
+ return {};
+}
+
+bool LexicalEnvironment::has_this_binding() const
+{
+ // More like "is_capable_of_having_a_this_binding".
+ switch (m_environment_record_type) {
+ case EnvironmentRecordType::Declarative:
+ case EnvironmentRecordType::Object:
+ return false;
+ case EnvironmentRecordType::Function:
+ return this_binding_status() != ThisBindingStatus::Lexical;
+ case EnvironmentRecordType::Module:
+ return true;
+ }
+ ASSERT_NOT_REACHED();
+}
+
+Value LexicalEnvironment::get_this_binding(GlobalObject& global_object) const
+{
+ ASSERT(has_this_binding());
+ if (this_binding_status() == ThisBindingStatus::Uninitialized) {
+ vm().throw_exception<ReferenceError>(global_object, ErrorType::ThisHasNotBeenInitialized);
+ return {};
+ }
+ return m_this_value;
+}
+
+void LexicalEnvironment::bind_this_value(GlobalObject& global_object, Value this_value)
+{
+ ASSERT(has_this_binding());
+ if (m_this_binding_status == ThisBindingStatus::Initialized) {
+ vm().throw_exception<ReferenceError>(global_object, ErrorType::ThisIsAlreadyInitialized);
+ return;
+ }
+ m_this_value = this_value;
+ m_this_binding_status = ThisBindingStatus::Initialized;
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/LexicalEnvironment.h b/Userland/Libraries/LibJS/Runtime/LexicalEnvironment.h
new file mode 100644
index 0000000000..1e4b4723d1
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/LexicalEnvironment.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <AK/HashMap.h>
+#include <LibJS/Runtime/ScopeObject.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+class LexicalEnvironment final : public ScopeObject {
+ JS_OBJECT(LexicalEnvironment, ScopeObject);
+
+public:
+ enum class ThisBindingStatus {
+ Lexical,
+ Initialized,
+ Uninitialized,
+ };
+
+ enum class EnvironmentRecordType {
+ Declarative,
+ Function,
+ Object,
+ Module,
+ };
+
+ LexicalEnvironment();
+ LexicalEnvironment(EnvironmentRecordType);
+ LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope);
+ LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope, EnvironmentRecordType);
+ virtual ~LexicalEnvironment() override;
+
+ // ^ScopeObject
+ virtual Optional<Variable> get_from_scope(const FlyString&) const override;
+ virtual void put_to_scope(const FlyString&, Variable) override;
+ virtual bool has_this_binding() const override;
+ virtual Value get_this_binding(GlobalObject&) const override;
+
+ void clear();
+
+ const HashMap<FlyString, Variable>& variables() const { return m_variables; }
+
+ void set_home_object(Value object) { m_home_object = object; }
+ bool has_super_binding() const;
+ Value get_super_base();
+
+ ThisBindingStatus this_binding_status() const { return m_this_binding_status; }
+ void bind_this_value(GlobalObject&, Value this_value);
+
+ // Not a standard operation.
+ void replace_this_binding(Value this_value) { m_this_value = this_value; }
+
+ Value new_target() const { return m_new_target; };
+ void set_new_target(Value new_target) { m_new_target = new_target; }
+
+ Function* current_function() const { return m_current_function; }
+ void set_current_function(Function& function) { m_current_function = &function; }
+
+ EnvironmentRecordType type() const { return m_environment_record_type; }
+
+private:
+ virtual void visit_edges(Visitor&) override;
+
+ EnvironmentRecordType m_environment_record_type : 8 { EnvironmentRecordType::Declarative };
+ ThisBindingStatus m_this_binding_status : 8 { ThisBindingStatus::Uninitialized };
+ HashMap<FlyString, Variable> m_variables;
+ Value m_home_object;
+ Value m_this_value;
+ Value m_new_target;
+ // Corresponds to [[FunctionObject]]
+ Function* m_current_function { nullptr };
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/MarkedValueList.cpp b/Userland/Libraries/LibJS/Runtime/MarkedValueList.cpp
new file mode 100644
index 0000000000..f268a21c6b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/MarkedValueList.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/MarkedValueList.h>
+
+namespace JS {
+
+MarkedValueList::MarkedValueList(Heap& heap)
+ : m_heap(heap)
+{
+ m_heap.did_create_marked_value_list({}, *this);
+}
+
+MarkedValueList::MarkedValueList(MarkedValueList&& other)
+ : AK::Vector<Value, 32>(move(static_cast<Vector<Value, 32>&>(other)))
+ , m_heap(other.m_heap)
+{
+ m_heap.did_create_marked_value_list({}, *this);
+}
+
+MarkedValueList::~MarkedValueList()
+{
+ m_heap.did_destroy_marked_value_list({}, *this);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/MarkedValueList.h b/Userland/Libraries/LibJS/Runtime/MarkedValueList.h
new file mode 100644
index 0000000000..e311972181
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/MarkedValueList.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Noncopyable.h>
+#include <AK/Vector.h>
+#include <LibJS/Forward.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+class MarkedValueList : public AK::Vector<Value, 32> {
+ AK_MAKE_NONCOPYABLE(MarkedValueList);
+
+public:
+ explicit MarkedValueList(Heap&);
+ MarkedValueList(MarkedValueList&&);
+ ~MarkedValueList();
+
+ MarkedValueList& operator=(MarkedValueList&&) = delete;
+
+ Vector<Value, 32>& values() { return *this; }
+
+ MarkedValueList copy() const
+ {
+ MarkedValueList copy { m_heap };
+ copy.append(*this);
+ return copy;
+ }
+
+private:
+ Heap& m_heap;
+};
+}
diff --git a/Userland/Libraries/LibJS/Runtime/MathObject.cpp b/Userland/Libraries/LibJS/Runtime/MathObject.cpp
new file mode 100644
index 0000000000..bdd3ec6e17
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/MathObject.cpp
@@ -0,0 +1,504 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/FlyString.h>
+#include <AK/Function.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/MathObject.h>
+#include <math.h>
+
+namespace JS {
+
+MathObject::MathObject(GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void MathObject::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_function(vm.names.abs, abs, 1, attr);
+ define_native_function(vm.names.random, random, 0, attr);
+ define_native_function(vm.names.sqrt, sqrt, 1, attr);
+ define_native_function(vm.names.floor, floor, 1, attr);
+ define_native_function(vm.names.ceil, ceil, 1, attr);
+ define_native_function(vm.names.round, round, 1, attr);
+ define_native_function(vm.names.max, max, 2, attr);
+ define_native_function(vm.names.min, min, 2, attr);
+ define_native_function(vm.names.trunc, trunc, 1, attr);
+ define_native_function(vm.names.sin, sin, 1, attr);
+ define_native_function(vm.names.cos, cos, 1, attr);
+ define_native_function(vm.names.tan, tan, 1, attr);
+ define_native_function(vm.names.pow, pow, 2, attr);
+ define_native_function(vm.names.exp, exp, 1, attr);
+ define_native_function(vm.names.expm1, expm1, 1, attr);
+ define_native_function(vm.names.sign, sign, 1, attr);
+ define_native_function(vm.names.clz32, clz32, 1, attr);
+ define_native_function(vm.names.acos, acos, 1, attr);
+ define_native_function(vm.names.acosh, acosh, 1, attr);
+ define_native_function(vm.names.asin, asin, 1, attr);
+ define_native_function(vm.names.asinh, asinh, 1, attr);
+ define_native_function(vm.names.atan, atan, 1, attr);
+ define_native_function(vm.names.atanh, atanh, 1, attr);
+ define_native_function(vm.names.log1p, log1p, 1, attr);
+ define_native_function(vm.names.cbrt, cbrt, 1, attr);
+ define_native_function(vm.names.atan2, atan2, 2, attr);
+ define_native_function(vm.names.fround, fround, 1, attr);
+ define_native_function(vm.names.hypot, hypot, 2, attr);
+ define_native_function(vm.names.log, log, 1, attr);
+ define_native_function(vm.names.log2, log2, 1, attr);
+ define_native_function(vm.names.log10, log10, 1, attr);
+ define_native_function(vm.names.sinh, sinh, 1, attr);
+ define_native_function(vm.names.cosh, cosh, 1, attr);
+ define_native_function(vm.names.tanh, tanh, 1, attr);
+
+ define_property(vm.names.E, Value(M_E), 0);
+ define_property(vm.names.LN2, Value(M_LN2), 0);
+ define_property(vm.names.LN10, Value(M_LN10), 0);
+ define_property(vm.names.LOG2E, Value(::log2(M_E)), 0);
+ define_property(vm.names.LOG10E, Value(::log10(M_E)), 0);
+ define_property(vm.names.PI, Value(M_PI), 0);
+ define_property(vm.names.SQRT1_2, Value(M_SQRT1_2), 0);
+ define_property(vm.names.SQRT2, Value(M_SQRT2), 0);
+
+ define_property(vm.well_known_symbol_to_string_tag(), js_string(vm.heap(), "Math"), Attribute::Configurable);
+}
+
+MathObject::~MathObject()
+{
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::abs)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan())
+ return js_nan();
+ return Value(number.as_double() >= 0 ? number.as_double() : -number.as_double());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::random)
+{
+#ifdef __serenity__
+ double r = (double)arc4random() / (double)UINT32_MAX;
+#else
+ double r = (double)rand() / (double)RAND_MAX;
+#endif
+ return Value(r);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::sqrt)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan())
+ return js_nan();
+ return Value(::sqrt(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::floor)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan())
+ return js_nan();
+ return Value(::floor(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::ceil)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan())
+ return js_nan();
+ auto number_double = number.as_double();
+ if (number_double < 0 && number_double > -1)
+ return Value(-0.f);
+ return Value(::ceil(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::round)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan())
+ return js_nan();
+ return Value(::round(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::max)
+{
+ if (!vm.argument_count())
+ return js_negative_infinity();
+
+ auto max = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ for (size_t i = 1; i < vm.argument_count(); ++i) {
+ auto cur = vm.argument(i).to_number(global_object);
+ if (vm.exception())
+ return {};
+ max = Value(cur.as_double() > max.as_double() ? cur : max);
+ }
+ return max;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::min)
+{
+ if (!vm.argument_count())
+ return js_infinity();
+
+ auto min = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ for (size_t i = 1; i < vm.argument_count(); ++i) {
+ auto cur = vm.argument(i).to_number(global_object);
+ if (vm.exception())
+ return {};
+ min = Value(cur.as_double() < min.as_double() ? cur : min);
+ }
+ return min;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::trunc)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan())
+ return js_nan();
+ if (number.as_double() < 0)
+ return MathObject::ceil(vm, global_object);
+ return MathObject::floor(vm, global_object);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::sin)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan())
+ return js_nan();
+ return Value(::sin(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::cos)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan())
+ return js_nan();
+ return Value(::cos(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::tan)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan())
+ return js_nan();
+ return Value(::tan(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::pow)
+{
+ return JS::exp(global_object, vm.argument(0), vm.argument(1));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::exp)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan())
+ return js_nan();
+ return Value(::exp(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::expm1)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan())
+ return js_nan();
+ return Value(::expm1(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::sign)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_positive_zero())
+ return Value(0);
+ if (number.is_negative_zero())
+ return Value(-0.0);
+ if (number.as_double() > 0)
+ return Value(1);
+ if (number.as_double() < 0)
+ return Value(-1);
+ return js_nan();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::clz32)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (!number.is_finite_number() || (unsigned)number.as_double() == 0)
+ return Value(32);
+ return Value(__builtin_clz((unsigned)number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::acos)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan() || number.as_double() > 1 || number.as_double() < -1)
+ return js_nan();
+ if (number.as_double() == 1)
+ return Value(0);
+ return Value(::acos(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::acosh)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.as_double() < 1)
+ return js_nan();
+ return Value(::acosh(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::asin)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan() || number.is_positive_zero() || number.is_negative_zero())
+ return number;
+ return Value(::asin(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::asinh)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ return Value(::asinh(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::atan)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan() || number.is_positive_zero() || number.is_negative_zero())
+ return number;
+ if (number.is_positive_infinity())
+ return Value(M_PI_2);
+ if (number.is_negative_infinity())
+ return Value(-M_PI_2);
+ return Value(::atan(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::atanh)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.as_double() > 1 || number.as_double() < -1)
+ return js_nan();
+ return Value(::atanh(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::log1p)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.as_double() < -1)
+ return js_nan();
+ return Value(::log1p(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::cbrt)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ return Value(::cbrt(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::atan2)
+{
+ auto y = vm.argument(0).to_number(global_object), x = vm.argument(1).to_number(global_object);
+ auto pi_4 = M_PI_2 / 2;
+ auto three_pi_4 = pi_4 + M_PI_2;
+ if (vm.exception())
+ return {};
+ if (x.is_positive_zero()) {
+ if (y.is_positive_zero() || y.is_negative_zero())
+ return y;
+ else
+ return (y.as_double() > 0) ? Value(M_PI_2) : Value(-M_PI_2);
+ }
+ if (x.is_negative_zero()) {
+ if (y.is_positive_zero())
+ return Value(M_PI);
+ else if (y.is_negative_zero())
+ return Value(-M_PI);
+ else
+ return (y.as_double() > 0) ? Value(M_PI_2) : Value(-M_PI_2);
+ }
+ if (x.is_positive_infinity()) {
+ if (y.is_infinity())
+ return (y.is_positive_infinity()) ? Value(pi_4) : Value(-pi_4);
+ else
+ return (y.as_double() > 0) ? Value(+0.0) : Value(-0.0);
+ }
+ if (x.is_negative_infinity()) {
+ if (y.is_infinity())
+ return (y.is_positive_infinity()) ? Value(three_pi_4) : Value(-three_pi_4);
+ else
+ return (y.as_double() > 0) ? Value(M_PI) : Value(-M_PI);
+ }
+ if (y.is_infinity())
+ return (y.is_positive_infinity()) ? Value(M_PI_2) : Value(-M_PI_2);
+ if (y.is_positive_zero())
+ return (x.as_double() > 0) ? Value(+0.0) : Value(M_PI);
+ if (y.is_negative_zero())
+ return (x.as_double() > 0) ? Value(-0.0) : Value(-M_PI);
+
+ return Value(::atan2(y.as_double(), x.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::fround)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan())
+ return js_nan();
+ return Value((float)number.as_double());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::hypot)
+{
+ if (!vm.argument_count())
+ return Value(0);
+
+ auto hypot = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ hypot = Value(hypot.as_double() * hypot.as_double());
+ for (size_t i = 1; i < vm.argument_count(); ++i) {
+ auto cur = vm.argument(i).to_number(global_object);
+ if (vm.exception())
+ return {};
+ hypot = Value(hypot.as_double() + cur.as_double() * cur.as_double());
+ }
+ return Value(::sqrt(hypot.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::log)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.as_double() < 0)
+ return js_nan();
+ return Value(::log(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::log2)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.as_double() < 0)
+ return js_nan();
+ return Value(::log2(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::log10)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.as_double() < 0)
+ return js_nan();
+ return Value(::log10(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::sinh)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan())
+ return js_nan();
+ return Value(::sinh(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::cosh)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan())
+ return js_nan();
+ return Value(::cosh(number.as_double()));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(MathObject::tanh)
+{
+ auto number = vm.argument(0).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (number.is_nan())
+ return js_nan();
+ if (number.is_positive_infinity())
+ return Value(1);
+ if (number.is_negative_infinity())
+ return Value(-1);
+ return Value(::tanh(number.as_double()));
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/MathObject.h b/Userland/Libraries/LibJS/Runtime/MathObject.h
new file mode 100644
index 0000000000..06f09c2e4a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/MathObject.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class MathObject final : public Object {
+ JS_OBJECT(MathObject, Object);
+
+public:
+ explicit MathObject(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~MathObject() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(abs);
+ JS_DECLARE_NATIVE_FUNCTION(random);
+ JS_DECLARE_NATIVE_FUNCTION(sqrt);
+ JS_DECLARE_NATIVE_FUNCTION(floor);
+ JS_DECLARE_NATIVE_FUNCTION(ceil);
+ JS_DECLARE_NATIVE_FUNCTION(round);
+ JS_DECLARE_NATIVE_FUNCTION(max);
+ JS_DECLARE_NATIVE_FUNCTION(min);
+ JS_DECLARE_NATIVE_FUNCTION(trunc);
+ JS_DECLARE_NATIVE_FUNCTION(sin);
+ JS_DECLARE_NATIVE_FUNCTION(cos);
+ JS_DECLARE_NATIVE_FUNCTION(tan);
+ JS_DECLARE_NATIVE_FUNCTION(pow);
+ JS_DECLARE_NATIVE_FUNCTION(exp);
+ JS_DECLARE_NATIVE_FUNCTION(expm1);
+ JS_DECLARE_NATIVE_FUNCTION(sign);
+ JS_DECLARE_NATIVE_FUNCTION(clz32);
+ JS_DECLARE_NATIVE_FUNCTION(acos);
+ JS_DECLARE_NATIVE_FUNCTION(acosh);
+ JS_DECLARE_NATIVE_FUNCTION(asin);
+ JS_DECLARE_NATIVE_FUNCTION(asinh);
+ JS_DECLARE_NATIVE_FUNCTION(atan);
+ JS_DECLARE_NATIVE_FUNCTION(atanh);
+ JS_DECLARE_NATIVE_FUNCTION(log1p);
+ JS_DECLARE_NATIVE_FUNCTION(cbrt);
+ JS_DECLARE_NATIVE_FUNCTION(atan2);
+ JS_DECLARE_NATIVE_FUNCTION(fround);
+ JS_DECLARE_NATIVE_FUNCTION(hypot);
+ JS_DECLARE_NATIVE_FUNCTION(log);
+ JS_DECLARE_NATIVE_FUNCTION(log2);
+ JS_DECLARE_NATIVE_FUNCTION(log10);
+ JS_DECLARE_NATIVE_FUNCTION(sinh);
+ JS_DECLARE_NATIVE_FUNCTION(cosh);
+ JS_DECLARE_NATIVE_FUNCTION(tanh);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/NativeFunction.cpp b/Userland/Libraries/LibJS/Runtime/NativeFunction.cpp
new file mode 100644
index 0000000000..8453724414
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/NativeFunction.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/LexicalEnvironment.h>
+#include <LibJS/Runtime/NativeFunction.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+NativeFunction* NativeFunction::create(GlobalObject& global_object, const FlyString& name, AK::Function<Value(VM&, GlobalObject&)> function)
+{
+ return global_object.heap().allocate<NativeFunction>(global_object, name, move(function), *global_object.function_prototype());
+}
+
+NativeFunction::NativeFunction(Object& prototype)
+ : Function(prototype)
+{
+}
+
+NativeFunction::NativeFunction(const FlyString& name, AK::Function<Value(VM&, GlobalObject&)> native_function, Object& prototype)
+ : Function(prototype)
+ , m_name(name)
+ , m_native_function(move(native_function))
+{
+}
+
+NativeFunction::NativeFunction(const FlyString& name, Object& prototype)
+ : Function(prototype)
+ , m_name(name)
+{
+}
+
+NativeFunction::~NativeFunction()
+{
+}
+
+Value NativeFunction::call()
+{
+ return m_native_function(vm(), global_object());
+}
+
+Value NativeFunction::construct(Function&)
+{
+ return {};
+}
+
+LexicalEnvironment* NativeFunction::create_environment()
+{
+ return heap().allocate<LexicalEnvironment>(global_object(), LexicalEnvironment::EnvironmentRecordType::Function);
+}
+
+bool NativeFunction::is_strict_mode() const
+{
+ return vm().in_strict_mode();
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/NativeFunction.h b/Userland/Libraries/LibJS/Runtime/NativeFunction.h
new file mode 100644
index 0000000000..a37336329f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/NativeFunction.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <LibJS/Runtime/Function.h>
+
+namespace JS {
+
+class NativeFunction : public Function {
+ JS_OBJECT(NativeFunction, Function);
+
+public:
+ static NativeFunction* create(GlobalObject&, const FlyString& name, AK::Function<Value(VM&, GlobalObject&)>);
+
+ explicit NativeFunction(const FlyString& name, AK::Function<Value(VM&, GlobalObject&)>, Object& prototype);
+ virtual void initialize(GlobalObject&) override { }
+ virtual ~NativeFunction() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+ virtual const FlyString& name() const override { return m_name; };
+ virtual bool has_constructor() const { return false; }
+
+ virtual bool is_strict_mode() const override;
+
+protected:
+ NativeFunction(const FlyString& name, Object& prototype);
+ explicit NativeFunction(Object& prototype);
+
+private:
+ virtual LexicalEnvironment* create_environment() override final;
+
+ FlyString m_name;
+ AK::Function<Value(VM&, GlobalObject&)> m_native_function;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/NativeProperty.cpp b/Userland/Libraries/LibJS/Runtime/NativeProperty.cpp
new file mode 100644
index 0000000000..d52e7995fa
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/NativeProperty.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/NativeProperty.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+NativeProperty::NativeProperty(AK::Function<Value(VM&, GlobalObject&)> getter, AK::Function<void(VM&, GlobalObject&, Value)> setter)
+ : m_getter(move(getter))
+ , m_setter(move(setter))
+{
+}
+
+NativeProperty::~NativeProperty()
+{
+}
+
+Value NativeProperty::get(VM& vm, GlobalObject& global_object) const
+{
+ if (!m_getter)
+ return js_undefined();
+ return m_getter(vm, global_object);
+}
+
+void NativeProperty::set(VM& vm, GlobalObject& global_object, Value value)
+{
+ if (!m_setter)
+ return;
+ m_setter(vm, global_object, move(value));
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/NativeProperty.h b/Userland/Libraries/LibJS/Runtime/NativeProperty.h
new file mode 100644
index 0000000000..e0bb4140b7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/NativeProperty.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class NativeProperty final : public Cell {
+public:
+ NativeProperty(AK::Function<Value(VM&, GlobalObject&)> getter, AK::Function<void(VM&, GlobalObject&, Value)> setter);
+ virtual ~NativeProperty() override;
+
+ Value get(VM&, GlobalObject&) const;
+ void set(VM&, GlobalObject&, Value);
+
+private:
+ virtual const char* class_name() const override { return "NativeProperty"; }
+
+ AK::Function<Value(VM&, GlobalObject&)> m_getter;
+ AK::Function<void(VM&, GlobalObject&, Value)> m_setter;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/NumberConstructor.cpp b/Userland/Libraries/LibJS/Runtime/NumberConstructor.cpp
new file mode 100644
index 0000000000..c95441166e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/NumberConstructor.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/NumberConstructor.h>
+#include <LibJS/Runtime/NumberObject.h>
+#include <math.h>
+
+#ifdef __clang__
+# define EPSILON_VALUE pow(2, -52)
+# define MAX_SAFE_INTEGER_VALUE pow(2, 53) - 1
+# define MIN_SAFE_INTEGER_VALUE -(pow(2, 53) - 1)
+#else
+constexpr const double EPSILON_VALUE { __builtin_pow(2, -52) };
+constexpr const double MAX_SAFE_INTEGER_VALUE { __builtin_pow(2, 53) - 1 };
+constexpr const double MIN_SAFE_INTEGER_VALUE { -(__builtin_pow(2, 53) - 1) };
+#endif
+
+namespace JS {
+
+NumberConstructor::NumberConstructor(GlobalObject& global_object)
+ : NativeFunction(vm().names.Number, *global_object.function_prototype())
+{
+}
+
+void NumberConstructor::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_function(vm.names.isFinite, is_finite, 1, attr);
+ define_native_function(vm.names.isInteger, is_integer, 1, attr);
+ define_native_function(vm.names.isNaN, is_nan, 1, attr);
+ define_native_function(vm.names.isSafeInteger, is_safe_integer, 1, attr);
+ define_property(vm.names.parseFloat, global_object.get(vm.names.parseFloat));
+ define_property(vm.names.prototype, global_object.number_prototype(), 0);
+ define_property(vm.names.length, Value(1), Attribute::Configurable);
+ define_property(vm.names.EPSILON, Value(EPSILON_VALUE), 0);
+ define_property(vm.names.MAX_SAFE_INTEGER, Value(MAX_SAFE_INTEGER_VALUE), 0);
+ define_property(vm.names.MIN_SAFE_INTEGER, Value(MIN_SAFE_INTEGER_VALUE), 0);
+ define_property(vm.names.NEGATIVE_INFINITY, js_negative_infinity(), 0);
+ define_property(vm.names.POSITIVE_INFINITY, js_infinity(), 0);
+ define_property(vm.names.NaN, js_nan(), 0);
+}
+
+NumberConstructor::~NumberConstructor()
+{
+}
+
+Value NumberConstructor::call()
+{
+ if (!vm().argument_count())
+ return Value(0);
+ return vm().argument(0).to_number(global_object());
+}
+
+Value NumberConstructor::construct(Function&)
+{
+ double number = 0;
+ if (vm().argument_count()) {
+ number = vm().argument(0).to_double(global_object());
+ if (vm().exception())
+ return {};
+ }
+ return NumberObject::create(global_object(), number);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(NumberConstructor::is_finite)
+{
+ return Value(vm.argument(0).is_finite_number());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(NumberConstructor::is_integer)
+{
+ return Value(vm.argument(0).is_integer());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(NumberConstructor::is_nan)
+{
+ return Value(vm.argument(0).is_nan());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(NumberConstructor::is_safe_integer)
+{
+ if (!vm.argument(0).is_number())
+ return Value(false);
+ auto value = vm.argument(0).as_double();
+ return Value((int64_t)value == value && value >= MIN_SAFE_INTEGER_VALUE && value <= MAX_SAFE_INTEGER_VALUE);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/NumberConstructor.h b/Userland/Libraries/LibJS/Runtime/NumberConstructor.h
new file mode 100644
index 0000000000..0ed58b70cb
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/NumberConstructor.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class NumberConstructor final : public NativeFunction {
+ JS_OBJECT(NumberConstructor, NativeFunction);
+
+public:
+ explicit NumberConstructor(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~NumberConstructor() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+
+ JS_DECLARE_NATIVE_FUNCTION(is_finite);
+ JS_DECLARE_NATIVE_FUNCTION(is_integer);
+ JS_DECLARE_NATIVE_FUNCTION(is_nan);
+ JS_DECLARE_NATIVE_FUNCTION(is_safe_integer);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/NumberObject.cpp b/Userland/Libraries/LibJS/Runtime/NumberObject.cpp
new file mode 100644
index 0000000000..1376be1571
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/NumberObject.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/NumberObject.h>
+#include <LibJS/Runtime/NumberPrototype.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+NumberObject* NumberObject::create(GlobalObject& global_object, double value)
+{
+ return global_object.heap().allocate<NumberObject>(global_object, value, *global_object.number_prototype());
+}
+
+NumberObject::NumberObject(double value, Object& prototype)
+ : Object(prototype)
+ , m_value(value)
+{
+}
+
+NumberObject::~NumberObject()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/NumberObject.h b/Userland/Libraries/LibJS/Runtime/NumberObject.h
new file mode 100644
index 0000000000..7ae5e71451
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/NumberObject.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class NumberObject : public Object {
+ JS_OBJECT(NumberObject, Object);
+
+public:
+ static NumberObject* create(GlobalObject&, double);
+
+ NumberObject(double, Object& prototype);
+ virtual ~NumberObject() override;
+
+ virtual Value value_of() const override { return Value(m_value); }
+
+ double number() const { return m_value; }
+
+private:
+ double m_value { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp b/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp
new file mode 100644
index 0000000000..94add9a146
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/NumberObject.h>
+#include <LibJS/Runtime/NumberPrototype.h>
+
+namespace JS {
+
+static const u8 max_precision_for_radix[37] = {
+ // clang-format off
+ 0, 0, 52, 32, 26, 22, 20, 18, 17, 16,
+ 15, 15, 14, 14, 13, 13, 13, 12, 12, 12,
+ 12, 11, 11, 11, 11, 11, 11, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10,
+ // clang-format on
+};
+
+static char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
+
+NumberPrototype::NumberPrototype(GlobalObject& global_object)
+ : NumberObject(0, *global_object.object_prototype())
+{
+}
+
+void NumberPrototype::initialize(GlobalObject& object)
+{
+ auto& vm = this->vm();
+ Object::initialize(object);
+ define_native_function(vm.names.toString, to_string, 1, Attribute::Configurable | Attribute::Writable);
+}
+
+NumberPrototype::~NumberPrototype()
+{
+}
+
+JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::to_string)
+{
+ Value number_value;
+
+ auto this_value = vm.this_value(global_object);
+ if (this_value.is_number()) {
+ number_value = this_value;
+ } else if (this_value.is_object() && is<NumberObject>(this_value.as_object())) {
+ number_value = static_cast<NumberObject&>(this_value.as_object()).value_of();
+ } else {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NumberIncompatibleThis, "toString");
+ return {};
+ }
+
+ int radix;
+ auto argument = vm.argument(0);
+ if (argument.is_undefined()) {
+ radix = 10;
+ } else {
+ radix = argument.to_i32(global_object);
+ }
+
+ if (vm.exception() || radix < 2 || radix > 36) {
+ vm.throw_exception<RangeError>(global_object, ErrorType::InvalidRadix);
+ return {};
+ }
+
+ if (number_value.is_positive_infinity())
+ return js_string(vm, "Infinity");
+ if (number_value.is_negative_infinity())
+ return js_string(vm, "-Infinity");
+ if (number_value.is_nan())
+ return js_string(vm, "NaN");
+ if (number_value.is_positive_zero() || number_value.is_negative_zero())
+ return js_string(vm, "0");
+
+ double number = number_value.as_double();
+ bool negative = number < 0;
+ if (negative)
+ number *= -1;
+
+ int int_part = floor(number);
+ double decimal_part = number - int_part;
+
+ Vector<char> backwards_characters;
+
+ if (int_part == 0) {
+ backwards_characters.append('0');
+ } else {
+ while (int_part > 0) {
+ backwards_characters.append(digits[int_part % radix]);
+ int_part /= radix;
+ }
+ }
+
+ Vector<char> characters;
+ if (negative)
+ characters.append('-');
+
+ // Reverse characters;
+ for (ssize_t i = backwards_characters.size() - 1; i >= 0; --i) {
+ characters.append(backwards_characters[i]);
+ }
+
+ // decimal part
+ if (decimal_part != 0.0) {
+ characters.append('.');
+
+ int precision = max_precision_for_radix[radix];
+
+ for (int i = 0; i < precision; ++i) {
+ decimal_part *= radix;
+ int integral = floor(decimal_part);
+ characters.append(digits[integral]);
+ decimal_part -= integral;
+ }
+
+ while (characters.last() == '0')
+ characters.take_last();
+ }
+
+ return js_string(vm, String(characters.data(), characters.size()));
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/NumberPrototype.h b/Userland/Libraries/LibJS/Runtime/NumberPrototype.h
new file mode 100644
index 0000000000..a2b94143f1
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/NumberPrototype.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NumberObject.h>
+
+namespace JS {
+
+class NumberPrototype final : public NumberObject {
+ JS_OBJECT(NumberPrototype, NumberObject);
+
+public:
+ explicit NumberPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~NumberPrototype() override;
+
+ JS_DECLARE_NATIVE_FUNCTION(to_string);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Object.cpp b/Userland/Libraries/LibJS/Runtime/Object.cpp
new file mode 100644
index 0000000000..87a9d75dd5
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Object.cpp
@@ -0,0 +1,927 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <AK/TemporaryChange.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/Accessor.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/NativeFunction.h>
+#include <LibJS/Runtime/NativeProperty.h>
+#include <LibJS/Runtime/Object.h>
+#include <LibJS/Runtime/Shape.h>
+#include <LibJS/Runtime/StringObject.h>
+#include <LibJS/Runtime/Value.h>
+
+//#define OBJECT_DEBUG
+
+namespace JS {
+
+PropertyDescriptor PropertyDescriptor::from_dictionary(VM& vm, const Object& object)
+{
+ PropertyAttributes attributes;
+ if (object.has_property(vm.names.configurable)) {
+ attributes.set_has_configurable();
+ if (object.get(vm.names.configurable).value_or(Value(false)).to_boolean())
+ attributes.set_configurable();
+ if (vm.exception())
+ return {};
+ }
+ if (object.has_property(vm.names.enumerable)) {
+ attributes.set_has_enumerable();
+ if (object.get(vm.names.enumerable).value_or(Value(false)).to_boolean())
+ attributes.set_enumerable();
+ if (vm.exception())
+ return {};
+ }
+ if (object.has_property(vm.names.writable)) {
+ attributes.set_has_writable();
+ if (object.get(vm.names.writable).value_or(Value(false)).to_boolean())
+ attributes.set_writable();
+ if (vm.exception())
+ return {};
+ }
+ PropertyDescriptor descriptor { attributes, object.get(vm.names.value), nullptr, nullptr };
+ if (vm.exception())
+ return {};
+ auto getter = object.get(vm.names.get);
+ if (vm.exception())
+ return {};
+ if (getter.is_function())
+ descriptor.getter = &getter.as_function();
+ auto setter = object.get(vm.names.set);
+ if (vm.exception())
+ return {};
+ if (setter.is_function())
+ descriptor.setter = &setter.as_function();
+ return descriptor;
+}
+
+Object* Object::create_empty(GlobalObject& global_object)
+{
+ return global_object.heap().allocate<Object>(global_object, *global_object.new_object_shape());
+}
+
+Object::Object(GlobalObjectTag)
+{
+ // This is the global object
+ m_shape = heap().allocate_without_global_object<Shape>(*this);
+}
+
+Object::Object(ConstructWithoutPrototypeTag, GlobalObject& global_object)
+{
+ m_shape = heap().allocate_without_global_object<Shape>(global_object);
+}
+
+Object::Object(Object& prototype)
+{
+ m_shape = prototype.global_object().empty_object_shape();
+ set_prototype(&prototype);
+}
+
+Object::Object(Shape& shape)
+ : m_shape(&shape)
+{
+ m_storage.resize(shape.property_count());
+}
+
+void Object::initialize(GlobalObject&)
+{
+}
+
+Object::~Object()
+{
+}
+
+Object* Object::prototype()
+{
+ return shape().prototype();
+}
+
+const Object* Object::prototype() const
+{
+ return shape().prototype();
+}
+
+bool Object::set_prototype(Object* new_prototype)
+{
+ if (prototype() == new_prototype)
+ return true;
+ if (!m_is_extensible)
+ return false;
+ if (shape().is_unique()) {
+ shape().set_prototype_without_transition(new_prototype);
+ return true;
+ }
+ m_shape = m_shape->create_prototype_transition(new_prototype);
+ return true;
+}
+
+bool Object::has_prototype(const Object* prototype) const
+{
+ for (auto* object = this->prototype(); object; object = object->prototype()) {
+ if (vm().exception())
+ return false;
+ if (object == prototype)
+ return true;
+ }
+ return false;
+}
+
+bool Object::prevent_extensions()
+{
+ m_is_extensible = false;
+ return true;
+}
+
+Value Object::get_own_property(const PropertyName& property_name, Value receiver) const
+{
+ ASSERT(property_name.is_valid());
+ ASSERT(!receiver.is_empty());
+
+ Value value_here;
+
+ if (property_name.is_number()) {
+ auto existing_property = m_indexed_properties.get(nullptr, property_name.as_number(), false);
+ if (!existing_property.has_value())
+ return {};
+ value_here = existing_property.value().value.value_or(js_undefined());
+ } else {
+ auto metadata = shape().lookup(property_name.to_string_or_symbol());
+ if (!metadata.has_value())
+ return {};
+ value_here = m_storage[metadata.value().offset].value_or(js_undefined());
+ }
+
+ ASSERT(!value_here.is_empty());
+ if (value_here.is_accessor())
+ return value_here.as_accessor().call_getter(receiver);
+ if (value_here.is_native_property())
+ return call_native_property_getter(value_here.as_native_property(), receiver);
+ return value_here;
+}
+
+Value Object::get_own_properties(const Object& this_object, PropertyKind kind, bool only_enumerable_properties, GetOwnPropertyReturnType return_type) const
+{
+ auto* properties_array = Array::create(global_object());
+
+ // FIXME: Support generic iterables
+ if (is<StringObject>(this_object)) {
+ auto str = static_cast<const StringObject&>(this_object).primitive_string().string();
+
+ for (size_t i = 0; i < str.length(); ++i) {
+ if (kind == PropertyKind::Key) {
+ properties_array->define_property(i, js_string(vm(), String::number(i)));
+ } else if (kind == PropertyKind::Value) {
+ properties_array->define_property(i, js_string(vm(), String::formatted("{:c}", str[i])));
+ } else {
+ auto* entry_array = Array::create(global_object());
+ entry_array->define_property(0, js_string(vm(), String::number(i)));
+ entry_array->define_property(1, js_string(vm(), String::formatted("{:c}", str[i])));
+ properties_array->define_property(i, entry_array);
+ }
+ if (vm().exception())
+ return {};
+ }
+
+ return properties_array;
+ }
+
+ size_t property_index = 0;
+ for (auto& entry : m_indexed_properties) {
+ auto value_and_attributes = entry.value_and_attributes(const_cast<Object*>(&this_object));
+ if (only_enumerable_properties && !value_and_attributes.attributes.is_enumerable())
+ continue;
+
+ if (kind == PropertyKind::Key) {
+ properties_array->define_property(property_index, js_string(vm(), String::number(entry.index())));
+ } else if (kind == PropertyKind::Value) {
+ properties_array->define_property(property_index, value_and_attributes.value);
+ } else {
+ auto* entry_array = Array::create(global_object());
+ entry_array->define_property(0, js_string(vm(), String::number(entry.index())));
+ entry_array->define_property(1, value_and_attributes.value);
+ properties_array->define_property(property_index, entry_array);
+ }
+ if (vm().exception())
+ return {};
+
+ ++property_index;
+ }
+
+ for (auto& it : this_object.shape().property_table_ordered()) {
+ if (only_enumerable_properties && !it.value.attributes.is_enumerable())
+ continue;
+
+ if (return_type == GetOwnPropertyReturnType::StringOnly && it.key.is_symbol())
+ continue;
+ if (return_type == GetOwnPropertyReturnType::SymbolOnly && it.key.is_string())
+ continue;
+
+ if (kind == PropertyKind::Key) {
+ properties_array->define_property(property_index, it.key.to_value(vm()));
+ } else if (kind == PropertyKind::Value) {
+ properties_array->define_property(property_index, this_object.get(it.key));
+ } else {
+ auto* entry_array = Array::create(global_object());
+ entry_array->define_property(0, it.key.to_value(vm()));
+ entry_array->define_property(1, this_object.get(it.key));
+ properties_array->define_property(property_index, entry_array);
+ }
+ if (vm().exception())
+ return {};
+
+ ++property_index;
+ }
+
+ return properties_array;
+}
+
+Optional<PropertyDescriptor> Object::get_own_property_descriptor(const PropertyName& property_name) const
+{
+ ASSERT(property_name.is_valid());
+
+ Value value;
+ PropertyAttributes attributes;
+
+ if (property_name.is_number()) {
+ auto existing_value = m_indexed_properties.get(nullptr, property_name.as_number(), false);
+ if (!existing_value.has_value())
+ return {};
+ value = existing_value.value().value;
+ attributes = existing_value.value().attributes;
+ attributes = default_attributes;
+ } else {
+ auto metadata = shape().lookup(property_name.to_string_or_symbol());
+ if (!metadata.has_value())
+ return {};
+ value = m_storage[metadata.value().offset];
+ if (vm().exception())
+ return {};
+ attributes = metadata.value().attributes;
+ }
+
+ PropertyDescriptor descriptor { attributes, {}, nullptr, nullptr };
+ if (value.is_native_property()) {
+ auto result = call_native_property_getter(value.as_native_property(), const_cast<Object*>(this));
+ descriptor.value = result.value_or(js_undefined());
+ } else if (value.is_accessor()) {
+ auto& pair = value.as_accessor();
+ if (pair.getter())
+ descriptor.getter = pair.getter();
+ if (pair.setter())
+ descriptor.setter = pair.setter();
+ } else {
+ descriptor.value = value.value_or(js_undefined());
+ }
+
+ return descriptor;
+}
+
+Value Object::get_own_property_descriptor_object(const PropertyName& property_name) const
+{
+ ASSERT(property_name.is_valid());
+
+ auto& vm = this->vm();
+ auto descriptor_opt = get_own_property_descriptor(property_name);
+ if (!descriptor_opt.has_value())
+ return js_undefined();
+ auto descriptor = descriptor_opt.value();
+
+ auto* descriptor_object = Object::create_empty(global_object());
+ descriptor_object->define_property(vm.names.enumerable, Value(descriptor.attributes.is_enumerable()));
+ if (vm.exception())
+ return {};
+ descriptor_object->define_property(vm.names.configurable, Value(descriptor.attributes.is_configurable()));
+ if (vm.exception())
+ return {};
+ if (descriptor.is_data_descriptor()) {
+ descriptor_object->define_property(vm.names.value, descriptor.value.value_or(js_undefined()));
+ if (vm.exception())
+ return {};
+ descriptor_object->define_property(vm.names.writable, Value(descriptor.attributes.is_writable()));
+ if (vm.exception())
+ return {};
+ } else if (descriptor.is_accessor_descriptor()) {
+ if (descriptor.getter) {
+ descriptor_object->define_property(vm.names.get, Value(descriptor.getter));
+ if (vm.exception())
+ return {};
+ }
+ if (descriptor.setter) {
+ descriptor_object->define_property(vm.names.set, Value(descriptor.setter));
+ if (vm.exception())
+ return {};
+ }
+ }
+ return descriptor_object;
+}
+
+void Object::set_shape(Shape& new_shape)
+{
+ m_storage.resize(new_shape.property_count());
+ m_shape = &new_shape;
+}
+
+bool Object::define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions)
+{
+ auto& vm = this->vm();
+ bool is_accessor_property = descriptor.has_property(vm.names.get) || descriptor.has_property(vm.names.set);
+ PropertyAttributes attributes;
+ if (descriptor.has_property(vm.names.configurable)) {
+ attributes.set_has_configurable();
+ if (descriptor.get(vm.names.configurable).value_or(Value(false)).to_boolean())
+ attributes.set_configurable();
+ if (vm.exception())
+ return false;
+ }
+ if (descriptor.has_property(vm.names.enumerable)) {
+ attributes.set_has_enumerable();
+ if (descriptor.get(vm.names.enumerable).value_or(Value(false)).to_boolean())
+ attributes.set_enumerable();
+ if (vm.exception())
+ return false;
+ }
+
+ if (is_accessor_property) {
+ if (descriptor.has_property(vm.names.value) || descriptor.has_property(vm.names.writable)) {
+ if (throw_exceptions)
+ vm.throw_exception<TypeError>(global_object(), ErrorType::AccessorValueOrWritable);
+ return false;
+ }
+
+ auto getter = descriptor.get(vm.names.get).value_or(js_undefined());
+ if (vm.exception())
+ return {};
+ auto setter = descriptor.get(vm.names.set).value_or(js_undefined());
+ if (vm.exception())
+ return {};
+
+ Function* getter_function { nullptr };
+ Function* setter_function { nullptr };
+
+ if (getter.is_function()) {
+ getter_function = &getter.as_function();
+ } else if (!getter.is_undefined()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::AccessorBadField, "get");
+ return false;
+ }
+
+ if (setter.is_function()) {
+ setter_function = &setter.as_function();
+ } else if (!setter.is_undefined()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::AccessorBadField, "set");
+ return false;
+ }
+
+#ifdef OBJECT_DEBUG
+ dbgln("Defining new property {} with accessor descriptor {{ attributes={}, getter={}, setter={} }}", property_name.to_display_string(), attributes, getter, setter);
+#endif
+
+ return define_property(property_name, Accessor::create(vm, getter_function, setter_function), attributes, throw_exceptions);
+ }
+
+ auto value = descriptor.get(vm.names.value);
+ if (vm.exception())
+ return {};
+ if (descriptor.has_property(vm.names.writable)) {
+ attributes.set_has_writable();
+ if (descriptor.get(vm.names.writable).value_or(Value(false)).to_boolean())
+ attributes.set_writable();
+ if (vm.exception())
+ return false;
+ }
+ if (vm.exception())
+ return {};
+
+#ifdef OBJECT_DEBUG
+ dbgln("Defining new property {} with data descriptor {{ attributes={}, value={} }}", property_name.to_display_string(), attributes, value);
+#endif
+
+ return define_property(property_name, value, attributes, throw_exceptions);
+}
+
+bool Object::define_property_without_transition(const PropertyName& property_name, Value value, PropertyAttributes attributes, bool throw_exceptions)
+{
+ TemporaryChange change(m_transitions_enabled, false);
+ return define_property(property_name, value, attributes, throw_exceptions);
+}
+
+bool Object::define_property(const PropertyName& property_name, Value value, PropertyAttributes attributes, bool throw_exceptions)
+{
+ ASSERT(property_name.is_valid());
+
+ if (property_name.is_number())
+ return put_own_property_by_index(*this, property_name.as_number(), value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions);
+
+ if (property_name.is_string()) {
+ i32 property_index = property_name.as_string().to_int().value_or(-1);
+ if (property_index >= 0)
+ return put_own_property_by_index(*this, property_index, value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions);
+ }
+ return put_own_property(*this, property_name.to_string_or_symbol(), value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions);
+}
+
+bool Object::define_accessor(const PropertyName& property_name, Function& getter_or_setter, bool is_getter, PropertyAttributes attributes, bool throw_exceptions)
+{
+ ASSERT(property_name.is_valid());
+
+ Accessor* accessor { nullptr };
+ auto property_metadata = shape().lookup(property_name.to_string_or_symbol());
+ if (property_metadata.has_value()) {
+ auto existing_property = get_direct(property_metadata.value().offset);
+ if (existing_property.is_accessor())
+ accessor = &existing_property.as_accessor();
+ }
+ if (!accessor) {
+ accessor = Accessor::create(vm(), nullptr, nullptr);
+ bool definition_success = define_property(property_name, accessor, attributes, throw_exceptions);
+ if (vm().exception())
+ return {};
+ if (!definition_success)
+ return false;
+ }
+ if (is_getter)
+ accessor->set_getter(&getter_or_setter);
+ else
+ accessor->set_setter(&getter_or_setter);
+
+ return true;
+}
+
+bool Object::put_own_property(Object& this_object, const StringOrSymbol& property_name, Value value, PropertyAttributes attributes, PutOwnPropertyMode mode, bool throw_exceptions)
+{
+ ASSERT(!(mode == PutOwnPropertyMode::Put && value.is_accessor()));
+
+ if (value.is_accessor()) {
+ auto& accessor = value.as_accessor();
+ if (accessor.getter())
+ attributes.set_has_getter();
+ if (accessor.setter())
+ attributes.set_has_setter();
+ }
+
+ // NOTE: We disable transitions during initialize(), this makes building common runtime objects significantly faster.
+ // Transitions are primarily interesting when scripts add properties to objects.
+ if (!m_transitions_enabled && !m_shape->is_unique()) {
+ m_shape->add_property_without_transition(property_name, attributes);
+ m_storage.resize(m_shape->property_count());
+ m_storage[m_shape->property_count() - 1] = value;
+ return true;
+ }
+
+ auto metadata = shape().lookup(property_name);
+ bool new_property = !metadata.has_value();
+
+ if (!is_extensible() && new_property) {
+#ifdef OBJECT_DEBUG
+ dbgln("Disallow define_property of non-extensible object");
+#endif
+ if (throw_exceptions && vm().in_strict_mode())
+ vm().throw_exception<TypeError>(global_object(), ErrorType::NonExtensibleDefine, property_name.to_display_string());
+ return false;
+ }
+
+ if (new_property) {
+ if (!m_shape->is_unique() && shape().property_count() > 100) {
+ // If you add more than 100 properties to an object, let's stop doing
+ // transitions to avoid filling up the heap with shapes.
+ ensure_shape_is_unique();
+ }
+
+ if (m_shape->is_unique()) {
+ m_shape->add_property_to_unique_shape(property_name, attributes);
+ m_storage.resize(m_shape->property_count());
+ } else if (m_transitions_enabled) {
+ set_shape(*m_shape->create_put_transition(property_name, attributes));
+ } else {
+ m_shape->add_property_without_transition(property_name, attributes);
+ m_storage.resize(m_shape->property_count());
+ }
+ metadata = shape().lookup(property_name);
+ ASSERT(metadata.has_value());
+ }
+
+ if (!new_property && mode == PutOwnPropertyMode::DefineProperty && !metadata.value().attributes.is_configurable() && attributes != metadata.value().attributes) {
+#ifdef OBJECT_DEBUG
+ dbgln("Disallow reconfig of non-configurable property");
+#endif
+ if (throw_exceptions)
+ vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_name.to_display_string());
+ return false;
+ }
+
+ if (mode == PutOwnPropertyMode::DefineProperty && attributes != metadata.value().attributes) {
+ if (m_shape->is_unique()) {
+ m_shape->reconfigure_property_in_unique_shape(property_name, attributes);
+ } else {
+ set_shape(*m_shape->create_configure_transition(property_name, attributes));
+ }
+ metadata = shape().lookup(property_name);
+
+#ifdef OBJECT_DEBUG
+ dbgln("Reconfigured property {}, new shape says offset is {} and my storage capacity is {}", property_name.to_display_string(), metadata.value().offset, m_storage.size());
+#endif
+ }
+
+ auto value_here = m_storage[metadata.value().offset];
+ if (!new_property && mode == PutOwnPropertyMode::Put && !value_here.is_accessor() && !metadata.value().attributes.is_writable()) {
+#ifdef OBJECT_DEBUG
+ dbgln("Disallow write to non-writable property");
+#endif
+ return false;
+ }
+
+ if (value.is_empty())
+ return true;
+
+ if (value_here.is_native_property()) {
+ call_native_property_setter(value_here.as_native_property(), &this_object, value);
+ } else {
+ m_storage[metadata.value().offset] = value;
+ }
+ return true;
+}
+
+bool Object::put_own_property_by_index(Object& this_object, u32 property_index, Value value, PropertyAttributes attributes, PutOwnPropertyMode mode, bool throw_exceptions)
+{
+ ASSERT(!(mode == PutOwnPropertyMode::Put && value.is_accessor()));
+
+ auto existing_property = m_indexed_properties.get(nullptr, property_index, false);
+ auto new_property = !existing_property.has_value();
+
+ if (!is_extensible() && new_property) {
+#ifdef OBJECT_DEBUG
+ dbgln("Disallow define_property of non-extensible object");
+#endif
+ if (throw_exceptions && vm().in_strict_mode())
+ vm().throw_exception<TypeError>(global_object(), ErrorType::NonExtensibleDefine, property_index);
+ return false;
+ }
+
+ if (value.is_accessor()) {
+ auto& accessor = value.as_accessor();
+ if (accessor.getter())
+ attributes.set_has_getter();
+ if (accessor.setter())
+ attributes.set_has_setter();
+ }
+
+ PropertyAttributes existing_attributes = new_property ? 0 : existing_property.value().attributes;
+
+ if (!new_property && mode == PutOwnPropertyMode::DefineProperty && !existing_attributes.is_configurable() && attributes != existing_attributes) {
+#ifdef OBJECT_DEBUG
+ dbgln("Disallow reconfig of non-configurable property");
+#endif
+ if (throw_exceptions)
+ vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_index);
+ return false;
+ }
+
+ auto value_here = new_property ? Value() : existing_property.value().value;
+ if (!new_property && mode == PutOwnPropertyMode::Put && !value_here.is_accessor() && !existing_attributes.is_writable()) {
+#ifdef OBJECT_DEBUG
+ dbgln("Disallow write to non-writable property");
+#endif
+ return false;
+ }
+
+ if (value.is_empty())
+ return true;
+
+ if (value_here.is_native_property()) {
+ call_native_property_setter(value_here.as_native_property(), &this_object, value);
+ } else {
+ m_indexed_properties.put(&this_object, property_index, value, attributes, mode == PutOwnPropertyMode::Put);
+ }
+ return true;
+}
+
+Value Object::delete_property(const PropertyName& property_name)
+{
+ ASSERT(property_name.is_valid());
+
+ if (property_name.is_number())
+ return Value(m_indexed_properties.remove(property_name.as_number()));
+
+ if (property_name.is_string()) {
+ i32 property_index = property_name.as_string().to_int().value_or(-1);
+ if (property_index >= 0)
+ return Value(m_indexed_properties.remove(property_index));
+ }
+
+ auto metadata = shape().lookup(property_name.to_string_or_symbol());
+ if (!metadata.has_value())
+ return Value(true);
+ if (!metadata.value().attributes.is_configurable())
+ return Value(false);
+
+ size_t deleted_offset = metadata.value().offset;
+
+ ensure_shape_is_unique();
+
+ shape().remove_property_from_unique_shape(property_name.to_string_or_symbol(), deleted_offset);
+ m_storage.remove(deleted_offset);
+ return Value(true);
+}
+
+void Object::ensure_shape_is_unique()
+{
+ if (shape().is_unique())
+ return;
+
+ m_shape = m_shape->create_unique_clone();
+}
+
+Value Object::get_by_index(u32 property_index) const
+{
+ const Object* object = this;
+ while (object) {
+ if (is<StringObject>(*this)) {
+ auto& string = static_cast<const StringObject*>(this)->primitive_string().string();
+ if (property_index < string.length())
+ return js_string(heap(), string.substring(property_index, 1));
+ return js_undefined();
+ }
+ if (static_cast<size_t>(property_index) < object->m_indexed_properties.array_like_size()) {
+ auto result = object->m_indexed_properties.get(const_cast<Object*>(this), property_index);
+ if (vm().exception())
+ return {};
+ if (result.has_value() && !result.value().value.is_empty())
+ return result.value().value;
+ return {};
+ }
+ object = object->prototype();
+ if (vm().exception())
+ return {};
+ }
+ return {};
+}
+
+Value Object::get(const PropertyName& property_name, Value receiver) const
+{
+ ASSERT(property_name.is_valid());
+
+ if (property_name.is_number())
+ return get_by_index(property_name.as_number());
+
+ if (property_name.is_string()) {
+ auto property_string = property_name.to_string();
+ i32 property_index = property_string.to_int().value_or(-1);
+ if (property_index >= 0)
+ return get_by_index(property_index);
+ }
+
+ const Object* object = this;
+ while (object) {
+ if (receiver.is_empty())
+ receiver = Value(const_cast<Object*>(this));
+ auto value = object->get_own_property(property_name, receiver);
+ if (vm().exception())
+ return {};
+ if (!value.is_empty())
+ return value;
+ object = object->prototype();
+ if (vm().exception())
+ return {};
+ }
+ return {};
+}
+
+bool Object::put_by_index(u32 property_index, Value value)
+{
+ ASSERT(!value.is_empty());
+
+ // If there's a setter in the prototype chain, we go to the setter.
+ // Otherwise, it goes in the own property storage.
+ Object* object = this;
+ while (object) {
+ auto existing_value = object->m_indexed_properties.get(nullptr, property_index, false);
+ if (existing_value.has_value()) {
+ auto value_here = existing_value.value();
+ if (value_here.value.is_accessor()) {
+ value_here.value.as_accessor().call_setter(object, value);
+ return true;
+ }
+ if (value_here.value.is_native_property()) {
+ // FIXME: Why doesn't put_by_index() receive the receiver value from put()?!
+ auto receiver = this;
+ call_native_property_setter(value_here.value.as_native_property(), receiver, value);
+ return true;
+ }
+ }
+ object = object->prototype();
+ if (vm().exception())
+ return {};
+ }
+ return put_own_property_by_index(*this, property_index, value, default_attributes, PutOwnPropertyMode::Put);
+}
+
+bool Object::put(const PropertyName& property_name, Value value, Value receiver)
+{
+ ASSERT(property_name.is_valid());
+
+ if (property_name.is_number())
+ return put_by_index(property_name.as_number(), value);
+
+ ASSERT(!value.is_empty());
+
+ if (property_name.is_string()) {
+ auto& property_string = property_name.as_string();
+ i32 property_index = property_string.to_int().value_or(-1);
+ if (property_index >= 0)
+ return put_by_index(property_index, value);
+ }
+
+ auto string_or_symbol = property_name.to_string_or_symbol();
+
+ if (receiver.is_empty())
+ receiver = Value(this);
+
+ // If there's a setter in the prototype chain, we go to the setter.
+ // Otherwise, it goes in the own property storage.
+ Object* object = this;
+ while (object) {
+ auto metadata = object->shape().lookup(string_or_symbol);
+ if (metadata.has_value()) {
+ auto value_here = object->m_storage[metadata.value().offset];
+ if (value_here.is_accessor()) {
+ value_here.as_accessor().call_setter(receiver, value);
+ return true;
+ }
+ if (value_here.is_native_property()) {
+ call_native_property_setter(value_here.as_native_property(), receiver, value);
+ return true;
+ }
+ }
+ object = object->prototype();
+ if (vm().exception())
+ return false;
+ }
+ return put_own_property(*this, string_or_symbol, value, default_attributes, PutOwnPropertyMode::Put);
+}
+
+bool Object::define_native_function(const StringOrSymbol& property_name, AK::Function<Value(VM&, GlobalObject&)> native_function, i32 length, PropertyAttributes attribute)
+{
+ auto& vm = this->vm();
+ String function_name;
+ if (property_name.is_string()) {
+ function_name = property_name.as_string();
+ } else {
+ function_name = String::formatted("[{}]", property_name.as_symbol()->description());
+ }
+ auto* function = NativeFunction::create(global_object(), function_name, move(native_function));
+ function->define_property_without_transition(vm.names.length, Value(length), Attribute::Configurable);
+ if (vm.exception())
+ return {};
+ function->define_property_without_transition(vm.names.name, js_string(vm.heap(), function_name), Attribute::Configurable);
+ if (vm.exception())
+ return {};
+ return define_property(property_name, function, attribute);
+}
+
+bool Object::define_native_property(const StringOrSymbol& property_name, AK::Function<Value(VM&, GlobalObject&)> getter, AK::Function<void(VM&, GlobalObject&, Value)> setter, PropertyAttributes attribute)
+{
+ return define_property(property_name, heap().allocate_without_global_object<NativeProperty>(move(getter), move(setter)), attribute);
+}
+
+void Object::visit_edges(Cell::Visitor& visitor)
+{
+ Cell::visit_edges(visitor);
+ visitor.visit(m_shape);
+
+ for (auto& value : m_storage)
+ visitor.visit(value);
+
+ m_indexed_properties.for_each_value([&visitor](auto& value) {
+ visitor.visit(value);
+ });
+}
+
+bool Object::has_property(const PropertyName& property_name) const
+{
+ const Object* object = this;
+ while (object) {
+ if (object->has_own_property(property_name))
+ return true;
+ object = object->prototype();
+ if (vm().exception())
+ return false;
+ }
+ return false;
+}
+
+bool Object::has_own_property(const PropertyName& property_name) const
+{
+ ASSERT(property_name.is_valid());
+
+ auto has_indexed_property = [&](u32 index) -> bool {
+ if (is<StringObject>(*this))
+ return index < static_cast<const StringObject*>(this)->primitive_string().string().length();
+ return m_indexed_properties.has_index(index);
+ };
+
+ if (property_name.is_number())
+ return has_indexed_property(property_name.as_number());
+
+ if (property_name.is_string()) {
+ i32 property_index = property_name.as_string().to_int().value_or(-1);
+ if (property_index >= 0)
+ return has_indexed_property(property_index);
+ }
+
+ return shape().lookup(property_name.to_string_or_symbol()).has_value();
+}
+
+Value Object::ordinary_to_primitive(Value::PreferredType preferred_type) const
+{
+ ASSERT(preferred_type == Value::PreferredType::String || preferred_type == Value::PreferredType::Number);
+
+ auto& vm = this->vm();
+
+ Vector<FlyString, 2> method_names;
+ if (preferred_type == Value::PreferredType::String)
+ method_names = { vm.names.toString, vm.names.valueOf };
+ else
+ method_names = { vm.names.valueOf, vm.names.toString };
+
+ for (auto& method_name : method_names) {
+ auto method = get(method_name);
+ if (vm.exception())
+ return {};
+ if (method.is_function()) {
+ auto result = vm.call(method.as_function(), const_cast<Object*>(this));
+ if (!result.is_object())
+ return result;
+ }
+ }
+ vm.throw_exception<TypeError>(global_object(), ErrorType::Convert, "object", preferred_type == Value::PreferredType::String ? "string" : "number");
+ return {};
+}
+
+Value Object::invoke(const StringOrSymbol& property_name, Optional<MarkedValueList> arguments)
+{
+ auto& vm = this->vm();
+ auto property = get(property_name).value_or(js_undefined());
+ if (vm.exception())
+ return {};
+ if (!property.is_function()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::NotAFunction, property.to_string_without_side_effects());
+ return {};
+ }
+ return vm.call(property.as_function(), this, move(arguments));
+}
+
+Value Object::call_native_property_getter(NativeProperty& property, Value this_value) const
+{
+ auto& vm = this->vm();
+ CallFrame call_frame;
+ call_frame.is_strict_mode = vm.in_strict_mode();
+ call_frame.this_value = this_value;
+ vm.push_call_frame(call_frame, global_object());
+ if (vm.exception())
+ return {};
+ auto result = property.get(vm, global_object());
+ vm.pop_call_frame();
+ return result;
+}
+
+void Object::call_native_property_setter(NativeProperty& property, Value this_value, Value setter_value) const
+{
+ auto& vm = this->vm();
+ CallFrame call_frame;
+ call_frame.is_strict_mode = vm.in_strict_mode();
+ call_frame.this_value = this_value;
+ vm.push_call_frame(call_frame, global_object());
+ if (vm.exception())
+ return;
+ property.set(vm, global_object(), setter_value);
+ vm.pop_call_frame();
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Object.h b/Userland/Libraries/LibJS/Runtime/Object.h
new file mode 100644
index 0000000000..e869aee748
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Object.h
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/String.h>
+#include <LibJS/Forward.h>
+#include <LibJS/Runtime/Cell.h>
+#include <LibJS/Runtime/IndexedProperties.h>
+#include <LibJS/Runtime/MarkedValueList.h>
+#include <LibJS/Runtime/PrimitiveString.h>
+#include <LibJS/Runtime/PropertyName.h>
+#include <LibJS/Runtime/Shape.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+#define JS_OBJECT(class_, base_class) \
+public: \
+ using Base = base_class; \
+ virtual const char* class_name() const override { return #class_; }
+
+struct PropertyDescriptor {
+ PropertyAttributes attributes;
+ Value value;
+ Function* getter { nullptr };
+ Function* setter { nullptr };
+
+ static PropertyDescriptor from_dictionary(VM&, const Object&);
+
+ bool is_accessor_descriptor() const { return getter || setter; }
+ bool is_data_descriptor() const { return !(value.is_empty() && !attributes.has_writable()); }
+ bool is_generic_descriptor() const { return !is_accessor_descriptor() && !is_data_descriptor(); }
+};
+
+class Object : public Cell {
+public:
+ static Object* create_empty(GlobalObject&);
+
+ explicit Object(Object& prototype);
+ explicit Object(Shape&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~Object();
+
+ enum class PropertyKind {
+ Key,
+ Value,
+ KeyAndValue,
+ };
+
+ enum class GetOwnPropertyReturnType {
+ StringOnly,
+ SymbolOnly,
+ };
+
+ enum class PutOwnPropertyMode {
+ Put,
+ DefineProperty,
+ };
+
+ Shape& shape() { return *m_shape; }
+ const Shape& shape() const { return *m_shape; }
+
+ GlobalObject& global_object() const { return *shape().global_object(); }
+
+ virtual Value get(const PropertyName&, Value receiver = {}) const;
+
+ virtual bool has_property(const PropertyName&) const;
+ bool has_own_property(const PropertyName&) const;
+
+ virtual bool put(const PropertyName&, Value, Value receiver = {});
+
+ Value get_own_property(const PropertyName&, Value receiver) const;
+ Value get_own_properties(const Object& this_object, PropertyKind, bool only_enumerable_properties = false, GetOwnPropertyReturnType = GetOwnPropertyReturnType::StringOnly) const;
+ virtual Optional<PropertyDescriptor> get_own_property_descriptor(const PropertyName&) const;
+ Value get_own_property_descriptor_object(const PropertyName&) const;
+
+ virtual bool define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions = true);
+ bool define_property(const PropertyName&, Value value, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true);
+ bool define_property_without_transition(const PropertyName&, Value value, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true);
+ bool define_accessor(const PropertyName&, Function& getter_or_setter, bool is_getter, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true);
+
+ bool define_native_function(const StringOrSymbol& property_name, AK::Function<Value(VM&, GlobalObject&)>, i32 length = 0, PropertyAttributes attributes = default_attributes);
+ bool define_native_property(const StringOrSymbol& property_name, AK::Function<Value(VM&, GlobalObject&)> getter, AK::Function<void(VM&, GlobalObject&, Value)> setter, PropertyAttributes attributes = default_attributes);
+
+ virtual Value delete_property(const PropertyName&);
+
+ virtual bool is_array() const { return false; }
+ virtual bool is_function() const { return false; }
+ virtual bool is_typed_array() const { return false; }
+
+ virtual const char* class_name() const override { return "Object"; }
+ virtual void visit_edges(Cell::Visitor&) override;
+
+ virtual Object* prototype();
+ virtual const Object* prototype() const;
+ virtual bool set_prototype(Object* prototype);
+ bool has_prototype(const Object* prototype) const;
+
+ virtual bool is_extensible() const { return m_is_extensible; }
+ virtual bool prevent_extensions();
+
+ virtual Value value_of() const { return Value(const_cast<Object*>(this)); }
+ virtual Value ordinary_to_primitive(Value::PreferredType preferred_type) const;
+
+ Value get_direct(size_t index) const { return m_storage[index]; }
+
+ const IndexedProperties& indexed_properties() const { return m_indexed_properties; }
+ IndexedProperties& indexed_properties() { return m_indexed_properties; }
+ void set_indexed_property_elements(Vector<Value>&& values) { m_indexed_properties = IndexedProperties(move(values)); }
+
+ Value invoke(const StringOrSymbol& property_name, Optional<MarkedValueList> arguments = {});
+
+ void ensure_shape_is_unique();
+
+ void enable_transitions() { m_transitions_enabled = true; }
+ void disable_transitions() { m_transitions_enabled = false; }
+
+protected:
+ enum class GlobalObjectTag { Tag };
+ enum class ConstructWithoutPrototypeTag { Tag };
+ explicit Object(GlobalObjectTag);
+ Object(ConstructWithoutPrototypeTag, GlobalObject&);
+
+ virtual Value get_by_index(u32 property_index) const;
+ virtual bool put_by_index(u32 property_index, Value);
+
+private:
+ bool put_own_property(Object& this_object, const StringOrSymbol& property_name, Value, PropertyAttributes attributes, PutOwnPropertyMode = PutOwnPropertyMode::Put, bool throw_exceptions = true);
+ bool put_own_property_by_index(Object& this_object, u32 property_index, Value, PropertyAttributes attributes, PutOwnPropertyMode = PutOwnPropertyMode::Put, bool throw_exceptions = true);
+
+ Value call_native_property_getter(NativeProperty& property, Value this_value) const;
+ void call_native_property_setter(NativeProperty& property, Value this_value, Value) const;
+
+ void set_shape(Shape&);
+
+ bool m_is_extensible { true };
+ bool m_transitions_enabled { true };
+ Shape* m_shape { nullptr };
+ Vector<Value> m_storage;
+ IndexedProperties m_indexed_properties;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp
new file mode 100644
index 0000000000..a5601d9753
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/ObjectConstructor.h>
+#include <LibJS/Runtime/ProxyObject.h>
+#include <LibJS/Runtime/Shape.h>
+
+namespace JS {
+
+ObjectConstructor::ObjectConstructor(GlobalObject& global_object)
+ : NativeFunction(vm().names.Object, *global_object.function_prototype())
+{
+}
+
+void ObjectConstructor::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+ define_property(vm.names.prototype, global_object.object_prototype(), 0);
+ define_property(vm.names.length, Value(1), Attribute::Configurable);
+
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_function(vm.names.defineProperty, define_property_, 3, attr);
+ define_native_function(vm.names.is, is, 2, attr);
+ define_native_function(vm.names.getOwnPropertyDescriptor, get_own_property_descriptor, 2, attr);
+ define_native_function(vm.names.getOwnPropertyNames, get_own_property_names, 1, attr);
+ define_native_function(vm.names.getPrototypeOf, get_prototype_of, 1, attr);
+ define_native_function(vm.names.setPrototypeOf, set_prototype_of, 2, attr);
+ define_native_function(vm.names.isExtensible, is_extensible, 1, attr);
+ define_native_function(vm.names.preventExtensions, prevent_extensions, 1, attr);
+ define_native_function(vm.names.keys, keys, 1, attr);
+ define_native_function(vm.names.values, values, 1, attr);
+ define_native_function(vm.names.entries, entries, 1, attr);
+}
+
+ObjectConstructor::~ObjectConstructor()
+{
+}
+
+Value ObjectConstructor::call()
+{
+ auto value = vm().argument(0);
+ if (value.is_nullish())
+ return Object::create_empty(global_object());
+ return value.to_object(global_object());
+}
+
+Value ObjectConstructor::construct(Function&)
+{
+ return call();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_names)
+{
+ if (!vm.argument_count())
+ return {};
+ auto* object = vm.argument(0).to_object(global_object);
+ if (vm.exception())
+ return {};
+ auto* result = Array::create(global_object);
+ for (auto& entry : object->indexed_properties())
+ result->indexed_properties().append(js_string(vm, String::number(entry.index())));
+ for (auto& it : object->shape().property_table_ordered()) {
+ if (!it.key.is_string())
+ continue;
+ result->indexed_properties().append(js_string(vm, it.key.as_string()));
+ }
+
+ return result;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_prototype_of)
+{
+ if (!vm.argument_count())
+ return {};
+ auto* object = vm.argument(0).to_object(global_object);
+ if (vm.exception())
+ return {};
+ return object->prototype();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::set_prototype_of)
+{
+ if (vm.argument_count() < 2) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ObjectSetPrototypeOfTwoArgs);
+ return {};
+ }
+ auto* object = vm.argument(0).to_object(global_object);
+ if (vm.exception())
+ return {};
+ auto prototype_value = vm.argument(1);
+ Object* prototype;
+ if (prototype_value.is_null()) {
+ prototype = nullptr;
+ } else if (prototype_value.is_object()) {
+ prototype = &prototype_value.as_object();
+ } else {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType);
+ return {};
+ }
+ if (!object->set_prototype(prototype)) {
+ if (!vm.exception())
+ vm.throw_exception<TypeError>(global_object, ErrorType::ObjectSetPrototypeOfReturnedFalse);
+ return {};
+ }
+ return object;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::is_extensible)
+{
+ auto argument = vm.argument(0);
+ if (!argument.is_object())
+ return Value(false);
+ return Value(argument.as_object().is_extensible());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::prevent_extensions)
+{
+ auto argument = vm.argument(0);
+ if (!argument.is_object())
+ return argument;
+ if (!argument.as_object().prevent_extensions()) {
+ if (!vm.exception())
+ vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPreventExtensionsReturnedFalse);
+ return {};
+ }
+ return argument;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_descriptor)
+{
+ auto* object = vm.argument(0).to_object(global_object);
+ if (vm.exception())
+ return {};
+ auto property_key = PropertyName::from_value(global_object, vm.argument(1));
+ if (vm.exception())
+ return {};
+ return object->get_own_property_descriptor_object(property_key);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::define_property_)
+{
+ if (!vm.argument(0).is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Object argument");
+ return {};
+ }
+ if (!vm.argument(2).is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Descriptor argument");
+ return {};
+ }
+ auto& object = vm.argument(0).as_object();
+ auto property_key = StringOrSymbol::from_value(global_object, vm.argument(1));
+ if (vm.exception())
+ return {};
+ auto& descriptor = vm.argument(2).as_object();
+ if (!object.define_property(property_key, descriptor)) {
+ if (!vm.exception()) {
+ if (AK::is<ProxyObject>(object)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ObjectDefinePropertyReturnedFalse);
+ } else {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NonExtensibleDefine, property_key.to_display_string());
+ }
+ }
+ return {};
+ }
+ return &object;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::is)
+{
+ return Value(same_value(vm.argument(0), vm.argument(1)));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::keys)
+{
+ if (!vm.argument_count()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ConvertUndefinedToObject);
+ return {};
+ }
+
+ auto* obj_arg = vm.argument(0).to_object(global_object);
+ if (vm.exception())
+ return {};
+
+ return obj_arg->get_own_properties(*obj_arg, PropertyKind::Key, true);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::values)
+{
+ if (!vm.argument_count()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ConvertUndefinedToObject);
+ return {};
+ }
+ auto* obj_arg = vm.argument(0).to_object(global_object);
+ if (vm.exception())
+ return {};
+
+ return obj_arg->get_own_properties(*obj_arg, PropertyKind::Value, true);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::entries)
+{
+ if (!vm.argument_count()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ConvertUndefinedToObject);
+ return {};
+ }
+ auto* obj_arg = vm.argument(0).to_object(global_object);
+ if (vm.exception())
+ return {};
+
+ return obj_arg->get_own_properties(*obj_arg, PropertyKind::KeyAndValue, true);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.h b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.h
new file mode 100644
index 0000000000..717539651c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class ObjectConstructor final : public NativeFunction {
+ JS_OBJECT(ObjectConstructor, NativeFunction);
+
+public:
+ explicit ObjectConstructor(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~ObjectConstructor() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+
+ JS_DECLARE_NATIVE_FUNCTION(define_property_);
+ JS_DECLARE_NATIVE_FUNCTION(is);
+ JS_DECLARE_NATIVE_FUNCTION(get_own_property_descriptor);
+ JS_DECLARE_NATIVE_FUNCTION(get_own_property_names);
+ JS_DECLARE_NATIVE_FUNCTION(get_prototype_of);
+ JS_DECLARE_NATIVE_FUNCTION(set_prototype_of);
+ JS_DECLARE_NATIVE_FUNCTION(is_extensible);
+ JS_DECLARE_NATIVE_FUNCTION(prevent_extensions);
+ JS_DECLARE_NATIVE_FUNCTION(keys);
+ JS_DECLARE_NATIVE_FUNCTION(values);
+ JS_DECLARE_NATIVE_FUNCTION(entries);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp
new file mode 100644
index 0000000000..cfc31c6b91
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <AK/String.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/BooleanObject.h>
+#include <LibJS/Runtime/Date.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/NumberObject.h>
+#include <LibJS/Runtime/ObjectPrototype.h>
+#include <LibJS/Runtime/RegExpObject.h>
+#include <LibJS/Runtime/StringObject.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+ObjectPrototype::ObjectPrototype(GlobalObject& global_object)
+ : Object(Object::ConstructWithoutPrototypeTag::Tag, global_object)
+{
+}
+
+void ObjectPrototype::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+ // This must be called after the constructor has returned, so that the below code
+ // can find the ObjectPrototype through normal paths.
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_function(vm.names.hasOwnProperty, has_own_property, 1, attr);
+ define_native_function(vm.names.toString, to_string, 0, attr);
+ define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr);
+ define_native_function(vm.names.valueOf, value_of, 0, attr);
+ define_native_function(vm.names.propertyIsEnumerable, property_is_enumerable, 1, attr);
+ define_native_function(vm.names.isPrototypeOf, is_prototype_of, 1, attr);
+}
+
+ObjectPrototype::~ObjectPrototype()
+{
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::has_own_property)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ auto name = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+ return Value(this_object->has_own_property(name));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::to_string)
+{
+ auto this_value = vm.this_value(global_object);
+
+ if (this_value.is_undefined())
+ return js_string(vm, "[object Undefined]");
+ if (this_value.is_null())
+ return js_string(vm, "[object Null]");
+
+ auto* this_object = this_value.to_object(global_object);
+ if (!this_object)
+ return {};
+
+ String tag;
+ auto to_string_tag = this_object->get(global_object.vm().well_known_symbol_to_string_tag());
+
+ if (to_string_tag.is_string()) {
+ tag = to_string_tag.as_string().string();
+ } else if (this_object->is_array()) {
+ tag = "Array";
+ } else if (this_object->is_function()) {
+ tag = "Function";
+ } else if (is<Error>(this_object)) {
+ tag = "Error";
+ } else if (is<BooleanObject>(this_object)) {
+ tag = "Boolean";
+ } else if (is<NumberObject>(this_object)) {
+ tag = "Number";
+ } else if (is<StringObject>(this_object)) {
+ tag = "String";
+ } else if (is<Date>(this_object)) {
+ tag = "Date";
+ } else if (is<RegExpObject>(this_object)) {
+ tag = "RegExp";
+ } else {
+ tag = "Object";
+ }
+
+ return js_string(vm, String::formatted("[object {}]", tag));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::to_locale_string)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ return this_object->invoke("toString");
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::value_of)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ return this_object->value_of();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::property_is_enumerable)
+{
+ auto name = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ auto property_descriptor = this_object->get_own_property_descriptor(name);
+ if (!property_descriptor.has_value())
+ return Value(false);
+ return Value(property_descriptor.value().attributes.is_enumerable());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::is_prototype_of)
+{
+ auto object_argument = vm.argument(0);
+ if (!object_argument.is_object())
+ return Value(false);
+ auto* object = &object_argument.as_object();
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+
+ for (;;) {
+ object = object->prototype();
+ if (!object)
+ return Value(false);
+ if (same_value(this_object, object))
+ return Value(true);
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ObjectPrototype.h b/Userland/Libraries/LibJS/Runtime/ObjectPrototype.h
new file mode 100644
index 0000000000..a68857e8ae
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ObjectPrototype.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class ObjectPrototype final : public Object {
+ JS_OBJECT(ObjectPrototype, Object);
+
+public:
+ explicit ObjectPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~ObjectPrototype() override;
+
+ // public to serve as intrinsic function %Object.prototype.toString%
+ JS_DECLARE_NATIVE_FUNCTION(to_string);
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(has_own_property);
+ JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
+ JS_DECLARE_NATIVE_FUNCTION(value_of);
+ JS_DECLARE_NATIVE_FUNCTION(property_is_enumerable);
+ JS_DECLARE_NATIVE_FUNCTION(is_prototype_of);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/PrimitiveString.cpp b/Userland/Libraries/LibJS/Runtime/PrimitiveString.cpp
new file mode 100644
index 0000000000..b62377925d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/PrimitiveString.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/PrimitiveString.h>
+#include <LibJS/Runtime/VM.h>
+
+namespace JS {
+
+PrimitiveString::PrimitiveString(String string)
+ : m_string(move(string))
+{
+}
+
+PrimitiveString::~PrimitiveString()
+{
+}
+
+PrimitiveString* js_string(Heap& heap, String string)
+{
+ if (string.is_empty())
+ return &heap.vm().empty_string();
+
+ if (string.length() == 1 && (u8)string.characters()[0] < 0x80)
+ return &heap.vm().single_ascii_character_string(string.characters()[0]);
+
+ return heap.allocate_without_global_object<PrimitiveString>(move(string));
+}
+
+PrimitiveString* js_string(VM& vm, String string)
+{
+ return js_string(vm.heap(), move(string));
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/PrimitiveString.h b/Userland/Libraries/LibJS/Runtime/PrimitiveString.h
new file mode 100644
index 0000000000..7b1a9d9e4d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/PrimitiveString.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <LibJS/Runtime/Cell.h>
+
+namespace JS {
+
+class PrimitiveString final : public Cell {
+public:
+ explicit PrimitiveString(String);
+ virtual ~PrimitiveString();
+
+ const String& string() const { return m_string; }
+
+private:
+ virtual const char* class_name() const override { return "PrimitiveString"; }
+
+ String m_string;
+};
+
+PrimitiveString* js_string(Heap&, String);
+PrimitiveString* js_string(VM&, String);
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/PropertyAttributes.h b/Userland/Libraries/LibJS/Runtime/PropertyAttributes.h
new file mode 100644
index 0000000000..b3788bd897
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/PropertyAttributes.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Format.h>
+#include <AK/Types.h>
+
+namespace JS {
+
+struct Attribute {
+ enum {
+ Configurable = 1 << 0,
+ Enumerable = 1 << 1,
+ Writable = 1 << 2,
+ HasGetter = 1 << 3,
+ HasSetter = 1 << 4,
+ HasConfigurable = 1 << 5,
+ HasEnumerable = 1 << 6,
+ HasWritable = 1 << 7,
+ };
+};
+
+class PropertyAttributes {
+public:
+ PropertyAttributes(u8 bits = 0)
+ {
+ m_bits = bits;
+ if (bits & Attribute::Configurable)
+ m_bits |= Attribute::HasConfigurable;
+ if (bits & Attribute::Enumerable)
+ m_bits |= Attribute::HasEnumerable;
+ if (bits & Attribute::Writable)
+ m_bits |= Attribute::HasWritable;
+ }
+
+ bool is_empty() const { return !m_bits; }
+
+ bool has_configurable() const { return m_bits & Attribute::HasConfigurable; }
+ bool has_enumerable() const { return m_bits & Attribute::HasEnumerable; }
+ bool has_writable() const { return m_bits & Attribute::HasWritable; }
+ bool has_getter() const { return m_bits & Attribute::HasGetter; }
+ bool has_setter() const { return m_bits & Attribute::HasSetter; }
+
+ bool is_configurable() const { return m_bits & Attribute::Configurable; }
+ bool is_enumerable() const { return m_bits & Attribute::Enumerable; }
+ bool is_writable() const { return m_bits & Attribute::Writable; }
+
+ void set_has_configurable() { m_bits |= Attribute::HasConfigurable; }
+ void set_has_enumerable() { m_bits |= Attribute::HasEnumerable; }
+ void set_has_writable() { m_bits |= Attribute::HasWritable; }
+ void set_configurable() { m_bits |= Attribute::Configurable; }
+ void set_enumerable() { m_bits |= Attribute::Enumerable; }
+ void set_writable() { m_bits |= Attribute::Writable; }
+ void set_has_getter() { m_bits |= Attribute::HasGetter; }
+ void set_has_setter() { m_bits |= Attribute::HasSetter; }
+
+ bool operator==(const PropertyAttributes& other) const { return m_bits == other.m_bits; }
+ bool operator!=(const PropertyAttributes& other) const { return m_bits != other.m_bits; }
+
+ u8 bits() const { return m_bits; }
+
+private:
+ u8 m_bits;
+};
+
+const PropertyAttributes default_attributes = Attribute::Configurable | Attribute::Writable | Attribute::Enumerable;
+
+}
+
+namespace AK {
+
+template<>
+struct Formatter<JS::PropertyAttributes> : Formatter<u8> {
+ void format(FormatBuilder& builder, const JS::PropertyAttributes& attributes)
+ {
+ Formatter<u8>::format(builder, attributes.bits());
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/PropertyName.h b/Userland/Libraries/LibJS/Runtime/PropertyName.h
new file mode 100644
index 0000000000..bbd6a0c640
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/PropertyName.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <LibJS/Runtime/StringOrSymbol.h>
+
+namespace JS {
+
+class PropertyName {
+public:
+ enum class Type {
+ Invalid,
+ Number,
+ String,
+ Symbol,
+ };
+
+ static PropertyName from_value(GlobalObject& global_object, Value value)
+ {
+ if (value.is_empty())
+ return {};
+ if (value.is_symbol())
+ return &value.as_symbol();
+ if (value.is_integer() && value.as_i32() >= 0)
+ return value.as_i32();
+ auto string = value.to_string(global_object);
+ if (string.is_null())
+ return {};
+ return string;
+ }
+
+ PropertyName() { }
+
+ PropertyName(i32 index)
+ : m_type(Type::Number)
+ , m_number(index)
+ {
+ ASSERT(index >= 0);
+ }
+
+ PropertyName(const char* chars)
+ : m_type(Type::String)
+ , m_string(FlyString(chars))
+ {
+ }
+
+ PropertyName(const String& string)
+ : m_type(Type::String)
+ , m_string(FlyString(string))
+ {
+ ASSERT(!string.is_null());
+ }
+
+ PropertyName(const FlyString& string)
+ : m_type(Type::String)
+ , m_string(string)
+ {
+ ASSERT(!string.is_null());
+ }
+
+ PropertyName(Symbol* symbol)
+ : m_type(Type::Symbol)
+ , m_symbol(symbol)
+ {
+ ASSERT(symbol);
+ }
+
+ PropertyName(const StringOrSymbol& string_or_symbol)
+ {
+ if (string_or_symbol.is_string()) {
+ m_string = string_or_symbol.as_string();
+ m_type = Type::String;
+ } else if (string_or_symbol.is_symbol()) {
+ m_symbol = const_cast<Symbol*>(string_or_symbol.as_symbol());
+ m_type = Type::Symbol;
+ }
+ }
+
+ bool is_valid() const { return m_type != Type::Invalid; }
+ bool is_number() const { return m_type == Type::Number; }
+ bool is_string() const { return m_type == Type::String; }
+ bool is_symbol() const { return m_type == Type::Symbol; }
+
+ i32 as_number() const
+ {
+ ASSERT(is_number());
+ return m_number;
+ }
+
+ const FlyString& as_string() const
+ {
+ ASSERT(is_string());
+ return m_string;
+ }
+
+ const Symbol* as_symbol() const
+ {
+ ASSERT(is_symbol());
+ return m_symbol;
+ }
+
+ String to_string() const
+ {
+ ASSERT(is_valid());
+ ASSERT(!is_symbol());
+ if (is_string())
+ return as_string();
+ return String::number(as_number());
+ }
+
+ StringOrSymbol to_string_or_symbol() const
+ {
+ ASSERT(is_valid());
+ ASSERT(!is_number());
+ if (is_string())
+ return StringOrSymbol(as_string());
+ return StringOrSymbol(as_symbol());
+ }
+
+ Value to_value(VM& vm) const
+ {
+ if (is_string())
+ return js_string(vm, m_string);
+ if (is_number())
+ return Value(m_number);
+ if (is_symbol())
+ return m_symbol;
+ return js_undefined();
+ }
+
+private:
+ Type m_type { Type::Invalid };
+ FlyString m_string;
+ Symbol* m_symbol { nullptr };
+ u32 m_number { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp
new file mode 100644
index 0000000000..f83c5f7727
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/ProxyConstructor.h>
+#include <LibJS/Runtime/ProxyObject.h>
+
+namespace JS {
+
+ProxyConstructor::ProxyConstructor(GlobalObject& global_object)
+ : NativeFunction(vm().names.Proxy, *global_object.function_prototype())
+{
+}
+
+void ProxyConstructor::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+ define_property(vm.names.length, Value(2), Attribute::Configurable);
+}
+
+ProxyConstructor::~ProxyConstructor()
+{
+}
+
+Value ProxyConstructor::call()
+{
+ auto& vm = this->vm();
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ConstructorWithoutNew, vm.names.Proxy);
+ return {};
+}
+
+Value ProxyConstructor::construct(Function&)
+{
+ auto& vm = this->vm();
+ if (vm.argument_count() < 2) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyTwoArguments);
+ return {};
+ }
+
+ auto target = vm.argument(0);
+ auto handler = vm.argument(1);
+
+ if (!target.is_object()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyConstructorBadType, "target", target.to_string_without_side_effects());
+ return {};
+ }
+ if (!handler.is_object()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyConstructorBadType, "handler", handler.to_string_without_side_effects());
+ return {};
+ }
+ return ProxyObject::create(global_object(), target.as_object(), handler.as_object());
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ProxyConstructor.h b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.h
new file mode 100644
index 0000000000..69d23b847a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class ProxyConstructor final : public NativeFunction {
+ JS_OBJECT(ProxyConstructor, NativeFunction);
+
+public:
+ explicit ProxyConstructor(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~ProxyConstructor() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp b/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp
new file mode 100644
index 0000000000..85a0d9db1b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp
@@ -0,0 +1,559 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/Accessor.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/ProxyObject.h>
+
+namespace JS {
+
+bool static is_compatible_property_descriptor(bool is_extensible, PropertyDescriptor new_descriptor, Optional<PropertyDescriptor> current_descriptor_optional)
+{
+ if (!current_descriptor_optional.has_value())
+ return is_extensible;
+ auto current_descriptor = current_descriptor_optional.value();
+ if (new_descriptor.attributes.is_empty() && new_descriptor.value.is_empty() && !new_descriptor.getter && !new_descriptor.setter)
+ return true;
+ if (!current_descriptor.attributes.is_configurable()) {
+ if (new_descriptor.attributes.is_configurable())
+ return false;
+ if (new_descriptor.attributes.has_enumerable() && new_descriptor.attributes.is_enumerable() != current_descriptor.attributes.is_enumerable())
+ return false;
+ }
+ if (new_descriptor.is_generic_descriptor())
+ return true;
+ if (current_descriptor.is_data_descriptor() != new_descriptor.is_data_descriptor() && !current_descriptor.attributes.is_configurable())
+ return false;
+ if (current_descriptor.is_data_descriptor() && new_descriptor.is_data_descriptor() && !current_descriptor.attributes.is_configurable() && !current_descriptor.attributes.is_writable()) {
+ if (new_descriptor.attributes.is_writable())
+ return false;
+ return new_descriptor.value.is_empty() && same_value(new_descriptor.value, current_descriptor.value);
+ }
+ return true;
+}
+
+ProxyObject* ProxyObject::create(GlobalObject& global_object, Object& target, Object& handler)
+{
+ return global_object.heap().allocate<ProxyObject>(global_object, target, handler, *global_object.object_prototype());
+}
+
+ProxyObject::ProxyObject(Object& target, Object& handler, Object& prototype)
+ : Function(prototype)
+ , m_target(target)
+ , m_handler(handler)
+{
+}
+
+ProxyObject::~ProxyObject()
+{
+}
+
+Object* ProxyObject::prototype()
+{
+ auto& vm = this->vm();
+ if (m_is_revoked) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ return nullptr;
+ }
+ auto trap = m_handler.get(vm.names.getPrototypeOf);
+ if (vm.exception())
+ return nullptr;
+ if (trap.is_empty() || trap.is_nullish())
+ return m_target.prototype();
+ if (!trap.is_function()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "getPrototypeOf");
+ return nullptr;
+ }
+
+ auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target));
+ if (vm.exception())
+ return nullptr;
+ if (!trap_result.is_object() && !trap_result.is_null()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetPrototypeOfReturn);
+ return nullptr;
+ }
+ if (m_target.is_extensible()) {
+ if (vm.exception())
+ return nullptr;
+ if (trap_result.is_null())
+ return nullptr;
+ return &trap_result.as_object();
+ }
+ auto target_proto = m_target.prototype();
+ if (vm.exception())
+ return nullptr;
+ if (!same_value(trap_result, Value(target_proto))) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetPrototypeOfNonExtensible);
+ return nullptr;
+ }
+ return &trap_result.as_object();
+}
+
+const Object* ProxyObject::prototype() const
+{
+ auto& vm = this->vm();
+ if (m_is_revoked) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ return nullptr;
+ }
+ return const_cast<const Object*>(const_cast<ProxyObject*>(this)->prototype());
+}
+
+bool ProxyObject::set_prototype(Object* object)
+{
+ auto& vm = this->vm();
+ if (m_is_revoked) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ return false;
+ }
+ auto trap = m_handler.get(vm.names.setPrototypeOf);
+ if (vm.exception())
+ return false;
+ if (trap.is_empty() || trap.is_nullish())
+ return m_target.set_prototype(object);
+ if (!trap.is_function()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "setPrototypeOf");
+ return false;
+ }
+
+ auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target), Value(object));
+ if (vm.exception() || !trap_result.to_boolean())
+ return false;
+ if (m_target.is_extensible())
+ return true;
+ auto* target_proto = m_target.prototype();
+ if (vm.exception())
+ return false;
+ if (!same_value(Value(object), Value(target_proto))) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxySetPrototypeOfNonExtensible);
+ return false;
+ }
+ return true;
+}
+
+bool ProxyObject::is_extensible() const
+{
+ auto& vm = this->vm();
+ if (m_is_revoked) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ return false;
+ }
+ auto trap = m_handler.get(vm.names.isExtensible);
+ if (vm.exception())
+ return false;
+ if (trap.is_empty() || trap.is_nullish())
+ return m_target.is_extensible();
+ if (!trap.is_function()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "isExtensible");
+ return {};
+ }
+
+ auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target));
+ if (vm.exception())
+ return false;
+ if (trap_result.to_boolean() != m_target.is_extensible()) {
+ if (!vm.exception())
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyIsExtensibleReturn);
+ return false;
+ }
+ return trap_result.to_boolean();
+}
+
+bool ProxyObject::prevent_extensions()
+{
+ auto& vm = this->vm();
+ if (m_is_revoked) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ return false;
+ }
+ auto trap = m_handler.get(vm.names.preventExtensions);
+ if (vm.exception())
+ return false;
+ if (trap.is_empty() || trap.is_nullish())
+ return m_target.prevent_extensions();
+ if (!trap.is_function()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "preventExtensions");
+ return {};
+ }
+
+ auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target));
+ if (vm.exception())
+ return false;
+ if (trap_result.to_boolean() && m_target.is_extensible()) {
+ if (!vm.exception())
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyPreventExtensionsReturn);
+ return false;
+ }
+ return trap_result.to_boolean();
+}
+
+Optional<PropertyDescriptor> ProxyObject::get_own_property_descriptor(const PropertyName& name) const
+{
+ auto& vm = this->vm();
+ if (m_is_revoked) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ return {};
+ }
+ auto trap = m_handler.get(vm.names.getOwnPropertyDescriptor);
+ if (vm.exception())
+ return {};
+ if (trap.is_empty() || trap.is_nullish())
+ return m_target.get_own_property_descriptor(name);
+ if (!trap.is_function()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "getOwnPropertyDescriptor");
+ return {};
+ }
+
+ auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target), name.to_value(vm));
+ if (vm.exception())
+ return {};
+ if (!trap_result.is_object() && !trap_result.is_undefined()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorReturn);
+ return {};
+ }
+ auto target_desc = m_target.get_own_property_descriptor(name);
+ if (vm.exception())
+ return {};
+ if (trap_result.is_undefined()) {
+ if (!target_desc.has_value())
+ return {};
+ if (!target_desc.value().attributes.is_configurable()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorNonConfigurable);
+ return {};
+ }
+ if (!m_target.is_extensible()) {
+ if (!vm.exception())
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorUndefReturn);
+ return {};
+ }
+ return {};
+ }
+ auto result_desc = PropertyDescriptor::from_dictionary(vm, trap_result.as_object());
+ if (vm.exception())
+ return {};
+ if (!is_compatible_property_descriptor(m_target.is_extensible(), result_desc, target_desc)) {
+ if (!vm.exception())
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorInvalidDescriptor);
+ return {};
+ }
+ if (!result_desc.attributes.is_configurable() && (!target_desc.has_value() || target_desc.value().attributes.is_configurable())) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorInvalidNonConfig);
+ return {};
+ }
+ return result_desc;
+}
+
+bool ProxyObject::define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions)
+{
+ auto& vm = this->vm();
+ if (m_is_revoked) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ return false;
+ }
+ auto trap = m_handler.get(vm.names.defineProperty);
+ if (vm.exception())
+ return false;
+ if (trap.is_empty() || trap.is_nullish())
+ return m_target.define_property(property_name, descriptor, throw_exceptions);
+ if (!trap.is_function()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "defineProperty");
+ return false;
+ }
+
+ auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target), property_name.to_value(vm), Value(const_cast<Object*>(&descriptor)));
+ if (vm.exception() || !trap_result.to_boolean())
+ return false;
+ auto target_desc = m_target.get_own_property_descriptor(property_name);
+ if (vm.exception())
+ return false;
+ bool setting_config_false = false;
+ if (descriptor.has_property(vm.names.configurable) && !descriptor.get(vm.names.configurable).to_boolean())
+ setting_config_false = true;
+ if (vm.exception())
+ return false;
+ if (!target_desc.has_value()) {
+ if (!m_target.is_extensible()) {
+ if (!vm.exception())
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropNonExtensible);
+ return false;
+ }
+ if (setting_config_false) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropNonConfigurableNonExisting);
+ return false;
+ }
+ } else {
+ if (!is_compatible_property_descriptor(m_target.is_extensible(), PropertyDescriptor::from_dictionary(vm, descriptor), target_desc)) {
+ if (!vm.exception())
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropIncompatibleDescriptor);
+ return false;
+ }
+ if (setting_config_false && target_desc.value().attributes.is_configurable()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropExistingConfigurable);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool ProxyObject::has_property(const PropertyName& name) const
+{
+ auto& vm = this->vm();
+ if (m_is_revoked) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ return false;
+ }
+ auto trap = m_handler.get(vm.names.has);
+ if (vm.exception())
+ return false;
+ if (trap.is_empty() || trap.is_nullish())
+ return m_target.has_property(name);
+ if (!trap.is_function()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "has");
+ return false;
+ }
+
+ auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target), name.to_value(vm));
+ if (vm.exception())
+ return false;
+ if (!trap_result.to_boolean()) {
+ auto target_desc = m_target.get_own_property_descriptor(name);
+ if (vm.exception())
+ return false;
+ if (target_desc.has_value()) {
+ if (!target_desc.value().attributes.is_configurable()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyHasExistingNonConfigurable);
+ return false;
+ }
+ if (!m_target.is_extensible()) {
+ if (!vm.exception())
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyHasExistingNonExtensible);
+ return false;
+ }
+ }
+ }
+ return trap_result.to_boolean();
+}
+
+Value ProxyObject::get(const PropertyName& name, Value receiver) const
+{
+ auto& vm = this->vm();
+ if (m_is_revoked) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ return {};
+ }
+ if (receiver.is_empty())
+ receiver = Value(const_cast<ProxyObject*>(this));
+ auto trap = m_handler.get(vm.names.get);
+ if (vm.exception())
+ return {};
+ if (trap.is_empty() || trap.is_nullish())
+ return m_target.get(name, receiver);
+ if (!trap.is_function()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "get");
+ return {};
+ }
+
+ auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target), name.to_value(vm), receiver);
+ if (vm.exception())
+ return {};
+ auto target_desc = m_target.get_own_property_descriptor(name);
+ if (target_desc.has_value()) {
+ if (vm.exception())
+ return {};
+ if (target_desc.value().is_data_descriptor() && !target_desc.value().attributes.is_writable() && !same_value(trap_result, target_desc.value().value)) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetImmutableDataProperty);
+ return {};
+ }
+ if (target_desc.value().is_accessor_descriptor() && target_desc.value().getter == nullptr && !trap_result.is_undefined()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetNonConfigurableAccessor);
+ return {};
+ }
+ }
+ return trap_result;
+}
+
+bool ProxyObject::put(const PropertyName& name, Value value, Value receiver)
+{
+ auto& vm = this->vm();
+ if (m_is_revoked) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ return false;
+ }
+ if (receiver.is_empty())
+ receiver = Value(const_cast<ProxyObject*>(this));
+ auto trap = m_handler.get(vm.names.set);
+ if (vm.exception())
+ return false;
+ if (trap.is_empty() || trap.is_nullish())
+ return m_target.put(name, value, receiver);
+ if (!trap.is_function()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "set");
+ return false;
+ }
+ auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target), name.to_value(vm), value, receiver);
+ if (vm.exception() || !trap_result.to_boolean())
+ return false;
+ auto target_desc = m_target.get_own_property_descriptor(name);
+ if (vm.exception())
+ return false;
+ if (target_desc.has_value() && !target_desc.value().attributes.is_configurable()) {
+ if (target_desc.value().is_data_descriptor() && !target_desc.value().attributes.is_writable() && !same_value(value, target_desc.value().value)) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxySetImmutableDataProperty);
+ return false;
+ }
+ if (target_desc.value().is_accessor_descriptor() && !target_desc.value().setter) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxySetNonConfigurableAccessor);
+ }
+ }
+ return true;
+}
+
+Value ProxyObject::delete_property(const PropertyName& name)
+{
+ auto& vm = this->vm();
+ if (m_is_revoked) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ return {};
+ }
+ auto trap = m_handler.get(vm.names.deleteProperty);
+ if (vm.exception())
+ return {};
+ if (trap.is_empty() || trap.is_nullish())
+ return m_target.delete_property(name);
+ if (!trap.is_function()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "deleteProperty");
+ return {};
+ }
+
+ auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target), name.to_value(vm));
+ if (vm.exception())
+ return {};
+ if (!trap_result.to_boolean())
+ return Value(false);
+ auto target_desc = m_target.get_own_property_descriptor(name);
+ if (vm.exception())
+ return {};
+ if (!target_desc.has_value())
+ return Value(true);
+ if (!target_desc.value().attributes.is_configurable()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDeleteNonConfigurable);
+ return {};
+ }
+ return Value(true);
+}
+
+void ProxyObject::visit_edges(Cell::Visitor& visitor)
+{
+ Function::visit_edges(visitor);
+ visitor.visit(&m_target);
+ visitor.visit(&m_handler);
+}
+
+Value ProxyObject::call()
+{
+ auto& vm = this->vm();
+ if (!is_function()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::NotAFunction, Value(this).to_string_without_side_effects());
+ return {};
+ }
+ if (m_is_revoked) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ return {};
+ }
+ auto trap = m_handler.get(vm.names.apply);
+ if (vm.exception())
+ return {};
+ if (trap.is_empty() || trap.is_nullish())
+ return static_cast<Function&>(m_target).call();
+ if (!trap.is_function()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "apply");
+ return {};
+ }
+ MarkedValueList arguments(heap());
+ arguments.append(Value(&m_target));
+ arguments.append(Value(&m_handler));
+ // FIXME: Pass global object
+ auto arguments_array = Array::create(global_object());
+ vm.for_each_argument([&](auto& argument) {
+ arguments_array->indexed_properties().append(argument);
+ });
+ arguments.append(arguments_array);
+
+ return vm.call(trap.as_function(), Value(&m_handler), move(arguments));
+}
+
+Value ProxyObject::construct(Function& new_target)
+{
+ auto& vm = this->vm();
+ if (!is_function()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::NotAConstructor, Value(this).to_string_without_side_effects());
+ return {};
+ }
+ if (m_is_revoked) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ return {};
+ }
+ auto trap = m_handler.get(vm.names.construct);
+ if (vm.exception())
+ return {};
+ if (trap.is_empty() || trap.is_nullish())
+ return static_cast<Function&>(m_target).construct(new_target);
+ if (!trap.is_function()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "construct");
+ return {};
+ }
+
+ MarkedValueList arguments(vm.heap());
+ arguments.append(Value(&m_target));
+ auto arguments_array = Array::create(global_object());
+ vm.for_each_argument([&](auto& argument) {
+ arguments_array->indexed_properties().append(argument);
+ });
+ arguments.append(arguments_array);
+ arguments.append(Value(&new_target));
+ auto result = vm.call(trap.as_function(), Value(&m_handler), move(arguments));
+ if (!result.is_object()) {
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyConstructBadReturnType);
+ return {};
+ }
+ return result;
+}
+
+const FlyString& ProxyObject::name() const
+{
+ ASSERT(is_function());
+ return static_cast<Function&>(m_target).name();
+}
+
+LexicalEnvironment* ProxyObject::create_environment()
+{
+ ASSERT(is_function());
+ return static_cast<Function&>(m_target).create_environment();
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ProxyObject.h b/Userland/Libraries/LibJS/Runtime/ProxyObject.h
new file mode 100644
index 0000000000..70f30fac2c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ProxyObject.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Function.h>
+
+namespace JS {
+
+class ProxyObject final : public Function {
+ JS_OBJECT(ProxyObject, Function);
+
+public:
+ static ProxyObject* create(GlobalObject&, Object& target, Object& handler);
+
+ ProxyObject(Object& target, Object& handler, Object& prototype);
+ virtual ~ProxyObject() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+ virtual const FlyString& name() const override;
+ virtual LexicalEnvironment* create_environment() override;
+
+ const Object& target() const { return m_target; }
+ const Object& handler() const { return m_handler; }
+
+ virtual Object* prototype() override;
+ virtual const Object* prototype() const override;
+ virtual bool set_prototype(Object* object) override;
+ virtual bool is_extensible() const override;
+ virtual bool prevent_extensions() override;
+ virtual Optional<PropertyDescriptor> get_own_property_descriptor(const PropertyName&) const override;
+ virtual bool define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions = true) override;
+ virtual bool has_property(const PropertyName& name) const override;
+ virtual Value get(const PropertyName& name, Value receiver) const override;
+ virtual bool put(const PropertyName& name, Value value, Value receiver) override;
+ virtual Value delete_property(const PropertyName& name) override;
+
+ void revoke() { m_is_revoked = true; }
+
+private:
+ virtual void visit_edges(Visitor&) override;
+
+ virtual bool is_function() const override { return m_target.is_function(); }
+ virtual bool is_array() const override { return m_target.is_array(); };
+
+ Object& m_target;
+ Object& m_handler;
+ bool m_is_revoked { false };
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Reference.cpp b/Userland/Libraries/LibJS/Runtime/Reference.cpp
new file mode 100644
index 0000000000..6b6b06f80f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Reference.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Object.h>
+#include <LibJS/Runtime/Reference.h>
+
+namespace JS {
+
+void Reference::put(GlobalObject& global_object, Value value)
+{
+ auto& vm = global_object.vm();
+
+ if (is_unresolvable()) {
+ throw_reference_error(global_object);
+ return;
+ }
+
+ if (is_local_variable() || is_global_variable()) {
+ if (is_local_variable())
+ vm.set_variable(m_name.to_string(), value, global_object);
+ else
+ global_object.put(m_name, value);
+ return;
+ }
+
+ if (!base().is_object() && vm.in_strict_mode()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ReferencePrimitiveAssignment, m_name.to_value(global_object.vm()).to_string_without_side_effects());
+ return;
+ }
+
+ auto* object = base().to_object(global_object);
+ if (!object)
+ return;
+
+ object->put(m_name, value);
+}
+
+void Reference::throw_reference_error(GlobalObject& global_object)
+{
+ auto property_name = m_name.to_string();
+ String message;
+ if (property_name.is_empty()) {
+ global_object.vm().throw_exception<ReferenceError>(global_object, ErrorType::ReferenceUnresolvable);
+ } else {
+ global_object.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, property_name);
+ }
+}
+
+Value Reference::get(GlobalObject& global_object)
+{
+ auto& vm = global_object.vm();
+
+ if (is_unresolvable()) {
+ throw_reference_error(global_object);
+ return {};
+ }
+
+ if (is_local_variable() || is_global_variable()) {
+ Value value;
+ if (is_local_variable())
+ value = vm.get_variable(m_name.to_string(), global_object);
+ else
+ value = global_object.get(m_name);
+ if (vm.exception())
+ return {};
+ if (value.is_empty()) {
+ throw_reference_error(global_object);
+ return {};
+ }
+ return value;
+ }
+
+ auto* object = base().to_object(global_object);
+ if (!object)
+ return {};
+
+ return object->get(m_name).value_or(js_undefined());
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Reference.h b/Userland/Libraries/LibJS/Runtime/Reference.h
new file mode 100644
index 0000000000..c81d964440
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Reference.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <LibJS/Runtime/PropertyName.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+class Reference {
+public:
+ Reference() { }
+ Reference(Value base, const PropertyName& name, bool strict = false)
+ : m_base(base)
+ , m_name(name)
+ , m_strict(strict)
+ {
+ }
+
+ enum LocalVariableTag { LocalVariable };
+ Reference(LocalVariableTag, const String& name, bool strict = false)
+ : m_base(js_null())
+ , m_name(name)
+ , m_strict(strict)
+ , m_local_variable(true)
+ {
+ }
+
+ enum GlobalVariableTag { GlobalVariable };
+ Reference(GlobalVariableTag, const String& name, bool strict = false)
+ : m_base(js_null())
+ , m_name(name)
+ , m_strict(strict)
+ , m_global_variable(true)
+ {
+ }
+
+ Value base() const { return m_base; }
+ const PropertyName& name() const { return m_name; }
+ bool is_strict() const { return m_strict; }
+
+ bool is_unresolvable() const { return m_base.is_undefined(); }
+ bool is_property() const
+ {
+ return m_base.is_object() || has_primitive_base();
+ }
+
+ bool has_primitive_base() const
+ {
+ return m_base.is_boolean() || m_base.is_string() || m_base.is_number();
+ }
+
+ bool is_local_variable() const
+ {
+ return m_local_variable;
+ }
+
+ bool is_global_variable() const
+ {
+ return m_global_variable;
+ }
+
+ void put(GlobalObject&, Value);
+ Value get(GlobalObject&);
+
+private:
+ void throw_reference_error(GlobalObject&);
+
+ Value m_base { js_undefined() };
+ PropertyName m_name;
+ bool m_strict { false };
+ bool m_local_variable { false };
+ bool m_global_variable { false };
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ReflectObject.cpp b/Userland/Libraries/LibJS/Runtime/ReflectObject.cpp
new file mode 100644
index 0000000000..1b7865cc8e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ReflectObject.cpp
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/Function.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/NativeFunction.h>
+#include <LibJS/Runtime/ReflectObject.h>
+
+namespace JS {
+
+static Object* get_target_object_from(GlobalObject& global_object, const String& name)
+{
+ auto& vm = global_object.vm();
+ auto target = vm.argument(0);
+ if (!target.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ReflectArgumentMustBeAnObject, name);
+ return nullptr;
+ }
+ return static_cast<Object*>(&target.as_object());
+}
+
+static Function* get_target_function_from(GlobalObject& global_object, const String& name)
+{
+ auto& vm = global_object.vm();
+ auto target = vm.argument(0);
+ if (!target.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ReflectArgumentMustBeAFunction, name);
+ return nullptr;
+ }
+ return &target.as_function();
+}
+
+static void prepare_arguments_list(GlobalObject& global_object, Value value, MarkedValueList* arguments)
+{
+ auto& vm = global_object.vm();
+ if (!value.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ReflectBadArgumentsList);
+ return;
+ }
+ auto& arguments_list = value.as_object();
+ auto length = length_of_array_like(global_object, arguments_list);
+ if (vm.exception())
+ return;
+ for (size_t i = 0; i < length; ++i) {
+ auto element = arguments_list.get(String::number(i));
+ if (vm.exception())
+ return;
+ arguments->append(element.value_or(js_undefined()));
+ }
+}
+
+ReflectObject::ReflectObject(GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void ReflectObject::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_function(vm.names.apply, apply, 3, attr);
+ define_native_function(vm.names.construct, construct, 2, attr);
+ define_native_function(vm.names.defineProperty, define_property, 3, attr);
+ define_native_function(vm.names.deleteProperty, delete_property, 2, attr);
+ define_native_function(vm.names.get, get, 2, attr);
+ define_native_function(vm.names.getOwnPropertyDescriptor, get_own_property_descriptor, 2, attr);
+ define_native_function(vm.names.getPrototypeOf, get_prototype_of, 1, attr);
+ define_native_function(vm.names.has, has, 2, attr);
+ define_native_function(vm.names.isExtensible, is_extensible, 1, attr);
+ define_native_function(vm.names.ownKeys, own_keys, 1, attr);
+ define_native_function(vm.names.preventExtensions, prevent_extensions, 1, attr);
+ define_native_function(vm.names.set, set, 3, attr);
+ define_native_function(vm.names.setPrototypeOf, set_prototype_of, 2, attr);
+}
+
+ReflectObject::~ReflectObject()
+{
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ReflectObject::apply)
+{
+ auto* target = get_target_function_from(global_object, "apply");
+ if (!target)
+ return {};
+ auto this_arg = vm.argument(1);
+ MarkedValueList arguments(vm.heap());
+ prepare_arguments_list(global_object, vm.argument(2), &arguments);
+ if (vm.exception())
+ return {};
+ return vm.call(*target, this_arg, move(arguments));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ReflectObject::construct)
+{
+ auto* target = get_target_function_from(global_object, "construct");
+ if (!target)
+ return {};
+ MarkedValueList arguments(vm.heap());
+ prepare_arguments_list(global_object, vm.argument(1), &arguments);
+ if (vm.exception())
+ return {};
+ auto* new_target = target;
+ if (vm.argument_count() > 2) {
+ auto new_target_value = vm.argument(2);
+ if (!new_target_value.is_function()
+ || (is<NativeFunction>(new_target_value.as_object()) && !static_cast<NativeFunction&>(new_target_value.as_object()).has_constructor())) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ReflectBadNewTarget);
+ return {};
+ }
+ new_target = &new_target_value.as_function();
+ }
+ return vm.construct(*target, *new_target, move(arguments), global_object);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ReflectObject::define_property)
+{
+ auto* target = get_target_object_from(global_object, "defineProperty");
+ if (!target)
+ return {};
+ if (!vm.argument(2).is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ReflectBadDescriptorArgument);
+ return {};
+ }
+ auto property_key = StringOrSymbol::from_value(global_object, vm.argument(1));
+ if (vm.exception())
+ return {};
+ auto& descriptor = vm.argument(2).as_object();
+ auto success = target->define_property(property_key, descriptor, false);
+ if (vm.exception())
+ return {};
+ return Value(success);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ReflectObject::delete_property)
+{
+ auto* target = get_target_object_from(global_object, "deleteProperty");
+ if (!target)
+ return {};
+
+ auto property_key = vm.argument(1);
+ auto property_name = PropertyName::from_value(global_object, property_key);
+ if (vm.exception())
+ return {};
+ auto property_key_number = property_key.to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (property_key_number.is_finite_number()) {
+ auto property_key_as_double = property_key_number.as_double();
+ if (property_key_as_double >= 0 && (i32)property_key_as_double == property_key_as_double)
+ property_name = PropertyName(property_key_as_double);
+ }
+ return target->delete_property(property_name);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get)
+{
+ auto* target = get_target_object_from(global_object, "get");
+ if (!target)
+ return {};
+ auto property_key = PropertyName::from_value(global_object, vm.argument(1));
+ if (vm.exception())
+ return {};
+ Value receiver = {};
+ if (vm.argument_count() > 2)
+ receiver = vm.argument(2);
+ return target->get(property_key, receiver).value_or(js_undefined());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get_own_property_descriptor)
+{
+ auto* target = get_target_object_from(global_object, "getOwnPropertyDescriptor");
+ if (!target)
+ return {};
+ auto property_key = PropertyName::from_value(global_object, vm.argument(1));
+ if (vm.exception())
+ return {};
+ return target->get_own_property_descriptor_object(property_key);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get_prototype_of)
+{
+ auto* target = get_target_object_from(global_object, "getPrototypeOf");
+ if (!target)
+ return {};
+ return target->prototype();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ReflectObject::has)
+{
+ auto* target = get_target_object_from(global_object, "has");
+ if (!target)
+ return {};
+ auto property_key = PropertyName::from_value(global_object, vm.argument(1));
+ if (vm.exception())
+ return {};
+ return Value(target->has_property(property_key));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ReflectObject::is_extensible)
+{
+ auto* target = get_target_object_from(global_object, "isExtensible");
+ if (!target)
+ return {};
+ return Value(target->is_extensible());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ReflectObject::own_keys)
+{
+ auto* target = get_target_object_from(global_object, "ownKeys");
+ if (!target)
+ return {};
+ return target->get_own_properties(*target, PropertyKind::Key);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ReflectObject::prevent_extensions)
+{
+ auto* target = get_target_object_from(global_object, "preventExtensions");
+ if (!target)
+ return {};
+ return Value(target->prevent_extensions());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ReflectObject::set)
+{
+ auto* target = get_target_object_from(global_object, "set");
+ if (!target)
+ return {};
+ auto property_key = vm.argument(1).to_string(global_object);
+ if (vm.exception())
+ return {};
+ auto value = vm.argument(2);
+ Value receiver = {};
+ if (vm.argument_count() > 3)
+ receiver = vm.argument(3);
+ return Value(target->put(property_key, value, receiver));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(ReflectObject::set_prototype_of)
+{
+ auto* target = get_target_object_from(global_object, "setPrototypeOf");
+ if (!target)
+ return {};
+ auto prototype_value = vm.argument(1);
+ if (!prototype_value.is_object() && !prototype_value.is_null()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType);
+ return {};
+ }
+ Object* prototype = nullptr;
+ if (!prototype_value.is_null())
+ prototype = const_cast<Object*>(&prototype_value.as_object());
+ return Value(target->set_prototype(prototype));
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ReflectObject.h b/Userland/Libraries/LibJS/Runtime/ReflectObject.h
new file mode 100644
index 0000000000..68f2fd497e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ReflectObject.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class ReflectObject final : public Object {
+ JS_OBJECT(ReflectObject, Object);
+
+public:
+ explicit ReflectObject(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~ReflectObject() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(apply);
+ JS_DECLARE_NATIVE_FUNCTION(construct);
+ JS_DECLARE_NATIVE_FUNCTION(define_property);
+ JS_DECLARE_NATIVE_FUNCTION(delete_property);
+ JS_DECLARE_NATIVE_FUNCTION(get);
+ JS_DECLARE_NATIVE_FUNCTION(get_own_property_descriptor);
+ JS_DECLARE_NATIVE_FUNCTION(get_prototype_of);
+ JS_DECLARE_NATIVE_FUNCTION(has);
+ JS_DECLARE_NATIVE_FUNCTION(is_extensible);
+ JS_DECLARE_NATIVE_FUNCTION(own_keys);
+ JS_DECLARE_NATIVE_FUNCTION(prevent_extensions);
+ JS_DECLARE_NATIVE_FUNCTION(set);
+ JS_DECLARE_NATIVE_FUNCTION(set_prototype_of);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/RegExpConstructor.cpp b/Userland/Libraries/LibJS/Runtime/RegExpConstructor.cpp
new file mode 100644
index 0000000000..477261b664
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/RegExpConstructor.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/RegExpConstructor.h>
+#include <LibJS/Runtime/RegExpObject.h>
+
+namespace JS {
+
+RegExpConstructor::RegExpConstructor(GlobalObject& global_object)
+ : NativeFunction(vm().names.RegExp, *global_object.function_prototype())
+{
+}
+
+void RegExpConstructor::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+ define_property(vm.names.prototype, global_object.regexp_prototype(), 0);
+ define_property(vm.names.length, Value(2), Attribute::Configurable);
+}
+
+RegExpConstructor::~RegExpConstructor()
+{
+}
+
+Value RegExpConstructor::call()
+{
+ return construct(*this);
+}
+
+Value RegExpConstructor::construct(Function&)
+{
+ auto& vm = this->vm();
+ String pattern = "";
+ String flags = "";
+ if (!vm.argument(0).is_undefined()) {
+ pattern = vm.argument(0).to_string(global_object());
+ if (vm.exception())
+ return {};
+ }
+ if (!vm.argument(1).is_undefined()) {
+ flags = vm.argument(1).to_string(global_object());
+ if (vm.exception())
+ return {};
+ }
+ return RegExpObject::create(global_object(), pattern, flags);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/RegExpConstructor.h b/Userland/Libraries/LibJS/Runtime/RegExpConstructor.h
new file mode 100644
index 0000000000..0c3d055636
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/RegExpConstructor.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class RegExpConstructor final : public NativeFunction {
+ JS_OBJECT(RegExpConstructor, NativeFunction);
+
+public:
+ explicit RegExpConstructor(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~RegExpConstructor() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/RegExpObject.cpp b/Userland/Libraries/LibJS/Runtime/RegExpObject.cpp
new file mode 100644
index 0000000000..cfe0364497
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/RegExpObject.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <AK/StringBuilder.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/PrimitiveString.h>
+#include <LibJS/Runtime/RegExpObject.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+static Flags options_from(const String& flags, VM& vm, GlobalObject& global_object)
+{
+ bool g = false, i = false, m = false, s = false, u = false, y = false;
+ Flags options {
+ { (regex::ECMAScriptFlags)regex::AllFlags::Global }, // JS regexps are all 'global' by default as per our definition, but the "global" flag enables "stateful".
+ {},
+ };
+
+ for (auto ch : flags) {
+ switch (ch) {
+ case 'g':
+ if (g)
+ vm.throw_exception<SyntaxError>(global_object, ErrorType::RegExpObjectRepeatedFlag, ch);
+ g = true;
+ options.effective_flags |= regex::ECMAScriptFlags::Global;
+ options.declared_flags |= regex::ECMAScriptFlags::Global;
+ break;
+ case 'i':
+ if (i)
+ vm.throw_exception<SyntaxError>(global_object, ErrorType::RegExpObjectRepeatedFlag, ch);
+ i = true;
+ options.effective_flags |= regex::ECMAScriptFlags::Insensitive;
+ options.declared_flags |= regex::ECMAScriptFlags::Insensitive;
+ break;
+ case 'm':
+ if (m)
+ vm.throw_exception<SyntaxError>(global_object, ErrorType::RegExpObjectRepeatedFlag, ch);
+ m = true;
+ options.effective_flags |= regex::ECMAScriptFlags::Multiline;
+ options.declared_flags |= regex::ECMAScriptFlags::Multiline;
+ break;
+ case 's':
+ if (s)
+ vm.throw_exception<SyntaxError>(global_object, ErrorType::RegExpObjectRepeatedFlag, ch);
+ s = true;
+ options.effective_flags |= regex::ECMAScriptFlags::SingleLine;
+ options.declared_flags |= regex::ECMAScriptFlags::SingleLine;
+ break;
+ case 'u':
+ if (u)
+ vm.throw_exception<SyntaxError>(global_object, ErrorType::RegExpObjectRepeatedFlag, ch);
+ u = true;
+ options.effective_flags |= regex::ECMAScriptFlags::Unicode;
+ options.declared_flags |= regex::ECMAScriptFlags::Unicode;
+ break;
+ case 'y':
+ if (y)
+ vm.throw_exception<SyntaxError>(global_object, ErrorType::RegExpObjectRepeatedFlag, ch);
+ y = true;
+ // Now for the more interesting flag, 'sticky' actually unsets 'global', part of which is the default.
+ options.effective_flags.reset_flag(regex::ECMAScriptFlags::Global);
+ // "What's the difference between sticky and global, then", that's simple.
+ // all the other flags imply 'global', and the "global" flag implies 'stateful';
+ // however, the "sticky" flag does *not* imply 'global', only 'stateful'.
+ options.effective_flags |= (regex::ECMAScriptFlags)regex::AllFlags::Internal_Stateful;
+ options.effective_flags |= regex::ECMAScriptFlags::Sticky;
+ options.declared_flags |= regex::ECMAScriptFlags::Sticky;
+ break;
+ default:
+ vm.throw_exception<SyntaxError>(global_object, ErrorType::RegExpObjectBadFlag, ch);
+ return options;
+ }
+ }
+
+ return options;
+}
+
+RegExpObject* RegExpObject::create(GlobalObject& global_object, String pattern, String flags)
+{
+ return global_object.heap().allocate<RegExpObject>(global_object, pattern, flags, *global_object.regexp_prototype());
+}
+
+RegExpObject::RegExpObject(String pattern, String flags, Object& prototype)
+ : Object(prototype)
+ , m_pattern(pattern)
+ , m_flags(flags)
+ , m_active_flags(options_from(m_flags, this->vm(), this->global_object()))
+ , m_regex(pattern, m_active_flags.effective_flags)
+{
+ if (m_regex.parser_result.error != regex::Error::NoError) {
+ vm().throw_exception<SyntaxError>(global_object(), ErrorType::RegExpCompileError, m_regex.error_string());
+ }
+}
+
+void RegExpObject::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+
+ define_native_property(vm.names.lastIndex, last_index, set_last_index, Attribute::Writable);
+}
+
+RegExpObject::~RegExpObject()
+{
+}
+
+static RegExpObject* regexp_object_from(VM& vm, GlobalObject& global_object)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return nullptr;
+ if (!is<RegExpObject>(this_object)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "RegExp");
+ return nullptr;
+ }
+ return static_cast<RegExpObject*>(this_object);
+}
+
+JS_DEFINE_NATIVE_GETTER(RegExpObject::last_index)
+{
+ auto regexp_object = regexp_object_from(vm, global_object);
+ if (!regexp_object)
+ return {};
+
+ return Value((unsigned)regexp_object->regex().start_offset);
+}
+
+JS_DEFINE_NATIVE_SETTER(RegExpObject::set_last_index)
+{
+ auto regexp_object = regexp_object_from(vm, global_object);
+ if (!regexp_object)
+ return;
+
+ auto index = value.to_i32(global_object);
+ if (vm.exception())
+ return;
+
+ if (index < 0)
+ index = 0;
+
+ regexp_object->regex().start_offset = index;
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/RegExpObject.h b/Userland/Libraries/LibJS/Runtime/RegExpObject.h
new file mode 100644
index 0000000000..7be6b96069
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/RegExpObject.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/AST.h>
+#include <LibJS/Runtime/Object.h>
+#include <LibRegex/Regex.h>
+
+struct Flags {
+ regex::RegexOptions<ECMAScriptFlags> effective_flags;
+ regex::RegexOptions<ECMAScriptFlags> declared_flags;
+};
+
+namespace JS {
+
+class RegExpObject : public Object {
+ JS_OBJECT(RegExpObject, Object);
+
+public:
+ static RegExpObject* create(GlobalObject&, String pattern, String flags);
+
+ RegExpObject(String pattern, String flags, Object& prototype);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~RegExpObject() override;
+
+ const String& pattern() const { return m_pattern; }
+ const String& flags() const { return m_flags; }
+ const regex::RegexOptions<ECMAScriptFlags>& declared_options() { return m_active_flags.declared_flags; }
+ const Regex<ECMA262>& regex() { return m_regex; }
+ const Regex<ECMA262>& regex() const { return m_regex; }
+
+private:
+ JS_DECLARE_NATIVE_GETTER(last_index);
+ JS_DECLARE_NATIVE_SETTER(set_last_index);
+
+ String m_pattern;
+ String m_flags;
+ Flags m_active_flags;
+ Regex<ECMA262> m_regex;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp
new file mode 100644
index 0000000000..8540b7bc06
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/RegExpObject.h>
+#include <LibJS/Runtime/RegExpPrototype.h>
+#include <LibJS/Token.h>
+
+namespace JS {
+
+RegExpPrototype::RegExpPrototype(GlobalObject& global_object)
+ : RegExpObject({}, {}, *global_object.object_prototype())
+{
+}
+
+void RegExpPrototype::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_function(vm.names.toString, to_string, 0, attr);
+ define_native_function(vm.names.test, test, 1, attr);
+ define_native_function(vm.names.exec, exec, 1, attr);
+
+ u8 readable_attr = Attribute::Configurable;
+ define_native_property(vm.names.flags, flags, {}, readable_attr);
+ define_native_property(vm.names.source, source, {}, readable_attr);
+
+#define __JS_ENUMERATE(flagName, flag_name, flag_char, ECMAScriptFlagName) \
+ define_native_property(vm.names.flagName, flag_name, {}, readable_attr);
+ JS_ENUMERATE_REGEXP_FLAGS
+#undef __JS_ENUMERATE
+}
+
+RegExpPrototype::~RegExpPrototype()
+{
+}
+
+static Object* this_object_from(VM& vm, GlobalObject& global_object)
+{
+ auto this_value = vm.this_value(global_object);
+ if (!this_value.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, this_value.to_string_without_side_effects());
+ return {};
+ }
+ return &this_value.as_object();
+}
+
+static RegExpObject* regexp_object_from(VM& vm, GlobalObject& global_object)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return nullptr;
+ if (!is<RegExpObject>(this_object)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "RegExp");
+ return nullptr;
+ }
+ return static_cast<RegExpObject*>(this_object);
+}
+
+static String escape_regexp_pattern(const RegExpObject& regexp_object)
+{
+ auto pattern = regexp_object.pattern();
+ if (pattern.is_empty())
+ return "(?:)";
+ // FIXME: Check u flag and escape accordingly
+ pattern.replace("\n", "\\n", true);
+ pattern.replace("\r", "\\r", true);
+ pattern.replace(LINE_SEPARATOR, "\\u2028", true);
+ pattern.replace(PARAGRAPH_SEPARATOR, "\\u2029", true);
+ pattern.replace("/", "\\/", true);
+ return pattern;
+}
+
+#define __JS_ENUMERATE(flagName, flag_name, flag_char, ECMAScriptFlagName) \
+ JS_DEFINE_NATIVE_GETTER(RegExpPrototype::flag_name) \
+ { \
+ auto regexp_object = regexp_object_from(vm, global_object); \
+ if (!regexp_object) \
+ return {}; \
+ \
+ return Value(regexp_object->declared_options().has_flag_set(ECMAScriptFlags::ECMAScriptFlagName)); \
+ }
+JS_ENUMERATE_REGEXP_FLAGS
+#undef __JS_ENUMERATE
+
+JS_DEFINE_NATIVE_GETTER(RegExpPrototype::flags)
+{
+ auto this_object = this_object_from(vm, global_object);
+ if (!this_object)
+ return {};
+
+ StringBuilder builder(8);
+
+#define __JS_ENUMERATE(flagName, flag_name, flag_char, ECMAScriptFlagName) \
+ auto flag_##flag_name = this_object->get(vm.names.flagName).value_or(js_undefined()); \
+ if (vm.exception()) \
+ return {}; \
+ if (flag_##flag_name.to_boolean()) \
+ builder.append(#flag_char);
+ JS_ENUMERATE_REGEXP_FLAGS
+#undef __JS_ENUMERATE
+
+ return js_string(vm, builder.to_string());
+}
+
+JS_DEFINE_NATIVE_GETTER(RegExpPrototype::source)
+{
+ auto this_object = this_object_from(vm, global_object);
+ if (!this_object)
+ return {};
+
+ // FIXME: This is obnoxious - we should have an easier way of looking up %RegExp.prototype%.
+ auto& regexp_prototype = global_object.get(vm.names.RegExp).as_object().get(vm.names.prototype).as_object();
+ if (this_object == &regexp_prototype)
+ return js_string(vm, "(?:)");
+
+ auto regexp_object = regexp_object_from(vm, global_object);
+ if (!regexp_object)
+ return {};
+
+ return js_string(vm, escape_regexp_pattern(*regexp_object));
+}
+
+RegexResult RegExpPrototype::do_match(const Regex<ECMA262>& re, const StringView& subject)
+{
+ auto result = re.match(subject);
+ // The 'lastIndex' property is reset on failing tests (if 'global')
+ if (!result.success && re.options().has_flag_set(ECMAScriptFlags::Global))
+ re.start_offset = 0;
+
+ return result;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::exec)
+{
+ // FIXME: This should try using dynamic properties for 'lastIndex',
+ // and internal slots [[RegExpMatcher]], [[OriginalFlags]], etc.
+ auto regexp_object = regexp_object_from(vm, global_object);
+ if (!regexp_object)
+ return {};
+
+ auto str = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+
+ StringView str_to_match = str;
+
+ // RegExps without "global" and "sticky" always start at offset 0.
+ if (!regexp_object->regex().options().has_flag_set((ECMAScriptFlags)regex::AllFlags::Internal_Stateful))
+ regexp_object->regex().start_offset = 0;
+
+ auto result = do_match(regexp_object->regex(), str_to_match);
+ if (!result.success)
+ return js_null();
+
+ auto& match = result.matches[0];
+
+ // FIXME: Do code point index correction if the Unicode flag is set.
+ auto* array = Array::create(global_object);
+ array->indexed_properties().set_array_like_size(result.n_capture_groups + 1);
+ array->define_property(vm.names.index, Value((i32)match.column));
+ array->define_property(vm.names.input, js_string(vm, str));
+ array->indexed_properties().put(array, 0, js_string(vm, match.view.to_string()));
+
+ for (size_t i = 0; i < result.n_capture_groups; ++i) {
+ auto& capture = result.capture_group_matches[0][i];
+ array->indexed_properties().put(array, i + 1, js_string(vm, capture.view.to_string()));
+ }
+
+ Value groups = js_undefined();
+ if (result.n_named_capture_groups > 0) {
+ auto groups_object = create_empty(global_object);
+ for (auto& entry : result.named_capture_group_matches[0])
+ groups_object->define_property(entry.key, js_string(vm, entry.value.view.to_string()));
+ groups = move(groups_object);
+ }
+
+ array->define_property(vm.names.groups, groups);
+
+ return array;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::test)
+{
+ // FIXME: This should try using dynamic properties for 'exec' first,
+ // before falling back to builtin_exec.
+ auto regexp_object = regexp_object_from(vm, global_object);
+ if (!regexp_object)
+ return {};
+
+ auto str = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+
+ // RegExps without "global" and "sticky" always start at offset 0.
+ if (!regexp_object->regex().options().has_flag_set((ECMAScriptFlags)regex::AllFlags::Internal_Stateful))
+ regexp_object->regex().start_offset = 0;
+
+ auto result = do_match(regexp_object->regex(), str);
+ return Value(result.success);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::to_string)
+{
+ auto this_object = this_object_from(vm, global_object);
+ if (!this_object)
+ return {};
+
+ auto source_attr = this_object->get(vm.names.source).value_or(js_undefined());
+ if (vm.exception())
+ return {};
+ auto pattern = source_attr.to_string(global_object);
+ if (vm.exception())
+ return {};
+
+ auto flags_attr = this_object->get(vm.names.flags).value_or(js_undefined());
+ if (vm.exception())
+ return {};
+ auto flags = flags_attr.to_string(global_object);
+ if (vm.exception())
+ return {};
+
+ return js_string(vm, String::formatted("/{}/{}", pattern, flags));
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.h b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.h
new file mode 100644
index 0000000000..69b0708ce1
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/RegExpObject.h>
+
+namespace JS {
+
+class RegExpPrototype final : public RegExpObject {
+ JS_OBJECT(RegExpPrototype, RegExpObject);
+
+public:
+ explicit RegExpPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~RegExpPrototype() override;
+
+private:
+ static RegexResult do_match(const Regex<ECMA262>&, const StringView&);
+
+ JS_DECLARE_NATIVE_GETTER(flags);
+ JS_DECLARE_NATIVE_GETTER(source);
+
+ JS_DECLARE_NATIVE_FUNCTION(exec);
+ JS_DECLARE_NATIVE_FUNCTION(test);
+ JS_DECLARE_NATIVE_FUNCTION(to_string);
+
+#define __JS_ENUMERATE(_, flag_name, ...) \
+ JS_DECLARE_NATIVE_GETTER(flag_name);
+ JS_ENUMERATE_REGEXP_FLAGS
+#undef __JS_ENUMERATE
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ScopeObject.cpp b/Userland/Libraries/LibJS/Runtime/ScopeObject.cpp
new file mode 100644
index 0000000000..5a67011a55
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ScopeObject.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/ScopeObject.h>
+#include <LibJS/Runtime/VM.h>
+
+namespace JS {
+
+ScopeObject::ScopeObject(ScopeObject* parent)
+ : Object(vm().scope_object_shape())
+ , m_parent(parent)
+{
+}
+
+ScopeObject::ScopeObject(GlobalObjectTag tag)
+ : Object(tag)
+{
+}
+
+void ScopeObject::visit_edges(Visitor& visitor)
+{
+ Base::visit_edges(visitor);
+ visitor.visit(m_parent);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ScopeObject.h b/Userland/Libraries/LibJS/Runtime/ScopeObject.h
new file mode 100644
index 0000000000..ac29c11503
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ScopeObject.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+struct Variable {
+ Value value;
+ DeclarationKind declaration_kind;
+};
+
+class ScopeObject : public Object {
+ JS_OBJECT(ScopeObject, Object);
+
+public:
+ virtual Optional<Variable> get_from_scope(const FlyString&) const = 0;
+ virtual void put_to_scope(const FlyString&, Variable) = 0;
+ virtual bool has_this_binding() const = 0;
+ virtual Value get_this_binding(GlobalObject&) const = 0;
+
+ ScopeObject* parent() { return m_parent; }
+ const ScopeObject* parent() const { return m_parent; }
+
+protected:
+ explicit ScopeObject(ScopeObject* parent);
+ explicit ScopeObject(GlobalObjectTag);
+
+ virtual void visit_edges(Visitor&) override;
+
+private:
+ ScopeObject* m_parent { nullptr };
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp
new file mode 100644
index 0000000000..56ce94edd2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibJS/AST.h>
+#include <LibJS/Interpreter.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/ScriptFunction.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+static ScriptFunction* typed_this(VM& vm, GlobalObject& global_object)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return nullptr;
+ if (!this_object->is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunctionNoParam);
+ return nullptr;
+ }
+ return static_cast<ScriptFunction*>(this_object);
+}
+
+ScriptFunction* ScriptFunction::create(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, bool is_strict, bool is_arrow_function)
+{
+ return global_object.heap().allocate<ScriptFunction>(global_object, global_object, name, body, move(parameters), m_function_length, parent_scope, *global_object.function_prototype(), is_strict, is_arrow_function);
+}
+
+ScriptFunction::ScriptFunction(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, Object& prototype, bool is_strict, bool is_arrow_function)
+ : Function(prototype, is_arrow_function ? vm().this_value(global_object) : Value(), {})
+ , m_name(name)
+ , m_body(body)
+ , m_parameters(move(parameters))
+ , m_parent_scope(parent_scope)
+ , m_function_length(m_function_length)
+ , m_is_strict(is_strict)
+ , m_is_arrow_function(is_arrow_function)
+{
+}
+
+void ScriptFunction::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Function::initialize(global_object);
+ if (!m_is_arrow_function) {
+ Object* prototype = vm.heap().allocate<Object>(global_object, *global_object.new_script_function_prototype_object_shape());
+ prototype->define_property(vm.names.constructor, this, Attribute::Writable | Attribute::Configurable);
+ define_property(vm.names.prototype, prototype, Attribute::Writable);
+ }
+ define_native_property(vm.names.length, length_getter, {}, Attribute::Configurable);
+ define_native_property(vm.names.name, name_getter, {}, Attribute::Configurable);
+}
+
+ScriptFunction::~ScriptFunction()
+{
+}
+
+void ScriptFunction::visit_edges(Visitor& visitor)
+{
+ Function::visit_edges(visitor);
+ visitor.visit(m_parent_scope);
+}
+
+LexicalEnvironment* ScriptFunction::create_environment()
+{
+ HashMap<FlyString, Variable> variables;
+ for (auto& parameter : m_parameters) {
+ variables.set(parameter.name, { js_undefined(), DeclarationKind::Var });
+ }
+
+ if (is<ScopeNode>(body())) {
+ for (auto& declaration : static_cast<const ScopeNode&>(body()).variables()) {
+ for (auto& declarator : declaration.declarations()) {
+ variables.set(declarator.id().string(), { js_undefined(), DeclarationKind::Var });
+ }
+ }
+ }
+
+ auto* environment = heap().allocate<LexicalEnvironment>(global_object(), move(variables), m_parent_scope, LexicalEnvironment::EnvironmentRecordType::Function);
+ environment->set_home_object(home_object());
+ environment->set_current_function(*this);
+ if (m_is_arrow_function) {
+ if (is<LexicalEnvironment>(m_parent_scope))
+ environment->set_new_target(static_cast<LexicalEnvironment*>(m_parent_scope)->new_target());
+ }
+ return environment;
+}
+
+Value ScriptFunction::execute_function_body()
+{
+ auto& vm = this->vm();
+
+ OwnPtr<Interpreter> local_interpreter;
+ Interpreter* interpreter = vm.interpreter_if_exists();
+
+ if (!interpreter) {
+ local_interpreter = Interpreter::create_with_existing_global_object(global_object());
+ interpreter = local_interpreter.ptr();
+ }
+
+ VM::InterpreterExecutionScope scope(*interpreter);
+
+ auto& call_frame_args = vm.call_frame().arguments;
+ for (size_t i = 0; i < m_parameters.size(); ++i) {
+ auto parameter = m_parameters[i];
+ Value argument_value;
+ if (parameter.is_rest) {
+ auto* array = Array::create(global_object());
+ for (size_t rest_index = i; rest_index < call_frame_args.size(); ++rest_index)
+ array->indexed_properties().append(call_frame_args[rest_index]);
+ argument_value = move(array);
+ } else if (i < call_frame_args.size() && !call_frame_args[i].is_undefined()) {
+ argument_value = call_frame_args[i];
+ } else if (parameter.default_value) {
+ argument_value = parameter.default_value->execute(*interpreter, global_object());
+ if (vm.exception())
+ return {};
+ } else {
+ argument_value = js_undefined();
+ }
+ vm.current_scope()->put_to_scope(parameter.name, { argument_value, DeclarationKind::Var });
+ }
+
+ return interpreter->execute_statement(global_object(), m_body, ScopeType::Function);
+}
+
+Value ScriptFunction::call()
+{
+ if (m_is_class_constructor) {
+ vm().throw_exception<TypeError>(global_object(), ErrorType::ClassConstructorWithoutNew, m_name);
+ return {};
+ }
+ return execute_function_body();
+}
+
+Value ScriptFunction::construct(Function&)
+{
+ if (m_is_arrow_function) {
+ vm().throw_exception<TypeError>(global_object(), ErrorType::NotAConstructor, m_name);
+ return {};
+ }
+ return execute_function_body();
+}
+
+JS_DEFINE_NATIVE_GETTER(ScriptFunction::length_getter)
+{
+ auto* function = typed_this(vm, global_object);
+ if (!function)
+ return {};
+ return Value(static_cast<i32>(function->m_function_length));
+}
+
+JS_DEFINE_NATIVE_GETTER(ScriptFunction::name_getter)
+{
+ auto* function = typed_this(vm, global_object);
+ if (!function)
+ return {};
+ return js_string(vm, function->name().is_null() ? "" : function->name());
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ScriptFunction.h b/Userland/Libraries/LibJS/Runtime/ScriptFunction.h
new file mode 100644
index 0000000000..e3765509ab
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/ScriptFunction.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/AST.h>
+#include <LibJS/Runtime/Function.h>
+
+namespace JS {
+
+class ScriptFunction final : public Function {
+ JS_OBJECT(ScriptFunction, Function);
+
+public:
+ static ScriptFunction* create(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, bool is_strict, bool is_arrow_function = false);
+
+ ScriptFunction(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, Object& prototype, bool is_strict, bool is_arrow_function = false);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~ScriptFunction();
+
+ const Statement& body() const { return m_body; }
+ const Vector<FunctionNode::Parameter>& parameters() const { return m_parameters; };
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+ virtual const FlyString& name() const override { return m_name; };
+ void set_name(const FlyString& name) { m_name = name; };
+
+ void set_is_class_constructor() { m_is_class_constructor = true; };
+
+protected:
+ virtual bool is_strict_mode() const final { return m_is_strict; }
+
+private:
+ virtual LexicalEnvironment* create_environment() override;
+ virtual void visit_edges(Visitor&) override;
+
+ Value execute_function_body();
+
+ JS_DECLARE_NATIVE_GETTER(length_getter);
+ JS_DECLARE_NATIVE_GETTER(name_getter);
+
+ FlyString m_name;
+ NonnullRefPtr<Statement> m_body;
+ const Vector<FunctionNode::Parameter> m_parameters;
+ ScopeObject* m_parent_scope { nullptr };
+ i32 m_function_length { 0 };
+ bool m_is_strict { false };
+ bool m_is_arrow_function { false };
+ bool m_is_class_constructor { false };
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Shape.cpp b/Userland/Libraries/LibJS/Runtime/Shape.cpp
new file mode 100644
index 0000000000..1f74b14471
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Shape.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Heap/DeferGC.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Shape.h>
+
+namespace JS {
+
+Shape* Shape::create_unique_clone() const
+{
+ ASSERT(m_global_object);
+ auto* new_shape = heap().allocate_without_global_object<Shape>(*m_global_object);
+ new_shape->m_unique = true;
+ new_shape->m_prototype = m_prototype;
+ ensure_property_table();
+ new_shape->ensure_property_table();
+ (*new_shape->m_property_table) = *m_property_table;
+ new_shape->m_property_count = new_shape->m_property_table->size();
+ return new_shape;
+}
+
+Shape* Shape::create_put_transition(const StringOrSymbol& property_name, PropertyAttributes attributes)
+{
+ TransitionKey key { property_name, attributes };
+ if (auto* existing_shape = m_forward_transitions.get(key).value_or(nullptr))
+ return existing_shape;
+ auto* new_shape = heap().allocate_without_global_object<Shape>(*this, property_name, attributes, TransitionType::Put);
+ m_forward_transitions.set(key, new_shape);
+ return new_shape;
+}
+
+Shape* Shape::create_configure_transition(const StringOrSymbol& property_name, PropertyAttributes attributes)
+{
+ TransitionKey key { property_name, attributes };
+ if (auto* existing_shape = m_forward_transitions.get(key).value_or(nullptr))
+ return existing_shape;
+ auto* new_shape = heap().allocate_without_global_object<Shape>(*this, property_name, attributes, TransitionType::Configure);
+ m_forward_transitions.set(key, new_shape);
+ return new_shape;
+}
+
+Shape* Shape::create_prototype_transition(Object* new_prototype)
+{
+ return heap().allocate_without_global_object<Shape>(*this, new_prototype);
+}
+
+Shape::Shape(ShapeWithoutGlobalObjectTag)
+{
+}
+
+Shape::Shape(Object& global_object)
+ : m_global_object(&global_object)
+{
+}
+
+Shape::Shape(Shape& previous_shape, const StringOrSymbol& property_name, PropertyAttributes attributes, TransitionType transition_type)
+ : m_attributes(attributes)
+ , m_transition_type(transition_type)
+ , m_global_object(previous_shape.m_global_object)
+ , m_previous(&previous_shape)
+ , m_property_name(property_name)
+ , m_prototype(previous_shape.m_prototype)
+ , m_property_count(transition_type == TransitionType::Put ? previous_shape.m_property_count + 1 : previous_shape.m_property_count)
+{
+}
+
+Shape::Shape(Shape& previous_shape, Object* new_prototype)
+ : m_transition_type(TransitionType::Prototype)
+ , m_global_object(previous_shape.m_global_object)
+ , m_previous(&previous_shape)
+ , m_prototype(new_prototype)
+ , m_property_count(previous_shape.m_property_count)
+{
+}
+
+Shape::~Shape()
+{
+}
+
+void Shape::visit_edges(Cell::Visitor& visitor)
+{
+ Cell::visit_edges(visitor);
+ visitor.visit(m_global_object);
+ visitor.visit(m_prototype);
+ visitor.visit(m_previous);
+ m_property_name.visit_edges(visitor);
+ for (auto& it : m_forward_transitions)
+ visitor.visit(it.value);
+
+ if (m_property_table) {
+ for (auto& it : *m_property_table)
+ it.key.visit_edges(visitor);
+ }
+}
+
+Optional<PropertyMetadata> Shape::lookup(const StringOrSymbol& property_name) const
+{
+ if (m_property_count == 0)
+ return {};
+ auto property = property_table().get(property_name);
+ if (!property.has_value())
+ return {};
+ return property;
+}
+
+const HashMap<StringOrSymbol, PropertyMetadata>& Shape::property_table() const
+{
+ ensure_property_table();
+ return *m_property_table;
+}
+
+size_t Shape::property_count() const
+{
+ return m_property_count;
+}
+
+Vector<Shape::Property> Shape::property_table_ordered() const
+{
+ auto vec = Vector<Shape::Property>();
+ vec.resize(property_count());
+
+ for (auto& it : property_table()) {
+ vec[it.value.offset] = { it.key, it.value };
+ }
+
+ return vec;
+}
+
+void Shape::ensure_property_table() const
+{
+ if (m_property_table)
+ return;
+ m_property_table = make<HashMap<StringOrSymbol, PropertyMetadata>>();
+
+ u32 next_offset = 0;
+
+ Vector<const Shape*, 64> transition_chain;
+ for (auto* shape = m_previous; shape; shape = shape->m_previous) {
+ if (shape->m_property_table) {
+ *m_property_table = *shape->m_property_table;
+ next_offset = shape->m_property_count;
+ break;
+ }
+ transition_chain.append(shape);
+ }
+ transition_chain.append(this);
+
+ for (ssize_t i = transition_chain.size() - 1; i >= 0; --i) {
+ auto* shape = transition_chain[i];
+ if (!shape->m_property_name.is_valid()) {
+ // Ignore prototype transitions as they don't affect the key map.
+ continue;
+ }
+ if (shape->m_transition_type == TransitionType::Put) {
+ m_property_table->set(shape->m_property_name, { next_offset++, shape->m_attributes });
+ } else if (shape->m_transition_type == TransitionType::Configure) {
+ auto it = m_property_table->find(shape->m_property_name);
+ ASSERT(it != m_property_table->end());
+ it->value.attributes = shape->m_attributes;
+ }
+ }
+}
+
+void Shape::add_property_to_unique_shape(const StringOrSymbol& property_name, PropertyAttributes attributes)
+{
+ ASSERT(is_unique());
+ ASSERT(m_property_table);
+ ASSERT(!m_property_table->contains(property_name));
+ m_property_table->set(property_name, { m_property_table->size(), attributes });
+ ++m_property_count;
+}
+
+void Shape::reconfigure_property_in_unique_shape(const StringOrSymbol& property_name, PropertyAttributes attributes)
+{
+ ASSERT(is_unique());
+ ASSERT(m_property_table);
+ auto it = m_property_table->find(property_name);
+ ASSERT(it != m_property_table->end());
+ it->value.attributes = attributes;
+ m_property_table->set(property_name, it->value);
+}
+
+void Shape::remove_property_from_unique_shape(const StringOrSymbol& property_name, size_t offset)
+{
+ ASSERT(is_unique());
+ ASSERT(m_property_table);
+ if (m_property_table->remove(property_name))
+ --m_property_count;
+ for (auto& it : *m_property_table) {
+ ASSERT(it.value.offset != offset);
+ if (it.value.offset > offset)
+ --it.value.offset;
+ }
+}
+
+void Shape::add_property_without_transition(const StringOrSymbol& property_name, PropertyAttributes attributes)
+{
+ ensure_property_table();
+ if (m_property_table->set(property_name, { m_property_count, attributes }) == AK::HashSetResult::InsertedNewEntry)
+ ++m_property_count;
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Shape.h b/Userland/Libraries/LibJS/Runtime/Shape.h
new file mode 100644
index 0000000000..1d2bd51037
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Shape.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/OwnPtr.h>
+#include <LibJS/Forward.h>
+#include <LibJS/Runtime/Cell.h>
+#include <LibJS/Runtime/PropertyAttributes.h>
+#include <LibJS/Runtime/StringOrSymbol.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+struct PropertyMetadata {
+ size_t offset { 0 };
+ PropertyAttributes attributes { 0 };
+};
+
+struct TransitionKey {
+ StringOrSymbol property_name;
+ PropertyAttributes attributes { 0 };
+
+ bool operator==(const TransitionKey& other) const
+ {
+ return property_name == other.property_name && attributes == other.attributes;
+ }
+};
+
+class Shape final : public Cell {
+public:
+ virtual ~Shape() override;
+
+ enum class TransitionType {
+ Invalid,
+ Put,
+ Configure,
+ Prototype,
+ };
+
+ enum class ShapeWithoutGlobalObjectTag { Tag };
+
+ explicit Shape(ShapeWithoutGlobalObjectTag);
+ explicit Shape(Object& global_object);
+ Shape(Shape& previous_shape, const StringOrSymbol& property_name, PropertyAttributes attributes, TransitionType);
+ Shape(Shape& previous_shape, Object* new_prototype);
+
+ Shape* create_put_transition(const StringOrSymbol&, PropertyAttributes attributes);
+ Shape* create_configure_transition(const StringOrSymbol&, PropertyAttributes attributes);
+ Shape* create_prototype_transition(Object* new_prototype);
+
+ void add_property_without_transition(const StringOrSymbol&, PropertyAttributes);
+
+ bool is_unique() const { return m_unique; }
+ Shape* create_unique_clone() const;
+
+ GlobalObject* global_object() const;
+
+ Object* prototype() { return m_prototype; }
+ const Object* prototype() const { return m_prototype; }
+
+ Optional<PropertyMetadata> lookup(const StringOrSymbol&) const;
+ const HashMap<StringOrSymbol, PropertyMetadata>& property_table() const;
+ size_t property_count() const;
+
+ struct Property {
+ StringOrSymbol key;
+ PropertyMetadata value;
+ };
+
+ Vector<Property> property_table_ordered() const;
+
+ void set_prototype_without_transition(Object* new_prototype) { m_prototype = new_prototype; }
+
+ void remove_property_from_unique_shape(const StringOrSymbol&, size_t offset);
+ void add_property_to_unique_shape(const StringOrSymbol&, PropertyAttributes attributes);
+ void reconfigure_property_in_unique_shape(const StringOrSymbol& property_name, PropertyAttributes attributes);
+
+private:
+ virtual const char* class_name() const override { return "Shape"; }
+ virtual void visit_edges(Visitor&) override;
+
+ void ensure_property_table() const;
+
+ PropertyAttributes m_attributes { 0 };
+ TransitionType m_transition_type : 6 { TransitionType::Invalid };
+ bool m_unique : 1 { false };
+
+ Object* m_global_object { nullptr };
+
+ mutable OwnPtr<HashMap<StringOrSymbol, PropertyMetadata>> m_property_table;
+
+ HashMap<TransitionKey, Shape*> m_forward_transitions;
+ Shape* m_previous { nullptr };
+ StringOrSymbol m_property_name;
+ Object* m_prototype { nullptr };
+ size_t m_property_count { 0 };
+};
+
+}
+
+template<>
+struct AK::Traits<JS::TransitionKey> : public GenericTraits<JS::TransitionKey> {
+ static unsigned hash(const JS::TransitionKey& key)
+ {
+ return pair_int_hash(key.attributes.bits(), Traits<JS::StringOrSymbol>::hash(key.property_name));
+ }
+};
diff --git a/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp b/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp
new file mode 100644
index 0000000000..50872f2387
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <AK/Utf32View.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/StringConstructor.h>
+#include <LibJS/Runtime/StringObject.h>
+
+namespace JS {
+
+StringConstructor::StringConstructor(GlobalObject& global_object)
+ : NativeFunction(vm().names.String, *global_object.function_prototype())
+{
+}
+
+void StringConstructor::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+ define_property(vm.names.prototype, global_object.string_prototype(), 0);
+ define_property(vm.names.length, Value(1), Attribute::Configurable);
+
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+ define_native_function(vm.names.raw, raw, 1, attr);
+ define_native_function(vm.names.fromCharCode, from_char_code, 1, attr);
+}
+
+StringConstructor::~StringConstructor()
+{
+}
+
+Value StringConstructor::call()
+{
+ if (!vm().argument_count())
+ return js_string(heap(), "");
+ if (vm().argument(0).is_symbol())
+ return js_string(heap(), vm().argument(0).as_symbol().to_string());
+ auto* string = vm().argument(0).to_primitive_string(global_object());
+ if (vm().exception())
+ return {};
+ return string;
+}
+
+Value StringConstructor::construct(Function&)
+{
+ PrimitiveString* primitive_string = nullptr;
+ if (!vm().argument_count())
+ primitive_string = js_string(vm(), "");
+ else
+ primitive_string = vm().argument(0).to_primitive_string(global_object());
+ if (!primitive_string)
+ return {};
+ return StringObject::create(global_object(), *primitive_string);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringConstructor::raw)
+{
+ auto* template_object = vm.argument(0).to_object(global_object);
+ if (vm.exception())
+ return {};
+
+ auto raw = template_object->get(vm.names.raw);
+ if (vm.exception())
+ return {};
+ if (raw.is_empty() || raw.is_nullish()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::StringRawCannotConvert, raw.is_null() ? "null" : "undefined");
+ return {};
+ }
+ if (!raw.is_array())
+ return js_string(vm, "");
+
+ auto* array = static_cast<Array*>(raw.to_object(global_object));
+ auto& raw_array_elements = array->indexed_properties();
+ StringBuilder builder;
+
+ for (size_t i = 0; i < raw_array_elements.array_like_size(); ++i) {
+ auto result = raw_array_elements.get(array, i);
+ if (vm.exception())
+ return {};
+ if (!result.has_value())
+ continue;
+ builder.append(result.value().value.to_string(global_object));
+ if (vm.exception())
+ return {};
+ if (i + 1 < vm.argument_count() && i < raw_array_elements.array_like_size() - 1) {
+ builder.append(vm.argument(i + 1).to_string(global_object));
+ if (vm.exception())
+ return {};
+ }
+ }
+
+ return js_string(vm, builder.build());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringConstructor::from_char_code)
+{
+ StringBuilder builder;
+ for (size_t i = 0; i < vm.argument_count(); ++i) {
+ auto char_code = vm.argument(i).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ auto truncated = char_code & 0xffff;
+ // FIXME: We need an Utf16View :^)
+ builder.append(Utf32View((u32*)&truncated, 1));
+ }
+ return js_string(vm, builder.build());
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/StringConstructor.h b/Userland/Libraries/LibJS/Runtime/StringConstructor.h
new file mode 100644
index 0000000000..239d9e7596
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/StringConstructor.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class StringConstructor final : public NativeFunction {
+ JS_OBJECT(StringConstructor, NativeFunction);
+
+public:
+ explicit StringConstructor(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~StringConstructor() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+
+ JS_DECLARE_NATIVE_FUNCTION(raw);
+ JS_DECLARE_NATIVE_FUNCTION(from_char_code);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/StringIterator.cpp b/Userland/Libraries/LibJS/Runtime/StringIterator.cpp
new file mode 100644
index 0000000000..4c0b9a3355
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/StringIterator.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Utf8View.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/StringIterator.h>
+
+namespace JS {
+
+StringIterator* StringIterator::create(GlobalObject& global_object, String string)
+{
+ return global_object.heap().allocate<StringIterator>(global_object, *global_object.string_iterator_prototype(), move(string));
+}
+
+StringIterator::StringIterator(Object& prototype, String string)
+ : Object(prototype)
+ , m_string(move(string))
+ , m_iterator(Utf8View(m_string).begin())
+{
+}
+
+StringIterator::~StringIterator()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/StringIterator.h b/Userland/Libraries/LibJS/Runtime/StringIterator.h
new file mode 100644
index 0000000000..c5754cf147
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/StringIterator.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Utf8View.h>
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class StringIterator final : public Object {
+ JS_OBJECT(StringIterator, Object);
+
+public:
+ static StringIterator* create(GlobalObject&, String string);
+
+ explicit StringIterator(Object& prototype, String string);
+ virtual ~StringIterator() override;
+
+ Utf8CodepointIterator& iterator() { return m_iterator; }
+ bool done() const { return m_done; }
+
+private:
+ friend class StringIteratorPrototype;
+
+ String m_string;
+ Utf8CodepointIterator m_iterator;
+ bool m_done { false };
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/StringIteratorPrototype.cpp b/Userland/Libraries/LibJS/Runtime/StringIteratorPrototype.cpp
new file mode 100644
index 0000000000..2259ae7b26
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/StringIteratorPrototype.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/IteratorOperations.h>
+#include <LibJS/Runtime/StringIterator.h>
+#include <LibJS/Runtime/StringIteratorPrototype.h>
+
+namespace JS {
+
+StringIteratorPrototype::StringIteratorPrototype(GlobalObject& global_object)
+ : Object(*global_object.iterator_prototype())
+{
+}
+
+void StringIteratorPrototype::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+ define_native_function(vm.names.next, next, 0, Attribute::Configurable | Attribute::Writable);
+ define_property(vm.well_known_symbol_to_string_tag(), js_string(global_object.heap(), "String Iterator"), Attribute::Configurable);
+}
+
+StringIteratorPrototype::~StringIteratorPrototype()
+{
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringIteratorPrototype::next)
+{
+ auto this_value = vm.this_value(global_object);
+ if (!this_value.is_object() || !is<StringIterator>(this_value.as_object())) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "String Iterator");
+ return {};
+ }
+
+ auto& this_object = this_value.as_object();
+ auto& iterator = static_cast<StringIterator&>(this_object);
+ if (iterator.done())
+ return create_iterator_result_object(global_object, js_undefined(), true);
+
+ auto& utf8_iterator = iterator.iterator();
+
+ if (utf8_iterator.done()) {
+ iterator.m_done = true;
+ return create_iterator_result_object(global_object, js_undefined(), true);
+ }
+
+ StringBuilder builder;
+ builder.append_code_point(*utf8_iterator);
+ ++utf8_iterator;
+
+ return create_iterator_result_object(global_object, js_string(vm, builder.to_string()), false);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/StringIteratorPrototype.h b/Userland/Libraries/LibJS/Runtime/StringIteratorPrototype.h
new file mode 100644
index 0000000000..a17e2507eb
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/StringIteratorPrototype.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class StringIteratorPrototype final : public Object {
+ JS_OBJECT(StringIteratorPrototype, Object)
+
+public:
+ StringIteratorPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~StringIteratorPrototype() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(next);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/StringObject.cpp b/Userland/Libraries/LibJS/Runtime/StringObject.cpp
new file mode 100644
index 0000000000..cfe1fd6fd8
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/StringObject.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/PrimitiveString.h>
+#include <LibJS/Runtime/StringObject.h>
+#include <LibJS/Runtime/StringPrototype.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+StringObject* StringObject::create(GlobalObject& global_object, PrimitiveString& primitive_string)
+{
+ return global_object.heap().allocate<StringObject>(global_object, primitive_string, *global_object.string_prototype());
+}
+
+StringObject::StringObject(PrimitiveString& string, Object& prototype)
+ : Object(prototype)
+ , m_string(string)
+{
+}
+
+StringObject::~StringObject()
+{
+}
+
+void StringObject::visit_edges(Cell::Visitor& visitor)
+{
+ Object::visit_edges(visitor);
+ visitor.visit(&m_string);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/StringObject.h b/Userland/Libraries/LibJS/Runtime/StringObject.h
new file mode 100644
index 0000000000..7fb32ebc63
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/StringObject.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class StringObject : public Object {
+ JS_OBJECT(StringObject, Object);
+
+public:
+ static StringObject* create(GlobalObject&, PrimitiveString&);
+
+ StringObject(PrimitiveString&, Object& prototype);
+ virtual ~StringObject() override;
+
+ const PrimitiveString& primitive_string() const { return m_string; }
+ virtual Value value_of() const override
+ {
+ return Value(&m_string);
+ }
+
+private:
+ virtual void visit_edges(Visitor&) override;
+
+ PrimitiveString& m_string;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/StringOrSymbol.h b/Userland/Libraries/LibJS/Runtime/StringOrSymbol.h
new file mode 100644
index 0000000000..38311b61b2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/StringOrSymbol.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <LibJS/Runtime/PrimitiveString.h>
+#include <LibJS/Runtime/Symbol.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+class StringOrSymbol {
+public:
+ static StringOrSymbol from_value(GlobalObject& global_object, Value value)
+ {
+ if (value.is_empty())
+ return {};
+ if (value.is_symbol())
+ return &value.as_symbol();
+ auto string = value.to_string(global_object);
+ if (string.is_null())
+ return {};
+ return string;
+ }
+
+ StringOrSymbol() = default;
+
+ StringOrSymbol(const char* chars)
+ : m_ptr(StringImpl::create(chars).leak_ref())
+ {
+ }
+
+ StringOrSymbol(const String& string)
+ : m_ptr(string.impl())
+ {
+ ASSERT(!string.is_null());
+ as_string_impl().ref();
+ }
+
+ StringOrSymbol(const FlyString& string)
+ : m_ptr(string.impl())
+ {
+ ASSERT(!string.is_null());
+ as_string_impl().ref();
+ }
+
+ ~StringOrSymbol()
+ {
+ if (is_string())
+ as_string_impl().unref();
+ }
+
+ StringOrSymbol(const Symbol* symbol)
+ : m_ptr(symbol)
+ {
+ set_symbol_flag();
+ }
+
+ StringOrSymbol(const StringOrSymbol& other)
+ {
+ m_ptr = other.m_ptr;
+ if (is_string())
+ as_string_impl().ref();
+ }
+
+ StringOrSymbol(StringOrSymbol&& other)
+ {
+ m_ptr = exchange(other.m_ptr, nullptr);
+ }
+
+ ALWAYS_INLINE bool is_valid() const { return m_ptr != nullptr; }
+ ALWAYS_INLINE bool is_symbol() const { return is_valid() && (bits() & 1ul); }
+ ALWAYS_INLINE bool is_string() const { return is_valid() && !(bits() & 1ul); }
+
+ ALWAYS_INLINE String as_string() const
+ {
+ ASSERT(is_string());
+ return as_string_impl();
+ }
+
+ ALWAYS_INLINE const Symbol* as_symbol() const
+ {
+ ASSERT(is_symbol());
+ return reinterpret_cast<const Symbol*>(bits() & ~1ul);
+ }
+
+ String to_display_string() const
+ {
+ if (is_string())
+ return as_string();
+ if (is_symbol())
+ return as_symbol()->to_string();
+ ASSERT_NOT_REACHED();
+ }
+
+ Value to_value(VM& vm) const
+ {
+ if (is_string())
+ return js_string(vm, as_string());
+ if (is_symbol())
+ return const_cast<Symbol*>(as_symbol());
+ return {};
+ }
+
+ void visit_edges(Cell::Visitor& visitor)
+ {
+ if (is_symbol())
+ visitor.visit(const_cast<Symbol*>(as_symbol()));
+ }
+
+ ALWAYS_INLINE bool operator==(const StringOrSymbol& other) const
+ {
+ if (is_string())
+ return other.is_string() && as_string_impl() == other.as_string_impl();
+ if (is_symbol())
+ return other.is_symbol() && as_symbol() == other.as_symbol();
+ return true;
+ }
+
+ StringOrSymbol& operator=(const StringOrSymbol& other)
+ {
+ if (this == &other)
+ return *this;
+ m_ptr = other.m_ptr;
+ if (is_string())
+ as_string_impl().ref();
+ return *this;
+ }
+
+ StringOrSymbol& operator=(StringOrSymbol&& other)
+ {
+ if (this != &other)
+ m_ptr = exchange(other.m_ptr, nullptr);
+ return *this;
+ }
+
+ unsigned hash() const
+ {
+ if (is_string())
+ return as_string_impl().hash();
+ return ptr_hash(as_symbol());
+ }
+
+private:
+ ALWAYS_INLINE u64 bits() const
+ {
+ return reinterpret_cast<uintptr_t>(m_ptr);
+ }
+
+ ALWAYS_INLINE void set_symbol_flag()
+ {
+ m_ptr = reinterpret_cast<const void*>(bits() | 1ul);
+ }
+
+ ALWAYS_INLINE const StringImpl& as_string_impl() const
+ {
+ ASSERT(is_string());
+ return *reinterpret_cast<const StringImpl*>(m_ptr);
+ }
+
+ const void* m_ptr { nullptr };
+};
+
+}
+
+template<>
+struct AK::Traits<JS::StringOrSymbol> : public GenericTraits<JS::StringOrSymbol> {
+ static unsigned hash(const JS::StringOrSymbol& key)
+ {
+ return key.hash();
+ }
+};
diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp
new file mode 100644
index 0000000000..d0f4d495ff
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp
@@ -0,0 +1,629 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <AK/StringBuilder.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/PrimitiveString.h>
+#include <LibJS/Runtime/StringIterator.h>
+#include <LibJS/Runtime/StringObject.h>
+#include <LibJS/Runtime/StringPrototype.h>
+#include <LibJS/Runtime/Value.h>
+#include <string.h>
+
+namespace JS {
+
+static StringObject* typed_this(VM& vm, GlobalObject& global_object)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return nullptr;
+ if (!is<StringObject>(this_object)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "String");
+ return nullptr;
+ }
+ return static_cast<StringObject*>(this_object);
+}
+
+static String ak_string_from(VM& vm, GlobalObject& global_object)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ return Value(this_object).to_string(global_object);
+}
+
+static Optional<size_t> split_match(const String& haystack, size_t start, const String& needle)
+{
+ auto r = needle.length();
+ auto s = haystack.length();
+ if (start + r > s)
+ return {};
+ if (!haystack.substring_view(start).starts_with(needle))
+ return {};
+ return start + r;
+}
+
+StringPrototype::StringPrototype(GlobalObject& global_object)
+ : StringObject(*js_string(global_object.heap(), String::empty()), *global_object.object_prototype())
+{
+}
+
+void StringPrototype::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ StringObject::initialize(global_object);
+ u8 attr = Attribute::Writable | Attribute::Configurable;
+
+ define_native_property(vm.names.length, length_getter, {}, 0);
+ define_native_function(vm.names.charAt, char_at, 1, attr);
+ define_native_function(vm.names.charCodeAt, char_code_at, 1, attr);
+ define_native_function(vm.names.repeat, repeat, 1, attr);
+ define_native_function(vm.names.startsWith, starts_with, 1, attr);
+ define_native_function(vm.names.endsWith, ends_with, 1, attr);
+ define_native_function(vm.names.indexOf, index_of, 1, attr);
+ define_native_function(vm.names.toLowerCase, to_lowercase, 0, attr);
+ define_native_function(vm.names.toUpperCase, to_uppercase, 0, attr);
+ define_native_function(vm.names.toString, to_string, 0, attr);
+ define_native_function(vm.names.padStart, pad_start, 1, attr);
+ define_native_function(vm.names.padEnd, pad_end, 1, attr);
+ define_native_function(vm.names.trim, trim, 0, attr);
+ define_native_function(vm.names.trimStart, trim_start, 0, attr);
+ define_native_function(vm.names.trimEnd, trim_end, 0, attr);
+ define_native_function(vm.names.concat, concat, 1, attr);
+ define_native_function(vm.names.substr, substr, 2, attr);
+ define_native_function(vm.names.substring, substring, 2, attr);
+ define_native_function(vm.names.includes, includes, 1, attr);
+ define_native_function(vm.names.slice, slice, 2, attr);
+ define_native_function(vm.names.split, split, 2, attr);
+ define_native_function(vm.names.lastIndexOf, last_index_of, 1, attr);
+ define_native_function(vm.well_known_symbol_iterator(), symbol_iterator, 0, attr);
+}
+
+StringPrototype::~StringPrototype()
+{
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::char_at)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ i32 index = 0;
+ if (vm.argument_count()) {
+ index = vm.argument(0).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ }
+ if (index < 0 || index >= static_cast<i32>(string.length()))
+ return js_string(vm, String::empty());
+ // FIXME: This should return a character corresponding to the i'th UTF-16 code point.
+ return js_string(vm, string.substring(index, 1));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::char_code_at)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ i32 index = 0;
+ if (vm.argument_count()) {
+ index = vm.argument(0).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ }
+ if (index < 0 || index >= static_cast<i32>(string.length()))
+ return js_nan();
+ // FIXME: This should return the i'th UTF-16 code point.
+ return Value((i32)string[index]);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::repeat)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ if (!vm.argument_count())
+ return js_string(vm, String::empty());
+ auto count = vm.argument(0).to_integer_or_infinity(global_object);
+ if (vm.exception())
+ return {};
+ if (count < 0) {
+ vm.throw_exception<RangeError>(global_object, ErrorType::StringRepeatCountMustBe, "positive");
+ return {};
+ }
+ if (Value(count).is_infinity()) {
+ vm.throw_exception<RangeError>(global_object, ErrorType::StringRepeatCountMustBe, "finite");
+ return {};
+ }
+ StringBuilder builder;
+ for (size_t i = 0; i < count; ++i)
+ builder.append(string);
+ return js_string(vm, builder.to_string());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::starts_with)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ if (!vm.argument_count())
+ return Value(false);
+ auto search_string = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+ auto string_length = string.length();
+ auto search_string_length = search_string.length();
+ size_t start = 0;
+ if (!vm.argument(1).is_undefined()) {
+ auto position = vm.argument(1).to_integer_or_infinity(global_object);
+ if (vm.exception())
+ return {};
+ start = clamp(position, static_cast<double>(0), static_cast<double>(string_length));
+ }
+ if (start + search_string_length > string_length)
+ return Value(false);
+ if (search_string_length == 0)
+ return Value(true);
+ return Value(string.substring(start, search_string_length) == search_string);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::ends_with)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+
+ auto search_string_value = vm.argument(0);
+
+ bool search_is_regexp = search_string_value.is_regexp(global_object);
+ if (vm.exception())
+ return {};
+ if (search_is_regexp) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::IsNotA, "searchString", "string, but a regular expression");
+ return {};
+ }
+
+ auto search_string = search_string_value.to_string(global_object);
+ if (vm.exception())
+ return {};
+
+ auto string_length = string.length();
+ auto search_string_length = search_string.length();
+
+ size_t pos = string_length;
+
+ auto end_position_value = vm.argument(1);
+ if (!end_position_value.is_undefined()) {
+ double pos_as_double = end_position_value.to_integer_or_infinity(global_object);
+ if (vm.exception())
+ return {};
+ pos = clamp(pos_as_double, static_cast<double>(0), static_cast<double>(string_length));
+ }
+
+ if (search_string_length == 0)
+ return Value(true);
+ if (pos < search_string_length)
+ return Value(false);
+
+ auto start = pos - search_string_length;
+ return Value(string.substring(start, search_string_length) == search_string);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::index_of)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ auto needle = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+ return Value((i32)string.index_of(needle).value_or(-1));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_lowercase)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ return js_string(vm, string.to_lowercase());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_uppercase)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ return js_string(vm, string.to_uppercase());
+}
+
+JS_DEFINE_NATIVE_GETTER(StringPrototype::length_getter)
+{
+ auto* string_object = typed_this(vm, global_object);
+ if (!string_object)
+ return {};
+ return Value((i32)string_object->primitive_string().string().length());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_string)
+{
+ auto* string_object = typed_this(vm, global_object);
+ if (!string_object)
+ return {};
+ return js_string(vm, string_object->primitive_string().string());
+}
+
+enum class PadPlacement {
+ Start,
+ End,
+};
+
+static Value pad_string(GlobalObject& global_object, const String& string, PadPlacement placement)
+{
+ auto& vm = global_object.vm();
+ auto max_length = vm.argument(0).to_length(global_object);
+ if (vm.exception())
+ return {};
+ if (max_length <= string.length())
+ return js_string(vm, string);
+
+ String fill_string = " ";
+ if (!vm.argument(1).is_undefined()) {
+ fill_string = vm.argument(1).to_string(global_object);
+ if (vm.exception())
+ return {};
+ if (fill_string.is_empty())
+ return js_string(vm, string);
+ }
+
+ auto fill_length = max_length - string.length();
+
+ StringBuilder filler_builder;
+ while (filler_builder.length() < fill_length)
+ filler_builder.append(fill_string);
+ auto filler = filler_builder.build().substring(0, fill_length);
+
+ auto formatted = placement == PadPlacement::Start
+ ? String::formatted("{}{}", filler, string)
+ : String::formatted("{}{}", string, filler);
+ return js_string(vm, formatted);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::pad_start)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ return pad_string(global_object, string, PadPlacement::Start);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::pad_end)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ return pad_string(global_object, string, PadPlacement::End);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::trim)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ return js_string(vm, string.trim_whitespace(TrimMode::Both));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::trim_start)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ return js_string(vm, string.trim_whitespace(TrimMode::Left));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::trim_end)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ return js_string(vm, string.trim_whitespace(TrimMode::Right));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::concat)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ StringBuilder builder;
+ builder.append(string);
+ for (size_t i = 0; i < vm.argument_count(); ++i) {
+ auto string_argument = vm.argument(i).to_string(global_object);
+ if (vm.exception())
+ return {};
+ builder.append(string_argument);
+ }
+ return js_string(vm, builder.to_string());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::substring)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ if (vm.argument_count() == 0)
+ return js_string(vm, string);
+
+ // FIXME: index_start and index_end should index a UTF-16 code_point view of the string.
+ auto string_length = string.length();
+ auto start = vm.argument(0).to_integer_or_infinity(global_object);
+ if (vm.exception())
+ return {};
+ auto end = (double)string_length;
+ if (!vm.argument(1).is_undefined()) {
+ end = vm.argument(1).to_integer_or_infinity(global_object);
+ if (vm.exception())
+ return {};
+ }
+ size_t index_start = clamp(start, static_cast<double>(0), static_cast<double>(string_length));
+ size_t index_end = clamp(end, static_cast<double>(0), static_cast<double>(string_length));
+
+ if (index_start == index_end)
+ return js_string(vm, String(""));
+
+ if (index_start > index_end) {
+ if (vm.argument_count() == 1)
+ return js_string(vm, String(""));
+ auto temp_index_start = index_start;
+ index_start = index_end;
+ index_end = temp_index_start;
+ }
+
+ auto part_length = index_end - index_start;
+ auto string_part = string.substring(index_start, part_length);
+ return js_string(vm, string_part);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::substr)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ if (vm.argument_count() == 0)
+ return js_string(vm, string);
+
+ // FIXME: this should index a UTF-16 code_point view of the string.
+ auto string_length = (i32)string.length();
+
+ auto start_argument = vm.argument(0).to_i32(global_object);
+ if (vm.exception())
+ return {};
+
+ auto start = start_argument < 0 ? (string_length - -start_argument) : start_argument;
+
+ auto length = string_length - start;
+ if (vm.argument_count() >= 2) {
+ auto length_argument = vm.argument(1).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ length = max(0, min(length_argument, length));
+ if (vm.exception())
+ return {};
+ }
+
+ if (length == 0)
+ return js_string(vm, String(""));
+
+ auto string_part = string.substring(start, length);
+ return js_string(vm, string_part);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::includes)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ auto search_string = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+ auto string_length = string.length();
+ // FIXME: start should index a UTF-16 code_point view of the string.
+ size_t start = 0;
+ if (!vm.argument(1).is_undefined()) {
+ auto position = vm.argument(1).to_integer_or_infinity(global_object);
+ if (vm.exception())
+ return {};
+ start = clamp(position, static_cast<double>(0), static_cast<double>(string_length));
+ }
+ if (start == 0)
+ return Value(string.contains(search_string));
+ auto substring_length = string_length - start;
+ auto substring_search = string.substring(start, substring_length);
+ return Value(substring_search.contains(search_string));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::slice)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+
+ if (vm.argument_count() == 0)
+ return js_string(vm, string);
+
+ // FIXME: index_start and index_end should index a UTF-16 code_point view of the string.
+ auto string_length = static_cast<i32>(string.length());
+ auto index_start = vm.argument(0).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ auto index_end = string_length;
+
+ auto negative_min_index = -(string_length - 1);
+ if (index_start < negative_min_index)
+ index_start = 0;
+ else if (index_start < 0)
+ index_start = string_length + index_start;
+
+ if (vm.argument_count() >= 2) {
+ index_end = vm.argument(1).to_i32(global_object);
+ if (vm.exception())
+ return {};
+
+ if (index_end < negative_min_index)
+ return js_string(vm, String::empty());
+
+ if (index_end > string_length)
+ index_end = string_length;
+ else if (index_end < 0)
+ index_end = string_length + index_end;
+ }
+
+ if (index_start >= index_end)
+ return js_string(vm, String::empty());
+
+ auto part_length = index_end - index_start;
+ auto string_part = string.substring(index_start, part_length);
+ return js_string(vm, string_part);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split)
+{
+ // FIXME Implement the @@split part
+
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+
+ auto* result = Array::create(global_object);
+ size_t result_len = 0;
+
+ auto limit = static_cast<u32>(MAX_U32);
+ if (!vm.argument(1).is_undefined()) {
+ limit = vm.argument(1).to_u32(global_object);
+ if (vm.exception())
+ return {};
+ }
+
+ auto separator = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+
+ if (limit == 0)
+ return result;
+
+ if (vm.argument(0).is_undefined()) {
+ result->define_property(0, js_string(vm, string));
+ return result;
+ }
+
+ auto len = string.length();
+ auto separator_len = separator.length();
+ if (len == 0) {
+ if (separator_len > 0)
+ result->define_property(0, js_string(vm, string));
+ return result;
+ }
+
+ size_t start = 0;
+ auto pos = start;
+ if (separator_len == 0) {
+ for (pos = 0; pos < len; pos++)
+ result->define_property(pos, js_string(vm, string.substring(pos, 1)));
+ return result;
+ }
+
+ while (pos != len) {
+ auto e = split_match(string, pos, separator);
+ if (!e.has_value()) {
+ pos += 1;
+ continue;
+ }
+
+ auto segment = string.substring_view(start, pos - start);
+ result->define_property(result_len, js_string(vm, segment));
+ result_len++;
+ if (result_len == limit)
+ return result;
+ start = e.value();
+ pos = start;
+ }
+
+ auto rest = string.substring(start, len - start);
+ result->define_property(result_len, js_string(vm, rest));
+
+ return result;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::last_index_of)
+{
+ auto string = ak_string_from(vm, global_object);
+ if (string.is_null())
+ return {};
+ auto search_string = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+ auto position = vm.argument(1).to_number(global_object);
+ if (vm.exception())
+ return {};
+ if (search_string.length() > string.length())
+ return Value(-1);
+ auto max_index = string.length() - search_string.length();
+ auto from_index = max_index;
+ if (!position.is_nan()) {
+ // FIXME: from_index should index a UTF-16 code_point view of the string.
+ auto p = position.to_integer_or_infinity(global_object);
+ if (vm.exception())
+ return {};
+ from_index = clamp(p, static_cast<double>(0), static_cast<double>(max_index));
+ }
+
+ for (i32 i = from_index; i >= 0; --i) {
+ auto part_view = string.substring_view(i, search_string.length());
+ if (part_view == search_string)
+ return Value(i);
+ }
+
+ return Value(-1);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::symbol_iterator)
+{
+ auto this_object = vm.this_value(global_object);
+ if (this_object.is_nullish()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ToObjectNullOrUndef);
+ return {};
+ }
+
+ auto string = this_object.to_string(global_object);
+ if (vm.exception())
+ return {};
+ return StringIterator::create(global_object, string);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.h b/Userland/Libraries/LibJS/Runtime/StringPrototype.h
new file mode 100644
index 0000000000..5847ea2117
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/StringObject.h>
+
+namespace JS {
+
+class StringPrototype final : public StringObject {
+ JS_OBJECT(StringPrototype, StringObject);
+
+public:
+ explicit StringPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~StringPrototype() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(char_at);
+ JS_DECLARE_NATIVE_FUNCTION(char_code_at);
+ JS_DECLARE_NATIVE_FUNCTION(repeat);
+ JS_DECLARE_NATIVE_FUNCTION(starts_with);
+ JS_DECLARE_NATIVE_FUNCTION(ends_with);
+ JS_DECLARE_NATIVE_FUNCTION(index_of);
+ JS_DECLARE_NATIVE_FUNCTION(to_lowercase);
+ JS_DECLARE_NATIVE_FUNCTION(to_uppercase);
+ JS_DECLARE_NATIVE_FUNCTION(to_string);
+ JS_DECLARE_NATIVE_FUNCTION(pad_start);
+ JS_DECLARE_NATIVE_FUNCTION(pad_end);
+ JS_DECLARE_NATIVE_FUNCTION(substring);
+ JS_DECLARE_NATIVE_FUNCTION(substr);
+
+ JS_DECLARE_NATIVE_GETTER(length_getter);
+
+ JS_DECLARE_NATIVE_FUNCTION(trim);
+ JS_DECLARE_NATIVE_FUNCTION(trim_start);
+ JS_DECLARE_NATIVE_FUNCTION(trim_end);
+ JS_DECLARE_NATIVE_FUNCTION(concat);
+ JS_DECLARE_NATIVE_FUNCTION(includes);
+ JS_DECLARE_NATIVE_FUNCTION(slice);
+ JS_DECLARE_NATIVE_FUNCTION(split);
+ JS_DECLARE_NATIVE_FUNCTION(last_index_of);
+
+ JS_DECLARE_NATIVE_FUNCTION(symbol_iterator);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Symbol.cpp b/Userland/Libraries/LibJS/Runtime/Symbol.cpp
new file mode 100644
index 0000000000..c25ebf2fce
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Symbol.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/Symbol.h>
+#include <LibJS/Runtime/VM.h>
+
+namespace JS {
+
+Symbol::Symbol(String description, bool is_global)
+ : m_description(move(description))
+ , m_is_global(is_global)
+{
+}
+
+Symbol::~Symbol()
+{
+}
+
+Symbol* js_symbol(Heap& heap, String description, bool is_global)
+{
+ return heap.allocate_without_global_object<Symbol>(move(description), is_global);
+}
+
+Symbol* js_symbol(VM& vm, String description, bool is_global)
+{
+ return js_symbol(vm.heap(), move(description), is_global);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Symbol.h b/Userland/Libraries/LibJS/Runtime/Symbol.h
new file mode 100644
index 0000000000..d01179add7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Symbol.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <LibJS/Runtime/Cell.h>
+
+namespace JS {
+
+class Symbol final : public Cell {
+ AK_MAKE_NONCOPYABLE(Symbol);
+ AK_MAKE_NONMOVABLE(Symbol);
+
+public:
+ Symbol(String, bool);
+ virtual ~Symbol();
+
+ const String& description() const { return m_description; }
+ bool is_global() const { return m_is_global; }
+ String to_string() const { return String::formatted("Symbol({})", description()); }
+
+private:
+ virtual const char* class_name() const override { return "Symbol"; }
+
+ String m_description;
+ bool m_is_global;
+};
+
+Symbol* js_symbol(Heap&, String description, bool is_global);
+Symbol* js_symbol(VM&, String description, bool is_global);
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/SymbolConstructor.cpp b/Userland/Libraries/LibJS/Runtime/SymbolConstructor.cpp
new file mode 100644
index 0000000000..0697ad98e6
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/SymbolConstructor.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/SymbolConstructor.h>
+#include <LibJS/Runtime/SymbolObject.h>
+
+namespace JS {
+
+SymbolConstructor::SymbolConstructor(GlobalObject& global_object)
+ : NativeFunction(vm().names.Symbol, *global_object.function_prototype())
+{
+}
+
+void SymbolConstructor::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+ define_property(vm.names.prototype, global_object.symbol_prototype(), 0);
+ define_property(vm.names.length, Value(0), Attribute::Configurable);
+
+ define_native_function(vm.names.for_, for_, 1, Attribute::Writable | Attribute::Configurable);
+ define_native_function(vm.names.keyFor, key_for, 1, Attribute::Writable | Attribute::Configurable);
+
+#define __JS_ENUMERATE(SymbolName, snake_name) \
+ define_property(vm.names.SymbolName, vm.well_known_symbol_##snake_name(), 0);
+ JS_ENUMERATE_WELL_KNOWN_SYMBOLS
+#undef __JS_ENUMERATE
+}
+
+SymbolConstructor::~SymbolConstructor()
+{
+}
+
+Value SymbolConstructor::call()
+{
+ if (!vm().argument_count())
+ return js_symbol(heap(), "", false);
+ return js_symbol(heap(), vm().argument(0).to_string(global_object()), false);
+}
+
+Value SymbolConstructor::construct(Function&)
+{
+ vm().throw_exception<TypeError>(global_object(), ErrorType::NotAConstructor, "Symbol");
+ return {};
+}
+
+JS_DEFINE_NATIVE_FUNCTION(SymbolConstructor::for_)
+{
+ String description;
+ if (!vm.argument_count()) {
+ description = "undefined";
+ } else {
+ description = vm.argument(0).to_string(global_object);
+ }
+
+ return global_object.vm().get_global_symbol(description);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(SymbolConstructor::key_for)
+{
+ auto argument = vm.argument(0);
+ if (!argument.is_symbol()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotASymbol, argument.to_string_without_side_effects());
+ return {};
+ }
+
+ auto& symbol = argument.as_symbol();
+ if (symbol.is_global())
+ return js_string(vm, symbol.description());
+
+ return js_undefined();
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/SymbolConstructor.h b/Userland/Libraries/LibJS/Runtime/SymbolConstructor.h
new file mode 100644
index 0000000000..8afba45fb5
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/SymbolConstructor.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class SymbolConstructor final : public NativeFunction {
+ JS_OBJECT(SymbolConstructor, NativeFunction);
+
+public:
+ explicit SymbolConstructor(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~SymbolConstructor() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+
+ JS_DECLARE_NATIVE_FUNCTION(for_);
+ JS_DECLARE_NATIVE_FUNCTION(key_for);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/SymbolObject.cpp b/Userland/Libraries/LibJS/Runtime/SymbolObject.cpp
new file mode 100644
index 0000000000..deb1170928
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/SymbolObject.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Symbol.h>
+#include <LibJS/Runtime/SymbolObject.h>
+#include <LibJS/Runtime/SymbolPrototype.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+SymbolObject* SymbolObject::create(GlobalObject& global_object, Symbol& primitive_symbol)
+{
+ return global_object.heap().allocate<SymbolObject>(global_object, primitive_symbol, *global_object.symbol_prototype());
+}
+
+SymbolObject::SymbolObject(Symbol& symbol, Object& prototype)
+ : Object(prototype)
+ , m_symbol(symbol)
+{
+}
+
+SymbolObject::~SymbolObject()
+{
+}
+
+void SymbolObject::visit_edges(Cell::Visitor& visitor)
+{
+ Object::visit_edges(visitor);
+ visitor.visit(&m_symbol);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/SymbolObject.h b/Userland/Libraries/LibJS/Runtime/SymbolObject.h
new file mode 100644
index 0000000000..0ff212ffb5
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/SymbolObject.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+#include <LibJS/Runtime/Symbol.h>
+
+namespace JS {
+
+class SymbolObject : public Object {
+ JS_OBJECT(SymbolObject, Object);
+
+public:
+ static SymbolObject* create(GlobalObject&, Symbol&);
+
+ SymbolObject(Symbol&, Object& prototype);
+ virtual ~SymbolObject() override;
+
+ Symbol& primitive_symbol() { return m_symbol; }
+ const Symbol& primitive_symbol() const { return m_symbol; }
+
+ const String& description() const { return m_symbol.description(); }
+ bool is_global() const { return m_symbol.is_global(); }
+
+ virtual Value value_of() const override
+ {
+ return Value(&m_symbol);
+ }
+
+private:
+ virtual void visit_edges(Visitor&) override;
+
+ Symbol& m_symbol;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/SymbolPrototype.cpp b/Userland/Libraries/LibJS/Runtime/SymbolPrototype.cpp
new file mode 100644
index 0000000000..0a36ab4a20
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/SymbolPrototype.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <AK/StringBuilder.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Object.h>
+#include <LibJS/Runtime/PrimitiveString.h>
+#include <LibJS/Runtime/SymbolObject.h>
+#include <LibJS/Runtime/SymbolPrototype.h>
+#include <LibJS/Runtime/Value.h>
+#include <string.h>
+
+namespace JS {
+
+SymbolPrototype::SymbolPrototype(GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void SymbolPrototype::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+ define_native_property(vm.names.description, description_getter, {}, Attribute::Configurable);
+ define_native_function(vm.names.toString, to_string, 0, Attribute::Writable | Attribute::Configurable);
+ define_native_function(vm.names.valueOf, value_of, 0, Attribute::Writable | Attribute::Configurable);
+
+ define_property(global_object.vm().well_known_symbol_to_string_tag(), js_string(global_object.heap(), "Symbol"), Attribute::Configurable);
+}
+
+SymbolPrototype::~SymbolPrototype()
+{
+}
+
+static SymbolObject* typed_this(VM& vm, GlobalObject& global_object)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return nullptr;
+ if (!is<SymbolObject>(this_object)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Symbol");
+ return nullptr;
+ }
+ return static_cast<SymbolObject*>(this_object);
+}
+
+JS_DEFINE_NATIVE_GETTER(SymbolPrototype::description_getter)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return js_string(vm, this_object->description());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(SymbolPrototype::to_string)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ auto string = this_object->primitive_symbol().to_string();
+ return js_string(vm, move(string));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(SymbolPrototype::value_of)
+{
+ auto* this_object = typed_this(vm, global_object);
+ if (!this_object)
+ return {};
+ return this_object->value_of();
+}
+}
diff --git a/Userland/Libraries/LibJS/Runtime/SymbolPrototype.h b/Userland/Libraries/LibJS/Runtime/SymbolPrototype.h
new file mode 100644
index 0000000000..589b46e7a7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/SymbolPrototype.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class SymbolPrototype final : public Object {
+ JS_OBJECT(SymbolPrototype, Object);
+
+public:
+ explicit SymbolPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~SymbolPrototype() override;
+
+private:
+ JS_DECLARE_NATIVE_GETTER(description_getter);
+
+ JS_DECLARE_NATIVE_FUNCTION(to_string);
+ JS_DECLARE_NATIVE_FUNCTION(value_of);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/TypedArray.cpp b/Userland/Libraries/LibJS/Runtime/TypedArray.cpp
new file mode 100644
index 0000000000..d5bc286e41
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/TypedArray.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/ArrayBuffer.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/TypedArray.h>
+#include <LibJS/Runtime/TypedArrayConstructor.h>
+
+namespace JS {
+
+static void initialize_typed_array_from_array_buffer(GlobalObject& global_object, TypedArrayBase& typed_array, ArrayBuffer& array_buffer, Value byte_offset, Value length)
+{
+ // 22.2.5.1.3 InitializeTypedArrayFromArrayBuffer, https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer
+
+ auto& vm = global_object.vm();
+ auto element_size = typed_array.element_size();
+ auto offset = byte_offset.to_index(global_object);
+ if (vm.exception())
+ return;
+ if (offset % element_size != 0) {
+ vm.throw_exception<RangeError>(global_object, ErrorType::TypedArrayInvalidByteOffset, typed_array.class_name(), element_size, offset);
+ return;
+ }
+ size_t new_length { 0 };
+ if (!length.is_undefined()) {
+ new_length = length.to_index(global_object);
+ if (vm.exception())
+ return;
+ }
+ // FIXME: 8. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
+ auto buffer_byte_length = array_buffer.byte_length();
+ size_t new_byte_length;
+ if (length.is_undefined()) {
+ if (buffer_byte_length % element_size != 0) {
+ vm.throw_exception<RangeError>(global_object, ErrorType::TypedArrayInvalidBufferLength, typed_array.class_name(), element_size, buffer_byte_length);
+ return;
+ }
+ if (offset > buffer_byte_length) {
+ vm.throw_exception<RangeError>(global_object, ErrorType::TypedArrayOutOfRangeByteOffset, offset, buffer_byte_length);
+ return;
+ }
+ new_byte_length = buffer_byte_length - offset;
+ } else {
+ new_byte_length = new_length * element_size;
+ if (offset + new_byte_length > buffer_byte_length) {
+ vm.throw_exception<RangeError>(global_object, ErrorType::TypedArrayOutOfRangeByteOffsetOrLength, offset, offset + new_byte_length, buffer_byte_length);
+ return;
+ }
+ }
+ typed_array.set_viewed_array_buffer(&array_buffer);
+ typed_array.set_byte_length(new_byte_length);
+ typed_array.set_byte_offset(offset);
+ typed_array.set_array_length(new_byte_length / element_size);
+}
+
+void TypedArrayBase::visit_edges(Visitor& visitor)
+{
+ Object::visit_edges(visitor);
+ visitor.visit(m_viewed_array_buffer);
+}
+
+#define JS_DEFINE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, Type) \
+ ClassName* ClassName::create(GlobalObject& global_object, u32 length) \
+ { \
+ return global_object.heap().allocate<ClassName>(global_object, length, *global_object.snake_name##_prototype()); \
+ } \
+ \
+ ClassName::ClassName(u32 length, Object& prototype) \
+ : TypedArray(length, prototype) \
+ { \
+ } \
+ ClassName::~ClassName() { } \
+ \
+ PrototypeName::PrototypeName(GlobalObject& global_object) \
+ : Object(*global_object.typed_array_prototype()) \
+ { \
+ } \
+ PrototypeName::~PrototypeName() { } \
+ \
+ ConstructorName::ConstructorName(GlobalObject& global_object) \
+ : TypedArrayConstructor(vm().names.ClassName, *global_object.typed_array_constructor()) \
+ { \
+ } \
+ ConstructorName::~ConstructorName() { } \
+ void ConstructorName::initialize(GlobalObject& global_object) \
+ { \
+ auto& vm = this->vm(); \
+ NativeFunction::initialize(global_object); \
+ define_property(vm.names.prototype, global_object.snake_name##_prototype(), 0); \
+ define_property(vm.names.length, Value(1), Attribute::Configurable); \
+ define_property(vm.names.BYTES_PER_ELEMENT, Value((i32)sizeof(Type)), 0); \
+ } \
+ Value ConstructorName::call() \
+ { \
+ auto& vm = this->vm(); \
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ConstructorWithoutNew, vm.names.ClassName); \
+ return {}; \
+ } \
+ Value ConstructorName::construct(Function&) \
+ { \
+ auto& vm = this->vm(); \
+ if (vm.argument_count() == 0) \
+ return ClassName::create(global_object(), 0); \
+ \
+ auto first_argument = vm.argument(0); \
+ if (first_argument.is_object()) { \
+ auto* typed_array = ClassName::create(global_object(), 0); \
+ if (first_argument.as_object().is_typed_array()) { \
+ /* FIXME: Initialize from TypedArray */ \
+ TODO(); \
+ } else if (is<ArrayBuffer>(first_argument.as_object())) { \
+ auto& array_buffer = static_cast<ArrayBuffer&>(first_argument.as_object()); \
+ initialize_typed_array_from_array_buffer(global_object(), *typed_array, array_buffer, vm.argument(1), vm.argument(2)); \
+ if (vm.exception()) \
+ return {}; \
+ } else { \
+ /* FIXME: Initialize from Iterator or Array-like object */ \
+ TODO(); \
+ } \
+ return typed_array; \
+ } \
+ \
+ auto array_length = first_argument.to_index(global_object()); \
+ if (vm.exception()) { \
+ /* Re-throw more specific RangeError */ \
+ vm.clear_exception(); \
+ vm.throw_exception<RangeError>(global_object(), ErrorType::InvalidLength, "typed array"); \
+ return {}; \
+ } \
+ return ClassName::create(global_object(), array_length); \
+ }
+
+#undef __JS_ENUMERATE
+#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
+ JS_DEFINE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType);
+JS_ENUMERATE_TYPED_ARRAYS
+#undef __JS_ENUMERATE
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/TypedArray.h b/Userland/Libraries/LibJS/Runtime/TypedArray.h
new file mode 100644
index 0000000000..1a10a1a93f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/TypedArray.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/ArrayBuffer.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/TypedArrayConstructor.h>
+#include <LibJS/Runtime/VM.h>
+
+namespace JS {
+
+class TypedArrayBase : public Object {
+ JS_OBJECT(TypedArrayBase, Object);
+
+public:
+ u32 array_length() const { return m_array_length; }
+ u32 byte_length() const { return m_byte_length; }
+ u32 byte_offset() const { return m_byte_offset; }
+ ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; }
+
+ void set_array_length(u32 length) { m_array_length = length; }
+ void set_byte_length(u32 length) { m_byte_length = length; }
+ void set_byte_offset(u32 offset) { m_byte_offset = offset; }
+ void set_viewed_array_buffer(ArrayBuffer* array_buffer) { m_viewed_array_buffer = array_buffer; }
+
+ virtual size_t element_size() const = 0;
+
+protected:
+ TypedArrayBase(Object& prototype)
+ : Object(prototype)
+ {
+ }
+
+ u32 m_array_length { 0 };
+ u32 m_byte_length { 0 };
+ u32 m_byte_offset { 0 };
+ ArrayBuffer* m_viewed_array_buffer { nullptr };
+
+private:
+ virtual void visit_edges(Visitor&) override;
+};
+
+template<typename T>
+class TypedArray : public TypedArrayBase {
+ JS_OBJECT(TypedArray, TypedArrayBase);
+
+public:
+ virtual bool put_by_index(u32 property_index, Value value) override
+ {
+ property_index += m_byte_offset / sizeof(T);
+ if (property_index >= m_array_length)
+ return Base::put_by_index(property_index, value);
+
+ if constexpr (sizeof(T) < 4) {
+ auto number = value.to_i32(global_object());
+ if (vm().exception())
+ return {};
+ data()[property_index] = number;
+ } else if constexpr (sizeof(T) == 4 || sizeof(T) == 8) {
+ auto number = value.to_double(global_object());
+ if (vm().exception())
+ return {};
+ data()[property_index] = number;
+ } else {
+ static_assert(DependentFalse<T>, "TypedArray::put_by_index with unhandled type size");
+ }
+ return true;
+ }
+
+ virtual Value get_by_index(u32 property_index) const override
+ {
+ property_index += m_byte_offset / sizeof(T);
+ if (property_index >= m_array_length)
+ return Base::get_by_index(property_index);
+
+ if constexpr (sizeof(T) < 4) {
+ return Value((i32)data()[property_index]);
+ } else if constexpr (sizeof(T) == 4 || sizeof(T) == 8) {
+ auto value = data()[property_index];
+ if constexpr (IsFloatingPoint<T>::value) {
+ return Value((double)value);
+ } else if constexpr (NumericLimits<T>::is_signed()) {
+ if (value > NumericLimits<i32>::max() || value < NumericLimits<i32>::min())
+ return Value((double)value);
+ } else {
+ if (value > NumericLimits<i32>::max())
+ return Value((double)value);
+ }
+ return Value((i32)value);
+ } else {
+ static_assert(DependentFalse<T>, "TypedArray::get_by_index with unhandled type size");
+ }
+ }
+
+ T* data() const { return reinterpret_cast<T*>(m_viewed_array_buffer->buffer().data()); }
+
+ virtual size_t element_size() const override { return sizeof(T); };
+
+protected:
+ TypedArray(ArrayBuffer& array_buffer, u32 array_length, Object& prototype)
+ : TypedArrayBase(prototype)
+ {
+ m_viewed_array_buffer = &array_buffer;
+ m_array_length = array_length;
+ m_byte_length = m_viewed_array_buffer->byte_length();
+ }
+
+ TypedArray(u32 array_length, Object& prototype)
+ : TypedArrayBase(prototype)
+ {
+ m_viewed_array_buffer = ArrayBuffer::create(global_object(), array_length * sizeof(T));
+ m_array_length = array_length;
+ m_byte_length = m_viewed_array_buffer->byte_length();
+ }
+
+private:
+ virtual bool is_typed_array() const final { return true; }
+};
+
+#define JS_DECLARE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, Type) \
+ class ClassName : public TypedArray<Type> { \
+ JS_OBJECT(ClassName, TypedArray); \
+ \
+ public: \
+ virtual ~ClassName(); \
+ static ClassName* create(GlobalObject&, u32 length); \
+ ClassName(u32 length, Object& prototype); \
+ }; \
+ class PrototypeName final : public Object { \
+ JS_OBJECT(PrototypeName, Object); \
+ \
+ public: \
+ PrototypeName(GlobalObject&); \
+ virtual ~PrototypeName() override; \
+ }; \
+ class ConstructorName final : public TypedArrayConstructor { \
+ JS_OBJECT(ConstructorName, TypedArrayConstructor); \
+ \
+ public: \
+ explicit ConstructorName(GlobalObject&); \
+ virtual void initialize(GlobalObject&) override; \
+ virtual ~ConstructorName() override; \
+ \
+ virtual Value call() override; \
+ virtual Value construct(Function& new_target) override; \
+ \
+ private: \
+ virtual bool has_constructor() const override { return true; } \
+ };
+
+#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, Type) \
+ JS_DECLARE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, Type);
+JS_ENUMERATE_TYPED_ARRAYS
+#undef __JS_ENUMERATE
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/TypedArrayConstructor.cpp b/Userland/Libraries/LibJS/Runtime/TypedArrayConstructor.cpp
new file mode 100644
index 0000000000..d7d57cb50b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/TypedArrayConstructor.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/TypedArrayConstructor.h>
+
+namespace JS {
+
+TypedArrayConstructor::TypedArrayConstructor(const FlyString& name, Object& prototype)
+ : NativeFunction(name, prototype)
+{
+}
+
+TypedArrayConstructor::TypedArrayConstructor(GlobalObject& global_object)
+ : NativeFunction(vm().names.TypedArray, *global_object.function_prototype())
+{
+}
+
+void TypedArrayConstructor::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+ define_property(vm.names.prototype, global_object.typed_array_prototype(), 0);
+ define_property(vm.names.length, Value(0), Attribute::Configurable);
+}
+
+TypedArrayConstructor::~TypedArrayConstructor()
+{
+}
+
+Value TypedArrayConstructor::call()
+{
+ return construct(*this);
+}
+
+Value TypedArrayConstructor::construct(Function&)
+{
+ vm().throw_exception<TypeError>(global_object(), ErrorType::ClassIsAbstract, "TypedArray");
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/TypedArrayConstructor.h b/Userland/Libraries/LibJS/Runtime/TypedArrayConstructor.h
new file mode 100644
index 0000000000..b99a957ae4
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/TypedArrayConstructor.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class TypedArrayConstructor : public NativeFunction {
+ JS_OBJECT(TypedArrayConstructor, NativeFunction);
+
+public:
+ TypedArrayConstructor(const FlyString& name, Object& prototype);
+ explicit TypedArrayConstructor(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~TypedArrayConstructor() override;
+
+ virtual Value call() override;
+ virtual Value construct(Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp
new file mode 100644
index 0000000000..da0c43faf1
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/TypedArray.h>
+#include <LibJS/Runtime/TypedArrayPrototype.h>
+
+namespace JS {
+
+TypedArrayPrototype::TypedArrayPrototype(GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void TypedArrayPrototype::initialize(GlobalObject& object)
+{
+ auto& vm = this->vm();
+ Object::initialize(object);
+ // FIXME: This should be an accessor property
+ define_native_property(vm.names.length, length_getter, {}, Attribute::Configurable);
+}
+
+TypedArrayPrototype::~TypedArrayPrototype()
+{
+}
+
+static TypedArrayBase* typed_array_from(VM& vm, GlobalObject& global_object)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return nullptr;
+ if (!this_object->is_typed_array()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "TypedArray");
+ return nullptr;
+ }
+ return static_cast<TypedArrayBase*>(this_object);
+}
+
+JS_DEFINE_NATIVE_GETTER(TypedArrayPrototype::length_getter)
+{
+ auto typed_array = typed_array_from(vm, global_object);
+ if (!typed_array)
+ return {};
+ return Value(typed_array->array_length());
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h
new file mode 100644
index 0000000000..8c39f31c31
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class TypedArrayPrototype final : public Object {
+ JS_OBJECT(TypedArrayPrototype, Object);
+
+public:
+ explicit TypedArrayPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~TypedArrayPrototype() override;
+
+private:
+ JS_DECLARE_NATIVE_GETTER(length_getter);
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Uint8ClampedArray.cpp b/Userland/Libraries/LibJS/Runtime/Uint8ClampedArray.cpp
new file mode 100644
index 0000000000..c558f41119
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Uint8ClampedArray.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Uint8ClampedArray.h>
+#include <string.h>
+
+namespace JS {
+
+Uint8ClampedArray* Uint8ClampedArray::create(GlobalObject& global_object, u32 length)
+{
+ return global_object.heap().allocate<Uint8ClampedArray>(global_object, length, *global_object.array_prototype());
+}
+
+Uint8ClampedArray::Uint8ClampedArray(u32 length, Object& prototype)
+ : Object(prototype)
+ , m_length(length)
+{
+ auto& vm = this->vm();
+ define_native_property(vm.names.length, length_getter, {});
+ m_data = (u8*)calloc(m_length, 1);
+}
+
+Uint8ClampedArray::~Uint8ClampedArray()
+{
+ ASSERT(m_data);
+ free(m_data);
+ m_data = nullptr;
+}
+
+JS_DEFINE_NATIVE_GETTER(Uint8ClampedArray::length_getter)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ if (StringView(this_object->class_name()) != "Uint8ClampedArray") {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Uint8ClampedArray");
+ return {};
+ }
+ return Value(static_cast<const Uint8ClampedArray*>(this_object)->length());
+}
+
+bool Uint8ClampedArray::put_by_index(u32 property_index, Value value)
+{
+ if (property_index >= m_length)
+ return Base::put_by_index(property_index, value);
+ auto number = value.to_i32(global_object());
+ if (vm().exception())
+ return {};
+ m_data[property_index] = clamp(number, 0, 255);
+ return true;
+}
+
+Value Uint8ClampedArray::get_by_index(u32 property_index) const
+{
+ if (property_index >= m_length)
+ return Base::get_by_index(property_index);
+ return Value((i32)m_data[property_index]);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Uint8ClampedArray.h b/Userland/Libraries/LibJS/Runtime/Uint8ClampedArray.h
new file mode 100644
index 0000000000..056d490eab
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Uint8ClampedArray.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class Uint8ClampedArray final : public Object {
+ JS_OBJECT(Uint8ClampedArray, Object);
+
+public:
+ static Uint8ClampedArray* create(GlobalObject&, u32 length);
+
+ Uint8ClampedArray(u32 length, Object& prototype);
+ virtual ~Uint8ClampedArray() override;
+
+ i32 length() const { return m_length; }
+
+ virtual bool put_by_index(u32 property_index, Value value) override;
+ virtual Value get_by_index(u32 property_index) const override;
+
+ u8* data() { return m_data; }
+ const u8* data() const { return m_data; }
+
+private:
+ JS_DECLARE_NATIVE_GETTER(length_getter);
+
+ u8* m_data { nullptr };
+ u32 m_length { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp
new file mode 100644
index 0000000000..73842974c7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/VM.cpp
@@ -0,0 +1,365 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ScopeGuard.h>
+#include <AK/StringBuilder.h>
+#include <LibJS/Interpreter.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Reference.h>
+#include <LibJS/Runtime/ScriptFunction.h>
+#include <LibJS/Runtime/Symbol.h>
+#include <LibJS/Runtime/VM.h>
+
+namespace JS {
+
+NonnullRefPtr<VM> VM::create()
+{
+ return adopt(*new VM);
+}
+
+VM::VM()
+ : m_heap(*this)
+{
+ m_empty_string = m_heap.allocate_without_global_object<PrimitiveString>(String::empty());
+ for (size_t i = 0; i < 128; ++i) {
+ m_single_ascii_character_strings[i] = m_heap.allocate_without_global_object<PrimitiveString>(String::formatted("{:c}", i));
+ }
+
+ m_scope_object_shape = m_heap.allocate_without_global_object<Shape>(Shape::ShapeWithoutGlobalObjectTag::Tag);
+
+#define __JS_ENUMERATE(SymbolName, snake_name) \
+ m_well_known_symbol_##snake_name = js_symbol(*this, "Symbol." #SymbolName, false);
+ JS_ENUMERATE_WELL_KNOWN_SYMBOLS
+#undef __JS_ENUMERATE
+}
+
+VM::~VM()
+{
+}
+
+Interpreter& VM::interpreter()
+{
+ ASSERT(!m_interpreters.is_empty());
+ return *m_interpreters.last();
+}
+
+Interpreter* VM::interpreter_if_exists()
+{
+ if (m_interpreters.is_empty())
+ return nullptr;
+ return m_interpreters.last();
+}
+
+void VM::push_interpreter(Interpreter& interpreter)
+{
+ m_interpreters.append(&interpreter);
+}
+
+void VM::pop_interpreter(Interpreter& interpreter)
+{
+ ASSERT(!m_interpreters.is_empty());
+ auto* popped_interpreter = m_interpreters.take_last();
+ ASSERT(popped_interpreter == &interpreter);
+}
+
+VM::InterpreterExecutionScope::InterpreterExecutionScope(Interpreter& interpreter)
+ : m_interpreter(interpreter)
+{
+ m_interpreter.vm().push_interpreter(m_interpreter);
+}
+
+VM::InterpreterExecutionScope::~InterpreterExecutionScope()
+{
+ m_interpreter.vm().pop_interpreter(m_interpreter);
+}
+
+void VM::gather_roots(HashTable<Cell*>& roots)
+{
+ roots.set(m_empty_string);
+ for (auto* string : m_single_ascii_character_strings)
+ roots.set(string);
+
+ roots.set(m_scope_object_shape);
+ roots.set(m_exception);
+
+ if (m_last_value.is_cell())
+ roots.set(m_last_value.as_cell());
+
+ for (auto& call_frame : m_call_stack) {
+ if (call_frame->this_value.is_cell())
+ roots.set(call_frame->this_value.as_cell());
+ roots.set(call_frame->arguments_object);
+ for (auto& argument : call_frame->arguments) {
+ if (argument.is_cell())
+ roots.set(argument.as_cell());
+ }
+ roots.set(call_frame->scope);
+ }
+
+#define __JS_ENUMERATE(SymbolName, snake_name) \
+ roots.set(well_known_symbol_##snake_name());
+ JS_ENUMERATE_WELL_KNOWN_SYMBOLS
+#undef __JS_ENUMERATE
+
+ for (auto& symbol : m_global_symbol_map)
+ roots.set(symbol.value);
+}
+
+Symbol* VM::get_global_symbol(const String& description)
+{
+ auto result = m_global_symbol_map.get(description);
+ if (result.has_value())
+ return result.value();
+
+ auto new_global_symbol = js_symbol(*this, description, true);
+ m_global_symbol_map.set(description, new_global_symbol);
+ return new_global_symbol;
+}
+
+void VM::set_variable(const FlyString& name, Value value, GlobalObject& global_object, bool first_assignment)
+{
+ if (m_call_stack.size()) {
+ for (auto* scope = current_scope(); scope; scope = scope->parent()) {
+ auto possible_match = scope->get_from_scope(name);
+ if (possible_match.has_value()) {
+ if (!first_assignment && possible_match.value().declaration_kind == DeclarationKind::Const) {
+ throw_exception<TypeError>(global_object, ErrorType::InvalidAssignToConst);
+ return;
+ }
+
+ scope->put_to_scope(name, { value, possible_match.value().declaration_kind });
+ return;
+ }
+ }
+ }
+
+ global_object.put(move(name), move(value));
+}
+
+Value VM::get_variable(const FlyString& name, GlobalObject& global_object)
+{
+ if (m_call_stack.size()) {
+ if (name == names.arguments) {
+ // HACK: Special handling for the name "arguments":
+ // If the name "arguments" is defined in the current scope, for example via
+ // a function parameter, or by a local var declaration, we use that.
+ // Otherwise, we return a lazily constructed Array with all the argument values.
+ // FIXME: Do something much more spec-compliant.
+ auto possible_match = current_scope()->get_from_scope(name);
+ if (possible_match.has_value())
+ return possible_match.value().value;
+ if (!call_frame().arguments_object) {
+ call_frame().arguments_object = Array::create(global_object);
+ for (auto argument : call_frame().arguments) {
+ call_frame().arguments_object->indexed_properties().append(argument);
+ }
+ }
+ return call_frame().arguments_object;
+ }
+
+ for (auto* scope = current_scope(); scope; scope = scope->parent()) {
+ auto possible_match = scope->get_from_scope(name);
+ if (possible_match.has_value())
+ return possible_match.value().value;
+ }
+ }
+ auto value = global_object.get(name);
+ if (m_underscore_is_last_value && name == "_" && value.is_empty())
+ return m_last_value;
+ return value;
+}
+
+Reference VM::get_reference(const FlyString& name)
+{
+ if (m_call_stack.size()) {
+ for (auto* scope = current_scope(); scope; scope = scope->parent()) {
+ if (is<GlobalObject>(scope))
+ break;
+ auto possible_match = scope->get_from_scope(name);
+ if (possible_match.has_value())
+ return { Reference::LocalVariable, name };
+ }
+ }
+ return { Reference::GlobalVariable, name };
+}
+
+Value VM::construct(Function& function, Function& new_target, Optional<MarkedValueList> arguments, GlobalObject& global_object)
+{
+ CallFrame call_frame;
+ call_frame.is_strict_mode = function.is_strict_mode();
+
+ push_call_frame(call_frame, function.global_object());
+ if (exception())
+ return {};
+ ArmedScopeGuard call_frame_popper = [&] {
+ pop_call_frame();
+ };
+
+ call_frame.function_name = function.name();
+ call_frame.arguments = function.bound_arguments();
+ if (arguments.has_value())
+ call_frame.arguments.append(arguments.value().values());
+ auto* environment = function.create_environment();
+ call_frame.scope = environment;
+ environment->set_new_target(&new_target);
+
+ Object* new_object = nullptr;
+ if (function.constructor_kind() == Function::ConstructorKind::Base) {
+ new_object = Object::create_empty(global_object);
+ environment->bind_this_value(global_object, new_object);
+ if (exception())
+ return {};
+ auto prototype = new_target.get(names.prototype);
+ if (exception())
+ return {};
+ if (prototype.is_object()) {
+ new_object->set_prototype(&prototype.as_object());
+ if (exception())
+ return {};
+ }
+ }
+
+ // If we are a Derived constructor, |this| has not been constructed before super is called.
+ Value this_value = function.constructor_kind() == Function::ConstructorKind::Base ? new_object : Value {};
+ call_frame.this_value = this_value;
+ auto result = function.construct(new_target);
+
+ this_value = call_frame.scope->get_this_binding(global_object);
+ pop_call_frame();
+ call_frame_popper.disarm();
+
+ // If we are constructing an instance of a derived class,
+ // set the prototype on objects created by constructors that return an object (i.e. NativeFunction subclasses).
+ if (function.constructor_kind() == Function::ConstructorKind::Base && new_target.constructor_kind() == Function::ConstructorKind::Derived && result.is_object()) {
+ ASSERT(is<LexicalEnvironment>(current_scope()));
+ static_cast<LexicalEnvironment*>(current_scope())->replace_this_binding(result);
+ auto prototype = new_target.get(names.prototype);
+ if (exception())
+ return {};
+ if (prototype.is_object()) {
+ result.as_object().set_prototype(&prototype.as_object());
+ if (exception())
+ return {};
+ }
+ return result;
+ }
+
+ if (exception())
+ return {};
+
+ if (result.is_object())
+ return result;
+
+ return this_value;
+}
+
+void VM::throw_exception(Exception* exception)
+{
+ if (should_log_exceptions() && exception->value().is_object() && is<Error>(exception->value().as_object())) {
+ auto& error = static_cast<Error&>(exception->value().as_object());
+ dbgln("Throwing JavaScript Error: {}, {}", error.name(), error.message());
+
+ for (ssize_t i = m_call_stack.size() - 1; i >= 0; --i) {
+ auto function_name = m_call_stack[i]->function_name;
+ if (function_name.is_empty())
+ function_name = "<anonymous>";
+ dbgln(" {}", function_name);
+ }
+ }
+
+ m_exception = exception;
+ unwind(ScopeType::Try);
+}
+
+String VM::join_arguments() const
+{
+ StringBuilder joined_arguments;
+ for (size_t i = 0; i < argument_count(); ++i) {
+ joined_arguments.append(argument(i).to_string_without_side_effects().characters());
+ if (i != argument_count() - 1)
+ joined_arguments.append(' ');
+ }
+ return joined_arguments.build();
+}
+
+Value VM::resolve_this_binding(GlobalObject& global_object) const
+{
+ return find_this_scope()->get_this_binding(global_object);
+}
+
+const ScopeObject* VM::find_this_scope() const
+{
+ // We will always return because the Global environment will always be reached, which has a |this| binding.
+ for (auto* scope = current_scope(); scope; scope = scope->parent()) {
+ if (scope->has_this_binding())
+ return scope;
+ }
+ ASSERT_NOT_REACHED();
+}
+
+Value VM::get_new_target() const
+{
+ ASSERT(is<LexicalEnvironment>(find_this_scope()));
+ return static_cast<const LexicalEnvironment*>(find_this_scope())->new_target();
+}
+
+Value VM::call_internal(Function& function, Value this_value, Optional<MarkedValueList> arguments)
+{
+ ASSERT(!exception());
+
+ CallFrame call_frame;
+ call_frame.is_strict_mode = function.is_strict_mode();
+ call_frame.function_name = function.name();
+ call_frame.this_value = function.bound_this().value_or(this_value);
+ call_frame.arguments = function.bound_arguments();
+ if (arguments.has_value())
+ call_frame.arguments.append(move(arguments.release_value().values()));
+ auto* environment = function.create_environment();
+ call_frame.scope = environment;
+
+ ASSERT(environment->this_binding_status() == LexicalEnvironment::ThisBindingStatus::Uninitialized);
+ environment->bind_this_value(function.global_object(), call_frame.this_value);
+ if (exception())
+ return {};
+
+ push_call_frame(call_frame, function.global_object());
+ if (exception())
+ return {};
+ auto result = function.call();
+ pop_call_frame();
+ return result;
+}
+
+bool VM::in_strict_mode() const
+{
+ if (call_stack().is_empty())
+ return false;
+ return call_frame().is_strict_mode;
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/VM.h b/Userland/Libraries/LibJS/Runtime/VM.h
new file mode 100644
index 0000000000..320ea53405
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/VM.h
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <AK/HashMap.h>
+#include <AK/RefCounted.h>
+#include <AK/StackInfo.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/CommonPropertyNames.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/ErrorTypes.h>
+#include <LibJS/Runtime/Exception.h>
+#include <LibJS/Runtime/MarkedValueList.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+enum class ScopeType {
+ None,
+ Function,
+ Block,
+ Try,
+ Breakable,
+ Continuable,
+};
+
+struct ScopeFrame {
+ ScopeType type;
+ NonnullRefPtr<ScopeNode> scope_node;
+ bool pushed_environment { false };
+};
+
+struct CallFrame {
+ FlyString function_name;
+ Value this_value;
+ Vector<Value> arguments;
+ Array* arguments_object { nullptr };
+ ScopeObject* scope { nullptr };
+ bool is_strict_mode { false };
+};
+
+class VM : public RefCounted<VM> {
+public:
+ static NonnullRefPtr<VM> create();
+ ~VM();
+
+ bool should_log_exceptions() const { return m_should_log_exceptions; }
+ void set_should_log_exceptions(bool b) { m_should_log_exceptions = b; }
+
+ Heap& heap() { return m_heap; }
+ const Heap& heap() const { return m_heap; }
+
+ Interpreter& interpreter();
+ Interpreter* interpreter_if_exists();
+
+ void push_interpreter(Interpreter&);
+ void pop_interpreter(Interpreter&);
+
+ Exception* exception()
+ {
+ return m_exception;
+ }
+
+ void clear_exception() { m_exception = nullptr; }
+
+ class InterpreterExecutionScope {
+ public:
+ InterpreterExecutionScope(Interpreter&);
+ ~InterpreterExecutionScope();
+
+ private:
+ Interpreter& m_interpreter;
+ };
+
+ void gather_roots(HashTable<Cell*>&);
+
+#define __JS_ENUMERATE(SymbolName, snake_name) \
+ Symbol* well_known_symbol_##snake_name() const { return m_well_known_symbol_##snake_name; }
+ JS_ENUMERATE_WELL_KNOWN_SYMBOLS
+#undef __JS_ENUMERATE
+
+ Symbol* get_global_symbol(const String& description);
+
+ PrimitiveString& empty_string() { return *m_empty_string; }
+ PrimitiveString& single_ascii_character_string(u8 character)
+ {
+ ASSERT(character < 0x80);
+ return *m_single_ascii_character_strings[character];
+ }
+
+ void push_call_frame(CallFrame& call_frame, GlobalObject& global_object)
+ {
+ ASSERT(!exception());
+ // Ensure we got some stack space left, so the next function call doesn't kill us.
+ // This value is merely a guess and might need tweaking at a later point.
+ if (m_stack_info.size_free() < 16 * KiB)
+ throw_exception<Error>(global_object, "RuntimeError", "Call stack size limit exceeded");
+ else
+ m_call_stack.append(&call_frame);
+ }
+
+ void pop_call_frame() { m_call_stack.take_last(); }
+
+ void push_ast_node(const ASTNode& node) { m_ast_nodes.append(&node); }
+ void pop_ast_node() { m_ast_nodes.take_last(); }
+
+ CallFrame& call_frame() { return *m_call_stack.last(); }
+ const CallFrame& call_frame() const { return *m_call_stack.last(); }
+ const Vector<CallFrame*>& call_stack() const { return m_call_stack; }
+ Vector<CallFrame*>& call_stack() { return m_call_stack; }
+ const Vector<const ASTNode*>& node_stack() const { return m_ast_nodes; }
+
+ const ScopeObject* current_scope() const { return call_frame().scope; }
+ ScopeObject* current_scope() { return call_frame().scope; }
+
+ bool in_strict_mode() const;
+
+ template<typename Callback>
+ void for_each_argument(Callback callback)
+ {
+ if (m_call_stack.is_empty())
+ return;
+ for (auto& value : call_frame().arguments)
+ callback(value);
+ }
+
+ size_t argument_count() const
+ {
+ if (m_call_stack.is_empty())
+ return 0;
+ return call_frame().arguments.size();
+ }
+
+ Value argument(size_t index) const
+ {
+ if (m_call_stack.is_empty())
+ return {};
+ auto& arguments = call_frame().arguments;
+ return index < arguments.size() ? arguments[index] : js_undefined();
+ }
+
+ Value this_value(Object& global_object) const
+ {
+ if (m_call_stack.is_empty())
+ return &global_object;
+ return call_frame().this_value;
+ }
+
+ Value last_value() const { return m_last_value; }
+ void set_last_value(Badge<Interpreter>, Value value) { m_last_value = value; }
+
+ const StackInfo& stack_info() const { return m_stack_info; };
+
+ bool underscore_is_last_value() const { return m_underscore_is_last_value; }
+ void set_underscore_is_last_value(bool b) { m_underscore_is_last_value = b; }
+
+ void unwind(ScopeType type, FlyString label = {})
+ {
+ m_unwind_until = type;
+ m_unwind_until_label = label;
+ }
+ void stop_unwind() { m_unwind_until = ScopeType::None; }
+ bool should_unwind_until(ScopeType type, FlyString label = {}) const
+ {
+ if (m_unwind_until_label.is_null())
+ return m_unwind_until == type;
+ return m_unwind_until == type && m_unwind_until_label == label;
+ }
+ bool should_unwind() const { return m_unwind_until != ScopeType::None; }
+
+ ScopeType unwind_until() const { return m_unwind_until; }
+
+ Value get_variable(const FlyString& name, GlobalObject&);
+ void set_variable(const FlyString& name, Value, GlobalObject&, bool first_assignment = false);
+
+ Reference get_reference(const FlyString& name);
+
+ template<typename T, typename... Args>
+ void throw_exception(GlobalObject& global_object, Args&&... args)
+ {
+ return throw_exception(global_object, T::create(global_object, forward<Args>(args)...));
+ }
+
+ void throw_exception(Exception*);
+ void throw_exception(GlobalObject& global_object, Value value)
+ {
+ return throw_exception(heap().allocate<Exception>(global_object, value));
+ }
+
+ template<typename T, typename... Args>
+ void throw_exception(GlobalObject& global_object, ErrorType type, Args&&... args)
+ {
+ return throw_exception(global_object, T::create(global_object, String::formatted(type.message(), forward<Args>(args)...)));
+ }
+
+ Value construct(Function&, Function& new_target, Optional<MarkedValueList> arguments, GlobalObject&);
+
+ String join_arguments() const;
+
+ Value resolve_this_binding(GlobalObject&) const;
+ const ScopeObject* find_this_scope() const;
+ Value get_new_target() const;
+
+ template<typename... Args>
+ [[nodiscard]] ALWAYS_INLINE Value call(Function& function, Value this_value, Args... args)
+ {
+ if constexpr (sizeof...(Args) > 0) {
+ MarkedValueList arglist { heap() };
+ (..., arglist.append(move(args)));
+ return call(function, this_value, move(arglist));
+ }
+
+ return call(function, this_value);
+ }
+
+ CommonPropertyNames names;
+
+ Shape& scope_object_shape() { return *m_scope_object_shape; }
+
+private:
+ VM();
+
+ [[nodiscard]] Value call_internal(Function&, Value this_value, Optional<MarkedValueList> arguments);
+
+ Exception* m_exception { nullptr };
+
+ Heap m_heap;
+ Vector<Interpreter*> m_interpreters;
+
+ Vector<CallFrame*> m_call_stack;
+ Vector<const ASTNode*> m_ast_nodes;
+
+ Value m_last_value;
+ ScopeType m_unwind_until { ScopeType::None };
+ FlyString m_unwind_until_label;
+
+ StackInfo m_stack_info;
+
+ bool m_underscore_is_last_value { false };
+
+ HashMap<String, Symbol*> m_global_symbol_map;
+
+ PrimitiveString* m_empty_string { nullptr };
+ PrimitiveString* m_single_ascii_character_strings[128] {};
+
+#define __JS_ENUMERATE(SymbolName, snake_name) \
+ Symbol* m_well_known_symbol_##snake_name { nullptr };
+ JS_ENUMERATE_WELL_KNOWN_SYMBOLS
+#undef __JS_ENUMERATE
+
+ Shape* m_scope_object_shape { nullptr };
+
+ bool m_should_log_exceptions { false };
+};
+
+template<>
+[[nodiscard]] ALWAYS_INLINE Value VM::call(Function& function, Value this_value, MarkedValueList arguments) { return call_internal(function, this_value, move(arguments)); }
+
+template<>
+[[nodiscard]] ALWAYS_INLINE Value VM::call(Function& function, Value this_value, Optional<MarkedValueList> arguments) { return call_internal(function, this_value, move(arguments)); }
+
+template<>
+[[nodiscard]] ALWAYS_INLINE Value VM::call(Function& function, Value this_value) { return call(function, this_value, Optional<MarkedValueList> {}); }
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Value.cpp b/Userland/Libraries/LibJS/Runtime/Value.cpp
new file mode 100644
index 0000000000..1d397c9536
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Value.cpp
@@ -0,0 +1,1221 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/FlyString.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/Utf8View.h>
+#include <LibCrypto/BigInt/SignedBigInteger.h>
+#include <LibCrypto/NumberTheory/ModularFunctions.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/Accessor.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/BigInt.h>
+#include <LibJS/Runtime/BigIntObject.h>
+#include <LibJS/Runtime/BooleanObject.h>
+#include <LibJS/Runtime/BoundFunction.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/Function.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/NumberObject.h>
+#include <LibJS/Runtime/Object.h>
+#include <LibJS/Runtime/PrimitiveString.h>
+#include <LibJS/Runtime/RegExpObject.h>
+#include <LibJS/Runtime/StringObject.h>
+#include <LibJS/Runtime/Symbol.h>
+#include <LibJS/Runtime/SymbolObject.h>
+#include <LibJS/Runtime/Value.h>
+#include <ctype.h>
+#include <math.h>
+
+namespace JS {
+
+// Used in various abstract operations to make it obvious when a non-optional return value must be discarded.
+static const double INVALID { 0 };
+
+static const Crypto::SignedBigInteger BIGINT_ZERO { 0 };
+
+static bool is_valid_bigint_value(String string)
+{
+ string = string.trim_whitespace();
+ if (string.length() > 1 && (string[0] == '-' || string[0] == '+'))
+ string = string.substring_view(1, string.length() - 1);
+ for (auto& ch : string) {
+ if (!isdigit(ch))
+ return false;
+ }
+ return true;
+}
+
+ALWAYS_INLINE bool both_number(const Value& lhs, const Value& rhs)
+{
+ return lhs.is_number() && rhs.is_number();
+}
+
+ALWAYS_INLINE bool both_bigint(const Value& lhs, const Value& rhs)
+{
+ return lhs.is_bigint() && rhs.is_bigint();
+}
+
+static String double_to_string(double d)
+{
+ // https://tc39.es/ecma262/#sec-numeric-types-number-tostring
+ if (isnan(d))
+ return "NaN";
+ if (d == +0.0 || d == -0.0)
+ return "0";
+ if (d < +0.0) {
+ StringBuilder builder;
+ builder.append('-');
+ builder.append(double_to_string(-d));
+ return builder.to_string();
+ }
+ if (d == INFINITY)
+ return "Infinity";
+
+ StringBuilder number_string_builder;
+
+ size_t start_index = 0;
+ size_t end_index = 0;
+ size_t intpart_end = 0;
+
+ // generate integer part (reversed)
+ double intPart;
+ double frac_part;
+ frac_part = modf(d, &intPart);
+ while (intPart > 0) {
+ number_string_builder.append('0' + (int)fmod(intPart, 10));
+ end_index++;
+ intPart = floor(intPart / 10);
+ }
+
+ auto reversed_integer_part = number_string_builder.to_string().reverse();
+ number_string_builder.clear();
+ number_string_builder.append(reversed_integer_part);
+
+ intpart_end = end_index;
+
+ int exponent = 0;
+
+ // generate fractional part
+ while (frac_part > 0) {
+ double old_frac_part = frac_part;
+ frac_part *= 10;
+ frac_part = modf(frac_part, &intPart);
+ if (old_frac_part == frac_part)
+ break;
+ number_string_builder.append('0' + (int)intPart);
+ end_index++;
+ exponent--;
+ }
+
+ auto number_string = number_string_builder.to_string();
+
+ // FIXME: Remove this hack.
+ // FIXME: Instead find the shortest round-trippable representation.
+ // Remove decimals after the 15th position
+ if (end_index > intpart_end + 15) {
+ exponent += end_index - intpart_end - 15;
+ end_index = intpart_end + 15;
+ }
+
+ // remove leading zeroes
+ while (start_index < end_index && number_string[start_index] == '0') {
+ start_index++;
+ }
+
+ // remove trailing zeroes
+ while (end_index > 0 && number_string[end_index - 1] == '0') {
+ end_index--;
+ exponent++;
+ }
+
+ if (end_index <= start_index)
+ return "0";
+
+ auto digits = number_string.substring_view(start_index, end_index - start_index);
+
+ int number_of_digits = end_index - start_index;
+
+ exponent += number_of_digits;
+
+ StringBuilder builder;
+
+ if (number_of_digits <= exponent && exponent <= 21) {
+ builder.append(digits);
+ builder.append(String::repeated('0', exponent - number_of_digits));
+ return builder.to_string();
+ }
+ if (0 < exponent && exponent <= 21) {
+ builder.append(digits.substring_view(0, exponent));
+ builder.append('.');
+ builder.append(digits.substring_view(exponent));
+ return builder.to_string();
+ }
+ if (-6 < exponent && exponent <= 0) {
+ builder.append("0.");
+ builder.append(String::repeated('0', -exponent));
+ builder.append(digits);
+ return builder.to_string();
+ }
+ if (number_of_digits == 1) {
+ builder.append(digits);
+ builder.append('e');
+
+ if (exponent - 1 > 0)
+ builder.append('+');
+ else
+ builder.append('-');
+
+ builder.append(String::format("%d", abs(exponent - 1)));
+ return builder.to_string();
+ }
+
+ builder.append(digits[0]);
+ builder.append('.');
+ builder.append(digits.substring_view(1));
+ builder.append('e');
+
+ if (exponent - 1 > 0)
+ builder.append('+');
+ else
+ builder.append('-');
+
+ builder.append(String::format("%d", abs(exponent - 1)));
+ return builder.to_string();
+}
+
+bool Value::is_array() const
+{
+ return is_object() && as_object().is_array();
+}
+
+Array& Value::as_array()
+{
+ ASSERT(is_array());
+ return static_cast<Array&>(*m_value.as_object);
+}
+
+bool Value::is_function() const
+{
+ return is_object() && as_object().is_function();
+}
+
+Function& Value::as_function()
+{
+ ASSERT(is_function());
+ return static_cast<Function&>(as_object());
+}
+
+bool Value::is_regexp(GlobalObject& global_object) const
+{
+ // 7.2.8 IsRegExp, https://tc39.es/ecma262/#sec-isregexp
+
+ if (!is_object())
+ return false;
+
+ auto matcher = as_object().get(global_object.vm().well_known_symbol_match());
+ if (global_object.vm().exception())
+ return false;
+ if (!matcher.is_empty() && !matcher.is_undefined())
+ return matcher.to_boolean();
+
+ return is<RegExpObject>(as_object());
+}
+
+String Value::to_string_without_side_effects() const
+{
+ switch (m_type) {
+ case Type::Undefined:
+ return "undefined";
+ case Type::Null:
+ return "null";
+ case Type::Boolean:
+ return m_value.as_bool ? "true" : "false";
+ case Type::Number:
+ return double_to_string(m_value.as_double);
+ case Type::String:
+ return m_value.as_string->string();
+ case Type::Symbol:
+ return m_value.as_symbol->to_string();
+ case Type::BigInt:
+ return m_value.as_bigint->to_string();
+ case Type::Object:
+ return String::formatted("[object {}]", as_object().class_name());
+ case Type::Accessor:
+ return "<accessor>";
+ case Type::NativeProperty:
+ return "<native-property>";
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+PrimitiveString* Value::to_primitive_string(GlobalObject& global_object)
+{
+ if (is_string())
+ return &as_string();
+ auto string = to_string(global_object);
+ if (global_object.vm().exception())
+ return nullptr;
+ return js_string(global_object.heap(), string);
+}
+
+String Value::to_string(GlobalObject& global_object, bool legacy_null_to_empty_string) const
+{
+ switch (m_type) {
+ case Type::Undefined:
+ return "undefined";
+ case Type::Null:
+ return !legacy_null_to_empty_string ? "null" : String::empty();
+ case Type::Boolean:
+ return m_value.as_bool ? "true" : "false";
+ case Type::Number:
+ return double_to_string(m_value.as_double);
+ case Type::String:
+ return m_value.as_string->string();
+ case Type::Symbol:
+ global_object.vm().throw_exception<TypeError>(global_object, ErrorType::Convert, "symbol", "string");
+ return {};
+ case Type::BigInt:
+ return m_value.as_bigint->big_integer().to_base10();
+ case Type::Object: {
+ auto primitive_value = to_primitive(PreferredType::String);
+ if (global_object.vm().exception())
+ return {};
+ return primitive_value.to_string(global_object);
+ }
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+bool Value::to_boolean() const
+{
+ switch (m_type) {
+ case Type::Undefined:
+ case Type::Null:
+ return false;
+ case Type::Boolean:
+ return m_value.as_bool;
+ case Type::Number:
+ if (is_nan())
+ return false;
+ return m_value.as_double != 0;
+ case Type::String:
+ return !m_value.as_string->string().is_empty();
+ case Type::Symbol:
+ return true;
+ case Type::BigInt:
+ return m_value.as_bigint->big_integer() != BIGINT_ZERO;
+ case Type::Object:
+ return true;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+Value Value::to_primitive(PreferredType preferred_type) const
+{
+ if (is_object()) {
+ // FIXME: Also support @@toPrimitive
+ if (preferred_type == PreferredType::Default)
+ preferred_type = PreferredType::Number;
+ return as_object().ordinary_to_primitive(preferred_type);
+ }
+ return *this;
+}
+
+Object* Value::to_object(GlobalObject& global_object) const
+{
+ switch (m_type) {
+ case Type::Undefined:
+ case Type::Null:
+ global_object.vm().throw_exception<TypeError>(global_object, ErrorType::ToObjectNullOrUndef);
+ return nullptr;
+ case Type::Boolean:
+ return BooleanObject::create(global_object, m_value.as_bool);
+ case Type::Number:
+ return NumberObject::create(global_object, m_value.as_double);
+ case Type::String:
+ return StringObject::create(global_object, *m_value.as_string);
+ case Type::Symbol:
+ return SymbolObject::create(global_object, *m_value.as_symbol);
+ case Type::BigInt:
+ return BigIntObject::create(global_object, *m_value.as_bigint);
+ case Type::Object:
+ return &const_cast<Object&>(as_object());
+ default:
+ dbgln("Dying because I can't to_object() on {}", *this);
+ ASSERT_NOT_REACHED();
+ }
+}
+
+Value Value::to_numeric(GlobalObject& global_object) const
+{
+ auto primitive = to_primitive(Value::PreferredType::Number);
+ if (global_object.vm().exception())
+ return {};
+ if (primitive.is_bigint())
+ return primitive;
+ return primitive.to_number(global_object);
+}
+
+Value Value::to_number(GlobalObject& global_object) const
+{
+ switch (m_type) {
+ case Type::Undefined:
+ return js_nan();
+ case Type::Null:
+ return Value(0);
+ case Type::Boolean:
+ return Value(m_value.as_bool ? 1 : 0);
+ case Type::Number:
+ return Value(m_value.as_double);
+ case Type::String: {
+ auto string = as_string().string().trim_whitespace();
+ if (string.is_empty())
+ return Value(0);
+ if (string == "Infinity" || string == "+Infinity")
+ return js_infinity();
+ if (string == "-Infinity")
+ return js_negative_infinity();
+ char* endptr;
+ auto parsed_double = strtod(string.characters(), &endptr);
+ if (*endptr)
+ return js_nan();
+ return Value(parsed_double);
+ }
+ case Type::Symbol:
+ global_object.vm().throw_exception<TypeError>(global_object, ErrorType::Convert, "symbol", "number");
+ return {};
+ case Type::BigInt:
+ global_object.vm().throw_exception<TypeError>(global_object, ErrorType::Convert, "BigInt", "number");
+ return {};
+ case Type::Object: {
+ auto primitive = to_primitive(PreferredType::Number);
+ if (global_object.vm().exception())
+ return {};
+ return primitive.to_number(global_object);
+ }
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+BigInt* Value::to_bigint(GlobalObject& global_object) const
+{
+ auto& vm = global_object.vm();
+ auto primitive = to_primitive(PreferredType::Number);
+ if (vm.exception())
+ return nullptr;
+ switch (primitive.type()) {
+ case Type::Undefined:
+ vm.throw_exception<TypeError>(global_object, ErrorType::Convert, "undefined", "BigInt");
+ return nullptr;
+ case Type::Null:
+ vm.throw_exception<TypeError>(global_object, ErrorType::Convert, "null", "BigInt");
+ return nullptr;
+ case Type::Boolean: {
+ auto value = primitive.as_bool() ? 1 : 0;
+ return js_bigint(vm.heap(), Crypto::SignedBigInteger { value });
+ }
+ case Type::BigInt:
+ return &primitive.as_bigint();
+ case Type::Number:
+ vm.throw_exception<TypeError>(global_object, ErrorType::Convert, "number", "BigInt");
+ return {};
+ case Type::String: {
+ auto& string = primitive.as_string().string();
+ if (!is_valid_bigint_value(string)) {
+ vm.throw_exception<SyntaxError>(global_object, ErrorType::BigIntInvalidValue, string);
+ return {};
+ }
+ return js_bigint(vm.heap(), Crypto::SignedBigInteger::from_base10(string.trim_whitespace()));
+ }
+ case Type::Symbol:
+ vm.throw_exception<TypeError>(global_object, ErrorType::Convert, "symbol", "BigInt");
+ return {};
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+i32 Value::as_i32() const
+{
+ return static_cast<i32>(as_double());
+}
+
+u32 Value::as_u32() const
+{
+ ASSERT(as_double() >= 0);
+ return min((double)as_i32(), MAX_U32);
+}
+
+size_t Value::as_size_t() const
+{
+ ASSERT(as_double() >= 0);
+ return min((double)as_i32(), MAX_ARRAY_LIKE_INDEX);
+}
+
+double Value::to_double(GlobalObject& global_object) const
+{
+ auto number = to_number(global_object);
+ if (global_object.vm().exception())
+ return INVALID;
+ return number.as_double();
+}
+
+i32 Value::to_i32(GlobalObject& global_object) const
+{
+ auto number = to_number(global_object);
+ if (global_object.vm().exception())
+ return INVALID;
+ if (number.is_nan() || number.is_infinity())
+ return 0;
+ return number.as_i32();
+}
+
+u32 Value::to_u32(GlobalObject& global_object) const
+{
+ // 7.1.7 ToUint32, https://tc39.es/ecma262/#sec-touint32
+ auto number = to_number(global_object);
+ if (global_object.vm().exception())
+ return INVALID;
+ if (number.is_nan() || number.is_infinity())
+ return 0;
+ if (number.as_double() <= 0)
+ return 0;
+ return number.as_u32();
+}
+
+size_t Value::to_length(GlobalObject& global_object) const
+{
+ // 7.1.20 ToLength, https://tc39.es/ecma262/#sec-tolength
+
+ auto& vm = global_object.vm();
+
+ auto len = to_integer_or_infinity(global_object);
+ if (vm.exception())
+ return INVALID;
+ if (len <= 0)
+ return 0;
+ return min(len, MAX_ARRAY_LIKE_INDEX);
+}
+
+size_t Value::to_index(GlobalObject& global_object) const
+{
+ // 7.1.22 ToIndex, https://tc39.es/ecma262/#sec-toindex
+
+ auto& vm = global_object.vm();
+
+ if (is_undefined())
+ return 0;
+ auto integer_index = to_integer_or_infinity(global_object);
+ if (vm.exception())
+ return INVALID;
+ if (integer_index < 0) {
+ vm.throw_exception<RangeError>(global_object, ErrorType::InvalidIndex);
+ return INVALID;
+ }
+ auto index = Value(integer_index).to_length(global_object);
+ ASSERT(!vm.exception());
+ if (integer_index != index) {
+ vm.throw_exception<RangeError>(global_object, ErrorType::InvalidIndex);
+ return INVALID;
+ }
+ return index;
+}
+
+double Value::to_integer_or_infinity(GlobalObject& global_object) const
+{
+ // 7.1.5 ToIntegerOrInfinity, https://tc39.es/ecma262/#sec-tointegerorinfinity
+
+ auto& vm = global_object.vm();
+
+ auto number = to_number(global_object);
+ if (vm.exception())
+ return INVALID;
+ if (number.is_nan() || number.as_double() == 0)
+ return 0;
+ if (number.is_infinity())
+ return number.as_double();
+ auto integer = floor(abs(number.as_double()));
+ if (number.as_double() < 0)
+ integer = -integer;
+ return integer;
+}
+
+Value greater_than(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ TriState relation = abstract_relation(global_object, false, lhs, rhs);
+ if (relation == TriState::Unknown)
+ return Value(false);
+ return Value(relation == TriState::True);
+}
+
+Value greater_than_equals(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ TriState relation = abstract_relation(global_object, true, lhs, rhs);
+ if (relation == TriState::Unknown || relation == TriState::True)
+ return Value(false);
+ return Value(true);
+}
+
+Value less_than(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ TriState relation = abstract_relation(global_object, true, lhs, rhs);
+ if (relation == TriState::Unknown)
+ return Value(false);
+ return Value(relation == TriState::True);
+}
+
+Value less_than_equals(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ TriState relation = abstract_relation(global_object, false, lhs, rhs);
+ if (relation == TriState::Unknown || relation == TriState::True)
+ return Value(false);
+ return Value(true);
+}
+
+Value bitwise_and(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ auto lhs_numeric = lhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ auto rhs_numeric = rhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ if (both_number(lhs_numeric, rhs_numeric)) {
+ if (!lhs_numeric.is_finite_number() || !rhs_numeric.is_finite_number())
+ return Value(0);
+ return Value((i32)lhs_numeric.as_double() & (i32)rhs_numeric.as_double());
+ }
+ if (both_bigint(lhs_numeric, rhs_numeric))
+ return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().bitwise_and(rhs_numeric.as_bigint().big_integer()));
+ global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "bitwise AND");
+ return {};
+}
+
+Value bitwise_or(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ auto lhs_numeric = lhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ auto rhs_numeric = rhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ if (both_number(lhs_numeric, rhs_numeric)) {
+ if (!lhs_numeric.is_finite_number() && !rhs_numeric.is_finite_number())
+ return Value(0);
+ if (!lhs_numeric.is_finite_number())
+ return rhs_numeric;
+ if (!rhs_numeric.is_finite_number())
+ return lhs_numeric;
+ return Value((i32)lhs_numeric.as_double() | (i32)rhs_numeric.as_double());
+ }
+ if (both_bigint(lhs_numeric, rhs_numeric))
+ return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().bitwise_or(rhs_numeric.as_bigint().big_integer()));
+ global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "bitwise OR");
+ return {};
+}
+
+Value bitwise_xor(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ auto lhs_numeric = lhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ auto rhs_numeric = rhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ if (both_number(lhs_numeric, rhs_numeric)) {
+ if (!lhs_numeric.is_finite_number() && !rhs_numeric.is_finite_number())
+ return Value(0);
+ if (!lhs_numeric.is_finite_number())
+ return rhs_numeric;
+ if (!rhs_numeric.is_finite_number())
+ return lhs_numeric;
+ return Value((i32)lhs_numeric.as_double() ^ (i32)rhs_numeric.as_double());
+ }
+ if (both_bigint(lhs_numeric, rhs_numeric))
+ return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().bitwise_xor(rhs_numeric.as_bigint().big_integer()));
+ global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "bitwise XOR");
+ return {};
+}
+
+Value bitwise_not(GlobalObject& global_object, Value lhs)
+{
+ auto lhs_numeric = lhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ if (lhs_numeric.is_number())
+ return Value(~lhs_numeric.to_i32(global_object));
+ auto big_integer_bitwise_not = lhs_numeric.as_bigint().big_integer();
+ big_integer_bitwise_not = big_integer_bitwise_not.plus(Crypto::SignedBigInteger { 1 });
+ big_integer_bitwise_not.negate();
+ return js_bigint(global_object.heap(), big_integer_bitwise_not);
+}
+
+Value unary_plus(GlobalObject& global_object, Value lhs)
+{
+ return lhs.to_number(global_object.global_object());
+}
+
+Value unary_minus(GlobalObject& global_object, Value lhs)
+{
+ auto lhs_numeric = lhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ if (lhs_numeric.is_number()) {
+ if (lhs_numeric.is_nan())
+ return js_nan();
+ return Value(-lhs_numeric.as_double());
+ }
+ if (lhs_numeric.as_bigint().big_integer() == BIGINT_ZERO)
+ return js_bigint(global_object.heap(), BIGINT_ZERO);
+ auto big_integer_negated = lhs_numeric.as_bigint().big_integer();
+ big_integer_negated.negate();
+ return js_bigint(global_object.heap(), big_integer_negated);
+}
+
+Value left_shift(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ auto lhs_numeric = lhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ auto rhs_numeric = rhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ if (both_number(lhs_numeric, rhs_numeric)) {
+ if (!lhs_numeric.is_finite_number())
+ return Value(0);
+ if (!rhs_numeric.is_finite_number())
+ return lhs_numeric;
+ return Value((i32)lhs_numeric.as_double() << (i32)rhs_numeric.as_double());
+ }
+ if (both_bigint(lhs_numeric, rhs_numeric))
+ TODO();
+ global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "left-shift");
+ return {};
+}
+
+Value right_shift(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ auto lhs_numeric = lhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ auto rhs_numeric = rhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ if (both_number(lhs_numeric, rhs_numeric)) {
+ if (!lhs_numeric.is_finite_number())
+ return Value(0);
+ if (!rhs_numeric.is_finite_number())
+ return lhs_numeric;
+ return Value((i32)lhs_numeric.as_double() >> (i32)rhs_numeric.as_double());
+ }
+ if (both_bigint(lhs_numeric, rhs_numeric))
+ TODO();
+ global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "right-shift");
+ return {};
+}
+
+Value unsigned_right_shift(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ auto lhs_numeric = lhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ auto rhs_numeric = rhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ if (both_number(lhs_numeric, rhs_numeric)) {
+ if (!lhs_numeric.is_finite_number())
+ return Value(0);
+ if (!rhs_numeric.is_finite_number())
+ return lhs_numeric;
+ return Value((unsigned)lhs_numeric.as_double() >> (i32)rhs_numeric.as_double());
+ }
+ global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperator, "unsigned right-shift");
+ return {};
+}
+
+Value add(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ auto lhs_primitive = lhs.to_primitive();
+ if (global_object.vm().exception())
+ return {};
+ auto rhs_primitive = rhs.to_primitive();
+ if (global_object.vm().exception())
+ return {};
+
+ if (lhs_primitive.is_string() || rhs_primitive.is_string()) {
+ auto lhs_string = lhs_primitive.to_string(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ auto rhs_string = rhs_primitive.to_string(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ StringBuilder builder(lhs_string.length() + rhs_string.length());
+ builder.append(lhs_string);
+ builder.append(rhs_string);
+ return js_string(global_object.heap(), builder.to_string());
+ }
+
+ auto lhs_numeric = lhs_primitive.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ auto rhs_numeric = rhs_primitive.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ if (both_number(lhs_numeric, rhs_numeric))
+ return Value(lhs_numeric.as_double() + rhs_numeric.as_double());
+ if (both_bigint(lhs_numeric, rhs_numeric))
+ return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().plus(rhs_numeric.as_bigint().big_integer()));
+ global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "addition");
+ return {};
+}
+
+Value sub(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ auto lhs_numeric = lhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ auto rhs_numeric = rhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ if (both_number(lhs_numeric, rhs_numeric))
+ return Value(lhs_numeric.as_double() - rhs_numeric.as_double());
+ if (both_bigint(lhs_numeric, rhs_numeric))
+ return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().minus(rhs_numeric.as_bigint().big_integer()));
+ global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "subtraction");
+ return {};
+}
+
+Value mul(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ auto lhs_numeric = lhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ auto rhs_numeric = rhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ if (both_number(lhs_numeric, rhs_numeric))
+ return Value(lhs_numeric.as_double() * rhs_numeric.as_double());
+ if (both_bigint(lhs_numeric, rhs_numeric))
+ return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().multiplied_by(rhs_numeric.as_bigint().big_integer()));
+ global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "multiplication");
+ return {};
+}
+
+Value div(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ auto lhs_numeric = lhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ auto rhs_numeric = rhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ if (both_number(lhs_numeric, rhs_numeric))
+ return Value(lhs_numeric.as_double() / rhs_numeric.as_double());
+ if (both_bigint(lhs_numeric, rhs_numeric))
+ return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().divided_by(rhs_numeric.as_bigint().big_integer()).quotient);
+ global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "division");
+ return {};
+}
+
+Value mod(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ auto lhs_numeric = lhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ auto rhs_numeric = rhs.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ if (both_number(lhs_numeric, rhs_numeric)) {
+ if (lhs_numeric.is_nan() || rhs_numeric.is_nan())
+ return js_nan();
+ auto index = lhs_numeric.as_double();
+ auto period = rhs_numeric.as_double();
+ auto trunc = (double)(i32)(index / period);
+ return Value(index - trunc * period);
+ }
+ if (both_bigint(lhs_numeric, rhs_numeric))
+ return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().divided_by(rhs_numeric.as_bigint().big_integer()).remainder);
+ global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "modulo");
+ return {};
+}
+
+Value exp(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ auto& vm = global_object.vm();
+ auto lhs_numeric = lhs.to_numeric(global_object);
+ if (vm.exception())
+ return {};
+ auto rhs_numeric = rhs.to_numeric(global_object);
+ if (vm.exception())
+ return {};
+ if (both_number(lhs_numeric, rhs_numeric))
+ return Value(pow(lhs_numeric.as_double(), rhs_numeric.as_double()));
+ if (both_bigint(lhs_numeric, rhs_numeric))
+ return js_bigint(vm.heap(), Crypto::NumberTheory::Power(lhs_numeric.as_bigint().big_integer(), rhs_numeric.as_bigint().big_integer()));
+ vm.throw_exception<TypeError>(global_object, ErrorType::BigIntBadOperatorOtherType, "exponentiation");
+ return {};
+}
+
+Value in(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ if (!rhs.is_object()) {
+ global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::InOperatorWithObject);
+ return {};
+ }
+ auto lhs_string = lhs.to_string(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ return Value(rhs.as_object().has_property(lhs_string));
+}
+
+Value instance_of(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ auto& vm = global_object.vm();
+ if (!rhs.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, rhs.to_string_without_side_effects());
+ return {};
+ }
+ auto has_instance_method = rhs.as_object().get(vm.well_known_symbol_has_instance());
+ if (!has_instance_method.is_empty()) {
+ if (!has_instance_method.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, has_instance_method.to_string_without_side_effects());
+ return {};
+ }
+ auto has_instance_result = vm.call(has_instance_method.as_function(), rhs, lhs);
+ if (vm.exception())
+ return {};
+ return Value(has_instance_result.to_boolean());
+ }
+
+ if (!rhs.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, rhs.to_string_without_side_effects());
+ return {};
+ }
+ return ordinary_has_instance(global_object, lhs, rhs);
+}
+
+Value ordinary_has_instance(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ auto& vm = global_object.vm();
+ if (!rhs.is_function())
+ return Value(false);
+ auto& rhs_function = rhs.as_function();
+
+ if (is<BoundFunction>(rhs_function)) {
+ auto& bound_target = static_cast<const BoundFunction&>(rhs_function);
+ return instance_of(global_object, lhs, Value(&bound_target.target_function()));
+ }
+
+ if (!lhs.is_object())
+ return Value(false);
+
+ Object* lhs_object = &lhs.as_object();
+ auto rhs_prototype = rhs_function.get(vm.names.prototype);
+ if (vm.exception())
+ return {};
+
+ if (!rhs_prototype.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::InstanceOfOperatorBadPrototype, rhs.to_string_without_side_effects());
+ return {};
+ }
+ while (true) {
+ lhs_object = lhs_object->prototype();
+ if (vm.exception())
+ return {};
+ if (!lhs_object)
+ return Value(false);
+ if (same_value(rhs_prototype, lhs_object))
+ return Value(true);
+ }
+}
+
+bool same_value(Value lhs, Value rhs)
+{
+ if (lhs.type() != rhs.type())
+ return false;
+
+ if (lhs.is_number()) {
+ if (lhs.is_nan() && rhs.is_nan())
+ return true;
+ if (lhs.is_positive_zero() && rhs.is_negative_zero())
+ return false;
+ if (lhs.is_negative_zero() && rhs.is_positive_zero())
+ return false;
+ return lhs.as_double() == rhs.as_double();
+ }
+
+ if (lhs.is_bigint()) {
+ auto lhs_big_integer = lhs.as_bigint().big_integer();
+ auto rhs_big_integer = rhs.as_bigint().big_integer();
+ if (lhs_big_integer == BIGINT_ZERO && rhs_big_integer == BIGINT_ZERO && lhs_big_integer.is_negative() != rhs_big_integer.is_negative())
+ return false;
+ return lhs_big_integer == rhs_big_integer;
+ }
+
+ return same_value_non_numeric(lhs, rhs);
+}
+
+bool same_value_zero(Value lhs, Value rhs)
+{
+ if (lhs.type() != rhs.type())
+ return false;
+
+ if (lhs.is_number()) {
+ if (lhs.is_nan() && rhs.is_nan())
+ return true;
+ return lhs.as_double() == rhs.as_double();
+ }
+
+ if (lhs.is_bigint())
+ return lhs.as_bigint().big_integer() == rhs.as_bigint().big_integer();
+
+ return same_value_non_numeric(lhs, rhs);
+}
+
+bool same_value_non_numeric(Value lhs, Value rhs)
+{
+ ASSERT(!lhs.is_number() && !lhs.is_bigint());
+ ASSERT(lhs.type() == rhs.type());
+
+ switch (lhs.type()) {
+ case Value::Type::Undefined:
+ case Value::Type::Null:
+ return true;
+ case Value::Type::String:
+ return lhs.as_string().string() == rhs.as_string().string();
+ case Value::Type::Symbol:
+ return &lhs.as_symbol() == &rhs.as_symbol();
+ case Value::Type::Boolean:
+ return lhs.as_bool() == rhs.as_bool();
+ case Value::Type::Object:
+ return &lhs.as_object() == &rhs.as_object();
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+bool strict_eq(Value lhs, Value rhs)
+{
+ if (lhs.type() != rhs.type())
+ return false;
+
+ if (lhs.is_number()) {
+ if (lhs.is_nan() || rhs.is_nan())
+ return false;
+ if (lhs.as_double() == rhs.as_double())
+ return true;
+ return false;
+ }
+
+ if (lhs.is_bigint())
+ return lhs.as_bigint().big_integer() == rhs.as_bigint().big_integer();
+
+ return same_value_non_numeric(lhs, rhs);
+}
+
+bool abstract_eq(GlobalObject& global_object, Value lhs, Value rhs)
+{
+ if (lhs.type() == rhs.type())
+ return strict_eq(lhs, rhs);
+
+ if (lhs.is_nullish() && rhs.is_nullish())
+ return true;
+
+ if (lhs.is_number() && rhs.is_string())
+ return abstract_eq(global_object, lhs, rhs.to_number(global_object.global_object()));
+
+ if (lhs.is_string() && rhs.is_number())
+ return abstract_eq(global_object, lhs.to_number(global_object.global_object()), rhs);
+
+ if (lhs.is_bigint() && rhs.is_string()) {
+ auto& rhs_string = rhs.as_string().string();
+ if (!is_valid_bigint_value(rhs_string))
+ return false;
+ return abstract_eq(global_object, lhs, js_bigint(global_object.heap(), Crypto::SignedBigInteger::from_base10(rhs_string)));
+ }
+
+ if (lhs.is_string() && rhs.is_bigint())
+ return abstract_eq(global_object, rhs, lhs);
+
+ if (lhs.is_boolean())
+ return abstract_eq(global_object, lhs.to_number(global_object.global_object()), rhs);
+
+ if (rhs.is_boolean())
+ return abstract_eq(global_object, lhs, rhs.to_number(global_object.global_object()));
+
+ if ((lhs.is_string() || lhs.is_number() || lhs.is_bigint() || lhs.is_symbol()) && rhs.is_object())
+ return abstract_eq(global_object, lhs, rhs.to_primitive());
+
+ if (lhs.is_object() && (rhs.is_string() || rhs.is_number() || lhs.is_bigint() || rhs.is_symbol()))
+ return abstract_eq(global_object, lhs.to_primitive(), rhs);
+
+ if ((lhs.is_bigint() && rhs.is_number()) || (lhs.is_number() && rhs.is_bigint())) {
+ if (lhs.is_nan() || lhs.is_infinity() || rhs.is_nan() || rhs.is_infinity())
+ return false;
+ if ((lhs.is_number() && !lhs.is_integer()) || (rhs.is_number() && !rhs.is_integer()))
+ return false;
+ if (lhs.is_number())
+ return Crypto::SignedBigInteger { lhs.as_i32() } == rhs.as_bigint().big_integer();
+ else
+ return Crypto::SignedBigInteger { rhs.as_i32() } == lhs.as_bigint().big_integer();
+ }
+
+ return false;
+}
+
+TriState abstract_relation(GlobalObject& global_object, bool left_first, Value lhs, Value rhs)
+{
+ Value x_primitive;
+ Value y_primitive;
+
+ if (left_first) {
+ x_primitive = lhs.to_primitive(Value::PreferredType::Number);
+ if (global_object.vm().exception())
+ return {};
+ y_primitive = rhs.to_primitive(Value::PreferredType::Number);
+ if (global_object.vm().exception())
+ return {};
+ } else {
+ y_primitive = lhs.to_primitive(Value::PreferredType::Number);
+ if (global_object.vm().exception())
+ return {};
+ x_primitive = rhs.to_primitive(Value::PreferredType::Number);
+ if (global_object.vm().exception())
+ return {};
+ }
+
+ if (x_primitive.is_string() && y_primitive.is_string()) {
+ auto x_string = x_primitive.as_string().string();
+ auto y_string = y_primitive.as_string().string();
+
+ if (x_string.starts_with(y_string))
+ return TriState::False;
+ if (y_string.starts_with(x_string))
+ return TriState::True;
+
+ Utf8View x_code_points { x_string };
+ Utf8View y_code_points { y_string };
+ for (auto k = x_code_points.begin(), l = y_code_points.begin();
+ k != x_code_points.end() && l != y_code_points.end();
+ ++k, ++l) {
+ if (*k != *l) {
+ if (*k < *l) {
+ return TriState::True;
+ } else {
+ return TriState::False;
+ }
+ }
+ }
+ ASSERT_NOT_REACHED();
+ }
+
+ if (x_primitive.is_bigint() && y_primitive.is_string()) {
+ auto& y_string = y_primitive.as_string().string();
+ if (!is_valid_bigint_value(y_string))
+ return TriState::Unknown;
+ if (x_primitive.as_bigint().big_integer() < Crypto::SignedBigInteger::from_base10(y_string))
+ return TriState::True;
+ else
+ return TriState::False;
+ }
+
+ if (x_primitive.is_string() && y_primitive.is_bigint()) {
+ auto& x_string = x_primitive.as_string().string();
+ if (!is_valid_bigint_value(x_string))
+ return TriState::Unknown;
+ if (Crypto::SignedBigInteger::from_base10(x_string) < y_primitive.as_bigint().big_integer())
+ return TriState::True;
+ else
+ return TriState::False;
+ }
+
+ auto x_numeric = x_primitive.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+ auto y_numeric = y_primitive.to_numeric(global_object.global_object());
+ if (global_object.vm().exception())
+ return {};
+
+ if (x_numeric.is_nan() || y_numeric.is_nan())
+ return TriState::Unknown;
+
+ if (x_numeric.is_positive_infinity() || y_numeric.is_negative_infinity())
+ return TriState::False;
+
+ if (x_numeric.is_negative_infinity() || y_numeric.is_positive_infinity())
+ return TriState::True;
+
+ if (x_numeric.is_number() && y_numeric.is_number()) {
+ if (x_numeric.as_double() < y_numeric.as_double())
+ return TriState::True;
+ else
+ return TriState::False;
+ }
+
+ if (x_numeric.is_bigint() && y_numeric.is_bigint()) {
+ if (x_numeric.as_bigint().big_integer() < y_numeric.as_bigint().big_integer())
+ return TriState::True;
+ else
+ return TriState::False;
+ }
+
+ ASSERT((x_numeric.is_number() && y_numeric.is_bigint()) || (x_numeric.is_bigint() && y_numeric.is_number()));
+
+ bool x_lower_than_y;
+ if (x_numeric.is_number()) {
+ x_lower_than_y = x_numeric.is_integer()
+ ? Crypto::SignedBigInteger { x_numeric.as_i32() } < y_numeric.as_bigint().big_integer()
+ : (Crypto::SignedBigInteger { x_numeric.as_i32() } < y_numeric.as_bigint().big_integer() || Crypto::SignedBigInteger { x_numeric.as_i32() + 1 } < y_numeric.as_bigint().big_integer());
+ } else {
+ x_lower_than_y = y_numeric.is_integer()
+ ? x_numeric.as_bigint().big_integer() < Crypto::SignedBigInteger { y_numeric.as_i32() }
+ : (x_numeric.as_bigint().big_integer() < Crypto::SignedBigInteger { y_numeric.as_i32() } || x_numeric.as_bigint().big_integer() < Crypto::SignedBigInteger { y_numeric.as_i32() + 1 });
+ }
+ if (x_lower_than_y)
+ return TriState::True;
+ else
+ return TriState::False;
+}
+
+size_t length_of_array_like(GlobalObject& global_object, const Object& object)
+{
+ // 7.3.18 LengthOfArrayLike, https://tc39.es/ecma262/#sec-lengthofarraylike
+
+ auto& vm = global_object.vm();
+ auto result = object.get(vm.names.length).value_or(js_undefined());
+ if (vm.exception())
+ return INVALID;
+ return result.to_length(global_object);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/Value.h b/Userland/Libraries/LibJS/Runtime/Value.h
new file mode 100644
index 0000000000..a24db80057
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/Value.h
@@ -0,0 +1,357 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Assertions.h>
+#include <AK/Format.h>
+#include <AK/Forward.h>
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <LibJS/Forward.h>
+#include <math.h>
+
+// 2 ** 53 - 1
+static constexpr double MAX_ARRAY_LIKE_INDEX = 9007199254740991.0;
+// 2 ** 32 - 1
+static constexpr double MAX_U32 = 4294967295.0;
+
+namespace JS {
+
+class Value {
+public:
+ enum class Type {
+ Empty,
+ Undefined,
+ Null,
+ Number,
+ String,
+ Object,
+ Boolean,
+ Symbol,
+ Accessor,
+ BigInt,
+ NativeProperty,
+ };
+
+ enum class PreferredType {
+ Default,
+ String,
+ Number,
+ };
+
+ bool is_empty() const { return m_type == Type::Empty; }
+ bool is_undefined() const { return m_type == Type::Undefined; }
+ bool is_null() const { return m_type == Type::Null; }
+ bool is_number() const { return m_type == Type::Number; }
+ bool is_string() const { return m_type == Type::String; }
+ bool is_object() const { return m_type == Type::Object; }
+ bool is_boolean() const { return m_type == Type::Boolean; }
+ bool is_symbol() const { return m_type == Type::Symbol; }
+ bool is_accessor() const { return m_type == Type::Accessor; };
+ bool is_bigint() const { return m_type == Type::BigInt; };
+ bool is_native_property() const { return m_type == Type::NativeProperty; }
+ bool is_nullish() const { return is_null() || is_undefined(); }
+ bool is_cell() const { return is_string() || is_accessor() || is_object() || is_bigint() || is_symbol() || is_native_property(); }
+ bool is_array() const;
+ bool is_function() const;
+ bool is_regexp(GlobalObject& global_object) const;
+
+ bool is_nan() const { return is_number() && __builtin_isnan(as_double()); }
+ bool is_infinity() const { return is_number() && __builtin_isinf(as_double()); }
+ bool is_positive_infinity() const { return is_number() && __builtin_isinf_sign(as_double()) > 0; }
+ bool is_negative_infinity() const { return is_number() && __builtin_isinf_sign(as_double()) < 0; }
+ bool is_positive_zero() const { return is_number() && 1.0 / as_double() == INFINITY; }
+ bool is_negative_zero() const { return is_number() && 1.0 / as_double() == -INFINITY; }
+ bool is_integer() const { return is_finite_number() && (i32)as_double() == as_double(); }
+ bool is_finite_number() const
+ {
+ if (!is_number())
+ return false;
+ auto number = as_double();
+ return !__builtin_isnan(number) && !__builtin_isinf(number);
+ }
+
+ Value()
+ : m_type(Type::Empty)
+ {
+ }
+
+ explicit Value(bool value)
+ : m_type(Type::Boolean)
+ {
+ m_value.as_bool = value;
+ }
+
+ explicit Value(double value)
+ : m_type(Type::Number)
+ {
+ m_value.as_double = value;
+ }
+
+ explicit Value(unsigned value)
+ : m_type(Type::Number)
+ {
+ m_value.as_double = static_cast<double>(value);
+ }
+
+ explicit Value(i32 value)
+ : m_type(Type::Number)
+ {
+ m_value.as_double = value;
+ }
+
+ Value(const Object* object)
+ : m_type(object ? Type::Object : Type::Null)
+ {
+ m_value.as_object = const_cast<Object*>(object);
+ }
+
+ Value(const PrimitiveString* string)
+ : m_type(Type::String)
+ {
+ m_value.as_string = const_cast<PrimitiveString*>(string);
+ }
+
+ Value(const Symbol* symbol)
+ : m_type(Type::Symbol)
+ {
+ m_value.as_symbol = const_cast<Symbol*>(symbol);
+ }
+
+ Value(const Accessor* accessor)
+ : m_type(Type::Accessor)
+ {
+ m_value.as_accessor = const_cast<Accessor*>(accessor);
+ }
+
+ Value(const BigInt* bigint)
+ : m_type(Type::BigInt)
+ {
+ m_value.as_bigint = const_cast<BigInt*>(bigint);
+ }
+
+ Value(const NativeProperty* native_property)
+ : m_type(Type::NativeProperty)
+ {
+ m_value.as_native_property = const_cast<NativeProperty*>(native_property);
+ }
+
+ explicit Value(Type type)
+ : m_type(type)
+ {
+ }
+
+ Type type() const { return m_type; }
+
+ double as_double() const
+ {
+ ASSERT(type() == Type::Number);
+ return m_value.as_double;
+ }
+
+ bool as_bool() const
+ {
+ ASSERT(type() == Type::Boolean);
+ return m_value.as_bool;
+ }
+
+ Object& as_object()
+ {
+ ASSERT(type() == Type::Object);
+ return *m_value.as_object;
+ }
+
+ const Object& as_object() const
+ {
+ ASSERT(type() == Type::Object);
+ return *m_value.as_object;
+ }
+
+ PrimitiveString& as_string()
+ {
+ ASSERT(is_string());
+ return *m_value.as_string;
+ }
+
+ const PrimitiveString& as_string() const
+ {
+ ASSERT(is_string());
+ return *m_value.as_string;
+ }
+
+ Symbol& as_symbol()
+ {
+ ASSERT(is_symbol());
+ return *m_value.as_symbol;
+ }
+
+ const Symbol& as_symbol() const
+ {
+ ASSERT(is_symbol());
+ return *m_value.as_symbol;
+ }
+
+ Cell* as_cell()
+ {
+ ASSERT(is_cell());
+ return m_value.as_cell;
+ }
+
+ Accessor& as_accessor()
+ {
+ ASSERT(is_accessor());
+ return *m_value.as_accessor;
+ }
+
+ BigInt& as_bigint()
+ {
+ ASSERT(is_bigint());
+ return *m_value.as_bigint;
+ }
+
+ NativeProperty& as_native_property()
+ {
+ ASSERT(is_native_property());
+ return *m_value.as_native_property;
+ }
+
+ Array& as_array();
+ Function& as_function();
+
+ i32 as_i32() const;
+ u32 as_u32() const;
+ size_t as_size_t() const;
+
+ String to_string(GlobalObject&, bool legacy_null_to_empty_string = false) const;
+ PrimitiveString* to_primitive_string(GlobalObject&);
+ Value to_primitive(PreferredType preferred_type = PreferredType::Default) const;
+ Object* to_object(GlobalObject&) const;
+ Value to_numeric(GlobalObject&) const;
+ Value to_number(GlobalObject&) const;
+ BigInt* to_bigint(GlobalObject&) const;
+ double to_double(GlobalObject&) const;
+ i32 to_i32(GlobalObject&) const;
+ u32 to_u32(GlobalObject&) const;
+ size_t to_length(GlobalObject&) const;
+ size_t to_index(GlobalObject&) const;
+ double to_integer_or_infinity(GlobalObject&) const;
+ bool to_boolean() const;
+
+ String to_string_without_side_effects() const;
+
+ Value value_or(Value fallback) const
+ {
+ if (is_empty())
+ return fallback;
+ return *this;
+ }
+
+private:
+ Type m_type { Type::Empty };
+
+ union {
+ bool as_bool;
+ double as_double;
+ PrimitiveString* as_string;
+ Symbol* as_symbol;
+ Object* as_object;
+ Cell* as_cell;
+ Accessor* as_accessor;
+ BigInt* as_bigint;
+ NativeProperty* as_native_property;
+ } m_value;
+};
+
+inline Value js_undefined()
+{
+ return Value(Value::Type::Undefined);
+}
+
+inline Value js_null()
+{
+ return Value(Value::Type::Null);
+}
+
+inline Value js_nan()
+{
+ return Value(NAN);
+}
+
+inline Value js_infinity()
+{
+ return Value(INFINITY);
+}
+
+inline Value js_negative_infinity()
+{
+ return Value(-INFINITY);
+}
+
+Value greater_than(GlobalObject&, Value lhs, Value rhs);
+Value greater_than_equals(GlobalObject&, Value lhs, Value rhs);
+Value less_than(GlobalObject&, Value lhs, Value rhs);
+Value less_than_equals(GlobalObject&, Value lhs, Value rhs);
+Value bitwise_and(GlobalObject&, Value lhs, Value rhs);
+Value bitwise_or(GlobalObject&, Value lhs, Value rhs);
+Value bitwise_xor(GlobalObject&, Value lhs, Value rhs);
+Value bitwise_not(GlobalObject&, Value);
+Value unary_plus(GlobalObject&, Value);
+Value unary_minus(GlobalObject&, Value);
+Value left_shift(GlobalObject&, Value lhs, Value rhs);
+Value right_shift(GlobalObject&, Value lhs, Value rhs);
+Value unsigned_right_shift(GlobalObject&, Value lhs, Value rhs);
+Value add(GlobalObject&, Value lhs, Value rhs);
+Value sub(GlobalObject&, Value lhs, Value rhs);
+Value mul(GlobalObject&, Value lhs, Value rhs);
+Value div(GlobalObject&, Value lhs, Value rhs);
+Value mod(GlobalObject&, Value lhs, Value rhs);
+Value exp(GlobalObject&, Value lhs, Value rhs);
+Value in(GlobalObject&, Value lhs, Value rhs);
+Value instance_of(GlobalObject&, Value lhs, Value rhs);
+Value ordinary_has_instance(GlobalObject&, Value lhs, Value rhs);
+
+bool abstract_eq(GlobalObject&, Value lhs, Value rhs);
+bool strict_eq(Value lhs, Value rhs);
+bool same_value(Value lhs, Value rhs);
+bool same_value_zero(Value lhs, Value rhs);
+bool same_value_non_numeric(Value lhs, Value rhs);
+TriState abstract_relation(GlobalObject&, bool left_first, Value lhs, Value rhs);
+size_t length_of_array_like(GlobalObject&, const Object&);
+
+}
+
+namespace AK {
+
+template<>
+struct Formatter<JS::Value> : Formatter<StringView> {
+ void format(FormatBuilder& builder, const JS::Value& value)
+ {
+ Formatter<StringView>::format(builder, value.is_empty() ? "<empty>" : value.to_string_without_side_effects());
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/WithScope.cpp b/Userland/Libraries/LibJS/Runtime/WithScope.cpp
new file mode 100644
index 0000000000..8afb608bf3
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/WithScope.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/AST.h>
+#include <LibJS/Runtime/WithScope.h>
+
+namespace JS {
+
+WithScope::WithScope(Object& object, ScopeObject* parent_scope)
+ : ScopeObject(parent_scope)
+ , m_object(object)
+{
+}
+
+void WithScope::visit_edges(Cell::Visitor& visitor)
+{
+ Base::visit_edges(visitor);
+ visitor.visit(&m_object);
+}
+
+Optional<Variable> WithScope::get_from_scope(const FlyString& name) const
+{
+ auto value = m_object.get(name);
+ if (value.is_empty())
+ return {};
+ return Variable { value, DeclarationKind::Var };
+}
+
+void WithScope::put_to_scope(const FlyString& name, Variable variable)
+{
+ m_object.put(name, variable.value);
+}
+
+bool WithScope::has_this_binding() const
+{
+ return parent()->has_this_binding();
+}
+
+Value WithScope::get_this_binding(GlobalObject& global_object) const
+{
+ return parent()->get_this_binding(global_object);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/WithScope.h b/Userland/Libraries/LibJS/Runtime/WithScope.h
new file mode 100644
index 0000000000..b74542cb64
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/WithScope.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/ScopeObject.h>
+
+namespace JS {
+
+class WithScope : public ScopeObject {
+ JS_OBJECT(WithScope, ScopeObject);
+
+public:
+ WithScope(Object&, ScopeObject* parent_scope);
+
+ virtual Optional<Variable> get_from_scope(const FlyString&) const override;
+ virtual void put_to_scope(const FlyString&, Variable) override;
+ virtual bool has_this_binding() const override;
+ virtual Value get_this_binding(GlobalObject&) const override;
+
+private:
+ virtual void visit_edges(Visitor&) override;
+
+ Object& m_object;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/SourceRange.h b/Userland/Libraries/LibJS/SourceRange.h
new file mode 100644
index 0000000000..f0359ab84c
--- /dev/null
+++ b/Userland/Libraries/LibJS/SourceRange.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+
+namespace JS {
+
+struct Position {
+ size_t line { 0 };
+ size_t column { 0 };
+};
+
+struct SourceRange {
+ Position start;
+ Position end;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Tests/add-values-to-primitive.js b/Userland/Libraries/LibJS/Tests/add-values-to-primitive.js
new file mode 100644
index 0000000000..3bef2209ba
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/add-values-to-primitive.js
@@ -0,0 +1,6 @@
+test("adding objects", () => {
+ expect([] + []).toBe("");
+ expect([] + {}).toBe("[object Object]");
+ expect({} + {}).toBe("[object Object][object Object]");
+ expect({} + []).toBe("[object Object]");
+});
diff --git a/Userland/Libraries/LibJS/Tests/arguments-object.js b/Userland/Libraries/LibJS/Tests/arguments-object.js
new file mode 100644
index 0000000000..17d9a141b8
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/arguments-object.js
@@ -0,0 +1,15 @@
+test("basic arguments object", () => {
+ function foo() {
+ return arguments.length;
+ }
+ expect(foo()).toBe(0);
+ expect(foo(1)).toBe(1);
+ expect(foo(1, 2)).toBe(2);
+ expect(foo(1, 2, 3)).toBe(3);
+
+ function bar() {
+ return arguments[1];
+ }
+ expect(bar("hello", "friends", ":^)")).toBe("friends");
+ expect(bar("hello")).toBe(undefined);
+});
diff --git a/Userland/Libraries/LibJS/Tests/automatic-semicolon-insertion.js b/Userland/Libraries/LibJS/Tests/automatic-semicolon-insertion.js
new file mode 100644
index 0000000000..998b0b6622
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/automatic-semicolon-insertion.js
@@ -0,0 +1,65 @@
+test("Issue #1829, if-else without braces or semicolons", () => {
+ const source = `if (1)
+ foo;
+else
+ bar;
+
+if (1)
+ foo
+else
+ bar
+
+if (1)
+ foo
+else
+ bar;`;
+
+ expect(source).toEval();
+});
+
+test("break/continue, variable declaration, do-while, and return asi", () => {
+ const source = `function foo() {
+ label:
+ for (var i = 0; i < 4; i++) {
+ break // semicolon inserted here
+ continue // semicolon inserted here
+
+ break label // semicolon inserted here
+ continue label // semicolon inserted here
+ }
+
+ var j // semicolon inserted here
+
+ do {
+ } while (1 === 2) // semicolon inserted here
+
+ return // semicolon inserted here
+ 1;
+var curly/* semicolon inserted here */}
+
+return foo();`;
+
+ expect(source).toEvalTo(undefined);
+});
+
+test("more break and continue asi", () => {
+ const source = `let counter = 0;
+let outer;
+
+outer:
+for (let i = 0; i < 5; ++i) {
+ for (let j = 0; j < 5; ++j) {
+ continue // semicolon inserted here
+ outer // semicolon inserted here
+ }
+ counter++;
+}
+
+return counter;`;
+
+ expect(source).toEvalTo(5);
+});
+
+test("eof with no semicolon", () => {
+ expect("var eof").toEval();
+});
diff --git a/Userland/Libraries/LibJS/Tests/break-continue-syntax-errors.js b/Userland/Libraries/LibJS/Tests/break-continue-syntax-errors.js
new file mode 100644
index 0000000000..106118823f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/break-continue-syntax-errors.js
@@ -0,0 +1,17 @@
+test("'break' syntax errors", () => {
+ expect("break").not.toEval();
+ expect("break label").not.toEval();
+ expect("{ break }").not.toEval();
+ expect("{ break label }").not.toEval();
+ expect("label: { break label }").toEval();
+});
+
+test("'continue' syntax errors", () => {
+ expect("continue").not.toEval();
+ expect("continue label").not.toEval();
+ expect("{ continue }").not.toEval();
+ expect("{ continue label }").not.toEval();
+ expect("label: { continue label }").not.toEval();
+
+ expect("switch (true) { case true: continue; }").not.toEval();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.from.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.from.js
new file mode 100644
index 0000000000..0a015f8141
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.from.js
@@ -0,0 +1,69 @@
+test("length is 1", () => {
+ expect(Array.from).toHaveLength(1);
+});
+
+describe("normal behavior", () => {
+ test("empty array", () => {
+ var a = Array.from([]);
+ expect(a instanceof Array).toBeTrue();
+ expect(a).toHaveLength(0);
+ });
+
+ test("empty string", () => {
+ var a = Array.from("");
+ expect(a instanceof Array).toBeTrue();
+ expect(a).toHaveLength(0);
+ });
+
+ test("non-empty array", () => {
+ var a = Array.from([5, 8, 1]);
+ expect(a instanceof Array).toBeTrue();
+ expect(a).toHaveLength(3);
+ expect(a[0]).toBe(5);
+ expect(a[1]).toBe(8);
+ expect(a[2]).toBe(1);
+ });
+
+ test("non-empty string", () => {
+ var a = Array.from("what");
+ expect(a instanceof Array).toBeTrue();
+ expect(a).toHaveLength(4);
+ expect(a[0]).toBe("w");
+ expect(a[1]).toBe("h");
+ expect(a[2]).toBe("a");
+ expect(a[3]).toBe("t");
+ });
+
+ test("shallow array copy", () => {
+ var a = [1, 2, 3];
+ var b = Array.from([a]);
+ expect(b instanceof Array).toBeTrue();
+ expect(b).toHaveLength(1);
+ b[0][0] = 4;
+ expect(a[0]).toBe(4);
+ });
+
+ test("from iterator", () => {
+ function rangeIterator(begin, end) {
+ return {
+ [Symbol.iterator]() {
+ let value = begin - 1;
+ return {
+ next() {
+ if (value < end) {
+ value += 1;
+ }
+ return { value: value, done: value >= end };
+ },
+ };
+ },
+ };
+ }
+
+ var a = Array.from(rangeIterator(8, 10));
+ expect(a instanceof Array).toBeTrue();
+ expect(a).toHaveLength(2);
+ expect(a[0]).toBe(8);
+ expect(a[1]).toBe(9);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.isArray.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.isArray.js
new file mode 100644
index 0000000000..bbe12a8c44
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.isArray.js
@@ -0,0 +1,26 @@
+test("length is 1", () => {
+ expect(Array.isArray).toHaveLength(1);
+});
+
+test("arguments that evaluate to false", () => {
+ expect(Array.isArray()).toBeFalse();
+ expect(Array.isArray("1")).toBeFalse();
+ expect(Array.isArray("foo")).toBeFalse();
+ expect(Array.isArray(1)).toBeFalse();
+ expect(Array.isArray(1, 2, 3)).toBeFalse();
+ expect(Array.isArray(undefined)).toBeFalse();
+ expect(Array.isArray(null)).toBeFalse();
+ expect(Array.isArray(Infinity)).toBeFalse();
+ expect(Array.isArray({})).toBeFalse();
+});
+
+test("arguments that evaluate to true", () => {
+ expect(Array.isArray([])).toBeTrue();
+ expect(Array.isArray([1])).toBeTrue();
+ expect(Array.isArray([1, 2, 3])).toBeTrue();
+ expect(Array.isArray(new Array())).toBeTrue();
+ expect(Array.isArray(new Array(10))).toBeTrue();
+ expect(Array.isArray(new Array("a", "b", "c"))).toBeTrue();
+ // FIXME: Array.prototype is supposed to be an array!
+ // expect(Array.isArray(Array.prototype)).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.js
new file mode 100644
index 0000000000..652e366d2f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.js
@@ -0,0 +1,53 @@
+test("constructor properties", () => {
+ expect(Array).toHaveLength(1);
+ expect(Array.name).toBe("Array");
+ expect(Array.prototype.length).toBe(0);
+});
+
+describe("errors", () => {
+ test("invalid array length", () => {
+ [-1, -100, -0.1, 0.1, 1.23, Infinity, -Infinity, NaN].forEach(value => {
+ expect(() => {
+ new Array(value);
+ }).toThrowWithMessage(RangeError, "Invalid array length");
+ });
+ });
+});
+
+describe("normal behavior", () => {
+ test("typeof", () => {
+ expect(typeof Array()).toBe("object");
+ expect(typeof new Array()).toBe("object");
+ });
+
+ test("constructor with single numeric argument", () => {
+ var a = new Array(5);
+ expect(a instanceof Array).toBeTrue();
+ expect(a).toHaveLength(5);
+ });
+
+ test("constructor with single non-numeric argument", () => {
+ var a = new Array("5");
+ expect(a instanceof Array).toBeTrue();
+ expect(a).toHaveLength(1);
+ expect(a[0]).toBe("5");
+ });
+
+ test("constructor with multiple numeric arguments", () => {
+ var a = new Array(1, 2, 3);
+ expect(a instanceof Array).toBeTrue();
+ expect(a).toHaveLength(3);
+ expect(a[0]).toBe(1);
+ expect(a[1]).toBe(2);
+ expect(a[2]).toBe(3);
+ });
+
+ test("constructor with single array argument", () => {
+ var a = new Array([1, 2, 3]);
+ expect(a instanceof Array).toBeTrue();
+ expect(a).toHaveLength(1);
+ expect(a[0][0]).toBe(1);
+ expect(a[0][1]).toBe(2);
+ expect(a[0][2]).toBe(3);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.of.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.of.js
new file mode 100644
index 0000000000..8e03470263
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.of.js
@@ -0,0 +1,59 @@
+test("length is 0", () => {
+ expect(Array.of).toHaveLength(0);
+});
+
+describe("normal behavior", () => {
+ test("single numeric argument", () => {
+ var a = Array.of(5);
+ expect(a instanceof Array).toBeTrue();
+ expect(a).toHaveLength(1);
+ expect(a[0]).toBe(5);
+ });
+
+ test("single non-numeric argument", () => {
+ var a = Array.of("5");
+ expect(a instanceof Array).toBeTrue();
+ expect(a).toHaveLength(1);
+ expect(a[0]).toBe("5");
+ });
+
+ test("single infinite numeric argument", () => {
+ var a = Array.of(Infinity);
+ expect(a instanceof Array).toBeTrue();
+ expect(a).toHaveLength(1);
+ expect(a[0]).toBe(Infinity);
+ });
+
+ test("multiple numeric arguments", () => {
+ var a = Array.of(1, 2, 3);
+ expect(a instanceof Array).toBeTrue();
+ expect(a).toHaveLength(3);
+ expect(a[0]).toBe(1);
+ expect(a[1]).toBe(2);
+ expect(a[2]).toBe(3);
+ });
+
+ test("single array argument", () => {
+ var a = Array.of([1, 2, 3]);
+ expect(a instanceof Array).toBeTrue();
+ expect(a).toHaveLength(1);
+ expect(a[0][0]).toBe(1);
+ expect(a[0][1]).toBe(2);
+ expect(a[0][2]).toBe(3);
+ });
+
+ test("getter property is included in returned array", () => {
+ var t = [1, 2, 3];
+ Object.defineProperty(t, 3, {
+ get() {
+ return 4;
+ },
+ });
+ var a = Array.of(...t);
+ expect(a).toHaveLength(4);
+ expect(a[0]).toBe(1);
+ expect(a[1]).toBe(2);
+ expect(a[2]).toBe(3);
+ expect(a[3]).toBe(4);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype-generic-functions.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype-generic-functions.js
new file mode 100644
index 0000000000..a8afde0ea3
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype-generic-functions.js
@@ -0,0 +1,150 @@
+describe("ability to work with generic non-array objects", () => {
+ test("push, pop", () => {
+ [undefined, "foo", -42, 0].forEach(length => {
+ const o = { length };
+
+ expect(Array.prototype.push.call(o, "foo")).toBe(1);
+ expect(o).toHaveLength(1);
+ expect(o[0]).toBe("foo");
+ expect(Array.prototype.push.call(o, "bar", "baz")).toBe(3);
+ expect(o).toHaveLength(3);
+ expect(o[0]).toBe("foo");
+ expect(o[1]).toBe("bar");
+ expect(o[2]).toBe("baz");
+
+ expect(Array.prototype.pop.call(o)).toBe("baz");
+ expect(o).toHaveLength(2);
+ expect(Array.prototype.pop.call(o)).toBe("bar");
+ expect(o).toHaveLength(1);
+ expect(Array.prototype.pop.call(o)).toBe("foo");
+ expect(o).toHaveLength(0);
+ expect(Array.prototype.pop.call(o)).toBeUndefined();
+ expect(o).toHaveLength(0);
+
+ o.length = length;
+ expect(Array.prototype.pop.call(o)).toBeUndefined();
+ expect(o).toHaveLength(0);
+ });
+ });
+
+ test("splice", () => {
+ const o = { length: 3, 0: "hello", 2: "serenity" };
+ const removed = Array.prototype.splice.call(o, 0, 2, "hello", "friends");
+ expect(o).toHaveLength(3);
+ expect(o[0]).toBe("hello");
+ expect(o[1]).toBe("friends");
+ expect(o[2]).toBe("serenity");
+ expect(removed).toHaveLength(2);
+ expect(removed[0]).toBe("hello");
+ expect(removed[1]).toBeUndefined();
+ });
+
+ test("join", () => {
+ expect(Array.prototype.join.call({})).toBe("");
+ expect(Array.prototype.join.call({ length: "foo" })).toBe("");
+ expect(Array.prototype.join.call({ length: 3 })).toBe(",,");
+ expect(Array.prototype.join.call({ length: 2, 0: "foo", 1: "bar" })).toBe("foo,bar");
+ expect(Array.prototype.join.call({ length: 2, 0: "foo", 1: "bar", 2: "baz" })).toBe(
+ "foo,bar"
+ );
+ expect(Array.prototype.join.call({ length: 3, 1: "bar" }, "~")).toBe("~bar~");
+ expect(Array.prototype.join.call({ length: 3, 0: "foo", 1: "bar", 2: "baz" }, "~")).toBe(
+ "foo~bar~baz"
+ );
+ });
+
+ // FIXME: test-js asserts when this is just called "toString" ಠ_ಠ
+ test("toString (FIXME)", () => {
+ expect(Array.prototype.toString.call({})).toBe("[object Object]");
+ expect(Array.prototype.toString.call({ join: "foo" })).toBe("[object Object]");
+ expect(Array.prototype.toString.call({ join: () => "foo" })).toBe("foo");
+ });
+
+ test("indexOf", () => {
+ expect(Array.prototype.indexOf.call({})).toBe(-1);
+ expect(Array.prototype.indexOf.call({ 0: undefined })).toBe(-1);
+ expect(Array.prototype.indexOf.call({ length: 1, 0: undefined })).toBe(0);
+ expect(Array.prototype.indexOf.call({ length: 1, 2: "foo" }, "foo")).toBe(-1);
+ expect(Array.prototype.indexOf.call({ length: 5, 2: "foo" }, "foo")).toBe(2);
+ expect(Array.prototype.indexOf.call({ length: 5, 2: "foo", 4: "foo" }, "foo", 3)).toBe(4);
+ });
+
+ test("lastIndexOf", () => {
+ expect(Array.prototype.lastIndexOf.call({})).toBe(-1);
+ expect(Array.prototype.lastIndexOf.call({ 0: undefined })).toBe(-1);
+ expect(Array.prototype.lastIndexOf.call({ length: 1, 0: undefined })).toBe(0);
+ expect(Array.prototype.lastIndexOf.call({ length: 1, 2: "foo" }, "foo")).toBe(-1);
+ expect(Array.prototype.lastIndexOf.call({ length: 5, 2: "foo" }, "foo")).toBe(2);
+ expect(Array.prototype.lastIndexOf.call({ length: 5, 2: "foo", 4: "foo" }, "foo")).toBe(4);
+ expect(Array.prototype.lastIndexOf.call({ length: 5, 2: "foo", 4: "foo" }, "foo", -2)).toBe(
+ 2
+ );
+ });
+
+ test("includes", () => {
+ expect(Array.prototype.includes.call({})).toBeFalse();
+ expect(Array.prototype.includes.call({ 0: undefined })).toBeFalse();
+ expect(Array.prototype.includes.call({ length: 1, 0: undefined })).toBeTrue();
+ expect(Array.prototype.includes.call({ length: 1, 2: "foo" }, "foo")).toBeFalse();
+ expect(Array.prototype.includes.call({ length: 5, 2: "foo" }, "foo")).toBeTrue();
+ });
+
+ const o = { length: 5, 0: "foo", 1: "bar", 3: "baz" };
+
+ test("every", () => {
+ const visited = [];
+ Array.prototype.every.call(o, value => {
+ visited.push(value);
+ return true;
+ });
+ expect(visited).toEqual(["foo", "bar", "baz"]);
+ });
+
+ test("find, findIndex", () => {
+ ["find", "findIndex"].forEach(name => {
+ const visited = [];
+ Array.prototype[name].call(o, value => {
+ visited.push(value);
+ return false;
+ });
+ expect(visited).toEqual(["foo", "bar", undefined, "baz", undefined]);
+ });
+ });
+
+ test("filter, forEach, map, some", () => {
+ ["filter", "forEach", "map", "some"].forEach(name => {
+ const visited = [];
+ Array.prototype[name].call(o, value => {
+ visited.push(value);
+ return false;
+ });
+ expect(visited).toEqual(["foo", "bar", "baz"]);
+ });
+ });
+
+ test("reduce", () => {
+ const visited = [];
+ Array.prototype.reduce.call(
+ o,
+ (_, value) => {
+ visited.push(value);
+ return false;
+ },
+ "initial"
+ );
+ expect(visited).toEqual(["foo", "bar", "baz"]);
+ });
+
+ test("reduceRight", () => {
+ const visited = [];
+ Array.prototype.reduceRight.call(
+ o,
+ (_, value) => {
+ visited.push(value);
+ return false;
+ },
+ "initial"
+ );
+ expect(visited).toEqual(["baz", "bar", "foo"]);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.concat.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.concat.js
new file mode 100644
index 0000000000..c19b0ec13f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.concat.js
@@ -0,0 +1,43 @@
+test("length is 1", () => {
+ expect(Array.prototype.concat).toHaveLength(1);
+});
+
+describe("normal behavior", () => {
+ var array = ["hello"];
+
+ test("no arguments", () => {
+ var concatenated = array.concat();
+ expect(array).toHaveLength(1);
+ expect(concatenated).toHaveLength(1);
+ });
+
+ test("single argument", () => {
+ var concatenated = array.concat("friends");
+ expect(array).toHaveLength(1);
+ expect(concatenated).toHaveLength(2);
+ expect(concatenated[0]).toBe("hello");
+ expect(concatenated[1]).toBe("friends");
+ });
+
+ test("single array argument", () => {
+ var concatenated = array.concat([1, 2, 3]);
+ expect(array).toHaveLength(1);
+ expect(concatenated).toHaveLength(4);
+ expect(concatenated[0]).toBe("hello");
+ expect(concatenated[1]).toBe(1);
+ expect(concatenated[2]).toBe(2);
+ expect(concatenated[3]).toBe(3);
+ });
+
+ test("multiple arguments", () => {
+ var concatenated = array.concat(false, "serenity", { name: "libjs" }, [1, [2, 3]]);
+ expect(array).toHaveLength(1);
+ expect(concatenated).toHaveLength(6);
+ expect(concatenated[0]).toBe("hello");
+ expect(concatenated[1]).toBeFalse();
+ expect(concatenated[2]).toBe("serenity");
+ expect(concatenated[3]).toEqual({ name: "libjs" });
+ expect(concatenated[4]).toBe(1);
+ expect(concatenated[5]).toEqual([2, 3]);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.every.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.every.js
new file mode 100644
index 0000000000..0215f5b244
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.every.js
@@ -0,0 +1,55 @@
+test("length is 1", () => {
+ expect(Array.prototype.every).toHaveLength(1);
+});
+
+describe("errors", () => {
+ test("requires at least one argument", () => {
+ expect(() => {
+ [].every();
+ }).toThrowWithMessage(TypeError, "Array.prototype.every() requires at least one argument");
+ });
+
+ test("callback must be a function", () => {
+ expect(() => {
+ [].every(undefined);
+ }).toThrowWithMessage(TypeError, "undefined is not a function");
+ });
+});
+
+describe("normal behavior", () => {
+ test("basic functionality", () => {
+ var arrayOne = ["serenity", { test: "serenity" }];
+ var arrayTwo = [true, false, 1, 2, 3, "3"];
+
+ expect(arrayOne.every(value => value === "hello")).toBeFalse();
+ expect(arrayOne.every(value => value === "serenity")).toBeFalse();
+ expect(arrayOne.every((value, index, arr) => index < 2)).toBeTrue();
+ expect(arrayOne.every(value => typeof value === "string")).toBeFalse();
+ expect(arrayOne.every(value => arrayOne.pop())).toBeTrue();
+
+ expect(arrayTwo.every((value, index, arr) => index > 0)).toBeFalse();
+ expect(arrayTwo.every((value, index, arr) => index >= 0)).toBeTrue();
+ expect(arrayTwo.every(value => typeof value !== "string")).toBeFalse();
+ expect(arrayTwo.every(value => typeof value === "number")).toBeFalse();
+ expect(arrayTwo.every(value => value > 0)).toBeFalse();
+ expect(arrayTwo.every(value => value >= 0 && value < 4)).toBeTrue();
+ expect(arrayTwo.every(value => arrayTwo.pop())).toBeTrue();
+
+ expect(["", "hello", "friends", "serenity"].every(value => value.length >= 0)).toBeTrue();
+ });
+
+ test("empty array", () => {
+ expect([].every(value => value === 1)).toBeTrue();
+ });
+
+ test("elements past the initial array size are ignored", () => {
+ var array = [1, 2, 3, 4, 5];
+
+ expect(
+ arrayTwo.every((value, index, arr) => {
+ arr.push(6);
+ return value <= 5;
+ })
+ ).toBeTrue();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.fill.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.fill.js
new file mode 100644
index 0000000000..2773ec42cb
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.fill.js
@@ -0,0 +1,20 @@
+test("length is 1", () => {
+ expect(Array.prototype.fill).toHaveLength(1);
+});
+
+test("basic functionality", () => {
+ var array = [1, 2, 3, 4];
+
+ expect(array.fill(0, 2, 4)).toEqual([1, 2, 0, 0]);
+ expect(array.fill(5, 1)).toEqual([1, 5, 5, 5]);
+ expect(array.fill(6)).toEqual([6, 6, 6, 6]);
+
+ expect([1, 2, 3].fill(4)).toEqual([4, 4, 4]);
+ expect([1, 2, 3].fill(4, 1)).toEqual([1, 4, 4]);
+ expect([1, 2, 3].fill(4, 1, 2)).toEqual([1, 4, 3]);
+ expect([1, 2, 3].fill(4, 3, 3)).toEqual([1, 2, 3]);
+ expect([1, 2, 3].fill(4, -3, -2)).toEqual([4, 2, 3]);
+ expect([1, 2, 3].fill(4, NaN, NaN)).toEqual([1, 2, 3]);
+ expect([1, 2, 3].fill(4, 3, 5)).toEqual([1, 2, 3]);
+ expect(Array(3).fill(4)).toEqual([4, 4, 4]);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.filter.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.filter.js
new file mode 100644
index 0000000000..6f481cddb8
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.filter.js
@@ -0,0 +1,68 @@
+test("length is 1", () => {
+ expect(Array.prototype.filter).toHaveLength(1);
+});
+
+describe("errors", () => {
+ test("requires at least one argument", () => {
+ expect(() => {
+ [].filter();
+ }).toThrowWithMessage(TypeError, "Array.prototype.filter() requires at least one argument");
+ });
+
+ test("callback must be a function", () => {
+ expect(() => {
+ [].filter(undefined);
+ }).toThrowWithMessage(TypeError, "undefined is not a function");
+ });
+});
+
+describe("normal behavior", () => {
+ test("never calls callback with empty array", () => {
+ var callbackCalled = 0;
+ expect(
+ [].filter(() => {
+ callbackCalled++;
+ })
+ ).toEqual([]);
+ expect(callbackCalled).toBe(0);
+ });
+
+ test("calls callback once for every item", () => {
+ var callbackCalled = 0;
+ expect(
+ [1, 2, 3].filter(() => {
+ callbackCalled++;
+ })
+ ).toEqual([]);
+ expect(callbackCalled).toBe(3);
+ });
+
+ test("can filter based on callback return value", () => {
+ var evenNumbers = [0, 1, 2, 3, 4, 5, 6, 7].filter(x => x % 2 === 0);
+ expect(evenNumbers).toEqual([0, 2, 4, 6]);
+
+ var fruits = [
+ "Apple",
+ "Banana",
+ "Blueberry",
+ "Grape",
+ "Mango",
+ "Orange",
+ "Peach",
+ "Pineapple",
+ "Raspberry",
+ "Watermelon",
+ ];
+ const filterItems = (arr, query) => {
+ return arr.filter(el => el.toLowerCase().indexOf(query.toLowerCase()) !== -1);
+ };
+ expect(filterItems(fruits, "Berry")).toEqual(["Blueberry", "Raspberry"]);
+ expect(filterItems(fruits, "P")).toEqual([
+ "Apple",
+ "Grape",
+ "Peach",
+ "Pineapple",
+ "Raspberry",
+ ]);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.find.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.find.js
new file mode 100644
index 0000000000..4d12b61a11
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.find.js
@@ -0,0 +1,65 @@
+test("length is 1", () => {
+ expect(Array.prototype.find).toHaveLength(1);
+});
+
+describe("errors", () => {
+ test("requires at least one argument", () => {
+ expect(() => {
+ [].find();
+ }).toThrowWithMessage(TypeError, "Array.prototype.find() requires at least one argument");
+ });
+
+ test("callback must be a function", () => {
+ expect(() => {
+ [].find(undefined);
+ }).toThrowWithMessage(TypeError, "undefined is not a function");
+ });
+});
+
+describe("normal behavior", () => {
+ test("basic functionality", () => {
+ var array = ["hello", "friends", 1, 2, false];
+
+ expect(array.find(value => value === "hello")).toBe("hello");
+ expect(array.find((value, index, arr) => index === 1)).toBe("friends");
+ expect(array.find(value => value == "1")).toBe(1);
+ expect(array.find(value => value === 1)).toBe(1);
+ expect(array.find(value => typeof value !== "string")).toBe(1);
+ expect(array.find(value => typeof value === "boolean")).toBeFalse();
+ expect(array.find(value => value > 1)).toBe(2);
+ expect(array.find(value => value > 1 && value < 3)).toBe(2);
+ expect(array.find(value => value > 100)).toBeUndefined();
+ expect([].find(value => value === 1)).toBeUndefined();
+ });
+
+ test("never calls callback with empty array", () => {
+ var callbackCalled = 0;
+ expect(
+ [].find(() => {
+ callbackCalled++;
+ })
+ ).toBeUndefined();
+ expect(callbackCalled).toBe(0);
+ });
+
+ test("calls callback once for every item", () => {
+ var callbackCalled = 0;
+ expect(
+ [1, 2, 3].find(() => {
+ callbackCalled++;
+ })
+ ).toBeUndefined();
+ expect(callbackCalled).toBe(3);
+ });
+
+ test("empty slots are treated as undefined", () => {
+ var callbackCalled = 0;
+ expect(
+ [1, , , "foo", , undefined, , ,].find(value => {
+ callbackCalled++;
+ return value === undefined;
+ })
+ ).toBeUndefined();
+ expect(callbackCalled).toBe(2);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.findIndex.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.findIndex.js
new file mode 100644
index 0000000000..751caa64be
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.findIndex.js
@@ -0,0 +1,68 @@
+test("length is 1", () => {
+ expect(Array.prototype.findIndex).toHaveLength(1);
+});
+
+describe("errors", () => {
+ test("requires at least one argument", () => {
+ expect(() => {
+ [].findIndex();
+ }).toThrowWithMessage(
+ TypeError,
+ "Array.prototype.findIndex() requires at least one argument"
+ );
+ });
+
+ test("callback must be a function", () => {
+ expect(() => {
+ [].findIndex(undefined);
+ }).toThrowWithMessage(TypeError, "undefined is not a function");
+ });
+});
+
+describe("normal behavior", () => {
+ test("basic functionality", () => {
+ var array = ["hello", "friends", 1, 2, false];
+
+ expect(array.findIndex(value => value === "hello")).toBe(0);
+ expect(array.findIndex((value, index, arr) => index === 1)).toBe(1);
+ expect(array.findIndex(value => value == "1")).toBe(2);
+ expect(array.findIndex(value => value === 1)).toBe(2);
+ expect(array.findIndex(value => typeof value !== "string")).toBe(2);
+ expect(array.findIndex(value => typeof value === "boolean")).toBe(4);
+ expect(array.findIndex(value => value > 1)).toBe(3);
+ expect(array.findIndex(value => value > 1 && value < 3)).toBe(3);
+ expect(array.findIndex(value => value > 100)).toBe(-1);
+ expect([].findIndex(value => value === 1)).toBe(-1);
+ });
+
+ test("never calls callback with empty array", () => {
+ var callbackCalled = 0;
+ expect(
+ [].findIndex(() => {
+ callbackCalled++;
+ })
+ ).toBe(-1);
+ expect(callbackCalled).toBe(0);
+ });
+
+ test("calls callback once for every item", () => {
+ var callbackCalled = 0;
+ expect(
+ [1, 2, 3].findIndex(() => {
+ callbackCalled++;
+ })
+ ).toBe(-1);
+ expect(callbackCalled).toBe(3);
+ });
+
+ test("empty slots are treated as undefined", () => {
+ var callbackCalled = 0;
+ expect(
+ [1, , , "foo", , undefined, , ,].findIndex(value => {
+ callbackCalled++;
+ return value === undefined;
+ })
+ ).toBe(1);
+ expect(callbackCalled).toBe(2);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.forEach.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.forEach.js
new file mode 100644
index 0000000000..8baa2d5635
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.forEach.js
@@ -0,0 +1,70 @@
+test("length is 1", () => {
+ expect(Array.prototype.forEach).toHaveLength(1);
+});
+
+describe("errors", () => {
+ test("requires at least one argument", () => {
+ expect(() => {
+ [].forEach();
+ }).toThrowWithMessage(
+ TypeError,
+ "Array.prototype.forEach() requires at least one argument"
+ );
+ });
+
+ test("callback must be a function", () => {
+ expect(() => {
+ [].forEach(undefined);
+ }).toThrowWithMessage(TypeError, "undefined is not a function");
+ });
+});
+
+describe("normal behavior", () => {
+ test("never calls callback with empty array", () => {
+ var callbackCalled = 0;
+ expect(
+ [].forEach(() => {
+ callbackCalled++;
+ })
+ ).toBeUndefined();
+ expect(callbackCalled).toBe(0);
+ });
+
+ test("calls callback once for every item", () => {
+ var callbackCalled = 0;
+ expect(
+ [1, 2, 3].forEach(() => {
+ callbackCalled++;
+ })
+ ).toBeUndefined();
+ expect(callbackCalled).toBe(3);
+ });
+
+ test("callback receives value and index", () => {
+ var a = [1, 2, 3];
+ a.forEach((value, index) => {
+ expect(value).toBe(a[index]);
+ expect(index).toBe(a[index] - 1);
+ });
+ });
+
+ test("callback receives array", () => {
+ var callbackCalled = 0;
+ var a = [1, 2, 3];
+ a.forEach((_, __, array) => {
+ callbackCalled++;
+ expect(a).toEqual(array);
+ a.push("test");
+ });
+ expect(callbackCalled).toBe(3);
+ expect(a).toEqual([1, 2, 3, "test", "test", "test"]);
+ });
+
+ test("this value can be modified", () => {
+ var t = [];
+ [1, 2, 3].forEach(function (value) {
+ this.push(value);
+ }, t);
+ expect(t).toEqual([1, 2, 3]);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.includes.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.includes.js
new file mode 100644
index 0000000000..575cd84e1b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.includes.js
@@ -0,0 +1,18 @@
+test("length is 1", () => {
+ expect(Array.prototype.includes).toHaveLength(1);
+});
+
+test("basic functionality", () => {
+ var array = ["hello", "friends", 1, 2, false];
+
+ expect([].includes()).toBeFalse();
+ expect([undefined].includes()).toBeTrue();
+ expect(array.includes("hello")).toBeTrue();
+ expect(array.includes(1)).toBeTrue();
+ expect(array.includes(1, -3)).toBeTrue();
+ expect(array.includes("serenity")).toBeFalse();
+ expect(array.includes(false, -1)).toBeTrue();
+ expect(array.includes(2, -1)).toBeFalse();
+ expect(array.includes(2, -100)).toBeTrue();
+ expect(array.includes("friends", 100)).toBeFalse();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.indexOf.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.indexOf.js
new file mode 100644
index 0000000000..74caab93ee
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.indexOf.js
@@ -0,0 +1,25 @@
+test("length is 1", () => {
+ expect(Array.prototype.indexOf).toHaveLength(1);
+});
+
+test("basic functionality", () => {
+ var array = ["hello", "friends", 1, 2, false];
+
+ expect(array.indexOf("hello")).toBe(0);
+ expect(array.indexOf("friends")).toBe(1);
+ expect(array.indexOf(false)).toBe(4);
+ expect(array.indexOf(false, 2)).toBe(4);
+ expect(array.indexOf(false, -2)).toBe(4);
+ expect(array.indexOf(1)).toBe(2);
+ expect(array.indexOf(1, 1000)).toBe(-1);
+ expect(array.indexOf(1, -1000)).toBe(2);
+ expect(array.indexOf("serenity")).toBe(-1);
+ expect(array.indexOf(false, -1)).toBe(4);
+ expect(array.indexOf(2, -1)).toBe(-1);
+ expect(array.indexOf(2, -2)).toBe(3);
+ expect([].indexOf("serenity")).toBe(-1);
+ expect([].indexOf("serenity", 10)).toBe(-1);
+ expect([].indexOf("serenity", -10)).toBe(-1);
+ expect([].indexOf()).toBe(-1);
+ expect([undefined].indexOf()).toBe(0);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.join.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.join.js
new file mode 100644
index 0000000000..30efb7e2b5
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.join.js
@@ -0,0 +1,24 @@
+test("length is 1", () => {
+ expect(Array.prototype.join).toHaveLength(1);
+});
+
+test("basic functionality", () => {
+ expect(["hello", "friends"].join()).toBe("hello,friends");
+ expect(["hello", "friends"].join(undefined)).toBe("hello,friends");
+ expect(["hello", "friends"].join(" ")).toBe("hello friends");
+ expect(["hello", "friends", "foo"].join("~", "#")).toBe("hello~friends~foo");
+ expect([].join()).toBe("");
+ expect([null].join()).toBe("");
+ expect([undefined].join()).toBe("");
+ expect([undefined, null, ""].join()).toBe(",,");
+ expect([1, null, 2, undefined, 3].join()).toBe("1,,2,,3");
+ expect(Array(3).join()).toBe(",,");
+});
+
+test("circular references", () => {
+ const a = ["foo", [], [1, 2, []], ["bar"]];
+ a[1] = a;
+ a[2][2] = a;
+ // [ "foo", <circular>, [ 1, 2, <circular> ], [ "bar" ] ]
+ expect(a.join()).toBe("foo,,1,2,,bar");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.lastIndexOf.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.lastIndexOf.js
new file mode 100644
index 0000000000..eb0f7c6b37
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.lastIndexOf.js
@@ -0,0 +1,22 @@
+test("length is 1", () => {
+ expect(Array.prototype.lastIndexOf).toHaveLength(1);
+});
+
+test("basic functionality", () => {
+ var array = [1, 2, 3, 1, "hello"];
+
+ expect(array.lastIndexOf("hello")).toBe(4);
+ expect(array.lastIndexOf("hello", 1000)).toBe(4);
+ expect(array.lastIndexOf(1)).toBe(3);
+ expect(array.lastIndexOf(1, -1)).toBe(3);
+ expect(array.lastIndexOf(1, -2)).toBe(3);
+ expect(array.lastIndexOf(2)).toBe(1);
+ expect(array.lastIndexOf(2, -3)).toBe(1);
+ expect(array.lastIndexOf(2, -4)).toBe(1);
+ expect([].lastIndexOf("hello")).toBe(-1);
+ expect([].lastIndexOf("hello", 10)).toBe(-1);
+ expect([].lastIndexOf("hello", -10)).toBe(-1);
+ expect([].lastIndexOf()).toBe(-1);
+ expect([undefined].lastIndexOf()).toBe(0);
+ expect([undefined, undefined, undefined].lastIndexOf()).toBe(2);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.map.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.map.js
new file mode 100644
index 0000000000..a775fa8c32
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.map.js
@@ -0,0 +1,57 @@
+test("length is 1", () => {
+ expect(Array.prototype.map).toHaveLength(1);
+});
+
+describe("errors", () => {
+ test("requires at least one argument", () => {
+ expect(() => {
+ [].map();
+ }).toThrowWithMessage(TypeError, "Array.prototype.map() requires at least one argument");
+ });
+
+ test("callback must be a function", () => {
+ expect(() => {
+ [].map(undefined);
+ }).toThrowWithMessage(TypeError, "undefined is not a function");
+ });
+});
+
+describe("normal behavior", () => {
+ test("never calls callback with empty array", () => {
+ var callbackCalled = 0;
+ expect(
+ [].map(() => {
+ callbackCalled++;
+ })
+ ).toEqual([]);
+ expect(callbackCalled).toBe(0);
+ });
+
+ test("calls callback once for every item", () => {
+ var callbackCalled = 0;
+ expect(
+ [1, 2, 3].map(() => {
+ callbackCalled++;
+ })
+ ).toEqual([undefined, undefined, undefined]);
+ expect(callbackCalled).toBe(3);
+ });
+
+ test("can map based on callback return value", () => {
+ expect(
+ [undefined, null, true, "foo", 42, {}].map(
+ (value, index) => "" + index + " -> " + value
+ )
+ ).toEqual([
+ "0 -> undefined",
+ "1 -> null",
+ "2 -> true",
+ "3 -> foo",
+ "4 -> 42",
+ "5 -> [object Object]",
+ ]);
+
+ var squaredNumbers = [0, 1, 2, 3, 4].map(x => x ** 2);
+ expect(squaredNumbers).toEqual([0, 1, 4, 9, 16]);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.pop.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.pop.js
new file mode 100644
index 0000000000..d1c34ad60e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.pop.js
@@ -0,0 +1,29 @@
+test("length is 0", () => {
+ expect(Array.prototype.pop).toHaveLength(0);
+});
+
+describe("normal behavior", () => {
+ test("array with elements", () => {
+ var a = [1, 2, 3];
+ expect(a.pop()).toBe(3);
+ expect(a).toEqual([1, 2]);
+ expect(a.pop()).toBe(2);
+ expect(a).toEqual([1]);
+ expect(a.pop()).toBe(1);
+ expect(a).toEqual([]);
+ expect(a.pop()).toBeUndefined();
+ expect(a).toEqual([]);
+ });
+
+ test("empty array", () => {
+ var a = [];
+ expect(a.pop()).toBeUndefined();
+ expect(a).toEqual([]);
+ });
+
+ test("array with empty slot", () => {
+ var a = [,];
+ expect(a.pop()).toBeUndefined();
+ expect(a).toEqual([]);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.push.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.push.js
new file mode 100644
index 0000000000..209cd7a74d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.push.js
@@ -0,0 +1,23 @@
+test("length is 1", () => {
+ expect(Array.prototype.push).toHaveLength(1);
+});
+
+describe("normal behavior", () => {
+ test("no argument", () => {
+ var a = ["hello"];
+ expect(a.push()).toBe(1);
+ expect(a).toEqual(["hello"]);
+ });
+
+ test("single argument", () => {
+ var a = ["hello"];
+ expect(a.push("friends")).toBe(2);
+ expect(a).toEqual(["hello", "friends"]);
+ });
+
+ test("multiple arguments", () => {
+ var a = ["hello", "friends"];
+ expect(a.push(1, 2, 3)).toBe(5);
+ expect(a).toEqual(["hello", "friends", 1, 2, 3]);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.reduce.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.reduce.js
new file mode 100644
index 0000000000..9b984fea03
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.reduce.js
@@ -0,0 +1,113 @@
+test("length is 1", () => {
+ expect(Array.prototype.reduce).toHaveLength(1);
+});
+
+describe("errors", () => {
+ test("requires at least one argument", () => {
+ expect(() => {
+ [].reduce();
+ }).toThrowWithMessage(TypeError, "Array.prototype.reduce() requires at least one argument");
+ });
+
+ test("callback must be a function", () => {
+ expect(() => {
+ [].reduce(undefined);
+ }).toThrowWithMessage(TypeError, "undefined is not a function");
+ });
+
+ test("reduce of empty array with no initial value", () => {
+ expect(() => {
+ [].reduce((a, x) => x);
+ }).toThrowWithMessage(TypeError, "Reduce of empty array with no initial value");
+ });
+
+ test("reduce of array with only empty slots and no initial value", () => {
+ expect(() => {
+ [, ,].reduce((a, x) => x);
+ }).toThrowWithMessage(TypeError, "Reduce of empty array with no initial value");
+ });
+});
+
+describe("normal behavior", () => {
+ test("basic functionality", () => {
+ [1, 2].reduce(function () {
+ expect(this).toBeUndefined();
+ });
+
+ var callbackCalled = 0;
+ var callback = () => {
+ callbackCalled++;
+ return true;
+ };
+
+ expect([1].reduce(callback)).toBe(1);
+ expect(callbackCalled).toBe(0);
+
+ expect([, 1].reduce(callback)).toBe(1);
+ expect(callbackCalled).toBe(0);
+
+ callbackCalled = 0;
+ expect([1, 2, 3].reduce(callback)).toBeTrue();
+ expect(callbackCalled).toBe(2);
+
+ callbackCalled = 0;
+ expect([, , 1, 2, 3].reduce(callback)).toBeTrue();
+ expect(callbackCalled).toBe(2);
+
+ callbackCalled = 0;
+ expect([1, , , 10, , 100, , ,].reduce(callback)).toBeTrue();
+ expect(callbackCalled).toBe(2);
+
+ var constantlySad = () => ":^(";
+ var result = [].reduce(constantlySad, ":^)");
+ expect(result).toBe(":^)");
+
+ result = [":^0"].reduce(constantlySad, ":^)");
+ expect(result).toBe(":^(");
+
+ result = [":^0"].reduce(constantlySad);
+ expect(result).toBe(":^0");
+
+ result = [5, 4, 3, 2, 1].reduce((accum, elem) => accum + elem);
+ expect(result).toBe(15);
+
+ result = [1, 2, 3, 4, 5, 6].reduce((accum, elem) => accum + elem, 100);
+ expect(result).toBe(121);
+
+ result = [6, 5, 4, 3, 2, 1].reduce((accum, elem) => {
+ return accum + elem;
+ }, 100);
+ expect(result).toBe(121);
+
+ var indexes = [];
+ result = ["foo", 1, true].reduce((a, v, i) => {
+ indexes.push(i);
+ });
+ expect(result).toBeUndefined();
+ expect(indexes.length).toBe(2);
+ expect(indexes[0]).toBe(1);
+ expect(indexes[1]).toBe(2);
+
+ indexes = [];
+ result = ["foo", 1, true].reduce((a, v, i) => {
+ indexes.push(i);
+ }, "foo");
+ expect(result).toBeUndefined();
+ expect(indexes).toEqual([0, 1, 2]);
+
+ var mutable = { prop: 0 };
+ result = ["foo", 1, true].reduce((a, v) => {
+ a.prop = v;
+ return a;
+ }, mutable);
+ expect(result).toBe(mutable);
+ expect(result.prop).toBeTrue();
+
+ var a1 = [1, 2];
+ var a2 = null;
+ a1.reduce((a, v, i, t) => {
+ a2 = t;
+ });
+ expect(a1).toBe(a2);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.reduceRight.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.reduceRight.js
new file mode 100644
index 0000000000..3d718a0446
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.reduceRight.js
@@ -0,0 +1,118 @@
+test("length is 1", () => {
+ expect(Array.prototype.reduceRight).toHaveLength(1);
+});
+
+describe("errors", () => {
+ test("requires at least one argument", () => {
+ expect(() => {
+ [].reduceRight();
+ }).toThrowWithMessage(
+ TypeError,
+ "Array.prototype.reduceRight() requires at least one argument"
+ );
+ });
+
+ test("callback must be a function", () => {
+ expect(() => {
+ [].reduceRight(undefined);
+ }).toThrowWithMessage(TypeError, "undefined is not a function");
+ });
+
+ test("reduce of empty array with no initial value", () => {
+ expect(() => {
+ [].reduceRight((a, x) => x);
+ }).toThrowWithMessage(TypeError, "Reduce of empty array with no initial value");
+ });
+
+ test("reduce of array with only empty slots and no initial value", () => {
+ expect(() => {
+ [, ,].reduceRight((a, x) => x);
+ }).toThrowWithMessage(TypeError, "Reduce of empty array with no initial value");
+ });
+});
+
+describe("normal behavior", () => {
+ test("basic functionality", () => {
+ [1, 2].reduceRight(function () {
+ expect(this).toBeUndefined();
+ });
+
+ var callbackCalled = 0;
+ var callback = () => {
+ callbackCalled++;
+ return true;
+ };
+
+ expect([1].reduceRight(callback)).toBe(1);
+ expect(callbackCalled).toBe(0);
+
+ expect([1].reduceRight(callback)).toBe(1);
+ expect(callbackCalled).toBe(0);
+
+ callbackCalled = 0;
+ expect([1, 2, 3].reduceRight(callback)).toBe(true);
+ expect(callbackCalled).toBe(2);
+
+ callbackCalled = 0;
+ expect([1, 2, 3, ,].reduceRight(callback)).toBe(true);
+ expect(callbackCalled).toBe(2);
+
+ callbackCalled = 0;
+ expect([, , , 1, , , 10, , 100, , ,].reduceRight(callback)).toBe(true);
+ expect(callbackCalled).toBe(2);
+
+ var constantlySad = () => ":^(";
+ var result = [].reduceRight(constantlySad, ":^)");
+ expect(result).toBe(":^)");
+
+ result = [":^0"].reduceRight(constantlySad, ":^)");
+ expect(result).toBe(":^(");
+
+ result = [":^0"].reduceRight(constantlySad);
+ expect(result).toBe(":^0");
+
+ result = [5, 4, 3, 2, 1].reduceRight((accum, elem) => "" + accum + elem);
+ expect(result).toBe("12345");
+
+ result = [1, 2, 3, 4, 5, 6].reduceRight((accum, elem) => {
+ return "" + accum + elem;
+ }, 100);
+ expect(result).toBe("100654321");
+
+ result = [6, 5, 4, 3, 2, 1].reduceRight((accum, elem) => {
+ return "" + accum + elem;
+ }, 100);
+ expect(result).toBe("100123456");
+
+ var indexes = [];
+ result = ["foo", 1, true].reduceRight((a, v, i) => {
+ indexes.push(i);
+ });
+ expect(result).toBeUndefined();
+ expect(indexes.length).toBe(2);
+ expect(indexes[0]).toBe(1);
+ expect(indexes[1]).toBe(0);
+
+ indexes = [];
+ result = ["foo", 1, true].reduceRight((a, v, i) => {
+ indexes.push(i);
+ }, "foo");
+ expect(result).toBeUndefined();
+ expect(indexes).toEqual([2, 1, 0]);
+
+ var mutable = { prop: 0 };
+ result = ["foo", 1, true].reduceRight((a, v) => {
+ a.prop = v;
+ return a;
+ }, mutable);
+ expect(result).toBe(mutable);
+ expect(result.prop).toBe("foo");
+
+ var a1 = [1, 2];
+ var a2 = null;
+ a1.reduceRight((a, v, i, t) => {
+ a2 = t;
+ });
+ expect(a1).toBe(a2);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.reverse.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.reverse.js
new file mode 100644
index 0000000000..b2b71b15d6
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.reverse.js
@@ -0,0 +1,9 @@
+test("length is 0", () => {
+ expect(Array.prototype.reverse).toHaveLength(0);
+});
+
+test("basic functionality", () => {
+ var array = [1, 2, 3];
+ expect(array.reverse()).toEqual([3, 2, 1]);
+ expect(array).toEqual([3, 2, 1]);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.shift.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.shift.js
new file mode 100644
index 0000000000..0e154eeeb6
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.shift.js
@@ -0,0 +1,23 @@
+test("length is 0", () => {
+ expect(Array.prototype.shift).toHaveLength(0);
+});
+
+describe("normal behavior", () => {
+ test("array with elements", () => {
+ var a = [1, 2, 3];
+ expect(a.shift()).toBe(1);
+ expect(a).toEqual([2, 3]);
+ });
+
+ test("empty array", () => {
+ var a = [];
+ expect(a.shift()).toBeUndefined();
+ expect(a).toEqual([]);
+ });
+
+ test("array with empty slot", () => {
+ var a = [,];
+ expect(a.shift()).toBeUndefined();
+ expect(a).toEqual([]);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.slice.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.slice.js
new file mode 100644
index 0000000000..c1c45be082
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.slice.js
@@ -0,0 +1,39 @@
+test("length is 0", () => {
+ expect(Array.prototype.slice).toHaveLength(2);
+});
+
+test("basic functionality", () => {
+ var array = ["hello", "friends", "serenity", 1];
+
+ var slice = array.slice();
+ expect(array).toEqual(["hello", "friends", "serenity", 1]);
+ expect(slice).toEqual(["hello", "friends", "serenity", 1]);
+
+ slice = array.slice(1);
+ expect(array).toEqual(["hello", "friends", "serenity", 1]);
+ expect(slice).toEqual(["friends", "serenity", 1]);
+
+ slice = array.slice(0, 2);
+ expect(array).toEqual(["hello", "friends", "serenity", 1]);
+ expect(slice).toEqual(["hello", "friends"]);
+
+ slice = array.slice(-1);
+ expect(array).toEqual(["hello", "friends", "serenity", 1]);
+ expect(slice).toEqual([1]);
+
+ slice = array.slice(1, 1);
+ expect(array).toEqual(["hello", "friends", "serenity", 1]);
+ expect(slice).toEqual([]);
+
+ slice = array.slice(1, -1);
+ expect(array).toEqual(["hello", "friends", "serenity", 1]);
+ expect(slice).toEqual(["friends", "serenity"]);
+
+ slice = array.slice(2, -1);
+ expect(array).toEqual(["hello", "friends", "serenity", 1]);
+ expect(slice).toEqual(["serenity"]);
+
+ slice = array.slice(0, 100);
+ expect(array).toEqual(["hello", "friends", "serenity", 1]);
+ expect(slice).toEqual(["hello", "friends", "serenity", 1]);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.some.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.some.js
new file mode 100644
index 0000000000..ff9e62f2d1
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.some.js
@@ -0,0 +1,37 @@
+test("length is 1", () => {
+ expect(Array.prototype.some).toHaveLength(1);
+});
+
+describe("errors", () => {
+ test("requires at least one argument", () => {
+ expect(() => {
+ [].some();
+ }).toThrowWithMessage(TypeError, "Array.prototype.some() requires at least one argument");
+ });
+
+ test("callback must be a function", () => {
+ expect(() => {
+ [].some(undefined);
+ }).toThrowWithMessage(TypeError, "undefined is not a function");
+ });
+});
+
+test("basic functionality", () => {
+ var array = ["hello", "friends", 1, 2, false, -42, { name: "serenityos" }];
+
+ expect(array.some(value => value === "hello")).toBeTrue();
+ expect(array.some(value => value === "serenity")).toBeFalse();
+ expect(array.some((value, index, arr) => index === 1)).toBeTrue();
+ expect(array.some(value => value == "1")).toBeTrue();
+ expect(array.some(value => value === 1)).toBeTrue();
+ expect(array.some(value => value === 13)).toBeFalse();
+ expect(array.some(value => typeof value !== "string")).toBeTrue();
+ expect(array.some(value => typeof value === "boolean")).toBeTrue();
+ expect(array.some(value => value > 1)).toBeTrue();
+ expect(array.some(value => value > 1 && value < 3)).toBeTrue();
+ expect(array.some(value => value > 100)).toBeFalse();
+ expect(array.some(value => value < 0)).toBeTrue();
+ expect(array.some(value => array.pop())).toBeTrue();
+ expect(["", "hello", "friends", "serenity"].some(value => value.length === 0)).toBeTrue();
+ expect([].some(value => value === 1)).toBeFalse();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.sort.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.sort.js
new file mode 100644
index 0000000000..1b05995be2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.sort.js
@@ -0,0 +1,204 @@
+describe("Array.prototype.sort", () => {
+ test("basic functionality", () => {
+ expect(Array.prototype.sort).toHaveLength(1);
+
+ var arr = ["c", "b", "d", "a"];
+ expect(arr.sort()).toEqual(arr);
+ expect(arr).toEqual(["a", "b", "c", "d"]);
+
+ arr = ["aa", "a"];
+ expect(arr.sort()).toEqual(arr);
+ expect(arr).toEqual(["a", "aa"]);
+
+ arr = [1, 0];
+ expect(arr.sort()).toBe(arr); // should be exactly same object
+ expect(arr).toEqual([0, 1]);
+
+ // numbers are sorted as strings
+ arr = [205, -123, 22, 200, 3, -20, -2, -1, 25, 2, 0, 1];
+ expect(arr.sort()).toEqual([-1, -123, -2, -20, 0, 1, 2, 200, 205, 22, 25, 3]);
+
+ // mix of data, including empty slots and undefined
+ arr = ["2", Infinity, null, null, , undefined, 5, , undefined, null, 54, "5"];
+ expect(arr.sort()).toEqual([
+ "2",
+ 5,
+ "5",
+ 54,
+ Infinity,
+ null,
+ null,
+ null,
+ undefined,
+ undefined,
+ ,
+ ,
+ ]);
+ expect(arr.length).toEqual(12);
+
+ // undefined compare function
+ arr = ["2", Infinity, null, null, , undefined, 5n, , undefined, null, 54, "5"];
+ expect(arr.sort(undefined)).toEqual([
+ "2",
+ 5n,
+ "5",
+ 54,
+ Infinity,
+ null,
+ null,
+ null,
+ undefined,
+ undefined,
+ ,
+ ,
+ ]);
+ expect(arr.length).toEqual(12);
+
+ // numeric data with compare function to sort numerically
+ arr = [50, 500, 5, Infinity, -Infinity, 0, 10, -10, 1, -1, 5, 0, 15, Infinity];
+ expect(arr.sort((a, b) => a - b)).toEqual([
+ -Infinity,
+ -10,
+ -1,
+ 0,
+ 0,
+ 1,
+ 5,
+ 5,
+ 10,
+ 15,
+ 50,
+ 500,
+ Infinity,
+ Infinity,
+ ]);
+ expect(arr.length).toEqual(14);
+
+ // numeric data with compare function to sort reverse numerically
+ arr = [50, 500, 5, Infinity, -Infinity, 0, 10, -10, 1, -1, 5, 0, 15, Infinity];
+ expect(arr.sort((a, b) => b - a)).toEqual([
+ Infinity,
+ Infinity,
+ 500,
+ 50,
+ 15,
+ 10,
+ 5,
+ 5,
+ 1,
+ 0,
+ 0,
+ -1,
+ -10,
+ -Infinity,
+ ]);
+
+ // small/edge cases
+ expect([].sort()).toEqual([]);
+ expect([5].sort()).toEqual([5]);
+ expect([5, 5].sort()).toEqual([5, 5]);
+ expect([undefined].sort()).toEqual([undefined]);
+ expect([undefined, undefined].sort()).toEqual([undefined, undefined]);
+ expect([,].sort()).toEqual([,]);
+ expect([, ,].sort()).toEqual([, ,]);
+ expect([5, ,].sort()).toEqual([5, ,]);
+ expect([, , 5].sort()).toEqual([5, , ,]);
+
+ // sorting should be stable
+ arr = [
+ { sorted_key: 2, other_property: 1 },
+ { sorted_key: 2, other_property: 2 },
+ { sorted_key: 1, other_property: 3 },
+ ];
+ arr.sort((a, b) => a.sorted_key - b.sorted_key);
+ expect(arr[1].other_property == 1);
+ expect(arr[2].other_property == 2);
+ });
+
+ test("that it makes no unnecessary calls to compare function", () => {
+ expectNoCallCompareFunction = function (a, b) {
+ expect().fail();
+ };
+
+ expect([].sort(expectNoCallCompareFunction)).toEqual([]);
+ expect([1].sort(expectNoCallCompareFunction)).toEqual([1]);
+ expect([1, undefined].sort(expectNoCallCompareFunction)).toEqual([1, undefined]);
+ expect([undefined, undefined].sort(expectNoCallCompareFunction)).toEqual([
+ undefined,
+ undefined,
+ ]);
+ expect([, , 1, ,].sort(expectNoCallCompareFunction)).toEqual([1, , , ,]);
+ expect([undefined, , 1, , undefined, ,].sort(expectNoCallCompareFunction)).toEqual([
+ 1,
+ undefined,
+ undefined,
+ ,
+ ,
+ ,
+ ]);
+ });
+
+ test("that it works on non-arrays", () => {
+ var obj = { length: 0 };
+ expect(Array.prototype.sort.call(obj)).toBe(obj);
+ expect(obj).toEqual({ length: 0 });
+
+ obj = { 0: 1, length: 0 };
+ expect(Array.prototype.sort.call(obj, undefined)).toBe(obj);
+ expect(obj).toEqual({ 0: 1, length: 0 });
+
+ obj = { 0: 3, 1: 2, 2: 1, 3: 0, length: 2 };
+ expect(Array.prototype.sort.call(obj)).toBe(obj);
+ expect(obj).toEqual({ 0: 2, 1: 3, 2: 1, 3: 0, length: 2 });
+
+ obj = { 0: 3, 1: 2, 2: 1, a: "b", hello: "friends!", length: 2 };
+ expect(Array.prototype.sort.call(obj)).toBe(obj);
+ expect(obj).toEqual({ 0: 2, 1: 3, 2: 1, 3: 0, a: "b", hello: "friends!", length: 2 });
+
+ obj = { 0: 2, 1: 3, 2: 1, a: "b", hello: "friends!", length: 2 };
+ expect(
+ Array.prototype.sort.call(obj, (a, b) => {
+ expect(a == 2 || a == 3).toBeTrue();
+ expect(b == 2 || b == 3).toBeTrue();
+ return b - a;
+ })
+ ).toBe(obj);
+ expect(obj).toEqual({ 0: 3, 1: 2, 2: 1, 3: 0, a: "b", hello: "friends!", length: 2 });
+ });
+
+ test("that it handles abrupt completions correctly", () => {
+ class TestError extends Error {
+ constructor() {
+ super();
+ this.name = "TestError";
+ }
+ }
+
+ arr = [1, 2, 3];
+ expect(() =>
+ arr.sort((a, b) => {
+ throw new TestError();
+ })
+ ).toThrow(TestError);
+
+ class DangerousToString {
+ toString() {
+ throw new TestError();
+ }
+ }
+ arr = [new DangerousToString(), new DangerousToString()];
+ expect(() => arr.sort()).toThrow(TestError);
+ });
+
+ test("that it does not use deleteProperty unnecessarily", () => {
+ var obj = new Proxy(
+ { 0: 5, 1: 4, 2: 3, length: 3 },
+ {
+ deleteProperty: function (target, property) {
+ expect().fail();
+ },
+ }
+ );
+ Array.prototype.sort.call(obj);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.splice.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.splice.js
new file mode 100644
index 0000000000..245648760a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.splice.js
@@ -0,0 +1,49 @@
+test("length is 2", () => {
+ expect(Array.prototype.splice).toHaveLength(2);
+});
+
+test("basic functionality", () => {
+ var array = ["hello", "friends", "serenity", 1, 2];
+ var removed = array.splice(3);
+ expect(array).toEqual(["hello", "friends", "serenity"]);
+ expect(removed).toEqual([1, 2]);
+
+ array = ["hello", "friends", "serenity", 1, 2];
+ removed = array.splice(-2);
+ expect(array).toEqual(["hello", "friends", "serenity"]);
+ expect(removed).toEqual([1, 2]);
+
+ array = ["hello", "friends", "serenity", 1, 2];
+ removed = array.splice(-2, 1);
+ expect(array).toEqual(["hello", "friends", "serenity", 2]);
+ expect(removed).toEqual([1]);
+
+ array = ["serenity"];
+ removed = array.splice(0, 0, "hello", "friends");
+ expect(array).toEqual(["hello", "friends", "serenity"]);
+ expect(removed).toEqual([]);
+
+ array = ["goodbye", "friends", "serenity"];
+ removed = array.splice(0, 1, "hello");
+ expect(array).toEqual(["hello", "friends", "serenity"]);
+ expect(removed).toEqual(["goodbye"]);
+
+ array = ["foo", "bar", "baz"];
+ removed = array.splice();
+ expect(array).toEqual(["foo", "bar", "baz"]);
+ expect(removed).toEqual([]);
+
+ removed = array.splice(0, 123);
+ expect(array).toEqual([]);
+ expect(removed).toEqual(["foo", "bar", "baz"]);
+
+ array = ["foo", "bar", "baz"];
+ removed = array.splice(123, 123);
+ expect(array).toEqual(["foo", "bar", "baz"]);
+ expect(removed).toEqual([]);
+
+ array = ["foo", "bar", "baz"];
+ removed = array.splice(-123, 123);
+ expect(array).toEqual([]);
+ expect(removed).toEqual(["foo", "bar", "baz"]);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.toLocaleString.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.toLocaleString.js
new file mode 100644
index 0000000000..05854a1d0c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.toLocaleString.js
@@ -0,0 +1,62 @@
+test("length is 0", () => {
+ expect(Array.prototype.toLocaleString).toHaveLength(0);
+});
+
+describe("normal behavior", () => {
+ test("array with no elements", () => {
+ expect([].toLocaleString()).toBe("");
+ });
+
+ test("array with one element", () => {
+ expect(["foo"].toLocaleString()).toBe("foo");
+ });
+
+ test("array with multiple elements", () => {
+ expect(["foo", "bar", "baz"].toLocaleString()).toBe("foo,bar,baz");
+ });
+
+ test("null and undefined result in empty strings", () => {
+ expect([null].toLocaleString()).toBe("");
+ expect([undefined].toLocaleString()).toBe("");
+ expect([undefined, null].toLocaleString()).toBe(",");
+ });
+
+ test("empty values result in empty strings", () => {
+ expect(new Array(1).toLocaleString()).toBe("");
+ expect(new Array(3).toLocaleString()).toBe(",,");
+ var a = new Array(5);
+ a[2] = "foo";
+ a[4] = "bar";
+ expect(a.toLocaleString()).toBe(",,foo,,bar");
+ });
+
+ test("getter property is included in returned string", () => {
+ var a = ["foo"];
+ Object.defineProperty(a, 1, {
+ get() {
+ return "bar";
+ },
+ });
+ expect(a.toLocaleString()).toBe("foo,bar");
+ });
+
+ test("array with elements that have a custom toString() function", () => {
+ var toStringCalled = 0;
+ var o = {
+ toString() {
+ toStringCalled++;
+ return "o";
+ },
+ };
+ expect([o, undefined, o, null, o].toLocaleString()).toBe("o,,o,,o");
+ expect(toStringCalled).toBe(3);
+ });
+
+ test("array with circular references", () => {
+ const a = ["foo", [], [1, 2, []], ["bar"]];
+ a[1] = a;
+ a[2][2] = a;
+ // [ "foo", <circular>, [ 1, 2, <circular> ], [ "bar" ] ]
+ expect(a.toLocaleString()).toBe("foo,,1,2,,bar");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.toString.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.toString.js
new file mode 100644
index 0000000000..1c18fb54ed
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.toString.js
@@ -0,0 +1,66 @@
+test("length is 0", () => {
+ expect(Array.prototype.toString).toHaveLength(0);
+});
+
+describe("normal behavior", () => {
+ test("array with no elements", () => {
+ expect([].toString()).toBe("");
+ });
+
+ test("array with one element", () => {
+ expect([1].toString()).toBe("1");
+ });
+
+ test("array with multiple elements", () => {
+ expect([1, 2, 3].toString()).toBe("1,2,3");
+ });
+
+ test("string and array concatenation", () => {
+ expect("rgb(" + [10, 11, 12] + ")").toBe("rgb(10,11,12)");
+ });
+
+ test("null and undefined result in empty strings", () => {
+ expect([null].toString()).toBe("");
+ expect([undefined].toString()).toBe("");
+ expect([undefined, null].toString()).toBe(",");
+ });
+
+ test("empty values result in empty strings", () => {
+ expect(new Array(1).toString()).toBe("");
+ expect(new Array(3).toString()).toBe(",,");
+ var a = new Array(5);
+ a[2] = "foo";
+ a[4] = "bar";
+ expect(a.toString()).toBe(",,foo,,bar");
+ });
+
+ test("getter property is included in returned string", () => {
+ var a = [1, 2, 3];
+ Object.defineProperty(a, 3, {
+ get() {
+ return 10;
+ },
+ });
+ expect(a.toString()).toBe("1,2,3,10");
+ });
+
+ test("array with elements that have a custom toString() function", () => {
+ var toStringCalled = 0;
+ var o = {
+ toString() {
+ toStringCalled++;
+ return "o";
+ },
+ };
+ expect([o, undefined, o, null, o].toString()).toBe("o,,o,,o");
+ expect(toStringCalled).toBe(3);
+ });
+
+ test("array with circular references", () => {
+ const a = ["foo", [], [1, 2, []], ["bar"]];
+ a[1] = a;
+ a[2][2] = a;
+ // [ "foo", <circular>, [ 1, 2, <circular> ], [ "bar" ] ]
+ expect(a.toString()).toBe("foo,,1,2,,bar");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.unshift.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.unshift.js
new file mode 100644
index 0000000000..8e65e89775
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.unshift.js
@@ -0,0 +1,23 @@
+test("length is 1", () => {
+ expect(Array.prototype.unshift).toHaveLength(1);
+});
+
+describe("normal behavior", () => {
+ test("no argument", () => {
+ var a = ["hello"];
+ expect(a.unshift()).toBe(1);
+ expect(a).toEqual(["hello"]);
+ });
+
+ test("single argument", () => {
+ var a = ["hello"];
+ expect(a.unshift("friends")).toBe(2);
+ expect(a).toEqual(["friends", "hello"]);
+ });
+
+ test("multiple arguments", () => {
+ var a = ["friends", "hello"];
+ expect(a.unshift(1, 2, 3)).toBe(5);
+ expect(a).toEqual([1, 2, 3, "friends", "hello"]);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.values.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.values.js
new file mode 100644
index 0000000000..368f34429f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.values.js
@@ -0,0 +1,44 @@
+test("length", () => {
+ expect(Array.prototype.values.length).toBe(0);
+});
+
+test("basic functionality", () => {
+ const a = [1, 2, 3];
+ const it = a.values();
+ expect(it.next()).toEqual({ value: 1, done: false });
+ expect(it.next()).toEqual({ value: 2, done: false });
+ expect(it.next()).toEqual({ value: 3, done: false });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+});
+
+test("works when applied to non-object", () => {
+ [true, false, 9, 2n, Symbol()].forEach(primitive => {
+ const it = [].values.call(primitive);
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ });
+});
+
+test("item added to array before exhaustion is accessible", () => {
+ const a = [1, 2];
+ const it = a.values();
+ expect(it.next()).toEqual({ value: 1, done: false });
+ expect(it.next()).toEqual({ value: 2, done: false });
+ a.push(3);
+ expect(it.next()).toEqual({ value: 3, done: false });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+});
+
+test("item added to array after exhaustion is inaccessible", () => {
+ const a = [1, 2];
+ const it = a.values();
+ expect(it.next()).toEqual({ value: 1, done: false });
+ expect(it.next()).toEqual({ value: 2, done: false });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ a.push(3);
+ expect(it.next()).toEqual({ value: undefined, done: true });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/array-basic.js b/Userland/Libraries/LibJS/Tests/builtins/Array/array-basic.js
new file mode 100644
index 0000000000..d6e6976421
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/array-basic.js
@@ -0,0 +1,57 @@
+test("basic functionality", () => {
+ var a = [1, 2, 3];
+
+ expect(typeof a).toBe("object");
+ expect(a).toHaveLength(3);
+ expect(a[0]).toBe(1);
+ expect(a[1]).toBe(2);
+ expect(a[2]).toBe(3);
+
+ a[1] = 5;
+ expect(a[1]).toBe(5);
+ expect(a).toHaveLength(3);
+
+ a.push(7);
+ expect(a[3]).toBe(7);
+ expect(a).toHaveLength(4);
+
+ a = [,];
+ expect(a).toHaveLength(1);
+ expect(a.toString()).toBe("");
+ expect(a[0]).toBeUndefined();
+
+ a = [, , , ,];
+ expect(a).toHaveLength(4);
+ expect(a.toString()).toBe(",,,");
+ expect(a[0]).toBeUndefined();
+ expect(a[1]).toBeUndefined();
+ expect(a[2]).toBeUndefined();
+ expect(a[3]).toBeUndefined();
+
+ a = [1, , 2, , , 3];
+ expect(a).toHaveLength(6);
+ expect(a.toString()).toBe("1,,2,,,3");
+ expect(a[0]).toBe(1);
+ expect(a[1]).toBeUndefined();
+ expect(a[2]).toBe(2);
+ expect(a[3]).toBeUndefined();
+ expect(a[4]).toBeUndefined();
+ expect(a[5]).toBe(3);
+
+ a = [1, , 2, , , 3];
+ Object.defineProperty(a, 1, {
+ get() {
+ return this.getterSetterValue;
+ },
+ set(value) {
+ this.getterSetterValue = value;
+ },
+ });
+ expect(a).toHaveLength(6);
+ expect(a.toString()).toBe("1,,2,,,3");
+ expect(a.getterSetterValue).toBeUndefined();
+ a[1] = 20;
+ expect(a).toHaveLength(6);
+ expect(a.toString()).toBe("1,20,2,,,3");
+ expect(a.getterSetterValue).toBe(20);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/array-length-setter.js b/Userland/Libraries/LibJS/Tests/builtins/Array/array-length-setter.js
new file mode 100644
index 0000000000..9a1043f5d4
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/array-length-setter.js
@@ -0,0 +1,37 @@
+describe("errors", () => {
+ test("invalid array length value", () => {
+ var a = [1, 2, 3];
+ [undefined, "foo", -1, Infinity, -Infinity, NaN].forEach(value => {
+ expect(() => {
+ a.length = value;
+ }).toThrowWithMessage(RangeError, "Invalid array length");
+ expect(a).toHaveLength(3);
+ });
+ });
+});
+
+describe("normal behavior", () => {
+ test("extend array by setting length", () => {
+ var a = [1, 2, 3];
+ a.length = 5;
+ expect(a).toEqual([1, 2, 3, undefined, undefined]);
+ });
+
+ test("truncate array by setting length", () => {
+ var a = [1, 2, 3];
+ a.length = 2;
+ expect(a).toEqual([1, 2]);
+ a.length = 0;
+ expect(a).toEqual([]);
+ });
+
+ test("length value is coerced to number if possible", () => {
+ var a = [1, 2, 3];
+ a.length = "42";
+ expect(a).toHaveLength(42);
+ a.length = [];
+ expect(a).toHaveLength(0);
+ a.length = true;
+ expect(a).toHaveLength(1);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/array-shrink-during-find-crash.js b/Userland/Libraries/LibJS/Tests/builtins/Array/array-shrink-during-find-crash.js
new file mode 100644
index 0000000000..d43c5d0d01
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/array-shrink-during-find-crash.js
@@ -0,0 +1,19 @@
+test("Issue #1992, shrinking array during find() iteration", () => {
+ var a, callbackCalled;
+
+ callbackCalled = 0;
+ a = [1, 2, 3, 4, 5];
+ a.find(() => {
+ callbackCalled++;
+ a.pop();
+ });
+ expect(callbackCalled).toBe(5);
+
+ callbackCalled = 0;
+ a = [1, 2, 3, 4, 5];
+ a.findIndex(() => {
+ callbackCalled++;
+ a.pop();
+ });
+ expect(callbackCalled).toBe(5);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/array-simple-and-generic-storage-initialization.js b/Userland/Libraries/LibJS/Tests/builtins/Array/array-simple-and-generic-storage-initialization.js
new file mode 100644
index 0000000000..3202b02c33
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/array-simple-and-generic-storage-initialization.js
@@ -0,0 +1,15 @@
+describe("Issue #3382", () => {
+ test("Creating an array with simple storage (<= 200 initial elements)", () => {
+ var a = Array(200);
+ expect(a).toHaveLength(200);
+ expect(a.push("foo")).toBe(201);
+ expect(a).toHaveLength(201);
+ });
+
+ test("Creating an array with generic storage (> 200 initial elements)", () => {
+ var a = Array(201);
+ expect(a).toHaveLength(201);
+ expect(a.push("foo")).toBe(202);
+ expect(a).toHaveLength(202);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/array-spread.js b/Userland/Libraries/LibJS/Tests/builtins/Array/array-spread.js
new file mode 100644
index 0000000000..cb570edffe
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/array-spread.js
@@ -0,0 +1,25 @@
+describe("errors", () => {
+ test("cannot spread number in array", () => {
+ expect(() => {
+ [...1];
+ }).toThrowWithMessage(TypeError, "1 is not iterable");
+ });
+
+ test("cannot spread object in array", () => {
+ expect(() => {
+ [...{}];
+ }).toThrowWithMessage(TypeError, "[object Object] is not iterable");
+ });
+});
+
+test("basic functionality", () => {
+ expect([1, ...[2, 3], 4]).toEqual([1, 2, 3, 4]);
+
+ let a = [2, 3];
+ expect([1, ...a, 4]).toEqual([1, 2, 3, 4]);
+
+ let obj = { a: [2, 3] };
+ expect([1, ...obj.a, 4]).toEqual([1, 2, 3, 4]);
+
+ expect([...[], ...[...[1, 2, 3]], 4]).toEqual([1, 2, 3, 4]);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.isView.js b/Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.isView.js
new file mode 100644
index 0000000000..db1be84067
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.isView.js
@@ -0,0 +1,27 @@
+// Update when more typed arrays get added
+const TYPED_ARRAYS = [
+ Uint8Array,
+ Uint16Array,
+ Uint32Array,
+ Int8Array,
+ Int16Array,
+ Int32Array,
+ Float32Array,
+ Float64Array,
+];
+
+test("basic functionality", () => {
+ expect(ArrayBuffer.isView).toHaveLength(1);
+
+ expect(ArrayBuffer.isView()).toBeFalse();
+ expect(ArrayBuffer.isView(null)).toBeFalse();
+ expect(ArrayBuffer.isView(undefined)).toBeFalse();
+ expect(ArrayBuffer.isView([])).toBeFalse();
+ expect(ArrayBuffer.isView({})).toBeFalse();
+ expect(ArrayBuffer.isView(123)).toBeFalse();
+ expect(ArrayBuffer.isView("foo")).toBeFalse();
+ expect(ArrayBuffer.isView(new ArrayBuffer())).toBeFalse();
+ TYPED_ARRAYS.forEach(T => {
+ expect(ArrayBuffer.isView(new T())).toBeTrue();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.js b/Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.js
new file mode 100644
index 0000000000..8fb05a250c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.js
@@ -0,0 +1,13 @@
+test("basic functionality", () => {
+ expect(ArrayBuffer).toHaveLength(1);
+ expect(ArrayBuffer.name).toBe("ArrayBuffer");
+ expect(ArrayBuffer.prototype.constructor).toBe(ArrayBuffer);
+ expect(new ArrayBuffer()).toBeInstanceOf(ArrayBuffer);
+ expect(typeof new ArrayBuffer()).toBe("object");
+});
+
+test("ArrayBuffer constructor must be invoked with 'new'", () => {
+ expect(() => {
+ ArrayBuffer();
+ }).toThrowWithMessage(TypeError, "ArrayBuffer constructor must be called with 'new'");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.prototype.byteLength.js b/Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.prototype.byteLength.js
new file mode 100644
index 0000000000..9880e597aa
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/ArrayBuffer/ArrayBuffer.prototype.byteLength.js
@@ -0,0 +1,6 @@
+test("basic functionality", () => {
+ expect(new ArrayBuffer().byteLength).toBe(0);
+ expect(new ArrayBuffer(1).byteLength).toBe(1);
+ expect(new ArrayBuffer(64).byteLength).toBe(64);
+ expect(new ArrayBuffer(123).byteLength).toBe(123);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.js b/Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.js
new file mode 100644
index 0000000000..7e517754b7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.js
@@ -0,0 +1,68 @@
+describe("correct behavior", () => {
+ test("basic functionality", () => {
+ expect(BigInt).toHaveLength(1);
+ expect(BigInt.name).toBe("BigInt");
+ });
+
+ test("constructor with numbers", () => {
+ expect(BigInt(0)).toBe(0n);
+ expect(BigInt(1)).toBe(1n);
+ expect(BigInt(+1)).toBe(1n);
+ expect(BigInt(-1)).toBe(-1n);
+ expect(BigInt(123n)).toBe(123n);
+ });
+
+ test("constructor with strings", () => {
+ expect(BigInt("")).toBe(0n);
+ expect(BigInt("0")).toBe(0n);
+ expect(BigInt("1")).toBe(1n);
+ expect(BigInt("+1")).toBe(1n);
+ expect(BigInt("-1")).toBe(-1n);
+ expect(BigInt("-1")).toBe(-1n);
+ expect(BigInt("42")).toBe(42n);
+ expect(BigInt(" \n 00100 \n ")).toBe(100n);
+ expect(BigInt("3323214327642987348732109829832143298746432437532197321")).toBe(
+ 3323214327642987348732109829832143298746432437532197321n
+ );
+ });
+
+ test("constructor with objects", () => {
+ expect(BigInt([])).toBe(0n);
+ });
+});
+
+describe("errors", () => {
+ test('cannot be constructed with "new"', () => {
+ expect(() => {
+ new BigInt();
+ }).toThrowWithMessage(TypeError, "BigInt is not a constructor");
+ });
+
+ test("invalid arguments", () => {
+ expect(() => {
+ BigInt(null);
+ }).toThrowWithMessage(TypeError, "Cannot convert null to BigInt");
+
+ expect(() => {
+ BigInt(undefined);
+ }).toThrowWithMessage(TypeError, "Cannot convert undefined to BigInt");
+
+ expect(() => {
+ BigInt(Symbol());
+ }).toThrowWithMessage(TypeError, "Cannot convert symbol to BigInt");
+
+ ["foo", "123n", "1+1", {}, function () {}].forEach(value => {
+ expect(() => {
+ BigInt(value);
+ }).toThrowWithMessage(SyntaxError, `Invalid value for BigInt: ${value}`);
+ });
+ });
+
+ test("invalid numeric arguments", () => {
+ [1.23, Infinity, -Infinity, NaN].forEach(value => {
+ expect(() => {
+ BigInt(value);
+ }).toThrowWithMessage(RangeError, "BigInt argument must be an integer");
+ });
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.@@toStringTag.js b/Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.@@toStringTag.js
new file mode 100644
index 0000000000..44812778a4
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.@@toStringTag.js
@@ -0,0 +1,3 @@
+test("basic functionality", () => {
+ expect(BigInt.prototype[Symbol.toStringTag]).toBe("BigInt");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.toLocaleString.js b/Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.toLocaleString.js
new file mode 100644
index 0000000000..1c111c516d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.toLocaleString.js
@@ -0,0 +1,10 @@
+test("basic functionality", () => {
+ expect(BigInt.prototype.toLocaleString).toHaveLength(0);
+ expect(BigInt(123).toLocaleString()).toBe("123");
+});
+
+test("calling with non-BigInt |this|", () => {
+ expect(() => {
+ BigInt.prototype.toLocaleString.call("foo");
+ }).toThrowWithMessage(TypeError, "Not a BigInt object");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.toString.js b/Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.toString.js
new file mode 100644
index 0000000000..040dec60d9
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.toString.js
@@ -0,0 +1,10 @@
+test("basic functionality", () => {
+ expect(BigInt.prototype.toString).toHaveLength(0);
+ expect(BigInt(123).toString()).toBe("123");
+});
+
+test("calling with non-BigInt |this|", () => {
+ expect(() => {
+ BigInt.prototype.toString.call("foo");
+ }).toThrowWithMessage(TypeError, "Not a BigInt object");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.valueOf.js b/Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.valueOf.js
new file mode 100644
index 0000000000..d30a81f824
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/BigInt/BigInt.prototype.valueOf.js
@@ -0,0 +1,12 @@
+test("basic functionality", () => {
+ expect(BigInt.prototype.valueOf).toHaveLength(0);
+ expect(typeof BigInt(123).valueOf()).toBe("bigint");
+ // FIXME: Uncomment once we support Object() with argument
+ // expect(typeof Object(123n).valueOf()).toBe("bigint");
+});
+
+test("calling with non-BigInt |this|", () => {
+ expect(() => {
+ BigInt.prototype.valueOf.call("foo");
+ }).toThrowWithMessage(TypeError, "Not a BigInt object");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/BigInt/bigint-basic.js b/Userland/Libraries/LibJS/Tests/builtins/BigInt/bigint-basic.js
new file mode 100644
index 0000000000..b3fc47d0ad
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/BigInt/bigint-basic.js
@@ -0,0 +1,80 @@
+describe("correct behavior", () => {
+ test("typeof bigint", () => {
+ expect(typeof 1n).toBe("bigint");
+ });
+
+ test("bigint string coercion", () => {
+ expect("" + 123n).toBe("123");
+ });
+
+ test("arithmetic operators", () => {
+ let bigint = 123n;
+ expect(-bigint).toBe(-123n);
+
+ expect(12n + 34n).toBe(46n);
+ expect(12n - 34n).toBe(-22n);
+ expect(8n * 12n).toBe(96n);
+ expect(123n / 10n).toBe(12n);
+ expect(2n ** 3n).toBe(8n);
+ expect(5n % 3n).toBe(2n);
+ expect(
+ 45977665298704210987n +
+ (714320987142450987412098743217984576n / 4598741987421098765327980n) * 987498743n
+ ).toBe(199365500239020623962n);
+ });
+
+ test("bitwise operators", () => {
+ expect(12n & 5n).toBe(4n);
+ expect(1n | 2n).toBe(3n);
+ expect(5n ^ 3n).toBe(6n);
+ expect(~1n).toBe(-2n);
+ });
+
+ test("increment operators", () => {
+ let bigint = 1n;
+ expect(bigint++).toBe(1n);
+ expect(bigint).toBe(2n);
+ expect(bigint--).toBe(2n);
+ expect(bigint).toBe(1n);
+ expect(++bigint).toBe(2n);
+ expect(bigint).toBe(2n);
+ expect(--bigint).toBe(1n);
+ expect(bigint).toBe(1n);
+ });
+
+ test("weak equality operators", () => {
+ expect(1n == 1n).toBeTrue();
+ expect(1n == 1).toBeTrue();
+ expect(1 == 1n).toBeTrue();
+ expect(1n == 1.23).toBeFalse();
+ expect(1.23 == 1n).toBeFalse();
+
+ expect(1n != 1n).toBeFalse();
+ expect(1n != 1).toBeFalse();
+ expect(1 != 1n).toBeFalse();
+ expect(1n != 1.23).toBeTrue();
+ expect(1.23 != 1n).toBeTrue();
+ });
+
+ test("strong equality operators", () => {
+ expect(1n === 1n).toBeTrue();
+ expect(1n === 1).toBeFalse();
+ expect(1 === 1n).toBeFalse();
+ expect(1n === 1.23).toBeFalse();
+ expect(1.23 === 1n).toBeFalse();
+
+ expect(1n !== 1n).toBeFalse();
+ expect(1n !== 1).toBeTrue();
+ expect(1 !== 1n).toBeTrue();
+ expect(1n !== 1.23).toBeTrue();
+ expect(1.23 !== 1n).toBeTrue();
+ });
+});
+
+describe("errors", () => {
+ test("conversion to number", () => {
+ expect(() => {
+ +123n;
+ }).toThrowWithMessage(TypeError, "Cannot convert BigInt to number");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/BigInt/bigint-minus.js b/Userland/Libraries/LibJS/Tests/builtins/BigInt/bigint-minus.js
new file mode 100644
index 0000000000..5d88f85ce2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/BigInt/bigint-minus.js
@@ -0,0 +1,8 @@
+describe("minus behavior", () => {
+ test("the basics", () => {
+ expect(3n - 4n).toBe(-1n);
+ expect(3n - -4n).toBe(7n);
+ expect(-3n - -4n).toBe(-1n);
+ expect(-3n - 4n).toBe(-7n);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/BigInt/bigint-number-mix-errors.js b/Userland/Libraries/LibJS/Tests/builtins/BigInt/bigint-number-mix-errors.js
new file mode 100644
index 0000000000..de80b92b3a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/BigInt/bigint-number-mix-errors.js
@@ -0,0 +1,31 @@
+const doTest = (operatorName, executeOperation) => {
+ [1, null, undefined].forEach(value => {
+ const messageSuffix = operatorName === "unsigned right-shift" ? "" : " and other type";
+
+ expect(() => {
+ executeOperation(1n, value);
+ }).toThrowWithMessage(
+ TypeError,
+ `Cannot use ${operatorName} operator with BigInt${messageSuffix}`
+ );
+ });
+};
+
+[
+ ["addition", (a, b) => a + b],
+ ["subtraction", (a, b) => a - b],
+ ["multiplication", (a, b) => a * b],
+ ["division", (a, b) => a / b],
+ ["modulo", (a, b) => a % b],
+ ["exponentiation", (a, b) => a ** b],
+ ["bitwise OR", (a, b) => a | b],
+ ["bitwise AND", (a, b) => a & b],
+ ["bitwise XOR", (a, b) => a ^ b],
+ ["left-shift", (a, b) => a << b],
+ ["right-shift", (a, b) => a >> b],
+ ["unsigned right-shift", (a, b) => a >>> b],
+].forEach(testCase => {
+ test(`using ${testCase[0]} operator with BigInt and other type`, () => {
+ doTest(testCase[0], testCase[1]);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.js b/Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.js
new file mode 100644
index 0000000000..a2cf07b998
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.js
@@ -0,0 +1,32 @@
+test("constructor properties", () => {
+ expect(Boolean).toHaveLength(1);
+ expect(Boolean.name).toBe("Boolean");
+});
+
+test("typeof", () => {
+ expect(typeof new Boolean()).toBe("object");
+ expect(typeof Boolean()).toBe("boolean");
+ expect(typeof Boolean(true)).toBe("boolean");
+});
+
+test("basic functionality", () => {
+ var foo = new Boolean(true);
+ var bar = new Boolean(true);
+
+ expect(foo).not.toBe(bar);
+ expect(foo.valueOf()).toBe(bar.valueOf());
+
+ expect(Boolean()).toBeFalse();
+ expect(Boolean(false)).toBeFalse();
+ expect(Boolean(null)).toBeFalse();
+ expect(Boolean(undefined)).toBeFalse();
+ expect(Boolean(NaN)).toBeFalse();
+ expect(Boolean("")).toBeFalse();
+ expect(Boolean(0.0)).toBeFalse();
+ expect(Boolean(-0.0)).toBeFalse();
+ expect(Boolean(true)).toBeTrue();
+ expect(Boolean("0")).toBeTrue();
+ expect(Boolean({})).toBeTrue();
+ expect(Boolean([])).toBeTrue();
+ expect(Boolean(1)).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.prototype.js b/Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.prototype.js
new file mode 100644
index 0000000000..d818d91edb
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.prototype.js
@@ -0,0 +1,5 @@
+test("basic functionality", () => {
+ expect(typeof Boolean.prototype).toBe("object");
+ expect(Boolean.prototype.valueOf()).toBeFalse();
+ expect(Boolean.prototype).not.toHaveProperty("length");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.prototype.toString.js b/Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.prototype.toString.js
new file mode 100644
index 0000000000..506b23feac
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.prototype.toString.js
@@ -0,0 +1,17 @@
+test("basic functionality", () => {
+ var foo = true;
+ expect(foo.toString()).toBe("true");
+ expect(true.toString()).toBe("true");
+
+ expect(Boolean.prototype.toString.call(true)).toBe("true");
+ expect(Boolean.prototype.toString.call(false)).toBe("false");
+
+ expect(new Boolean(true).toString()).toBe("true");
+ expect(new Boolean(false).toString()).toBe("false");
+});
+
+test("errors on non-boolean |this|", () => {
+ expect(() => {
+ Boolean.prototype.toString.call("foo");
+ }).toThrowWithMessage(TypeError, "Not a Boolean object");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.prototype.valueOf.js b/Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.prototype.valueOf.js
new file mode 100644
index 0000000000..d510523c5b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Boolean/Boolean.prototype.valueOf.js
@@ -0,0 +1,16 @@
+test("basic functionality", () => {
+ var foo = true;
+ expect(foo.valueOf()).toBeTrue();
+ expect(true.valueOf()).toBeTrue();
+
+ expect(Boolean.prototype.valueOf.call(true)).toBeTrue();
+ expect(Boolean.prototype.valueOf.call(false)).toBeFalse();
+
+ expect(new Boolean().valueOf()).toBeFalse();
+});
+
+test("errors on non-boolean |this|", () => {
+ expect(() => {
+ Boolean.prototype.valueOf.call("foo");
+ }).toThrowWithMessage(TypeError, "Not a Boolean object");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.UTC.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.UTC.js
new file mode 100644
index 0000000000..d5c8082729
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.UTC.js
@@ -0,0 +1,51 @@
+test("basic functionality", () => {
+ expect(Date.UTC(2020)).toBe(1577836800000);
+ expect(Date.UTC(2000, 10)).toBe(973036800000);
+ expect(Date.UTC(1980, 5, 30)).toBe(331171200000);
+ expect(Date.UTC(1980, 5, 30, 13)).toBe(331218000000);
+ expect(Date.UTC(1970, 5, 30, 13, 30)).toBe(15600600000);
+ expect(Date.UTC(1970, 0, 1, 0, 0, 59)).toBe(59000);
+ expect(Date.UTC(1970, 0, 1, 0, 0, 0, 999)).toBe(999);
+
+ expect(Date.UTC(1969, 11, 31, 23, 59, 59, 817)).toBe(-183);
+
+ expect(Date.UTC(1799, 0)).toBe(-5396198400000);
+ expect(Date.UTC(1800, 0)).toBe(-5364662400000);
+ expect(Date.UTC(1801, 0)).toBe(-5333126400000);
+ expect(Date.UTC(1802, 0)).toBe(-5301590400000);
+ expect(Date.UTC(1803, 0)).toBe(-5270054400000);
+ expect(Date.UTC(1804, 0)).toBe(-5238518400000);
+
+ expect(Date.UTC(1999, 0)).toBe(915148800000);
+ expect(Date.UTC(2000, 0)).toBe(946684800000);
+ expect(Date.UTC(2001, 0)).toBe(978307200000);
+ expect(Date.UTC(2002, 0)).toBe(1009843200000);
+ expect(Date.UTC(2003, 0)).toBe(1041379200000);
+ expect(Date.UTC(2004, 0)).toBe(1072915200000);
+
+ expect(Date.UTC(20000, 0)).toBe(568971820800000);
+});
+
+test("leap year", () => {
+ expect(Date.UTC(2020, 2, 1)).toBe(1583020800000);
+});
+
+test("out of range", () => {
+ expect(Date.UTC(2020, -20)).toBe(1525132800000);
+ expect(Date.UTC(2020, 20)).toBe(1630454400000);
+
+ expect(Date.UTC(2020, 1, -10)).toBe(1579564800000);
+ expect(Date.UTC(2020, 1, 40)).toBe(1583884800000);
+
+ expect(Date.UTC(2020, 1, 15, -50)).toBe(1581544800000);
+ expect(Date.UTC(2020, 1, 15, 50)).toBe(1581904800000);
+
+ expect(Date.UTC(2020, 1, 15, 12, -123)).toBe(1581760620000);
+ expect(Date.UTC(2020, 1, 15, 12, 123)).toBe(1581775380000);
+
+ expect(Date.UTC(2020, 1, 15, 12, 30, -123)).toBe(1581769677000);
+ expect(Date.UTC(2020, 1, 15, 12, 30, 123)).toBe(1581769923000);
+
+ expect(Date.UTC(2020, 1, 15, 12, 30, 30, -2345)).toBe(1581769827655);
+ expect(Date.UTC(2020, 1, 15, 12, 30, 30, 2345)).toBe(1581769832345);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.js
new file mode 100644
index 0000000000..cc76212e22
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.js
@@ -0,0 +1,77 @@
+test("basic functionality", () => {
+ expect(Date).toHaveLength(7);
+ expect(Date.name === "Date");
+ expect(Date.prototype).not.toHaveProperty("length");
+});
+
+test("string constructor", () => {
+ // The string constructor is the same as calling the timestamp constructor with the result of Date.parse(arguments).
+ // Since that has exhaustive tests in Date.parse.js, just do some light smoke testing here.
+ expect(new Date("2017-09-07T21:08:59.001Z").toISOString()).toBe("2017-09-07T21:08:59.001Z");
+});
+
+test("timestamp constructor", () => {
+ // The timestamp constructor takes a timestamp in milliseconds since the start of the epoch, in UTC.
+
+ // 50 days and 1234 milliseconds after the start of the epoch.
+ // Most Date methods return values in local time, but since timezone offsets are less than 17 days,
+ // these checks will pass in all timezones.
+ let timestamp = 50 * 24 * 60 * 60 * 1000 + 1234;
+
+ let date = new Date(timestamp);
+ expect(date.getTime()).toBe(timestamp); // getTime() returns the timestamp in UTC.
+ expect(date.getMilliseconds()).toBe(234);
+ expect(date.getSeconds()).toBe(1);
+ expect(date.getFullYear()).toBe(1970);
+ expect(date.getMonth()).toBe(1); // Feb
+});
+
+test("tuple constructor", () => {
+ // The tuple constructor takes a date in local time.
+ expect(new Date(2019, 11).getFullYear()).toBe(2019);
+ expect(new Date(2019, 11).getMonth()).toBe(11);
+ expect(new Date(2019, 11).getDate()).toBe(1); // getDay() returns day of week, getDate() returns day in month
+ expect(new Date(2019, 11).getHours()).toBe(0);
+ expect(new Date(2019, 11).getMinutes()).toBe(0);
+ expect(new Date(2019, 11).getSeconds()).toBe(0);
+ expect(new Date(2019, 11).getMilliseconds()).toBe(0);
+ expect(new Date(2019, 11).getDay()).toBe(0);
+
+ let date = new Date(2019, 11, 15, 9, 16, 14, 123); // Note: Month is 0-based.
+ expect(date.getFullYear()).toBe(2019);
+ expect(date.getMonth()).toBe(11);
+ expect(date.getDate()).toBe(15);
+ expect(date.getHours()).toBe(9);
+ expect(date.getMinutes()).toBe(16);
+ expect(date.getSeconds()).toBe(14);
+ expect(date.getMilliseconds()).toBe(123);
+ expect(date.getDay()).toBe(0);
+
+ // getTime() returns a time stamp in UTC, but we can at least check it's in the right interval, which will be true independent of the local timezone if the range is big enough.
+ let timestamp_lower_bound = 1575072000000; // 2019-12-01T00:00:00Z
+ let timestamp_upper_bound = 1577750400000; // 2019-12-31T00:00:00Z
+ expect(date.getTime()).toBeGreaterThan(timestamp_lower_bound);
+ expect(date.getTime()).toBeLessThan(timestamp_upper_bound);
+});
+
+test("tuple constructor overflow", () => {
+ let date = new Date(2019, 13, 33, 30, 70, 80, 2345);
+ expect(date.getFullYear()).toBe(2020);
+ expect(date.getMonth()).toBe(2);
+ expect(date.getDate()).toBe(5);
+ expect(date.getHours()).toBe(7);
+ expect(date.getMinutes()).toBe(11);
+ expect(date.getSeconds()).toBe(22);
+ expect(date.getMilliseconds()).toBe(345);
+ expect(date.getDay()).toBe(4);
+
+ let date = new Date(2019, -13, -33, -30, -70, -80, -2345);
+ expect(date.getFullYear()).toBe(2017);
+ expect(date.getMonth()).toBe(9);
+ expect(date.getDate()).toBe(26);
+ expect(date.getHours()).toBe(16);
+ expect(date.getMinutes()).toBe(48);
+ expect(date.getSeconds()).toBe(37);
+ expect(date.getMilliseconds()).toBe(655);
+ expect(date.getDay()).toBe(4);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.now.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.now.js
new file mode 100644
index 0000000000..46c1b6d8cd
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.now.js
@@ -0,0 +1,10 @@
+test("basic functionality", () => {
+ var last = 0;
+ for (var i = 0; i < 100; ++i) {
+ var now = Date.now();
+ expect(now).not.toBeNaN();
+ expect(now).toBeGreaterThan(1580000000000);
+ expect(now).toBeGreaterThanOrEqual(last);
+ last = now;
+ }
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.parse.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.parse.js
new file mode 100644
index 0000000000..ca2766ae34
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.parse.js
@@ -0,0 +1,32 @@
+test("basic functionality", () => {
+ expect(Date.parse("2020")).toBe(1577836800000);
+ expect(Date.parse("2000-11")).toBe(973036800000);
+ expect(Date.parse("1980-06-30")).toBe(331171200000);
+ expect(Date.parse("1970-06-30T13:30Z")).toBe(15600600000);
+ expect(Date.parse("1970-01-01T00:00:59Z")).toBe(59000);
+ expect(Date.parse("1970-01-01T00:00:00.999Z")).toBe(999);
+ expect(Date.parse("2020T13:14+15:16")).toBe(1577829480000);
+ expect(Date.parse("2020T13:14-15:16")).toBe(1577939400000);
+ expect(Date.parse("2020T23:59Z")).toBe(1577923140000);
+
+ expect(Date.parse("+020000")).toBe(568971820800000);
+ expect(Date.parse("+020000-01")).toBe(568971820800000);
+ expect(Date.parse("+020000-01T00:00:00.000Z")).toBe(568971820800000);
+
+ expect(Date.parse(2020)).toBe(1577836800000);
+
+ expect(Date.parse("+1980")).toBe(NaN);
+ expect(Date.parse("1980-")).toBe(NaN);
+ expect(Date.parse("1980-05-")).toBe(NaN);
+ expect(Date.parse("1980-05-00T")).toBe(NaN);
+ expect(Date.parse("1980-05-00T15:15:")).toBe(NaN);
+ expect(Date.parse("1980-05-00T15:15:15.")).toBe(NaN);
+ expect(Date.parse("1980-5-30")).toBe(NaN);
+ expect(Date.parse("1980-05-30T13")).toBe(NaN);
+ expect(Date.parse("1980-05-30T13:4")).toBe(NaN);
+ expect(Date.parse("1980-05-30T13:40+")).toBe(NaN);
+ expect(Date.parse("1980-05-30T13:40+1")).toBe(NaN);
+ expect(Date.parse("1980-05-30T13:40+1:10")).toBe(NaN);
+ expect(Date.parse("1970-06-30T13:30Zoo")).toBe(NaN);
+ expect(Date.parse("2020T13:30.40:")).toBe(NaN);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getDate.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getDate.js
new file mode 100644
index 0000000000..647e43a997
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getDate.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ let d = new Date();
+ expect(d.getDate()).toBe(d.getDate());
+ expect(d.getDate()).not.toBeNaN();
+ expect(d.getDate()).toBeGreaterThanOrEqual(1);
+ expect(d.getDate()).toBeLessThanOrEqual(31);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getDay.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getDay.js
new file mode 100644
index 0000000000..534efeeee5
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getDay.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ var d = new Date();
+ expect(d.getDay()).toBe(d.getDay());
+ expect(d.getDay()).not.toBeNaN();
+ expect(d.getDay()).toBeGreaterThanOrEqual(0);
+ expect(d.getDay()).toBeLessThanOrEqual(6);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getFullYear.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getFullYear.js
new file mode 100644
index 0000000000..3748e835cf
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getFullYear.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ var d = new Date();
+ expect(d.getFullYear()).toBe(d.getFullYear());
+ expect(d.getFullYear()).not.toBeNaN();
+ expect(d.getFullYear()).toBe(d.getFullYear());
+ expect(d.getFullYear()).toBeGreaterThanOrEqual(2020);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getHours.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getHours.js
new file mode 100644
index 0000000000..7e562288f2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getHours.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ var d = new Date();
+ expect(d.getHours()).toBe(d.getHours());
+ expect(d.getHours()).not.toBeNaN();
+ expect(d.getHours()).toBeGreaterThanOrEqual(0);
+ expect(d.getHours()).toBeLessThanOrEqual(23);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getMilliseconds.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getMilliseconds.js
new file mode 100644
index 0000000000..90c686f729
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getMilliseconds.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ var d = new Date();
+ expect(d.getMilliseconds()).toBe(d.getMilliseconds());
+ expect(d.getMilliseconds()).not.toBeNaN();
+ expect(d.getMilliseconds()).toBeGreaterThanOrEqual(0);
+ expect(d.getMilliseconds()).toBeLessThanOrEqual(999);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getMinutes.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getMinutes.js
new file mode 100644
index 0000000000..5b87b8251d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getMinutes.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ var d = new Date();
+ expect(d.getMinutes()).toBe(d.getMinutes());
+ expect(d.getMinutes()).not.toBeNaN();
+ expect(d.getMinutes()).toBeGreaterThanOrEqual(0);
+ expect(d.getMinutes()).toBeLessThanOrEqual(59);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getMonth.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getMonth.js
new file mode 100644
index 0000000000..7d52866ce7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getMonth.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ var d = new Date();
+ expect(d.getMonth()).toBe(d.getMonth());
+ expect(d.getMonth()).not.toBeNaN();
+ expect(d.getMonth()).toBeGreaterThanOrEqual(0);
+ expect(d.getMonth()).toBeLessThanOrEqual(11);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getSeconds.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getSeconds.js
new file mode 100644
index 0000000000..df30881c9a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getSeconds.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ var d = new Date();
+ expect(d.getSeconds()).toBe(d.getSeconds());
+ expect(d.getSeconds()).not.toBeNaN();
+ expect(d.getSeconds()).toBeGreaterThanOrEqual(0);
+ expect(d.getSeconds()).toBeLessThanOrEqual(59);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getTime.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getTime.js
new file mode 100644
index 0000000000..b9ada6fcc9
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getTime.js
@@ -0,0 +1,6 @@
+test("basic functionality", () => {
+ var d = new Date();
+ expect(d.getTime()).toBe(d.getTime());
+ expect(d.getTime()).not.toBeNaN();
+ expect(d.getTime()).toBeGreaterThanOrEqual(1580000000000);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCDate.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCDate.js
new file mode 100644
index 0000000000..d207385bad
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCDate.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ let d = new Date();
+ expect(d.getUTCDate()).toBe(d.getUTCDate());
+ expect(d.getUTCDate()).not.toBeNaN();
+ expect(d.getUTCDate()).toBeGreaterThanOrEqual(1);
+ expect(d.getUTCDate()).toBeLessThanOrEqual(31);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCDay.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCDay.js
new file mode 100644
index 0000000000..aec696be4a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCDay.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ var d = new Date();
+ expect(d.getUTCDay()).toBe(d.getUTCDay());
+ expect(d.getUTCDay()).not.toBeNaN();
+ expect(d.getUTCDay()).toBeGreaterThanOrEqual(0);
+ expect(d.getUTCDay()).toBeLessThanOrEqual(6);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCFullYear.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCFullYear.js
new file mode 100644
index 0000000000..50336d8178
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCFullYear.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ var d = new Date();
+ expect(d.getUTCFullYear()).toBe(d.getUTCFullYear());
+ expect(d.getUTCFullYear()).not.toBeNaN();
+ expect(d.getUTCFullYear()).toBe(d.getUTCFullYear());
+ expect(d.getUTCFullYear()).toBeGreaterThanOrEqual(2020);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCHours.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCHours.js
new file mode 100644
index 0000000000..298ed178e5
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCHours.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ var d = new Date();
+ expect(d.getUTCHours()).toBe(d.getUTCHours());
+ expect(d.getUTCHours()).not.toBeNaN();
+ expect(d.getUTCHours()).toBeGreaterThanOrEqual(0);
+ expect(d.getUTCHours()).toBeLessThanOrEqual(23);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCMilliseconds.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCMilliseconds.js
new file mode 100644
index 0000000000..f53ea70428
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCMilliseconds.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ var d = new Date();
+ expect(d.getUTCMilliseconds()).toBe(d.getUTCMilliseconds());
+ expect(d.getUTCMilliseconds()).not.toBeNaN();
+ expect(d.getUTCMilliseconds()).toBeGreaterThanOrEqual(0);
+ expect(d.getUTCMilliseconds()).toBeLessThanOrEqual(999);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCMinutes.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCMinutes.js
new file mode 100644
index 0000000000..6b1146331c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCMinutes.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ var d = new Date();
+ expect(d.getUTCMinutes()).toBe(d.getUTCMinutes());
+ expect(d.getUTCMinutes()).not.toBeNaN();
+ expect(d.getUTCMinutes()).toBeGreaterThanOrEqual(0);
+ expect(d.getUTCMinutes()).toBeLessThanOrEqual(59);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCMonth.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCMonth.js
new file mode 100644
index 0000000000..ed6ed64d97
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCMonth.js
@@ -0,0 +1,26 @@
+test("basic functionality", () => {
+ var d = new Date();
+ expect(d.getUTCMonth()).toBe(d.getUTCMonth());
+ expect(d.getUTCMonth()).not.toBeNaN();
+ expect(d.getUTCMonth()).toBeGreaterThanOrEqual(0);
+ expect(d.getUTCMonth()).toBeLessThanOrEqual(11);
+
+ expect(new Date(Date.UTC(2020, 11)).getUTCMonth()).toBe(11);
+});
+
+test("leap years", () => {
+ expect(new Date(Date.UTC(2019, 1, 29)).getUTCDate()).toBe(1);
+ expect(new Date(Date.UTC(2019, 1, 29)).getUTCMonth()).toBe(2);
+ expect(new Date(Date.UTC(2100, 1, 29)).getUTCDate()).toBe(1);
+ expect(new Date(Date.UTC(2100, 1, 29)).getUTCMonth()).toBe(2);
+
+ expect(new Date(Date.UTC(2000, 1, 29)).getUTCDate()).toBe(29);
+ expect(new Date(Date.UTC(2000, 1, 29)).getUTCMonth()).toBe(1);
+ expect(new Date(Date.UTC(2020, 1, 29)).getUTCDate()).toBe(29);
+ expect(new Date(Date.UTC(2020, 1, 29)).getUTCMonth()).toBe(1);
+
+ expect(new Date(Date.UTC(2019, 2, 1)).getUTCDate()).toBe(1);
+ expect(new Date(Date.UTC(2019, 2, 1)).getUTCMonth()).toBe(2);
+ expect(new Date(Date.UTC(2020, 2, 1)).getUTCDate()).toBe(1);
+ expect(new Date(Date.UTC(2020, 2, 1)).getUTCMonth()).toBe(2);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCSeconds.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCSeconds.js
new file mode 100644
index 0000000000..d087a3c4c8
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.getUTCSeconds.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ var d = new Date();
+ expect(d.getUTCSeconds()).toBe(d.getUTCSeconds());
+ expect(d.getUTCSeconds()).not.toBeNaN();
+ expect(d.getUTCSeconds()).toBeGreaterThanOrEqual(0);
+ expect(d.getUTCSeconds()).toBeLessThanOrEqual(59);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toISOString.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toISOString.js
new file mode 100644
index 0000000000..2b380fc24d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toISOString.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ expect(new Date(1597955034555).toISOString()).toBe("2020-08-20T20:23:54.555Z");
+ expect(new Date(Date.UTC(22020)).toISOString()).toBe("+022020-01-01T00:00:00.000Z");
+ expect(new Date(Date.UTC(1950)).toISOString()).toBe("1950-01-01T00:00:00.000Z");
+ expect(new Date(Date.UTC(1800)).toISOString()).toBe("1800-01-01T00:00:00.000Z");
+ expect(new Date(Date.UTC(-100)).toISOString()).toBe("-000100-01-01T00:00:00.000Z");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Error/Error.js b/Userland/Libraries/LibJS/Tests/builtins/Error/Error.js
new file mode 100644
index 0000000000..a452bd7a88
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Error/Error.js
@@ -0,0 +1,18 @@
+test("basic functionality", () => {
+ expect(Error).toHaveLength(1);
+ expect(Error.name).toBe("Error");
+});
+
+test("name", () => {
+ [Error(), Error(undefined), Error("test"), Error(42), Error(null)].forEach(error => {
+ expect(error.name).toBe("Error");
+ });
+});
+
+test("message", () => {
+ expect(Error().message).toBe("");
+ expect(Error(undefined).message).toBe("");
+ expect(Error("test").message).toBe("test");
+ expect(Error(42).message).toBe("42");
+ expect(Error(null).message).toBe("null");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Error/Error.prototype.name.js b/Userland/Libraries/LibJS/Tests/builtins/Error/Error.prototype.name.js
new file mode 100644
index 0000000000..acfcb039fb
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Error/Error.prototype.name.js
@@ -0,0 +1,10 @@
+test("basic functionality", () => {
+ expect(Error.prototype).not.toHaveProperty("length");
+
+ var changedInstance = new Error("");
+ changedInstance.name = "NewCustomError";
+ expect(changedInstance.name).toBe("NewCustomError");
+
+ var normalInstance = new Error("");
+ expect(normalInstance.name).toBe("Error");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Error/Error.prototype.toString.js b/Userland/Libraries/LibJS/Tests/builtins/Error/Error.prototype.toString.js
new file mode 100644
index 0000000000..b5d2f7dceb
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Error/Error.prototype.toString.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ expect(Error().toString()).toBe("Error");
+ expect(Error(undefined).toString()).toBe("Error");
+ expect(Error(null).toString()).toBe("Error: null");
+ expect(Error("test").toString()).toBe("Error: test");
+ expect(Error(42).toString()).toBe("Error: 42");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Function/Function.js b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.js
new file mode 100644
index 0000000000..9a972ab81d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.js
@@ -0,0 +1,53 @@
+describe("correct behavior", () => {
+ test("constructor properties", () => {
+ expect(Function).toHaveLength(1);
+ expect(Function.name).toBe("Function");
+ expect(Function.prototype).toHaveLength(0);
+ expect(Function.prototype.name).toBe("");
+ });
+
+ test("typeof", () => {
+ expect(typeof Function()).toBe("function");
+ expect(typeof new Function()).toBe("function");
+ });
+
+ test("basic functionality", () => {
+ expect(Function()()).toBeUndefined();
+ expect(new Function()()).toBeUndefined();
+ expect(Function("return 42")()).toBe(42);
+ expect(new Function("return 42")()).toBe(42);
+ expect(new Function("foo", "return foo")(42)).toBe(42);
+ expect(new Function("foo,bar", "return foo + bar")(1, 2)).toBe(3);
+ expect(new Function("foo", "bar", "return foo + bar")(1, 2)).toBe(3);
+ expect(new Function("foo", "bar,baz", "return foo + bar + baz")(1, 2, 3)).toBe(6);
+ expect(new Function("foo", "bar", "baz", "return foo + bar + baz")(1, 2, 3)).toBe(6);
+ expect(new Function("foo", "if (foo) { return 42; } else { return 'bar'; }")(true)).toBe(
+ 42
+ );
+ expect(new Function("foo", "if (foo) { return 42; } else { return 'bar'; }")(false)).toBe(
+ "bar"
+ );
+ expect(new Function("return typeof Function()")()).toBe("function");
+ expect(new Function("x", "return function (y) { return x + y };")(1)(2)).toBe(3);
+
+ expect(new Function("-->")()).toBeUndefined();
+
+ expect(new Function().name).toBe("anonymous");
+ expect(new Function().toString()).toBe("function anonymous() {\n ???\n}");
+ });
+});
+
+describe("errors", () => {
+ test("syntax error", () => {
+ expect(() => {
+ new Function("[");
+ })
+ // This might be confusing at first but keep in mind it's actually parsing
+ // function anonymous() { [ }
+ // This is in line with what other engines are reporting.
+ .toThrowWithMessage(
+ SyntaxError,
+ "Unexpected token CurlyClose. Expected BracketClose (line: 4, column: 1)"
+ );
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.@@hasInstance.js b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.@@hasInstance.js
new file mode 100644
index 0000000000..d548a635fe
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.@@hasInstance.js
@@ -0,0 +1,8 @@
+test("basic functionality", () => {
+ expect(Function.prototype[Symbol.hasInstance]).toHaveLength(1);
+
+ function Foo() {}
+ const foo = new Foo();
+
+ expect(Function.prototype[Symbol.hasInstance].call(Foo, foo)).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.apply.js b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.apply.js
new file mode 100644
index 0000000000..1c57e4a657
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.apply.js
@@ -0,0 +1,51 @@
+test("basic functionality", () => {
+ function Foo(arg) {
+ this.foo = arg;
+ }
+ function Bar(arg) {
+ this.bar = arg;
+ }
+ function FooBar(arg) {
+ Foo.apply(this, [arg]);
+ Bar.apply(this, [arg]);
+ }
+ function FooBarBaz(arg) {
+ Foo.apply(this, [arg]);
+ Bar.apply(this, [arg]);
+ this.baz = arg;
+ }
+
+ expect(Function.prototype.apply).toHaveLength(2);
+
+ var foo = new Foo("test");
+ expect(foo.foo).toBe("test");
+ expect(foo.bar).toBeUndefined();
+ expect(foo.baz).toBeUndefined();
+
+ var bar = new Bar("test");
+ expect(bar.foo).toBeUndefined();
+ expect(bar.bar).toBe("test");
+ expect(bar.baz).toBeUndefined();
+
+ var foobar = new FooBar("test");
+ expect(foobar.foo).toBe("test");
+ expect(foobar.bar).toBe("test");
+ expect(foobar.baz).toBeUndefined();
+
+ var foobarbaz = new FooBarBaz("test");
+ expect(foobarbaz.foo).toBe("test");
+ expect(foobarbaz.bar).toBe("test");
+ expect(foobarbaz.baz).toBe("test");
+
+ expect(Math.abs.apply(null, [-1])).toBe(1);
+
+ var add = (x, y) => x + y;
+ expect(add.apply(null, [1, 2])).toBe(3);
+
+ var multiply = function (x, y) {
+ return x * y;
+ };
+ expect(multiply.apply(null, [3, 4])).toBe(12);
+
+ expect((() => this).apply("foo")).toBe(globalThis);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.bind.js b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.bind.js
new file mode 100644
index 0000000000..1bd5269f1d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.bind.js
@@ -0,0 +1,149 @@
+describe("basic behavior", () => {
+ test("basic binding", () => {
+ expect(Function.prototype.bind).toHaveLength(1);
+
+ var charAt = String.prototype.charAt.bind("bar");
+ expect(charAt(0) + charAt(1) + charAt(2)).toBe("bar");
+
+ function getB() {
+ return this.toUpperCase().charAt(0);
+ }
+ expect(getB.bind("bar")()).toBe("B");
+ });
+
+ test("bound functions work with array functions", () => {
+ var Make3 = Number.bind(null, 3);
+ expect([55].map(Make3)[0]).toBe(3);
+
+ var MakeTrue = Boolean.bind(null, true);
+
+ expect([1, 2, 3].filter(MakeTrue)).toHaveLength(3);
+ expect(
+ [1, 2, 3].reduce(
+ function (acc, x) {
+ return acc + x;
+ }.bind(null, 4, 5)
+ )
+ ).toBe(9);
+ expect(
+ [1, 2, 3].reduce(
+ function (acc, x) {
+ return acc + x + this;
+ }.bind(3)
+ )
+ ).toBe(12);
+ });
+});
+
+describe("bound function arguments", () => {
+ function sum(a, b, c) {
+ return a + b + c;
+ }
+ var boundSum = sum.bind(null, 10, 5);
+
+ test("arguments are bound to the function", () => {
+ expect(boundSum()).toBeNaN();
+ expect(boundSum(5)).toBe(20);
+ expect(boundSum(5, 6, 7)).toBe(20);
+ });
+
+ test("arguments are appended to a BoundFunction's bound arguments", () => {
+ expect(boundSum.bind(null, 5)()).toBe(20);
+ });
+
+ test("binding a constructor's arguments", () => {
+ var Make5 = Number.bind(null, 5);
+ expect(Make5()).toBe(5);
+ expect(new Make5().valueOf()).toBe(5);
+ });
+
+ test("length property", () => {
+ expect(sum).toHaveLength(3);
+ expect(boundSum).toHaveLength(1);
+ expect(boundSum.bind(null, 5)).toHaveLength(0);
+ expect(boundSum.bind(null, 5, 6, 7, 8)).toHaveLength(0);
+ });
+});
+
+describe("bound function |this|", () => {
+ function identity() {
+ return this;
+ }
+
+ test("captures global object as |this| if |this| is null or undefined", () => {
+ expect(identity.bind()()).toBe(globalThis);
+ expect(identity.bind(null)()).toBe(globalThis);
+ expect(identity.bind(undefined)()).toBe(globalThis);
+
+ function Foo() {
+ expect(identity.bind()()).toBe(globalThis);
+ expect(identity.bind(this)()).toBe(this);
+ }
+ new Foo();
+ });
+
+ test("does not capture global object as |this| if |this| is null or undefined in strict mode", () => {
+ "use strict";
+
+ function strictIdentity() {
+ return this;
+ }
+
+ expect(strictIdentity.bind()()).toBeUndefined();
+ expect(strictIdentity.bind(null)()).toBeNull();
+ expect(strictIdentity.bind(undefined)()).toBeUndefined();
+ });
+
+ test("primitive |this| values are converted to objects", () => {
+ expect(identity.bind("foo")()).toBeInstanceOf(String);
+ expect(identity.bind(123)()).toBeInstanceOf(Number);
+ expect(identity.bind(true)()).toBeInstanceOf(Boolean);
+ });
+
+ test("bound functions retain |this| values passed to them", () => {
+ var obj = { foo: "bar" };
+ expect(identity.bind(obj)()).toBe(obj);
+ });
+
+ test("bound |this| cannot be changed after being set", () => {
+ expect(identity.bind("foo").bind(123)()).toBeInstanceOf(String);
+ });
+
+ test("arrow functions cannot be bound", () => {
+ expect((() => this).bind("foo")()).toBe(globalThis);
+ });
+});
+
+describe("bound function constructors", () => {
+ function Bar() {
+ this.x = 3;
+ this.y = 4;
+ }
+
+ Bar.prototype.baz = "baz";
+ var BoundBar = Bar.bind({ u: 5, v: 6 });
+ var bar = new BoundBar();
+
+ test("bound |this| value does not affect constructor", () => {
+ expect(bar.x).toBe(3);
+ expect(bar.y).toBe(4);
+ expect(typeof bar.u).toBe("undefined");
+ expect(typeof bar.v).toBe("undefined");
+ });
+
+ test("bound functions retain original prototype", () => {
+ expect(bar.baz).toBe("baz");
+ });
+
+ test("bound functions do not have a prototype property", () => {
+ expect(BoundBar).not.toHaveProperty("prototype");
+ });
+});
+
+describe("errors", () => {
+ test("does not accept non-function values", () => {
+ expect(() => {
+ Function.prototype.bind.call("foo");
+ }).toThrowWithMessage(TypeError, "Not a Function object");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.call.js b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.call.js
new file mode 100644
index 0000000000..136f69b2bd
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.call.js
@@ -0,0 +1,53 @@
+test("length", () => {
+ expect(Function.prototype.call).toHaveLength(1);
+});
+
+test("basic functionality", () => {
+ function Foo(arg) {
+ this.foo = arg;
+ }
+ function Bar(arg) {
+ this.bar = arg;
+ }
+ function FooBar(arg) {
+ Foo.call(this, arg);
+ Bar.call(this, arg);
+ }
+ function FooBarBaz(arg) {
+ Foo.call(this, arg);
+ Bar.call(this, arg);
+ this.baz = arg;
+ }
+
+ var foo = new Foo("test");
+ expect(foo.foo).toBe("test");
+ expect(foo.bar).toBeUndefined();
+ expect(foo.baz).toBeUndefined();
+
+ var bar = new Bar("test");
+ expect(bar.foo).toBeUndefined();
+ expect(bar.bar).toBe("test");
+ expect(bar.baz).toBeUndefined();
+
+ var foobar = new FooBar("test");
+ expect(foobar.foo).toBe("test");
+ expect(foobar.bar).toBe("test");
+ expect(foobar.baz).toBeUndefined();
+
+ var foobarbaz = new FooBarBaz("test");
+ expect(foobarbaz.foo).toBe("test");
+ expect(foobarbaz.bar).toBe("test");
+ expect(foobarbaz.baz).toBe("test");
+
+ expect(Math.abs.call(null, -1)).toBe(1);
+
+ var add = (x, y) => x + y;
+ expect(add.call(null, 1, 2)).toBe(3);
+
+ var multiply = function (x, y) {
+ return x * y;
+ };
+ expect(multiply.call(null, 3, 4)).toBe(12);
+
+ expect((() => this).call("foo")).toBe(globalThis);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.toString.js b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.toString.js
new file mode 100644
index 0000000000..a8ed197930
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.toString.js
@@ -0,0 +1,17 @@
+test("basic functionality", () => {
+ expect(function () {}.toString()).toBe("function () {\n ???\n}");
+ expect(function (foo) {}.toString()).toBe("function (foo) {\n ???\n}");
+ expect(function (foo, bar, baz) {}.toString()).toBe("function (foo, bar, baz) {\n ???\n}");
+ expect(
+ function (foo, bar, baz) {
+ if (foo) {
+ return baz;
+ } else if (bar) {
+ return foo;
+ }
+ return bar + 42;
+ }.toString()
+ ).toBe("function (foo, bar, baz) {\n ???\n}");
+ expect(console.debug.toString()).toBe("function debug() {\n [NativeFunction]\n}");
+ expect(Function.toString()).toBe("function Function() {\n [FunctionConstructor]\n}");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Infinity/Infinity.js b/Userland/Libraries/LibJS/Tests/builtins/Infinity/Infinity.js
new file mode 100644
index 0000000000..0c88544f7f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Infinity/Infinity.js
@@ -0,0 +1,22 @@
+test("basic functionality", () => {
+ expect(Infinity + "").toBe("Infinity");
+ expect(-Infinity + "").toBe("-Infinity");
+ expect(Infinity).toBe(Infinity);
+ expect(Infinity - 1).toBe(Infinity);
+ expect(Infinity + 1).toBe(Infinity);
+ expect(-Infinity).toBe(-Infinity);
+ expect(-Infinity - 1).toBe(-Infinity);
+ expect(-Infinity + 1).toBe(-Infinity);
+ expect(1 / Infinity).toBe(0);
+ expect(1 / -Infinity).toBe(-0);
+ expect(1 / 0).toBe(Infinity);
+ expect(-1 / 0).toBe(-Infinity);
+ expect(-100).toBeLessThan(Infinity);
+ expect(0).toBeLessThan(Infinity);
+ expect(100).toBeLessThan(Infinity);
+ expect(-Infinity).toBeLessThan(Infinity);
+ expect(-100).toBeGreaterThan(-Infinity);
+ expect(0).toBeGreaterThan(-Infinity);
+ expect(100).toBeGreaterThan(-Infinity);
+ expect(Infinity).toBeGreaterThan(-Infinity);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.@@toStringTag.js b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.@@toStringTag.js
new file mode 100644
index 0000000000..5a1e51f7c6
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.@@toStringTag.js
@@ -0,0 +1,4 @@
+test("basic functionality", () => {
+ expect(JSON[Symbol.toStringTag]).toBe("JSON");
+ expect(JSON.toString()).toBe("[object JSON]");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.parse-reviver.js b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.parse-reviver.js
new file mode 100644
index 0000000000..4044e0200a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.parse-reviver.js
@@ -0,0 +1,11 @@
+test("basic functionality", () => {
+ let string = `{"var1":10,"var2":"hello","var3":{"nested":5}}`;
+
+ let object = JSON.parse(string, (key, value) =>
+ typeof value === "number" ? value * 2 : value
+ );
+ expect(object).toEqual({ var1: 20, var2: "hello", var3: { nested: 10 } });
+
+ object = JSON.parse(string, (key, value) => (typeof value === "number" ? undefined : value));
+ expect(object).toEqual({ var2: "hello", var3: {} });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.parse.js b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.parse.js
new file mode 100644
index 0000000000..7f82392666
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.parse.js
@@ -0,0 +1,37 @@
+test("basic functionality", () => {
+ expect(JSON.parse).toHaveLength(2);
+
+ const properties = [
+ ["5", 5],
+ ["null", null],
+ ["true", true],
+ ["false", false],
+ ['"test"', "test"],
+ ['[1,2,"foo"]', [1, 2, "foo"]],
+ ['{"foo":1,"bar":"baz"}', { foo: 1, bar: "baz" }],
+ ];
+
+ properties.forEach(testCase => {
+ expect(JSON.parse(testCase[0])).toEqual(testCase[1]);
+ });
+});
+
+test("syntax errors", () => {
+ [
+ undefined,
+ NaN,
+ -NaN,
+ Infinity,
+ -Infinity,
+ '{ "foo" }',
+ '{ foo: "bar" }',
+ "[1,2,3,]",
+ "[1,2,3, ]",
+ '{ "foo": "bar",}',
+ '{ "foo": "bar", }',
+ ].forEach(test => {
+ expect(() => {
+ JSON.parse(test);
+ }).toThrow(SyntaxError);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-exception-in-property-getter.js b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-exception-in-property-getter.js
new file mode 100644
index 0000000000..18845f842b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-exception-in-property-getter.js
@@ -0,0 +1,10 @@
+test("Issue #3548, exception in property getter with replacer function", () => {
+ const o = {
+ get foo() {
+ throw Error();
+ },
+ };
+ expect(() => {
+ JSON.stringify(o, (_, value) => value);
+ }).toThrow(Error);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-order.js b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-order.js
new file mode 100644
index 0000000000..3c2c8e3161
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-order.js
@@ -0,0 +1,30 @@
+test("basic functionality", () => {
+ let o = {
+ key1: "key1",
+ key2: "key2",
+ key3: "key3",
+ };
+
+ Object.defineProperty(o, "defined", {
+ enumerable: true,
+ get() {
+ o.prop = "prop";
+ return "defined";
+ },
+ });
+
+ o.key4 = "key4";
+
+ o[2] = 2;
+ o[0] = 0;
+ o[1] = 1;
+
+ delete o.key1;
+ delete o.key3;
+
+ o.key1 = "key1";
+
+ expect(JSON.stringify(o)).toBe(
+ '{"0":0,"1":1,"2":2,"key2":"key2","defined":"defined","key4":"key4","key1":"key1"}'
+ );
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-proxy.js b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-proxy.js
new file mode 100644
index 0000000000..eb4f4c85e8
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-proxy.js
@@ -0,0 +1,11 @@
+test("basic functionality", () => {
+ let p = new Proxy([], {
+ get(_, key) {
+ if (key === "length") return 3;
+ return Number(key);
+ },
+ });
+
+ expect(JSON.stringify(p)).toBe("[0,1,2]");
+ expect(JSON.stringify([[new Proxy(p, {})]])).toBe("[[[0,1,2]]]");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-replacer.js b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-replacer.js
new file mode 100644
index 0000000000..156edffee7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-replacer.js
@@ -0,0 +1,38 @@
+test("basic functionality", () => {
+ let o = {
+ var1: "foo",
+ var2: 42,
+ arr: [
+ 1,
+ 2,
+ {
+ nested: {
+ hello: "world",
+ },
+ get x() {
+ return 10;
+ },
+ },
+ ],
+ obj: {
+ subarr: [3],
+ },
+ };
+
+ let string = JSON.stringify(o, (key, value) => {
+ if (key === "hello") return undefined;
+ if (value === 10) return 20;
+ if (key === "subarr") return [3, 4, 5];
+ return value;
+ });
+
+ expect(string).toBe(
+ '{"var1":"foo","var2":42,"arr":[1,2,{"nested":{},"x":20}],"obj":{"subarr":[3,4,5]}}'
+ );
+
+ string = JSON.stringify(o, ["var1", "var1", "var2", "obj"]);
+ expect(string).toBe('{"var1":"foo","var2":42,"obj":{}}');
+
+ string = JSON.stringify(o, ["var1", "var1", "var2", "obj", "subarr"]);
+ expect(string).toBe('{"var1":"foo","var2":42,"obj":{"subarr":[3]}}');
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-space.js b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-space.js
new file mode 100644
index 0000000000..75873367bf
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify-space.js
@@ -0,0 +1,47 @@
+test("basic functionality", () => {
+ let o = {
+ foo: 1,
+ bar: "baz",
+ qux: {
+ get x() {
+ return 10;
+ },
+ y() {
+ return 20;
+ },
+ arr: [1, 2, 3],
+ },
+ };
+
+ let string = JSON.stringify(o, null, 4);
+ let expected = `{
+ "foo": 1,
+ "bar": "baz",
+ "qux": {
+ "x": 10,
+ "arr": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+}`;
+
+ expect(string).toBe(expected);
+
+ string = JSON.stringify(o, null, "abcd");
+ expected = `{
+abcd"foo": 1,
+abcd"bar": "baz",
+abcd"qux": {
+abcdabcd"x": 10,
+abcdabcd"arr": [
+abcdabcdabcd1,
+abcdabcdabcd2,
+abcdabcdabcd3
+abcdabcd]
+abcd}
+}`;
+
+ expect(string).toBe(expected);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify.js b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify.js
new file mode 100644
index 0000000000..1e5c174312
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/JSON/JSON.stringify.js
@@ -0,0 +1,71 @@
+describe("correct behavior", () => {
+ test("length", () => {
+ expect(JSON.stringify).toHaveLength(3);
+ });
+
+ test("basic functionality", () => {
+ [
+ [5, "5"],
+ [undefined, undefined],
+ [null, "null"],
+ [NaN, "null"],
+ [-NaN, "null"],
+ [Infinity, "null"],
+ [-Infinity, "null"],
+ [true, "true"],
+ [false, "false"],
+ ["test", '"test"'],
+ [new Number(5), "5"],
+ [new Boolean(false), "false"],
+ [new String("test"), '"test"'],
+ [() => {}, undefined],
+ [[1, 2, "foo"], '[1,2,"foo"]'],
+ [{ foo: 1, bar: "baz", qux() {} }, '{"foo":1,"bar":"baz"}'],
+ [
+ {
+ var1: 1,
+ var2: 2,
+ toJSON(key) {
+ let o = this;
+ o.var2 = 10;
+ return o;
+ },
+ },
+ '{"var1":1,"var2":10}',
+ ],
+ ].forEach(testCase => {
+ expect(JSON.stringify(testCase[0])).toEqual(testCase[1]);
+ });
+ });
+
+ test("ignores non-enumerable properties", () => {
+ let o = { foo: "bar" };
+ Object.defineProperty(o, "baz", { value: "qux", enumerable: false });
+ expect(JSON.stringify(o)).toBe('{"foo":"bar"}');
+ });
+});
+
+describe("errors", () => {
+ test("cannot serialize BigInt", () => {
+ expect(() => {
+ JSON.stringify(5n);
+ }).toThrow(TypeError, "Cannot serialize BigInt value to JSON");
+ });
+
+ test("cannot serialize circular structures", () => {
+ let bad1 = {};
+ bad1.foo = bad1;
+ let bad2 = [];
+ bad2[5] = [[[bad2]]];
+
+ let bad3a = { foo: "bar" };
+ let bad3b = [1, 2, bad3a];
+ bad3a.bad = bad3b;
+
+ [bad1, bad2, bad3a].forEach(bad => {
+ expect(() => {
+ JSON.stringify(bad);
+ }).toThrow(TypeError, "Cannot stringify circular object");
+ });
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math-constants.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math-constants.js
new file mode 100644
index 0000000000..f8c75c269b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math-constants.js
@@ -0,0 +1,10 @@
+test("basic functionality", () => {
+ expect(Math.E).toBeCloseTo(2.718281);
+ expect(Math.LN2).toBeCloseTo(0.693147);
+ expect(Math.LN10).toBeCloseTo(2.302585);
+ expect(Math.LOG2E).toBeCloseTo(1.442695);
+ expect(Math.LOG10E).toBeCloseTo(0.434294);
+ expect(Math.PI).toBeCloseTo(3.1415926);
+ expect(Math.SQRT1_2).toBeCloseTo(0.707106);
+ expect(Math.SQRT2).toBeCloseTo(1.414213);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.@@toStringTag.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.@@toStringTag.js
new file mode 100644
index 0000000000..69815a1499
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.@@toStringTag.js
@@ -0,0 +1,4 @@
+test("basic functionality", () => {
+ expect(Math[Symbol.toStringTag]).toBe("Math");
+ expect(Math.toString()).toBe("[object Math]");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.abs.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.abs.js
new file mode 100644
index 0000000000..a43482c28f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.abs.js
@@ -0,0 +1,14 @@
+test("basic functionality", () => {
+ expect(Math.abs).toHaveLength(1);
+
+ expect(Math.abs("-1")).toBe(1);
+ expect(Math.abs(-2)).toBe(2);
+ expect(Math.abs(null)).toBe(0);
+ expect(Math.abs("")).toBe(0);
+ expect(Math.abs([])).toBe(0);
+ expect(Math.abs([2])).toBe(2);
+ expect(Math.abs([1, 2])).toBeNaN();
+ expect(Math.abs({})).toBeNaN();
+ expect(Math.abs("string")).toBeNaN();
+ expect(Math.abs()).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.acosh.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.acosh.js
new file mode 100644
index 0000000000..11d16aabeb
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.acosh.js
@@ -0,0 +1,9 @@
+test("basic functionality", () => {
+ expect(Math.acosh).toHaveLength(1);
+
+ expect(Math.acosh(-1)).toBeNaN();
+ expect(Math.acosh(0)).toBeNaN();
+ expect(Math.acosh(0.5)).toBeNaN();
+ expect(Math.acosh(1)).toBeCloseTo(0);
+ expect(Math.acosh(2)).toBeCloseTo(1.316957);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.asin.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.asin.js
new file mode 100644
index 0000000000..928ce7be5c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.asin.js
@@ -0,0 +1,15 @@
+test("basic functionality", () => {
+ expect(Math.asin).toHaveLength(1);
+
+ expect(Math.asin(0)).toBe(0);
+ expect(Math.asin(null)).toBe(0);
+ expect(Math.asin("")).toBe(0);
+ expect(Math.asin([])).toBe(0);
+ // FIXME(LibM): expect(Math.asin(1)).toBeCloseTo(1.5707963267948966);
+ // FIXME(LibM): expect(Math.asin(-1)).toBeCloseTo(-1.5707963267948966);
+ expect(Math.asin()).toBeNaN();
+ expect(Math.asin(undefined)).toBeNaN();
+ expect(Math.asin([1, 2, 3])).toBeNaN();
+ expect(Math.asin({})).toBeNaN();
+ expect(Math.asin("foo")).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.asinh.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.asinh.js
new file mode 100644
index 0000000000..f52f6c2d0b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.asinh.js
@@ -0,0 +1,6 @@
+test("basic functionality", () => {
+ expect(Math.asinh).toHaveLength(1);
+
+ expect(Math.asinh(0)).toBeCloseTo(0);
+ expect(Math.asinh(1)).toBeCloseTo(0.881373);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.atan.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.atan.js
new file mode 100644
index 0000000000..ff526fa367
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.atan.js
@@ -0,0 +1,12 @@
+test("basic functionality", () => {
+ expect(Math.atan).toHaveLength(1);
+
+ expect(Math.atan(0)).toBe(0);
+ expect(Math.atan(-0)).toBe(-0);
+ expect(Math.atan(NaN)).toBeNaN();
+ expect(Math.atan(-2)).toBeCloseTo(-1.1071487177940904);
+ expect(Math.atan(2)).toBeCloseTo(1.1071487177940904);
+ expect(Math.atan(Infinity)).toBeCloseTo(Math.PI / 2);
+ expect(Math.atan(-Infinity)).toBeCloseTo(-Math.PI / 2);
+ expect(Math.atan(0.5)).toBeCloseTo(0.4636476090008061);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.atan2.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.atan2.js
new file mode 100644
index 0000000000..106fee51cc
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.atan2.js
@@ -0,0 +1,28 @@
+test("basic functionality", () => {
+ expect(Math.atan2).toHaveLength(2);
+
+ expect(Math.atan2(90, 15)).toBeCloseTo(1.4056476493802699);
+ expect(Math.atan2(15, 90)).toBeCloseTo(0.16514867741462683);
+ expect(Math.atan2(+0, -0)).toBeCloseTo(Math.PI);
+ expect(Math.atan2(-0, -0)).toBeCloseTo(-Math.PI);
+ expect(Math.atan2(+0, +0)).toBe(0);
+ expect(Math.atan2(-0, +0)).toBe(-0);
+ expect(Math.atan2(+0, -1)).toBeCloseTo(Math.PI);
+ expect(Math.atan2(-0, -1)).toBeCloseTo(-Math.PI);
+ expect(Math.atan2(+0, 1)).toBe(0);
+ expect(Math.atan2(-0, 1)).toBe(-0);
+ expect(Math.atan2(-1, +0)).toBeCloseTo(-Math.PI / 2);
+ expect(Math.atan2(-1, -0)).toBeCloseTo(-Math.PI / 2);
+ expect(Math.atan2(1, +0)).toBeCloseTo(Math.PI / 2);
+ expect(Math.atan2(1, -0)).toBeCloseTo(Math.PI / 2);
+ expect(Math.atan2(1, -Infinity)).toBeCloseTo(Math.PI);
+ expect(Math.atan2(-1, -Infinity)).toBeCloseTo(-Math.PI);
+ expect(Math.atan2(1, +Infinity)).toBe(0);
+ expect(Math.atan2(-1, +Infinity)).toBe(-0);
+ expect(Math.atan2(+Infinity, 1)).toBeCloseTo(Math.PI / 2);
+ expect(Math.atan2(-Infinity, 1)).toBeCloseTo(-Math.PI / 2);
+ expect(Math.atan2(+Infinity, -Infinity)).toBeCloseTo((3 * Math.PI) / 4);
+ expect(Math.atan2(-Infinity, -Infinity)).toBeCloseTo((-3 * Math.PI) / 4);
+ expect(Math.atan2(+Infinity, +Infinity)).toBeCloseTo(Math.PI / 4);
+ expect(Math.atan2(-Infinity, +Infinity)).toBeCloseTo(-Math.PI / 4);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.atanh.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.atanh.js
new file mode 100644
index 0000000000..54c8f5c54e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.atanh.js
@@ -0,0 +1,10 @@
+test("basic functionality", () => {
+ expect(Math.atanh).toHaveLength(1);
+
+ expect(Math.atanh(-2)).toBeNaN();
+ expect(Math.atanh(2)).toBeNaN();
+ expect(Math.atanh(-1)).toBe(-Infinity);
+ expect(Math.atanh(0)).toBe(0);
+ expect(Math.atanh(0.5)).toBeCloseTo(0.549306);
+ expect(Math.atanh(1)).toBe(Infinity);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.cbrt.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.cbrt.js
new file mode 100644
index 0000000000..414016a7af
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.cbrt.js
@@ -0,0 +1,12 @@
+test("basic functionality", () => {
+ expect(Math.cbrt).toHaveLength(1);
+
+ expect(Math.cbrt(NaN)).toBeNaN();
+ expect(Math.cbrt(-1)).toBe(-1);
+ expect(Math.cbrt(-0)).toBe(-0);
+ expect(Math.cbrt(-Infinity)).toBe(-Infinity);
+ expect(Math.cbrt(1)).toBe(1);
+ expect(Math.cbrt(Infinity)).toBe(Infinity);
+ expect(Math.cbrt(null)).toBe(0);
+ expect(Math.cbrt(2)).toBeCloseTo(1.259921);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.ceil.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.ceil.js
new file mode 100644
index 0000000000..93e35babca
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.ceil.js
@@ -0,0 +1,13 @@
+test("basic functionality", () => {
+ expect(Math.ceil).toHaveLength(1);
+
+ expect(Math.ceil(0.95)).toBe(1);
+ expect(Math.ceil(4)).toBe(4);
+ expect(Math.ceil(7.004)).toBe(8);
+ expect(Math.ceil(-0.95)).toBe(-0);
+ expect(Math.ceil(-4)).toBe(-4);
+ expect(Math.ceil(-7.004)).toBe(-7);
+
+ expect(Math.ceil()).toBeNaN();
+ expect(Math.ceil(NaN)).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.clz32.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.clz32.js
new file mode 100644
index 0000000000..e80f9f023e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.clz32.js
@@ -0,0 +1,44 @@
+test("basic functionality", () => {
+ expect(Math.clz32).toHaveLength(1);
+
+ expect(Math.clz32(0)).toBe(32);
+ expect(Math.clz32(1)).toBe(31);
+ expect(Math.clz32(2)).toBe(30);
+ expect(Math.clz32(3)).toBe(30);
+ expect(Math.clz32(4)).toBe(29);
+ expect(Math.clz32(5)).toBe(29);
+ expect(Math.clz32(-1)).toBe(0);
+ expect(Math.clz32(-10)).toBe(0);
+ expect(Math.clz32(-100)).toBe(0);
+ expect(Math.clz32(-1000)).toBe(0);
+ expect(Math.clz32(-0.123)).toBe(32);
+ expect(Math.clz32(0.123)).toBe(32);
+ expect(Math.clz32(1.23)).toBe(31);
+ expect(Math.clz32(12)).toBe(28);
+ expect(Math.clz32(123)).toBe(25);
+ expect(Math.clz32(1234)).toBe(21);
+ expect(Math.clz32(12345)).toBe(18);
+ expect(Math.clz32(123456)).toBe(15);
+ expect(Math.clz32(1234567)).toBe(11);
+ expect(Math.clz32(12345678)).toBe(8);
+ expect(Math.clz32(123456789)).toBe(5);
+ expect(Math.clz32(999999999)).toBe(2);
+ expect(Math.clz32(9999999999)).toBe(1);
+ expect(Math.clz32(99999999999)).toBe(1);
+ expect(Math.clz32(999999999999)).toBe(0);
+ expect(Math.clz32(9999999999999)).toBe(1);
+ expect(Math.clz32(99999999999999)).toBe(3);
+ expect(Math.clz32(999999999999999)).toBe(0);
+
+ expect(Math.clz32()).toBe(32);
+ expect(Math.clz32(NaN)).toBe(32);
+ expect(Math.clz32(Infinity)).toBe(32);
+ expect(Math.clz32(-Infinity)).toBe(32);
+ expect(Math.clz32(false)).toBe(32);
+ expect(Math.clz32(true)).toBe(31);
+ expect(Math.clz32(null)).toBe(32);
+ expect(Math.clz32(undefined)).toBe(32);
+ expect(Math.clz32([])).toBe(32);
+ expect(Math.clz32({})).toBe(32);
+ expect(Math.clz32("foo")).toBe(32);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.cos.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.cos.js
new file mode 100644
index 0000000000..8186bc4199
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.cos.js
@@ -0,0 +1,14 @@
+test("basic functionality", () => {
+ expect(Math.cos).toHaveLength(1);
+
+ expect(Math.cos(0)).toBe(1);
+ expect(Math.cos(null)).toBe(1);
+ expect(Math.cos("")).toBe(1);
+ expect(Math.cos([])).toBe(1);
+ expect(Math.cos(Math.PI)).toBe(-1);
+ expect(Math.cos()).toBeNaN();
+ expect(Math.cos(undefined)).toBeNaN();
+ expect(Math.cos([1, 2, 3])).toBeNaN();
+ expect(Math.cos({})).toBeNaN();
+ expect(Math.cos("foo")).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.cosh.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.cosh.js
new file mode 100644
index 0000000000..928b427028
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.cosh.js
@@ -0,0 +1,7 @@
+test("basic functionality", () => {
+ expect(Math.cosh).toHaveLength(1);
+
+ expect(Math.cosh(0)).toBe(1);
+ expect(Math.cosh(1)).toBeCloseTo(1.5430806348152437);
+ expect(Math.cosh(-1)).toBeCloseTo(1.5430806348152437);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.exp.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.exp.js
new file mode 100644
index 0000000000..d7111be916
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.exp.js
@@ -0,0 +1,13 @@
+test("basic functionality", () => {
+ expect(Math.exp).toHaveLength(1);
+
+ expect(Math.exp(0)).toBe(1);
+ expect(Math.exp(-2)).toBeCloseTo(0.135335);
+ expect(Math.exp(-1)).toBeCloseTo(0.367879);
+ expect(Math.exp(1)).toBeCloseTo(2.718281);
+ expect(Math.exp(2)).toBeCloseTo(7.389056);
+
+ expect(Math.exp()).toBeNaN();
+ expect(Math.exp(undefined)).toBeNaN();
+ expect(Math.exp("foo")).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.expm1.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.expm1.js
new file mode 100644
index 0000000000..791eb8730a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.expm1.js
@@ -0,0 +1,13 @@
+test("basic functionality", () => {
+ expect(Math.expm1).toHaveLength(1);
+
+ expect(Math.expm1(0)).toBe(0);
+ expect(Math.expm1(-2)).toBeCloseTo(-0.864664);
+ expect(Math.expm1(-1)).toBeCloseTo(-0.63212);
+ expect(Math.expm1(1)).toBeCloseTo(1.718281);
+ expect(Math.expm1(2)).toBeCloseTo(6.389056);
+
+ expect(Math.expm1()).toBeNaN();
+ expect(Math.expm1(undefined)).toBeNaN();
+ expect(Math.expm1("foo")).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.floor.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.floor.js
new file mode 100644
index 0000000000..f1420fe202
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.floor.js
@@ -0,0 +1,13 @@
+test("basic functionality", () => {
+ expect(Math.floor).toHaveLength(1);
+
+ expect(Math.floor(0.95)).toBe(0);
+ expect(Math.floor(4)).toBe(4);
+ expect(Math.floor(7.004)).toBe(7);
+ expect(Math.floor(-0.95)).toBe(-1);
+ expect(Math.floor(-4)).toBe(-4);
+ expect(Math.floor(-7.004)).toBe(-8);
+
+ expect(Math.floor()).toBeNaN();
+ expect(Math.floor(NaN)).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.fround.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.fround.js
new file mode 100644
index 0000000000..34b6c3f29f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.fround.js
@@ -0,0 +1,9 @@
+test("basic functionality", () => {
+ expect(Math.fround).toHaveLength(1);
+
+ expect(Math.fround(0)).toBe(0);
+ expect(Math.fround(1)).toBe(1);
+ expect(Math.fround(1.337)).toBeCloseTo(1.3370000123977661);
+ expect(Math.fround(1.5)).toBe(1.5);
+ expect(Math.fround(NaN)).toBe(NaN);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.hypot.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.hypot.js
new file mode 100644
index 0000000000..ee4b1ea1b7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.hypot.js
@@ -0,0 +1,9 @@
+test("basic functionality", () => {
+ expect(Math.hypot(3, 4)).toBe(5);
+ expect(Math.hypot(3, 4, 5)).toBeCloseTo(7.0710678118654755);
+ expect(Math.hypot()).toBe(0);
+ expect(Math.hypot(NaN)).toBe(NaN);
+ expect(Math.hypot(3, 4, "foo")).toBe(NaN);
+ expect(Math.hypot(3, 4, "5")).toBeCloseTo(7.0710678118654755);
+ expect(Math.hypot(-3)).toBe(3);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.log.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.log.js
new file mode 100644
index 0000000000..9b19569dce
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.log.js
@@ -0,0 +1,8 @@
+test("basic functionality", () => {
+ expect(Math.log).toHaveLength(1);
+
+ expect(Math.log(-1)).toBe(NaN);
+ expect(Math.log(0)).toBe(-Infinity);
+ expect(Math.log(1)).toBe(0);
+ expect(Math.log(10)).toBeCloseTo(2.302585092994046);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.log10.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.log10.js
new file mode 100644
index 0000000000..f63a0daa77
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.log10.js
@@ -0,0 +1,9 @@
+test("basic functionality", () => {
+ expect(Math.log10).toHaveLength(1);
+
+ expect(Math.log10(2)).toBeCloseTo(0.3010299956639812);
+ expect(Math.log10(1)).toBe(0);
+ expect(Math.log10(0)).toBe(-Infinity);
+ expect(Math.log10(-2)).toBe(NaN);
+ expect(Math.log10(100000)).toBe(5);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.log1p.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.log1p.js
new file mode 100644
index 0000000000..5d35c1d03e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.log1p.js
@@ -0,0 +1,8 @@
+test("basic functionality", () => {
+ expect(Math.log1p).toHaveLength(1);
+
+ expect(Math.log1p(-2)).toBeNaN();
+ expect(Math.log1p(-1)).toBe(-Infinity);
+ expect(Math.log1p(0)).toBe(0);
+ expect(Math.log1p(1)).toBeCloseTo(0.693147);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.log2.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.log2.js
new file mode 100644
index 0000000000..efa84a2377
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.log2.js
@@ -0,0 +1,10 @@
+test("basic functionality", () => {
+ expect(Math.log2).toHaveLength(1);
+
+ expect(Math.log2(3)).toBeCloseTo(1.584962500721156);
+ expect(Math.log2(2)).toBe(1);
+ expect(Math.log2(1)).toBe(0);
+ expect(Math.log2(0)).toBe(-Infinity);
+ expect(Math.log2(-2)).toBe(NaN);
+ expect(Math.log2(1024)).toBe(10);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.max.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.max.js
new file mode 100644
index 0000000000..f99a39cc9c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.max.js
@@ -0,0 +1,10 @@
+test("basic functionality", () => {
+ expect(Math.max).toHaveLength(2);
+
+ expect(Math.max()).toBe(-Infinity);
+ expect(Math.max(1)).toBe(1);
+ expect(Math.max(2, 1)).toBe(2);
+ expect(Math.max(1, 2, 3)).toBe(3);
+ expect(Math.max(NaN)).toBeNaN();
+ expect(Math.max("String", 1)).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.min.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.min.js
new file mode 100644
index 0000000000..fedfcabe27
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.min.js
@@ -0,0 +1,9 @@
+test("basic functionality", () => {
+ expect(Math.min).toHaveLength(2);
+
+ expect(Math.min(1)).toBe(1);
+ expect(Math.min(2, 1)).toBe(1);
+ expect(Math.min(1, 2, 3)).toBe(1);
+ expect(Math.min(NaN)).toBeNaN();
+ expect(Math.min("String", 1)).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.pow.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.pow.js
new file mode 100644
index 0000000000..036060b238
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.pow.js
@@ -0,0 +1,25 @@
+test("basic functionality", () => {
+ expect(Math.pow).toHaveLength(2);
+
+ expect(Math.pow(2, 0)).toBe(1);
+ expect(Math.pow(2, 1)).toBe(2);
+ expect(Math.pow(2, 2)).toBe(4);
+ expect(Math.pow(2, 3)).toBe(8);
+ expect(Math.pow(2, -3)).toBe(0.125);
+ expect(Math.pow(3, 2)).toBe(9);
+ expect(Math.pow(0, 0)).toBe(1);
+ expect(Math.pow(2, Math.pow(3, 2))).toBe(512);
+ expect(Math.pow(Math.pow(2, 3), 2)).toBe(64);
+ expect(Math.pow("2", "3")).toBe(8);
+ expect(Math.pow("", [])).toBe(1);
+ expect(Math.pow([], null)).toBe(1);
+ expect(Math.pow(null, null)).toBe(1);
+ expect(Math.pow(undefined, null)).toBe(1);
+ expect(Math.pow(NaN, 2)).toBeNaN();
+ expect(Math.pow(2, NaN)).toBeNaN();
+ expect(Math.pow(undefined, 2)).toBeNaN();
+ expect(Math.pow(2, undefined)).toBeNaN();
+ expect(Math.pow(null, undefined)).toBeNaN();
+ expect(Math.pow(2, "foo")).toBeNaN();
+ expect(Math.pow("foo", 2)).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.sign.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.sign.js
new file mode 100644
index 0000000000..a1e8a6eb72
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.sign.js
@@ -0,0 +1,36 @@
+function isPositiveZero(value) {
+ return value === 0 && 1 / value === Infinity;
+}
+
+function isNegativeZero(value) {
+ return value === 0 && 1 / value === -Infinity;
+}
+
+test("basic functionality", () => {
+ expect(Math.sign).toHaveLength(1);
+
+ expect(Math.sign(0.0001)).toBe(1);
+ expect(Math.sign(1)).toBe(1);
+ expect(Math.sign(42)).toBe(1);
+ expect(Math.sign(Infinity)).toBe(1);
+ expect(isPositiveZero(Math.sign(0))).toBeTrue();
+ expect(isPositiveZero(Math.sign(null))).toBeTrue();
+ expect(isPositiveZero(Math.sign(""))).toBeTrue();
+ expect(isPositiveZero(Math.sign([]))).toBeTrue();
+
+ expect(Math.sign(-0.0001)).toBe(-1);
+ expect(Math.sign(-1)).toBe(-1);
+ expect(Math.sign(-42)).toBe(-1);
+ expect(Math.sign(-Infinity)).toBe(-1);
+ expect(isNegativeZero(Math.sign(-0))).toBeTrue();
+ expect(isNegativeZero(Math.sign(-null))).toBeTrue();
+ expect(isNegativeZero(Math.sign(-""))).toBeTrue();
+ expect(isNegativeZero(Math.sign(-[]))).toBeTrue();
+
+ expect(Math.sign()).toBeNaN();
+ expect(Math.sign(undefined)).toBeNaN();
+ expect(Math.sign([1, 2, 3])).toBeNaN();
+ expect(Math.sign({})).toBeNaN();
+ expect(Math.sign(NaN)).toBeNaN();
+ expect(Math.sign("foo")).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.sin.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.sin.js
new file mode 100644
index 0000000000..f5434d94c7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.sin.js
@@ -0,0 +1,15 @@
+test("basic functionality", () => {
+ expect(Math.sin).toHaveLength(1);
+
+ expect(Math.sin(0)).toBe(0);
+ expect(Math.sin(null)).toBe(0);
+ expect(Math.sin("")).toBe(0);
+ expect(Math.sin([])).toBe(0);
+ expect(Math.sin((Math.PI * 3) / 2)).toBe(-1);
+ expect(Math.sin(Math.PI / 2)).toBe(1);
+ expect(Math.sin()).toBeNaN();
+ expect(Math.sin(undefined)).toBeNaN();
+ expect(Math.sin([1, 2, 3])).toBeNaN();
+ expect(Math.sin({})).toBeNaN();
+ expect(Math.sin("foo")).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.sinh.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.sinh.js
new file mode 100644
index 0000000000..6ea3049c4c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.sinh.js
@@ -0,0 +1,6 @@
+test("basic functionality", () => {
+ expect(Math.sinh).toHaveLength(1);
+
+ expect(Math.sinh(0)).toBe(0);
+ expect(Math.sinh(1)).toBeCloseTo(1.1752011936438014);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.sqrt.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.sqrt.js
new file mode 100644
index 0000000000..1e7b2115e0
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.sqrt.js
@@ -0,0 +1,4 @@
+test("basic functionality", () => {
+ expect(Math.sqrt).toHaveLength(1);
+ expect(Math.sqrt(9)).toBe(3);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.tan.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.tan.js
new file mode 100644
index 0000000000..ef1f32f163
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.tan.js
@@ -0,0 +1,14 @@
+test("basic functionality", () => {
+ expect(Math.tan).toHaveLength(1);
+
+ expect(Math.tan(0)).toBe(0);
+ expect(Math.tan(null)).toBe(0);
+ expect(Math.tan("")).toBe(0);
+ expect(Math.tan([])).toBe(0);
+ expect(Math.ceil(Math.tan(Math.PI / 4))).toBe(1);
+ expect(Math.tan()).toBeNaN();
+ expect(Math.tan(undefined)).toBeNaN();
+ expect(Math.tan([1, 2, 3])).toBeNaN();
+ expect(Math.tan({})).toBeNaN();
+ expect(Math.tan("foo")).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.tanh.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.tanh.js
new file mode 100644
index 0000000000..e24dae76c4
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.tanh.js
@@ -0,0 +1,8 @@
+test("basic functionality", () => {
+ expect(Math.tanh).toHaveLength(1);
+
+ expect(Math.tanh(0)).toBe(0);
+ expect(Math.tanh(Infinity)).toBe(1);
+ expect(Math.tanh(-Infinity)).toBe(-1);
+ expect(Math.tanh(1)).toBeCloseTo(0.7615941559557649);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Math/Math.trunc.js b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.trunc.js
new file mode 100644
index 0000000000..baf8418d1e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Math/Math.trunc.js
@@ -0,0 +1,12 @@
+test("basic functionality", () => {
+ expect(Math.trunc).toHaveLength(1);
+
+ expect(Math.trunc(13.37)).toBe(13);
+ expect(Math.trunc(42.84)).toBe(42);
+ expect(Math.trunc(0.123)).toBe(0);
+ expect(Math.trunc(-0.123)).toBe(-0);
+
+ expect(Math.trunc(NaN)).toBeNaN();
+ expect(Math.trunc("foo")).toBeNaN();
+ expect(Math.trunc()).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/NaN/NaN.js b/Userland/Libraries/LibJS/Tests/builtins/NaN/NaN.js
new file mode 100644
index 0000000000..9c496238b9
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/NaN/NaN.js
@@ -0,0 +1,13 @@
+test("basic functionality", () => {
+ const nan = undefined + 1;
+
+ expect(nan + "").toBe("NaN");
+ expect(NaN + "").toBe("NaN");
+ expect(nan !== nan).toBeTrue();
+ expect(NaN !== NaN).toBeTrue();
+ expect(nan).toBeNaN();
+ expect(NaN).toBeNaN();
+ expect(0).not.toBeNaN();
+ expect(!!nan).toBeFalse();
+ expect(!!NaN).toBeFalse();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Number/Number-constants.js b/Userland/Libraries/LibJS/Tests/builtins/Number/Number-constants.js
new file mode 100644
index 0000000000..2958821546
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Number/Number-constants.js
@@ -0,0 +1,11 @@
+test("basic functionality", () => {
+ expect(Number.EPSILON).toBe(2 ** -52);
+ expect(Number.EPSILON).toBeGreaterThan(0);
+ expect(Number.MAX_SAFE_INTEGER).toBe(2 ** 53 - 1);
+ expect(Number.MAX_SAFE_INTEGER + 1).toBe(Number.MAX_SAFE_INTEGER + 2);
+ expect(Number.MIN_SAFE_INTEGER).toBe(-(2 ** 53 - 1));
+ expect(Number.MIN_SAFE_INTEGER - 1).toBe(Number.MIN_SAFE_INTEGER - 2);
+ expect(Number.POSITIVE_INFINITY).toBe(Infinity);
+ expect(Number.NEGATIVE_INFINITY).toBe(-Infinity);
+ expect(Number.NaN).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Number/Number.isFinite.js b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.isFinite.js
new file mode 100644
index 0000000000..19a23c0292
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.isFinite.js
@@ -0,0 +1,24 @@
+test("basic functionality", () => {
+ expect(Number.isFinite).toHaveLength(1);
+ expect(Number.isFinite).not.toBe(isFinite);
+
+ expect(Number.isFinite(0)).toBeTrue();
+ expect(Number.isFinite(1.23)).toBeTrue();
+ expect(Number.isFinite(42)).toBeTrue();
+
+ expect(Number.isFinite("")).toBeFalse();
+ expect(Number.isFinite("0")).toBeFalse();
+ expect(Number.isFinite("42")).toBeFalse();
+ expect(Number.isFinite(true)).toBeFalse();
+ expect(Number.isFinite(false)).toBeFalse();
+ expect(Number.isFinite(null)).toBeFalse();
+ expect(Number.isFinite([])).toBeFalse();
+ expect(Number.isFinite()).toBeFalse();
+ expect(Number.isFinite(NaN)).toBeFalse();
+ expect(Number.isFinite(undefined)).toBeFalse();
+ expect(Number.isFinite(Infinity)).toBeFalse();
+ expect(Number.isFinite(-Infinity)).toBeFalse();
+ expect(Number.isFinite("foo")).toBeFalse();
+ expect(Number.isFinite({})).toBeFalse();
+ expect(Number.isFinite([1, 2, 3])).toBeFalse();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Number/Number.isInteger.js b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.isInteger.js
new file mode 100644
index 0000000000..ff0bc9370c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.isInteger.js
@@ -0,0 +1,32 @@
+test("basic functionality", () => {
+ expect(Number.isInteger).toHaveLength(1);
+
+ expect(Number.isInteger(0)).toBeTrue();
+ expect(Number.isInteger(42)).toBeTrue();
+ expect(Number.isInteger(-10000)).toBeTrue();
+ expect(Number.isInteger(5)).toBeTrue();
+ expect(Number.isInteger(5.0)).toBeTrue();
+ expect(Number.isInteger(5 + 1 / 10000000000000000)).toBeTrue();
+ // FIXME: values outside of i32's range should still return true
+ // expect(Number.isInteger(+2147483647 + 1)).toBeTrue();
+ // expect(Number.isInteger(-2147483648 - 1)).toBeTrue();
+ // expect(Number.isInteger(99999999999999999999999999999999999)).toBeTrue();
+
+ expect(Number.isInteger(5 + 1 / 1000000000000000)).toBeFalse();
+ expect(Number.isInteger(1.23)).toBeFalse();
+ expect(Number.isInteger("")).toBeFalse();
+ expect(Number.isInteger("0")).toBeFalse();
+ expect(Number.isInteger("42")).toBeFalse();
+ expect(Number.isInteger(true)).toBeFalse();
+ expect(Number.isInteger(false)).toBeFalse();
+ expect(Number.isInteger(null)).toBeFalse();
+ expect(Number.isInteger([])).toBeFalse();
+ expect(Number.isInteger(Infinity)).toBeFalse();
+ expect(Number.isInteger(-Infinity)).toBeFalse();
+ expect(Number.isInteger(NaN)).toBeFalse();
+ expect(Number.isInteger()).toBeFalse();
+ expect(Number.isInteger(undefined)).toBeFalse();
+ expect(Number.isInteger("foo")).toBeFalse();
+ expect(Number.isInteger({})).toBeFalse();
+ expect(Number.isInteger([1, 2, 3])).toBeFalse();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Number/Number.isNaN.js b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.isNaN.js
new file mode 100644
index 0000000000..6a7df463df
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.isNaN.js
@@ -0,0 +1,25 @@
+test("basic functionality", () => {
+ expect(Number.isNaN).toHaveLength(1);
+ expect(Number.isNaN).not.toBe(isNaN);
+
+ expect(Number.isNaN(0)).toBeFalse();
+ expect(Number.isNaN(42)).toBeFalse();
+ expect(Number.isNaN("")).toBeFalse();
+ expect(Number.isNaN("0")).toBeFalse();
+ expect(Number.isNaN("42")).toBeFalse();
+ expect(Number.isNaN(true)).toBeFalse();
+ expect(Number.isNaN(false)).toBeFalse();
+ expect(Number.isNaN(null)).toBeFalse();
+ expect(Number.isNaN([])).toBeFalse();
+ expect(Number.isNaN(Infinity)).toBeFalse();
+ expect(Number.isNaN(-Infinity)).toBeFalse();
+ expect(Number.isNaN()).toBeFalse();
+ expect(Number.isNaN(undefined)).toBeFalse();
+ expect(Number.isNaN("foo")).toBeFalse();
+ expect(Number.isNaN({})).toBeFalse();
+ expect(Number.isNaN([1, 2, 3])).toBeFalse();
+
+ expect(Number.isNaN(NaN)).toBeTrue();
+ expect(Number.isNaN(Number.NaN)).toBeTrue();
+ expect(Number.isNaN(0 / 0)).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Number/Number.isSafeInteger.js b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.isSafeInteger.js
new file mode 100644
index 0000000000..3bba1682a6
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.isSafeInteger.js
@@ -0,0 +1,24 @@
+test("basic functionality", () => {
+ expect(Number.isSafeInteger).toHaveLength(1);
+
+ expect(Number.isSafeInteger(0)).toBeTrue();
+ expect(Number.isSafeInteger(1)).toBeTrue();
+ expect(Number.isSafeInteger(2.0)).toBeTrue();
+ expect(Number.isSafeInteger(42)).toBeTrue();
+ expect(Number.isSafeInteger(Number.MAX_SAFE_INTEGER)).toBeTrue();
+ expect(Number.isSafeInteger(Number.MIN_SAFE_INTEGER)).toBeTrue();
+
+ expect(Number.isSafeInteger()).toBeFalse();
+ expect(Number.isSafeInteger("1")).toBeFalse();
+ expect(Number.isSafeInteger(2.1)).toBeFalse();
+ expect(Number.isSafeInteger(42.42)).toBeFalse();
+ expect(Number.isSafeInteger("")).toBeFalse();
+ expect(Number.isSafeInteger([])).toBeFalse();
+ expect(Number.isSafeInteger(null)).toBeFalse();
+ expect(Number.isSafeInteger(undefined)).toBeFalse();
+ expect(Number.isSafeInteger(NaN)).toBeFalse();
+ expect(Number.isSafeInteger(Infinity)).toBeFalse();
+ expect(Number.isSafeInteger(-Infinity)).toBeFalse();
+ expect(Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1)).toBeFalse();
+ expect(Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1)).toBeFalse();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Number/Number.js b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.js
new file mode 100644
index 0000000000..7fd7067449
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.js
@@ -0,0 +1,33 @@
+test("basic functionality", () => {
+ expect(Number).toHaveLength(1);
+ expect(Number.name).toBe("Number");
+ expect(Number.prototype).not.toHaveProperty("length");
+
+ expect(typeof Number()).toBe("number");
+ expect(typeof new Number()).toBe("object");
+
+ expect(Number()).toBe(0);
+ expect(new Number().valueOf()).toBe(0);
+ expect(Number("42")).toBe(42);
+ expect(new Number("42").valueOf()).toBe(42);
+ expect(Number(null)).toBe(0);
+ expect(new Number(null).valueOf()).toBe(0);
+ expect(Number(true)).toBe(1);
+ expect(new Number(true).valueOf()).toBe(1);
+ expect(Number("Infinity")).toBe(Infinity);
+ expect(new Number("Infinity").valueOf()).toBe(Infinity);
+ expect(Number("+Infinity")).toBe(Infinity);
+ expect(new Number("+Infinity").valueOf()).toBe(Infinity);
+ expect(Number("-Infinity")).toBe(-Infinity);
+ expect(new Number("-Infinity").valueOf()).toBe(-Infinity);
+ expect(Number(undefined)).toBeNaN();
+ expect(new Number(undefined).valueOf()).toBeNaN();
+ expect(Number({})).toBeNaN();
+ expect(new Number({}).valueOf()).toBeNaN();
+ expect(Number({ a: 1 })).toBeNaN();
+ expect(new Number({ a: 1 }).valueOf()).toBeNaN();
+ expect(Number([1, 2, 3])).toBeNaN();
+ expect(new Number([1, 2, 3]).valueOf()).toBeNaN();
+ expect(Number("foo")).toBeNaN();
+ expect(new Number("foo").valueOf()).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Number/Number.parseFloat.js b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.parseFloat.js
new file mode 100644
index 0000000000..89de9ff0d9
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.parseFloat.js
@@ -0,0 +1,5 @@
+test("basic functionality", () => {
+ // Ensuring it's the same function as the global
+ // parseFloat() is enough as that already has tests :^)
+ expect(Number.parseFloat).toBe(parseFloat);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.js b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.js
new file mode 100644
index 0000000000..91f2e93c0e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.js
@@ -0,0 +1,4 @@
+test("basic functionality", () => {
+ expect(typeof Number.prototype).toBe("object");
+ expect(Number.prototype.valueOf()).toBe(0);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toString.js b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toString.js
new file mode 100644
index 0000000000..c07ca792f6
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toString.js
@@ -0,0 +1,83 @@
+describe("correct behavior", () => {
+ test("length", () => {
+ expect(Number.prototype.toString).toHaveLength(1);
+ });
+
+ test("basic functionality", () => {
+ [
+ [+0, "0"],
+ [-0, "0"],
+ [Infinity, "Infinity"],
+ [-Infinity, "-Infinity"],
+ [NaN, "NaN"],
+ [12, "12"],
+ [93465, "93465"],
+ [358000, "358000"],
+ ].forEach(testCase => {
+ expect(testCase[0].toString()).toBe(testCase[1]);
+ });
+ });
+
+ test("radix", () => {
+ let number = 7857632;
+
+ [
+ [2, "11101111110010111100000"],
+ [3, "112210012122102"],
+ [4, "131332113200"],
+ [5, "4002421012"],
+ [6, "440225532"],
+ [7, "123534356"],
+ [8, "35762740"],
+ [9, "15705572"],
+ [10, "7857632"],
+ [11, "4487612"],
+ [12, "276b2a8"],
+ [13, "18216b3"],
+ [14, "10877d6"],
+ [15, "a532c2"],
+ [16, "77e5e0"],
+ [17, "59160b"],
+ [18, "42f5h2"],
+ [19, "335b5b"],
+ [20, "29241c"],
+ [21, "1j89fk"],
+ [22, "1bbkh2"],
+ [23, "151ih4"],
+ [24, "ng9h8"],
+ [25, "k2m57"],
+ [26, "h51ig"],
+ [27, "el5hb"],
+ [28, "clqdk"],
+ [29, "b355o"],
+ [30, "9l0l2"],
+ [31, "8fng0"],
+ [32, "7fpf0"],
+ [33, "6klf2"],
+ [34, "5tv8s"],
+ [35, "589dr"],
+ [36, "4oezk"],
+ ].forEach(testCase => {
+ expect(number.toString(testCase[0])).toBe(testCase[1]);
+ });
+ });
+
+ test("decimal radix gets converted to int", () => {
+ expect((30).toString(10.1)).toBe("30");
+ expect((30).toString(10.9)).toBe("30");
+ });
+});
+
+test("errors", () => {
+ test("must be called with numeric |this|", () => {
+ [true, [], {}, Symbol("foo"), "bar", 1n].forEach(value => {
+ expect(() => Number.prototype.toString.call(value)).toThrow(TypeError);
+ });
+ });
+
+ test("radix RangeError", () => {
+ [0, 1, 37, 100].forEach(value => {
+ expect(() => (0).toString(value)).toThrow(RangeError);
+ });
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperty.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperty.js
new file mode 100644
index 0000000000..bebe32997d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperty.js
@@ -0,0 +1,242 @@
+describe("normal functionality", () => {
+ let s = Symbol("foo");
+
+ test("non-configurable string property", () => {
+ let o = {};
+ Object.defineProperty(o, "foo", { value: 1, writable: false, enumerable: false });
+
+ expect(o.foo).toBe(1);
+ o.foo = 2;
+ expect(o.foo).toBe(1);
+
+ expect(o).not.toHaveConfigurableProperty("foo");
+ expect(o).not.toHaveEnumerableProperty("foo");
+ expect(o).not.toHaveWritableProperty("foo");
+ expect(o).toHaveValueProperty("foo", 1);
+ });
+
+ test("non-configurable symbol property", () => {
+ let o = {};
+ Object.defineProperty(o, s, { value: 1, writable: false, enumerable: false });
+
+ expect(o[s]).toBe(1);
+ o[s] = 2;
+ expect(o[s]).toBe(1);
+
+ expect(o).not.toHaveConfigurableProperty(s);
+ expect(o).not.toHaveEnumerableProperty(s);
+ expect(o).not.toHaveWritableProperty(s);
+ expect(o).toHaveValueProperty(s, 1);
+ });
+
+ test("array index getter", () => {
+ let o = {};
+ Object.defineProperty(o, 2, {
+ get() {
+ return 10;
+ },
+ });
+ expect(o[2]).toBe(10);
+ });
+
+ test("symbol property getter", () => {
+ let o = {};
+ Object.defineProperty(o, s, {
+ get() {
+ return 10;
+ },
+ });
+ expect(o[s]).toBe(10);
+ });
+
+ test("configurable string property", () => {
+ let o = {};
+ Object.defineProperty(o, "foo", { value: "hi", writable: true, enumerable: true });
+
+ expect(o.foo).toBe("hi");
+ o.foo = "ho";
+ expect(o.foo).toBe("ho");
+
+ expect(o).not.toHaveConfigurableProperty("foo");
+ expect(o).toHaveEnumerableProperty("foo");
+ expect(o).toHaveWritableProperty("foo");
+ expect(o).toHaveValueProperty("foo", "ho");
+ });
+
+ test("configurable symbol property", () => {
+ let o = {};
+ Object.defineProperty(o, s, { value: "hi", writable: true, enumerable: true });
+
+ expect(o[s]).toBe("hi");
+ o[s] = "ho";
+ expect(o[s]).toBe("ho");
+
+ expect(o).not.toHaveConfigurableProperty(s);
+ expect(o).toHaveEnumerableProperty(s);
+ expect(o).toHaveWritableProperty(s);
+ expect(o).toHaveValueProperty(s, "ho");
+ });
+
+ test("reconfigure configurable string property", () => {
+ let o = {};
+ Object.defineProperty(o, "foo", { value: 9, configurable: true, writable: false });
+ Object.defineProperty(o, "foo", { configurable: true, writable: true });
+
+ expect(o).toHaveConfigurableProperty("foo");
+ expect(o).toHaveWritableProperty("foo");
+ expect(o).not.toHaveEnumerableProperty("foo");
+ expect(o).toHaveValueProperty("foo", 9);
+ });
+
+ test("reconfigure configurable symbol property", () => {
+ let o = {};
+ Object.defineProperty(o, s, { value: 9, configurable: true, writable: false });
+ Object.defineProperty(o, s, { configurable: true, writable: true });
+
+ expect(o).toHaveConfigurableProperty(s);
+ expect(o).toHaveWritableProperty(s);
+ expect(o).not.toHaveEnumerableProperty(s);
+ expect(o).toHaveValueProperty(s, 9);
+ });
+
+ test("define string accessor", () => {
+ let o = {};
+
+ Object.defineProperty(o, "foo", {
+ configurable: true,
+ get() {
+ return o.secret_foo + 1;
+ },
+ set(value) {
+ this.secret_foo = value + 1;
+ },
+ });
+
+ o.foo = 10;
+ expect(o.foo).toBe(12);
+ o.foo = 20;
+ expect(o.foo).toBe(22);
+
+ Object.defineProperty(o, "foo", { configurable: true, value: 4 });
+
+ expect(o.foo).toBe(4);
+ expect((o.foo = 5)).toBe(5);
+ expect((o.foo = 4)).toBe(4);
+ });
+
+ test("define symbol accessor", () => {
+ let o = {};
+
+ Object.defineProperty(o, s, {
+ configurable: true,
+ get() {
+ return o.secret_foo + 1;
+ },
+ set(value) {
+ this.secret_foo = value + 1;
+ },
+ });
+
+ o[s] = 10;
+ expect(o[s]).toBe(12);
+ o[s] = 20;
+ expect(o[s]).toBe(22);
+
+ Object.defineProperty(o, s, { configurable: true, value: 4 });
+
+ expect(o[s]).toBe(4);
+ expect((o[s] = 5)).toBe(5);
+ expect((o[s] = 4)).toBe(4);
+ });
+
+ test("issue #3735, reconfiguring property in unique shape", () => {
+ const o = {};
+ // In LibJS an object with more than 100 properties gets a unique shape
+ for (let i = 0; i < 101; ++i) {
+ o[`property${i}`] = i;
+ }
+ Object.defineProperty(o, "x", { configurable: true });
+ Object.defineProperty(o, "x", { configurable: false });
+ });
+});
+
+describe("errors", () => {
+ test("redefine non-configurable property", () => {
+ let o = {};
+ Object.defineProperty(o, "foo", { value: 1, writable: true, enumerable: true });
+
+ expect(() => {
+ Object.defineProperty(o, "foo", { value: 2, writable: false, enumerable: true });
+ }).toThrowWithMessage(
+ TypeError,
+ "Cannot change attributes of non-configurable property 'foo'"
+ );
+ });
+
+ test("redefine non-configurable symbol property", () => {
+ let o = {};
+ let s = Symbol("foo");
+ Object.defineProperty(o, s, { value: 1, writable: true, enumerable: true });
+
+ expect(() => {
+ Object.defineProperty(o, s, { value: 2, writable: false, enumerable: true });
+ }).toThrowWithMessage(
+ TypeError,
+ "Cannot change attributes of non-configurable property 'Symbol(foo)'"
+ );
+ });
+
+ test("cannot define 'value' and 'get' in the same descriptor", () => {
+ let o = {};
+
+ expect(() => {
+ Object.defineProperty(o, "a", {
+ get() {},
+ value: 9,
+ });
+ }).toThrowWithMessage(
+ TypeError,
+ "Accessor property descriptor cannot specify a value or writable key"
+ );
+ });
+
+ test("cannot define 'value' and 'set' in the same descriptor", () => {
+ let o = {};
+
+ expect(() => {
+ Object.defineProperty(o, "a", {
+ set() {},
+ writable: true,
+ });
+ }).toThrowWithMessage(
+ TypeError,
+ "Accessor property descriptor cannot specify a value or writable key"
+ );
+ });
+
+ test("redefine non-configurable accessor", () => {
+ let o = {};
+
+ Object.defineProperty(o, "foo", {
+ configurable: false,
+ get() {
+ return this.secret_foo + 2;
+ },
+ set(value) {
+ o.secret_foo = value + 2;
+ },
+ });
+
+ expect(() => {
+ Object.defineProperty(o, "foo", {
+ configurable: false,
+ get() {
+ return this.secret_foo + 2;
+ },
+ });
+ }).toThrowWithMessage(
+ TypeError,
+ "Cannot change attributes of non-configurable property 'foo'"
+ );
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.entries.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.entries.js
new file mode 100644
index 0000000000..c8ed8ffc4c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.entries.js
@@ -0,0 +1,64 @@
+describe("basic functionality", () => {
+ test("length", () => {
+ expect(Object.entries).toHaveLength(1);
+ expect(Object.entries(true)).toHaveLength(0);
+ expect(Object.entries(45)).toHaveLength(0);
+ expect(Object.entries(-998)).toHaveLength(0);
+ expect(Object.entries("abcd")).toHaveLength(4);
+ expect(Object.entries([1, 2, 3])).toHaveLength(3);
+ expect(Object.entries({ a: 1, b: 2, c: 3 })).toHaveLength(3);
+ });
+
+ test("entries with object", () => {
+ let entries = Object.entries({ foo: 1, bar: 2, baz: 3 });
+
+ expect(entries).toEqual([
+ ["foo", 1],
+ ["bar", 2],
+ ["baz", 3],
+ ]);
+ });
+
+ test("entries with objects with symbol keys", () => {
+ let entries = Object.entries({ foo: 1, [Symbol("bar")]: 2, baz: 3 });
+
+ expect(entries).toEqual([
+ ["foo", 1],
+ ["baz", 3],
+ ]);
+ });
+
+ test("entries with array", () => {
+ entries = Object.entries(["a", "b", "c"]);
+ expect(entries).toEqual([
+ ["0", "a"],
+ ["1", "b"],
+ ["2", "c"],
+ ]);
+ });
+
+ test("ignores non-enumerable properties", () => {
+ let obj = { foo: 1 };
+ Object.defineProperty(obj, "getFoo", {
+ value: function () {
+ return this.foo;
+ },
+ });
+ let entries = Object.entries(obj);
+ expect(entries).toEqual([["foo", 1]]);
+ });
+});
+
+describe("errors", () => {
+ test("null argument", () => {
+ expect(() => {
+ Object.entries(null);
+ }).toThrowWithMessage(TypeError, "ToObject on null or undefined");
+ });
+
+ test("undefined argument", () => {
+ expect(() => {
+ Object.entries(undefined);
+ }).toThrowWithMessage(TypeError, "ToObject on null or undefined");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.getOwnPropertyDescriptor.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.getOwnPropertyDescriptor.js
new file mode 100644
index 0000000000..3b69241d9d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.getOwnPropertyDescriptor.js
@@ -0,0 +1,61 @@
+test("plain property", () => {
+ let o = { foo: "bar" };
+
+ expect(o).toHaveConfigurableProperty("foo");
+ expect(o).toHaveEnumerableProperty("foo");
+ expect(o).toHaveWritableProperty("foo");
+ expect(o).toHaveValueProperty("foo", "bar");
+ expect(o).not.toHaveGetterProperty("foo");
+ expect(o).not.toHaveSetterProperty("foo");
+});
+
+test("symbol property", () => {
+ let s = Symbol("foo");
+ let o = { [s]: "bar" };
+
+ expect(o).toHaveConfigurableProperty(s);
+ expect(o).toHaveEnumerableProperty(s);
+ expect(o).toHaveWritableProperty(s);
+ expect(o).toHaveValueProperty(s, "bar");
+ expect(o).not.toHaveGetterProperty(s);
+ expect(o).not.toHaveSetterProperty(s);
+});
+
+test("getter property", () => {
+ let o = { get foo() {} };
+
+ expect(o).toHaveConfigurableProperty("foo");
+ expect(o).toHaveEnumerableProperty("foo");
+ expect(o).not.toHaveWritableProperty("foo");
+ expect(o).not.toHaveValueProperty("foo");
+ expect(o).toHaveGetterProperty("foo");
+ expect(o).not.toHaveSetterProperty("foo");
+});
+
+test("setter property", () => {
+ let o = { set foo(_) {} };
+
+ expect(o).toHaveConfigurableProperty("foo");
+ expect(o).toHaveEnumerableProperty("foo");
+ expect(o).not.toHaveWritableProperty("foo");
+ expect(o).not.toHaveValueProperty("foo");
+ expect(o).not.toHaveGetterProperty("foo");
+ expect(o).toHaveSetterProperty("foo");
+});
+
+test("defined property", () => {
+ let o = {};
+
+ Object.defineProperty(o, "foo", {
+ enumerable: false,
+ writable: true,
+ value: 10,
+ });
+
+ expect(o).not.toHaveConfigurableProperty("foo");
+ expect(o).not.toHaveEnumerableProperty("foo");
+ expect(o).toHaveWritableProperty("foo");
+ expect(o).toHaveValueProperty("foo", 10);
+ expect(o).not.toHaveGetterProperty("foo");
+ expect(o).not.toHaveSetterProperty("foo");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.getOwnPropertyNames.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.getOwnPropertyNames.js
new file mode 100644
index 0000000000..da7bccbeba
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.getOwnPropertyNames.js
@@ -0,0 +1,14 @@
+test("use with array", () => {
+ let names = Object.getOwnPropertyNames([1, 2, 3]);
+ expect(names).toEqual(["0", "1", "2", "length"]);
+});
+
+test("use with object", () => {
+ let names = Object.getOwnPropertyNames({ foo: 1, bar: 2, baz: 3 });
+ expect(names).toEqual(["foo", "bar", "baz"]);
+});
+
+test("use with object with symbol keys", () => {
+ let names = Object.getOwnPropertyNames({ foo: 1, [Symbol("bar")]: 2, baz: 3 });
+ expect(names).toEqual(["foo", "baz"]);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.getPrototypeOf.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.getPrototypeOf.js
new file mode 100644
index 0000000000..8679efc7dc
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.getPrototypeOf.js
@@ -0,0 +1,10 @@
+test("basic functionality", () => {
+ let o1 = new Object();
+ let o2 = {};
+
+ expect(Object.getPrototypeOf(o1)).toBe(Object.getPrototypeOf(o2));
+ expect(Object.getPrototypeOf(Object.getPrototypeOf(o1))).toBeNull();
+
+ Object.setPrototypeOf(o1, o2);
+ expect(Object.getPrototypeOf(o1)).toBe(o2);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.is.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.is.js
new file mode 100644
index 0000000000..fcad3484da
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.is.js
@@ -0,0 +1,54 @@
+test("length", () => {
+ expect(Object.is).toHaveLength(2);
+});
+
+test("arguments that evaluate to true", () => {
+ let a = [1, 2, 3];
+ let o = { foo: "bar" };
+
+ expect(Object.is("", "")).toBeTrue();
+ expect(Object.is("foo", "foo")).toBeTrue();
+ expect(Object.is(0, 0)).toBeTrue();
+ expect(Object.is(+0, +0)).toBeTrue();
+ expect(Object.is(-0, -0)).toBeTrue();
+ expect(Object.is(1.23, 1.23)).toBeTrue();
+ expect(Object.is(42, 42)).toBeTrue();
+ expect(Object.is(NaN, NaN)).toBeTrue();
+ expect(Object.is(Infinity, Infinity)).toBeTrue();
+ expect(Object.is(+Infinity, +Infinity)).toBeTrue();
+ expect(Object.is(-Infinity, -Infinity)).toBeTrue();
+ expect(Object.is(true, true)).toBeTrue();
+ expect(Object.is(false, false)).toBeTrue();
+ expect(Object.is(null, null)).toBeTrue();
+ expect(Object.is(undefined, undefined)).toBeTrue();
+ expect(Object.is(undefined)).toBeTrue();
+ expect(Object.is()).toBeTrue();
+ expect(Object.is(a, a)).toBeTrue();
+ expect(Object.is(o, o)).toBeTrue();
+});
+
+test("arguments that evaluate to false", () => {
+ let a = [1, 2, 3];
+ let o = { foo: "bar" };
+
+ expect(Object.is("test")).toBeFalse();
+ expect(Object.is("foo", "bar")).toBeFalse();
+ expect(Object.is(1, "1")).toBeFalse();
+ expect(Object.is(+0, -0)).toBeFalse();
+ expect(Object.is(-0, +0)).toBeFalse();
+ expect(Object.is(42, 24)).toBeFalse();
+ expect(Object.is(Infinity, -Infinity)).toBeFalse();
+ expect(Object.is(-Infinity, +Infinity)).toBeFalse();
+ expect(Object.is(true, false)).toBeFalse();
+ expect(Object.is(false, true)).toBeFalse();
+ expect(Object.is(undefined, null)).toBeFalse();
+ expect(Object.is(null, undefined)).toBeFalse();
+ expect(Object.is([], [])).toBeFalse();
+ expect(Object.is(a, [1, 2, 3])).toBeFalse();
+ expect(Object.is([1, 2, 3], a)).toBeFalse();
+ expect(Object.is({}, {})).toBeFalse();
+ expect(Object.is(o, { foo: "bar" })).toBeFalse();
+ expect(Object.is({ foo: "bar" }, o)).toBeFalse();
+ expect(Object.is(a, o)).toBeFalse();
+ expect(Object.is(o, a)).toBeFalse();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.isExtensible.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.isExtensible.js
new file mode 100644
index 0000000000..b426114f20
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.isExtensible.js
@@ -0,0 +1,18 @@
+test("basic functionality", () => {
+ expect(Object.isExtensible).toHaveLength(1);
+
+ expect(Object.isExtensible()).toBeFalse();
+ expect(Object.isExtensible(undefined)).toBeFalse();
+ expect(Object.isExtensible(null)).toBeFalse();
+ expect(Object.isExtensible(true)).toBeFalse();
+ expect(Object.isExtensible(6)).toBeFalse();
+ expect(Object.isExtensible("test")).toBeFalse();
+
+ let s = Symbol();
+ expect(Object.isExtensible(s)).toBeFalse();
+
+ let o = { foo: "foo" };
+ expect(Object.isExtensible(o)).toBeTrue();
+ Object.preventExtensions(o);
+ expect(Object.isExtensible(o)).toBeFalse();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.js
new file mode 100644
index 0000000000..544704c1f0
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.js
@@ -0,0 +1,13 @@
+test("basic functionality", () => {
+ expect(Object).toHaveLength(1);
+ expect(Object.name).toBe("Object");
+ expect(Object.prototype).not.toHaveProperty("length");
+
+ expect(typeof Object()).toBe("object");
+ expect(typeof new Object()).toBe("object");
+
+ expect(typeof Object(42)).toBe("object");
+ expect(Object(42).valueOf()).toBe(42);
+ expect(typeof Object("foo")).toBe("object");
+ expect(Object("foo").valueOf()).toBe("foo");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.keys.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.keys.js
new file mode 100644
index 0000000000..64e822abd7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.keys.js
@@ -0,0 +1,51 @@
+describe("correct behavior", () => {
+ test("length", () => {
+ expect(Object.keys).toHaveLength(1);
+ expect(Object.keys(true)).toHaveLength(0);
+ expect(Object.keys(45)).toHaveLength(0);
+ expect(Object.keys(-998)).toHaveLength(0);
+ expect(Object.keys("abcd")).toHaveLength(4);
+ expect(Object.keys([1, 2, 3])).toHaveLength(3);
+ expect(Object.keys({ a: 1, b: 2, c: 3 })).toHaveLength(3);
+ });
+
+ test("object argument", () => {
+ let keys = Object.keys({ foo: 1, bar: 2, baz: 3 });
+ expect(keys).toEqual(["foo", "bar", "baz"]);
+ });
+
+ test("object argument with symbol keys", () => {
+ let keys = Object.keys({ foo: 1, [Symbol("bar")]: 2, baz: 3 });
+ expect(keys).toEqual(["foo", "baz"]);
+ });
+
+ test("array argument", () => {
+ let keys = Object.keys(["a", "b", "c"]);
+ expect(keys).toEqual(["0", "1", "2"]);
+ });
+
+ test("ignores non-enumerable properties", () => {
+ let obj = { foo: 1 };
+ Object.defineProperty(obj, "getFoo", {
+ value: function () {
+ return this.foo;
+ },
+ });
+ keys = Object.keys(obj);
+ expect(keys).toEqual(["foo"]);
+ });
+});
+
+describe("errors", () => {
+ test("null argument value", () => {
+ expect(() => {
+ Object.keys(null);
+ }).toThrowWithMessage(TypeError, "ToObject on null or undefined");
+ });
+
+ test("undefined argument value", () => {
+ expect(() => {
+ Object.keys(undefined);
+ }).toThrowWithMessage(TypeError, "ToObject on null or undefined");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.preventExtensions.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.preventExtensions.js
new file mode 100644
index 0000000000..292c3f5999
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.preventExtensions.js
@@ -0,0 +1,67 @@
+describe("correct behavior", () => {
+ test("non-object arguments", () => {
+ expect(Object.preventExtensions()).toBeUndefined();
+ expect(Object.preventExtensions(undefined)).toBeUndefined();
+ expect(Object.preventExtensions(null)).toBeNull();
+ expect(Object.preventExtensions(true)).toBeTrue();
+ expect(Object.preventExtensions(6)).toBe(6);
+ expect(Object.preventExtensions("test")).toBe("test");
+
+ let s = Symbol();
+ expect(Object.preventExtensions(s)).toBe(s);
+ });
+
+ test("basic functionality", () => {
+ let o = { foo: "foo" };
+ expect(o.foo).toBe("foo");
+ o.bar = "bar";
+ expect(o.bar).toBe("bar");
+
+ expect(Object.preventExtensions(o)).toBe(o);
+ expect(o.foo).toBe("foo");
+ expect(o.bar).toBe("bar");
+
+ o.baz = "baz";
+ expect(o.baz).toBeUndefined();
+ });
+
+ test("modifying existing properties", () => {
+ const o = { foo: "bar" };
+ Object.preventExtensions(o);
+ o.foo = "baz";
+ expect(o.foo).toBe("baz");
+ });
+
+ test("deleting existing properties", () => {
+ const o = { foo: "bar" };
+ Object.preventExtensions(o);
+ delete o.foo;
+ expect(o).not.toHaveProperty("foo");
+ });
+});
+
+describe("errors", () => {
+ test("defining a property on a non-extensible object", () => {
+ let o = {};
+ Object.preventExtensions(o);
+
+ expect(() => {
+ Object.defineProperty(o, "baz", { value: "baz" });
+ }).toThrowWithMessage(TypeError, "Cannot define property baz on non-extensible object");
+
+ expect(o.baz).toBeUndefined();
+ });
+
+ test("putting property on a non-extensible object", () => {
+ let o = {};
+ Object.preventExtensions(o);
+
+ expect(() => {
+ "use strict";
+ o.foo = "foo";
+ }).toThrowWithMessage(TypeError, "Cannot define property foo on non-extensible object");
+
+ expect((o.foo = "foo")).toBe("foo");
+ expect(o.foo).toBeUndefined();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.constructor.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.constructor.js
new file mode 100644
index 0000000000..50e4935c75
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.constructor.js
@@ -0,0 +1,18 @@
+test("basic functionality", () => {
+ [Array, BigInt, Boolean, Date, Error, Function, Number, Object, String].forEach(constructor => {
+ expect(constructor.prototype.constructor).toBe(constructor);
+ if (constructor !== BigInt)
+ expect(Reflect.construct(constructor, []).constructor).toBe(constructor);
+ });
+
+ let o = {};
+ expect(o.constructor).toBe(Object);
+
+ a = [];
+ expect(a.constructor).toBe(Array);
+
+ expect(Object.prototype).toHaveConfigurableProperty("constructor");
+ expect(Object.prototype).not.toHaveEnumerableProperty("constructor");
+ expect(Object.prototype).toHaveWritableProperty("constructor");
+ expect(Object.prototype).toHaveValueProperty("constructor", Object);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.hasOwnProperty.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.hasOwnProperty.js
new file mode 100644
index 0000000000..11063f42ec
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.hasOwnProperty.js
@@ -0,0 +1,13 @@
+test("basic functionality", () => {
+ var o = {};
+
+ o.foo = 1;
+ expect(o.hasOwnProperty("foo")).toBeTrue();
+ expect(o.hasOwnProperty("bar")).toBeFalse();
+ expect(o.hasOwnProperty()).toBeFalse();
+ expect(o.hasOwnProperty(undefined)).toBeFalse();
+
+ o.undefined = 2;
+ expect(o.hasOwnProperty()).toBeTrue();
+ expect(o.hasOwnProperty(undefined)).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.isPrototypeOf.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.isPrototypeOf.js
new file mode 100644
index 0000000000..f3f82174d2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.isPrototypeOf.js
@@ -0,0 +1,18 @@
+test("basic functionality", () => {
+ function A() {}
+ function B() {}
+
+ A.prototype = new B();
+ const C = new A();
+
+ expect(A.prototype.isPrototypeOf(C)).toBeTrue();
+ expect(B.prototype.isPrototypeOf(C)).toBeTrue();
+
+ expect(A.isPrototypeOf(C)).toBeFalse();
+ expect(B.isPrototypeOf(C)).toBeFalse();
+
+ const D = new Object();
+ expect(Object.prototype.isPrototypeOf(D)).toBeTrue();
+ expect(Function.prototype.isPrototypeOf(D.toString)).toBeTrue();
+ expect(Array.prototype.isPrototypeOf([])).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.js
new file mode 100644
index 0000000000..eab7093b3b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.js
@@ -0,0 +1,5 @@
+test("basic functionality", () => {
+ var o = new Object();
+ Object.prototype.foo = 123;
+ expect(o.foo).toBe(123);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.propertyIsEnumerable.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.propertyIsEnumerable.js
new file mode 100644
index 0000000000..01f55cd749
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.propertyIsEnumerable.js
@@ -0,0 +1,15 @@
+test("basic functionality", () => {
+ var o = {};
+
+ o.foo = 1;
+ expect(o.propertyIsEnumerable("foo")).toBeTrue();
+ expect(o.propertyIsEnumerable("bar")).toBeFalse();
+ expect(o.propertyIsEnumerable()).toBeFalse();
+ expect(o.propertyIsEnumerable(undefined)).toBeFalse();
+
+ o.undefined = 2;
+ expect(o.propertyIsEnumerable()).toBeTrue();
+ expect(o.propertyIsEnumerable(undefined)).toBeTrue();
+
+ expect(globalThis.propertyIsEnumerable("globalThis")).toBeFalse();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.toLocaleString.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.toLocaleString.js
new file mode 100644
index 0000000000..b64cb099e4
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.toLocaleString.js
@@ -0,0 +1,35 @@
+describe("correct behavior", () => {
+ test("length", () => {
+ expect(Object.prototype.toLocaleString).toHaveLength(0);
+ });
+
+ test("basic functionality", () => {
+ let o;
+
+ o = {};
+ expect(o.toString()).toBe(o.toLocaleString());
+
+ o = { toString: () => 42 };
+ expect(o.toString()).toBe(42);
+ });
+});
+
+describe("errors", () => {
+ test("toString that throws error", () => {
+ let o = {
+ toString: () => {
+ throw new Error();
+ },
+ };
+ expect(() => {
+ o.toLocaleString();
+ }).toThrow(Error);
+ });
+
+ test("toString that is not a function", () => {
+ let o = { toString: "foo" };
+ expect(() => {
+ o.toLocaleString();
+ }).toThrowWithMessage(TypeError, "foo is not a function");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.toString.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.toString.js
new file mode 100644
index 0000000000..5bdd157de4
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.prototype.toString.js
@@ -0,0 +1,20 @@
+test("length", () => {
+ expect(Object.prototype.toString).toHaveLength(0);
+});
+
+test("result for various object types", () => {
+ const oToString = o => Object.prototype.toString.call(o);
+
+ expect(oToString(undefined)).toBe("[object Undefined]");
+ expect(oToString(null)).toBe("[object Null]");
+ expect(oToString([])).toBe("[object Array]");
+ expect(oToString(function () {})).toBe("[object Function]");
+ expect(oToString(new Error())).toBe("[object Error]");
+ expect(oToString(new Boolean())).toBe("[object Boolean]");
+ expect(oToString(new Number())).toBe("[object Number]");
+ expect(oToString(new Date())).toBe("[object Date]");
+ expect(oToString(new RegExp())).toBe("[object RegExp]");
+ expect(oToString({})).toBe("[object Object]");
+
+ expect(globalThis.toString()).toBe("[object Object]");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.setPrototypeOf.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.setPrototypeOf.js
new file mode 100644
index 0000000000..69a7b24ab3
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.setPrototypeOf.js
@@ -0,0 +1,43 @@
+describe("correct behavior", () => {
+ test("length", () => {
+ expect(Object.setPrototypeOf).toHaveLength(2);
+ });
+
+ test("basic functionality", () => {
+ let o = {};
+ let p = {};
+ expect(Object.setPrototypeOf(o, p)).toBe(o);
+ expect(Object.getPrototypeOf(o)).toBe(p);
+ });
+});
+
+describe("errors", () => {
+ test("requires two arguments", () => {
+ expect(() => {
+ Object.setPrototypeOf();
+ }).toThrowWithMessage(TypeError, "Object.setPrototypeOf requires at least two arguments");
+
+ expect(() => {
+ Object.setPrototypeOf({});
+ }).toThrowWithMessage(TypeError, "Object.setPrototypeOf requires at least two arguments");
+ });
+
+ test("prototype must be an object", () => {
+ expect(() => {
+ Object.setPrototypeOf({}, "foo");
+ }).toThrowWithMessage(TypeError, "Prototype must be an object or null");
+ });
+
+ test("non-extensible target", () => {
+ let o = {};
+ let p = {};
+ Object.setPrototypeOf(o, p);
+ Object.preventExtensions(o);
+
+ expect(() => {
+ Object.setPrototypeOf(o, {});
+ }).toThrowWithMessage(TypeError, "Object's [[SetPrototypeOf]] method returned false");
+
+ expect(Object.setPrototypeOf(o, p)).toBe(o);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.values.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.values.js
new file mode 100644
index 0000000000..5917d94cc8
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.values.js
@@ -0,0 +1,51 @@
+describe("correct behavior", () => {
+ test("lengths", () => {
+ expect(Object.values).toHaveLength(1);
+ expect(Object.values(true)).toHaveLength(0);
+ expect(Object.values(45)).toHaveLength(0);
+ expect(Object.values(-998)).toHaveLength(0);
+ expect(Object.values("abcd")).toHaveLength(4);
+ expect(Object.values([1, 2, 3])).toHaveLength(3);
+ expect(Object.values({ a: 1, b: 2, c: 3 })).toHaveLength(3);
+ });
+
+ test("object argument", () => {
+ let values = Object.values({ foo: 1, bar: 2, baz: 3 });
+ expect(values).toEqual([1, 2, 3]);
+ });
+
+ test("object argument with symbol keys", () => {
+ let values = Object.values({ foo: 1, [Symbol("bar")]: 2, baz: 3 });
+ expect(values).toEqual([1, 3]);
+ });
+
+ test("array argument", () => {
+ let values = Object.values(["a", "b", "c"]);
+ expect(values).toEqual(["a", "b", "c"]);
+ });
+
+ test("ignores non-enumerable properties", () => {
+ let obj = { foo: 1 };
+ Object.defineProperty(obj, "getFoo", {
+ value: function () {
+ return this.foo;
+ },
+ });
+ let values = Object.values(obj);
+ expect(values).toEqual([1]);
+ });
+});
+
+describe("errors", () => {
+ test("null argument", () => {
+ expect(() => {
+ Object.values(null);
+ }).toThrowWithMessage(TypeError, "ToObject on null or undefined");
+ });
+
+ test("undefined argument", () => {
+ expect(() => {
+ Object.values(undefined);
+ }).toThrowWithMessage(TypeError, "ToObject on null or undefined");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-apply.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-apply.js
new file mode 100644
index 0000000000..0e59d4f473
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-apply.js
@@ -0,0 +1,36 @@
+describe("[[Call]] trap normal behavior", () => {
+ test("forwarding when not defined in handler", () => {
+ let p = new Proxy(() => 5, { apply: null });
+ expect(p()).toBe(5);
+ p = new Proxy(() => 5, { apply: undefined });
+ expect(p()).toBe(5);
+ p = new Proxy(() => 5, {});
+ expect(p()).toBe(5);
+ });
+
+ test("correct arguments supplied to trap", () => {
+ const f = (a, b) => a + b;
+ const handler = {
+ apply(target, this_, arguments) {
+ expect(target).toBe(f);
+ expect(this_).toBe(handler);
+ if (arguments[2]) return arguments[0] * arguments[1];
+ return f(...arguments);
+ },
+ };
+ p = new Proxy(f, handler);
+
+ expect(p(2, 4)).toBe(6);
+ expect(p(2, 4, true)).toBe(8);
+ });
+});
+
+describe("[[Call]] invariants", () => {
+ test("target must have a [[Call]] slot", () => {
+ [{}, [], new Proxy({}, {})].forEach(item => {
+ expect(() => {
+ new Proxy(item, {})();
+ }).toThrowWithMessage(TypeError, "[object ProxyObject] is not a function");
+ });
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-construct.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-construct.js
new file mode 100644
index 0000000000..73a63346a3
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-construct.js
@@ -0,0 +1,72 @@
+describe("[[Construct]] trap normal behavior", () => {
+ test("forwarding when not defined in handler", () => {
+ let p = new Proxy(
+ function () {
+ this.x = 5;
+ },
+ { construct: null }
+ );
+ expect(new p().x).toBe(5);
+ p = new Proxy(
+ function () {
+ this.x = 5;
+ },
+ { construct: undefined }
+ );
+ expect(new p().x).toBe(5);
+ p = new Proxy(function () {
+ this.x = 5;
+ }, {});
+ expect(new p().x).toBe(5);
+ });
+
+ test("trapping 'new'", () => {
+ function f(value) {
+ this.x = value;
+ }
+
+ let p;
+ const handler = {
+ construct(target, arguments, newTarget) {
+ expect(target).toBe(f);
+ expect(newTarget).toBe(p);
+ if (arguments[1]) return Reflect.construct(target, [arguments[0] * 2], newTarget);
+ return Reflect.construct(target, arguments, newTarget);
+ },
+ };
+ p = new Proxy(f, handler);
+
+ expect(new p(15).x).toBe(15);
+ expect(new p(15, true).x).toBe(30);
+ });
+
+ test("trapping Reflect.construct", () => {
+ function f(value) {
+ this.x = value;
+ }
+
+ let p;
+ function theNewTarget() {}
+ const handler = {
+ construct(target, arguments, newTarget) {
+ expect(target).toBe(f);
+ expect(newTarget).toBe(theNewTarget);
+ if (arguments[1]) return Reflect.construct(target, [arguments[0] * 2], newTarget);
+ return Reflect.construct(target, arguments, newTarget);
+ },
+ };
+ p = new Proxy(f, handler);
+
+ Reflect.construct(p, [15], theNewTarget);
+ });
+});
+
+describe("[[Construct]] invariants", () => {
+ test("target must have a [[Construct]] slot", () => {
+ [{}, [], new Proxy({}, {})].forEach(item => {
+ expect(() => {
+ new new Proxy(item, {})();
+ }).toThrowWithMessage(TypeError, "[object ProxyObject] is not a constructor");
+ });
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-defineProperty.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-defineProperty.js
new file mode 100644
index 0000000000..2044ea8084
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-defineProperty.js
@@ -0,0 +1,133 @@
+describe("[[DefineProperty]] trap normal behavior", () => {
+ test("forwarding when not defined in handler", () => {
+ let p = new Proxy({}, { defineProperty: null });
+ expect(Object.defineProperty(p, "foo", {})).toBe(p);
+ p = new Proxy({}, { defineProperty: undefined });
+ expect(Object.defineProperty(p, "foo", {})).toBe(p);
+ p = new Proxy({}, {});
+ expect(Object.defineProperty(p, "foo", {})).toBe(p);
+ });
+
+ test("correct arguments passed to trap", () => {
+ let o = {};
+ p = new Proxy(o, {
+ defineProperty(target, name, descriptor) {
+ expect(target).toBe(o);
+ expect(name).toBe("foo");
+ expect(descriptor.configurable).toBeTrue();
+ expect(descriptor.enumerable).toBeUndefined();
+ expect(descriptor.writable).toBeTrue();
+ expect(descriptor.value).toBe(10);
+ expect(descriptor.get).toBeUndefined();
+ expect(descriptor.set).toBeUndefined();
+ return true;
+ },
+ });
+
+ Object.defineProperty(p, "foo", { configurable: true, writable: true, value: 10 });
+ });
+
+ test("optionally ignoring the define call", () => {
+ let o = {};
+ let p = new Proxy(o, {
+ defineProperty(target, name, descriptor) {
+ if (target[name] === undefined) Object.defineProperty(target, name, descriptor);
+ return true;
+ },
+ });
+
+ Object.defineProperty(p, "foo", {
+ value: 10,
+ enumerable: true,
+ configurable: false,
+ writable: true,
+ });
+ expect(p).toHaveEnumerableProperty("foo");
+ expect(p).not.toHaveConfigurableProperty("foo");
+ expect(p).toHaveWritableProperty("foo");
+ expect(p).toHaveValueProperty("foo", 10);
+ expect(p).not.toHaveGetterProperty("foo");
+ expect(p).not.toHaveSetterProperty("foo");
+
+ Object.defineProperty(p, "foo", {
+ value: 20,
+ enumerable: true,
+ configurable: false,
+ writable: true,
+ });
+ expect(p).toHaveEnumerableProperty("foo");
+ expect(p).not.toHaveConfigurableProperty("foo");
+ expect(p).toHaveWritableProperty("foo");
+ expect(p).toHaveValueProperty("foo", 10);
+ expect(p).not.toHaveGetterProperty("foo");
+ expect(p).not.toHaveSetterProperty("foo");
+ });
+});
+
+describe("[[DefineProperty]] invariants", () => {
+ test("trap can't return false", () => {
+ let p = new Proxy(
+ {},
+ {
+ defineProperty() {
+ return false;
+ },
+ }
+ );
+
+ expect(() => {
+ Object.defineProperty(p, "foo", {});
+ }).toThrowWithMessage(TypeError, "Object's [[DefineProperty]] method returned false");
+ });
+
+ test("trap cannot return true for a non-extensible target if the property does not exist", () => {
+ let o = {};
+ Object.preventExtensions(o);
+ let p = new Proxy(o, {
+ defineProperty() {
+ return true;
+ },
+ });
+
+ expect(() => {
+ Object.defineProperty(p, "foo", {});
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's defineProperty trap violates invariant: a property cannot be reported as being defined if the property does not exist on the target and the target is non-extensible"
+ );
+ });
+
+ test("trap cannot return true for a non-configurable property if it doesn't already exist on the target", () => {
+ let o = {};
+ Object.defineProperty(o, "foo", { value: 10, configurable: true });
+ let p = new Proxy(o, {
+ defineProperty() {
+ return true;
+ },
+ });
+
+ expect(() => {
+ Object.defineProperty(p, "bar", { value: 6, configurable: false });
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's defineProperty trap violates invariant: a property cannot be defined as non-configurable if it does not already exist on the target object"
+ );
+ });
+
+ test("trap cannot return true for a non-configurable property if it already exists as a configurable property", () => {
+ let o = {};
+ Object.defineProperty(o, "foo", { value: 10, configurable: true });
+ let p = new Proxy(o, {
+ defineProperty() {
+ return true;
+ },
+ });
+
+ expect(() => {
+ Object.defineProperty(p, "foo", { value: 6, configurable: false });
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's defineProperty trap violates invariant: a property cannot be defined as non-configurable if it already exists on the target object as a configurable property"
+ );
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-deleteProperty.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-deleteProperty.js
new file mode 100644
index 0000000000..26ef993789
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-deleteProperty.js
@@ -0,0 +1,58 @@
+describe("[[Delete]] trap normal behavior", () => {
+ test("forwarding when not defined in handler", () => {
+ expect(delete new Proxy({}, { deleteProperty: undefined }).foo).toBeTrue();
+ expect(delete new Proxy({}, { deleteProperty: null }).foo).toBeTrue();
+ expect(delete new Proxy({}, {}).foo).toBeTrue();
+ });
+
+ test("correct arguments supplied to trap", () => {
+ let o = {};
+ let p = new Proxy(o, {
+ deleteProperty(target, property) {
+ expect(target).toBe(o);
+ expect(property).toBe("foo");
+ return true;
+ },
+ });
+
+ delete p.foo;
+ });
+
+ test("conditional deletion", () => {
+ o = { foo: 1, bar: 2 };
+ p = new Proxy(o, {
+ deleteProperty(target, property) {
+ if (property === "foo") {
+ delete target[property];
+ return true;
+ }
+ return false;
+ },
+ });
+
+ expect(delete p.foo).toBeTrue();
+ expect(delete p.bar).toBeFalse();
+
+ expect(o.foo).toBeUndefined();
+ expect(o.bar).toBe(2);
+ });
+});
+
+describe("[[Delete]] invariants", () => {
+ test("cannot report a non-configurable own property as deleted", () => {
+ let o = {};
+ Object.defineProperty(o, "foo", { configurable: false });
+ let p = new Proxy(o, {
+ deleteProperty() {
+ return true;
+ },
+ });
+
+ expect(() => {
+ delete p.foo;
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's deleteProperty trap violates invariant: cannot report a non-configurable own property of the target as deleted"
+ );
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-get.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-get.js
new file mode 100644
index 0000000000..7919b196fe
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-get.js
@@ -0,0 +1,96 @@
+describe("[[Get]] trap normal behavior", () => {
+ test("forwarding when not defined in handler", () => {
+ expect(new Proxy({}, { get: undefined }).foo).toBeUndefined();
+ expect(new Proxy({}, { get: null }).foo).toBeUndefined();
+ expect(new Proxy({}, {}).foo).toBeUndefined();
+ });
+
+ test("correct arguments supplied to trap", () => {
+ let o = {};
+ let p = new Proxy(o, {
+ get(target, property, receiver) {
+ expect(target).toBe(o);
+ expect(property).toBe("foo");
+ expect(receiver).toBe(p);
+ },
+ });
+
+ p.foo;
+ });
+
+ test("conditional return", () => {
+ let o = { foo: 1 };
+ let p = new Proxy(o, {
+ get(target, property, receiver) {
+ if (property === "bar") {
+ return 2;
+ } else if (property === "baz") {
+ return receiver.qux;
+ } else if (property === "qux") {
+ return 3;
+ }
+ return target[property];
+ },
+ });
+
+ expect(p.foo).toBe(1);
+ expect(p.bar).toBe(2);
+ expect(p.baz).toBe(3);
+ expect(p.qux).toBe(3);
+ expect(p.test).toBeUndefined();
+ expect(p[Symbol.hasInstance]).toBeUndefined();
+ });
+
+ test("custom receiver value", () => {
+ let p = new Proxy(
+ {},
+ {
+ get(target, property, receiver) {
+ return receiver;
+ },
+ }
+ );
+
+ expect(Reflect.get(p, "foo", 42)).toBe(42);
+ });
+});
+
+describe("[[Get]] invariants", () => {
+ test("returned value must match the target property value if the property is non-configurable and non-writable", () => {
+ let o = {};
+ Object.defineProperty(o, "foo", { value: 5, configurable: false, writable: true });
+ Object.defineProperty(o, "bar", { value: 10, configurable: false, writable: false });
+
+ let p = new Proxy(o, {
+ get() {
+ return 8;
+ },
+ });
+
+ expect(p.foo).toBe(8);
+ expect(() => {
+ p.bar;
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's get trap violates invariant: the returned value must match the value on the target if the property exists on the target as a non-writable, non-configurable own data property"
+ );
+ });
+
+ test("returned value must be undefined if the property is a non-configurable accessor with no getter", () => {
+ let o = {};
+ Object.defineProperty(o, "foo", { configurable: false, set(_) {} });
+
+ let p = new Proxy(o, {
+ get() {
+ return 8;
+ },
+ });
+
+ expect(() => {
+ p.foo;
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's get trap violates invariant: the returned value must be undefined if the property exists on the target as a non-configurable accessor property with an undefined get attribute"
+ );
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-getOwnPropertyDescriptor.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-getOwnPropertyDescriptor.js
new file mode 100644
index 0000000000..9b9addf14a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-getOwnPropertyDescriptor.js
@@ -0,0 +1,220 @@
+describe("[Call][GetOwnProperty]] trap normal behavior", () => {
+ test("forwarding when not defined in handler", () => {
+ expect(
+ Object.getOwnPropertyDescriptor(new Proxy({}, { getOwnPropertyDescriptor: null }), "a")
+ ).toBeUndefined();
+ expect(
+ Object.getOwnPropertyDescriptor(
+ new Proxy({}, { getOwnPropertyDescriptor: undefined }),
+ "a"
+ )
+ ).toBeUndefined();
+ expect(Object.getOwnPropertyDescriptor(new Proxy({}, {}), "a")).toBeUndefined();
+ });
+
+ test("correct arguments supplied to trap", () => {
+ let o = {};
+ let p = new Proxy(o, {
+ getOwnPropertyDescriptor(target, property) {
+ expect(target).toBe(o);
+ expect(property).toBe("foo");
+ },
+ });
+
+ Object.getOwnPropertyDescriptor(p, "foo");
+ });
+
+ test("conditional returned descriptor", () => {
+ let o = { foo: "bar" };
+ Object.defineProperty(o, "baz", {
+ value: "qux",
+ enumerable: false,
+ configurable: true,
+ writable: false,
+ });
+
+ let p = new Proxy(o, {
+ getOwnPropertyDescriptor(target, property) {
+ if (property === "baz") return Object.getOwnPropertyDescriptor(target, "baz");
+ return {
+ value: target[property],
+ enumerable: false,
+ configurable: true,
+ writable: true,
+ };
+ },
+ });
+
+ expect(p).toHaveConfigurableProperty("baz");
+ expect(p).not.toHaveEnumerableProperty("baz");
+ expect(p).not.toHaveWritableProperty("baz");
+ expect(p).toHaveValueProperty("baz", "qux");
+ expect(p).not.toHaveGetterProperty("baz");
+ expect(p).not.toHaveSetterProperty("baz");
+
+ d = Object.getOwnPropertyDescriptor(p, "foo");
+
+ expect(p).toHaveConfigurableProperty("foo");
+ expect(p).not.toHaveEnumerableProperty("foo");
+ expect(p).toHaveWritableProperty("foo");
+ expect(p).toHaveValueProperty("foo", "bar");
+ expect(p).not.toHaveGetterProperty("foo");
+ expect(p).not.toHaveSetterProperty("foo");
+ });
+});
+
+describe("[[GetOwnProperty]] invariants", () => {
+ test("must return an object or undefined", () => {
+ expect(() => {
+ Object.getOwnPropertyDescriptor(
+ new Proxy(
+ {},
+ {
+ getOwnPropertyDescriptor() {
+ return 1;
+ },
+ }
+ )
+ );
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's getOwnPropertyDescriptor trap violates invariant: must return an object or undefined"
+ );
+ });
+
+ test("cannot return undefined for a non-configurable property", () => {
+ let o = {};
+ Object.defineProperty(o, "foo", { value: 10, configurable: false });
+
+ let p = new Proxy(o, {
+ getOwnPropertyDescriptor() {
+ return undefined;
+ },
+ });
+
+ expect(Object.getOwnPropertyDescriptor(p, "bar")).toBeUndefined();
+
+ expect(() => {
+ Object.getOwnPropertyDescriptor(p, "foo");
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's getOwnPropertyDescriptor trap violates invariant: cannot return undefined for a property on the target which is a non-configurable property"
+ );
+ });
+
+ test("cannot return undefined for an existing property if the target is non-extensible", () => {
+ let o = {};
+ Object.defineProperty(o, "baz", {
+ value: 20,
+ configurable: true,
+ writable: true,
+ enumerable: true,
+ });
+ Object.preventExtensions(o);
+
+ let p = new Proxy(o, {
+ getOwnPropertyDescriptor() {
+ return undefined;
+ },
+ });
+
+ expect(() => {
+ Object.getOwnPropertyDescriptor(p, "baz");
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's getOwnPropertyDescriptor trap violates invariant: cannot report a property as being undefined if it exists as an own property of the target and the target is non-extensible"
+ );
+ });
+
+ test("invalid property descriptors", () => {
+ let o = {};
+ Object.defineProperty(o, "v1", { value: 10, configurable: false });
+ Object.defineProperty(o, "v2", { value: 10, configurable: false, enumerable: true });
+ Object.defineProperty(o, "v3", {
+ configurable: false,
+ get() {
+ return 1;
+ },
+ });
+ Object.defineProperty(o, "v4", {
+ value: 10,
+ configurable: false,
+ writable: false,
+ enumerable: true,
+ });
+
+ expect(() => {
+ Object.getOwnPropertyDescriptor(
+ new Proxy(o, {
+ getOwnPropertyDescriptor() {
+ return { configurable: true };
+ },
+ }),
+ "v1"
+ );
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's getOwnPropertyDescriptor trap violates invariant: invalid property descriptor for existing property on the target"
+ );
+
+ expect(() => {
+ Object.getOwnPropertyDescriptor(
+ new Proxy(o, {
+ getOwnPropertyDescriptor() {
+ return { enumerable: false };
+ },
+ }),
+ "v2"
+ );
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's getOwnPropertyDescriptor trap violates invariant: invalid property descriptor for existing property on the target"
+ );
+
+ expect(() => {
+ Object.getOwnPropertyDescriptor(
+ new Proxy(o, {
+ getOwnPropertyDescriptor() {
+ return { value: 10 };
+ },
+ }),
+ "v3"
+ );
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's getOwnPropertyDescriptor trap violates invariant: invalid property descriptor for existing property on the target"
+ );
+
+ expect(() => {
+ Object.getOwnPropertyDescriptor(
+ new Proxy(o, {
+ getOwnPropertyDescriptor() {
+ return { value: 10, writable: true };
+ },
+ }),
+ "v4"
+ );
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's getOwnPropertyDescriptor trap violates invariant: invalid property descriptor for existing property on the target"
+ );
+ });
+
+ test("cannot report a property as non-configurable if it does not exist or is non-configurable", () => {
+ let o = {};
+ Object.defineProperty(o, "v", { configurable: true });
+ expect(() => {
+ Object.getOwnPropertyDescriptor(
+ new Proxy(o, {
+ getOwnPropertyDescriptor() {
+ return { configurable: false };
+ },
+ }),
+ "v"
+ );
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's getOwnPropertyDescriptor trap violates invariant: cannot report target's property as non-configurable if the property does not exist, or if it is configurable"
+ );
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-getPrototypeOf.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-getPrototypeOf.js
new file mode 100644
index 0000000000..823e1ff392
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-getPrototypeOf.js
@@ -0,0 +1,95 @@
+describe("[[GetPrototypeOf]] trap normal behavior", () => {
+ test("forwarding when not defined in handler", () => {
+ let proto = {};
+ let o = {};
+ Object.setPrototypeOf(o, proto);
+
+ let p = new Proxy(o, { prototype: null });
+ expect(Object.getPrototypeOf(p)).toBe(proto);
+ p = new Proxy(o, { apply: undefined });
+ expect(Object.getPrototypeOf(p)).toBe(proto);
+ p = new Proxy(o, {});
+ expect(Object.getPrototypeOf(p)).toBe(proto);
+ });
+
+ test("correct arguments supplied to trap", () => {
+ let o = {};
+ let p = new Proxy(o, {
+ getPrototypeOf(target) {
+ expect(target).toBe(o);
+ return null;
+ },
+ });
+
+ Object.getPrototypeOf(p);
+ });
+
+ test("conditional return", () => {
+ let o = {};
+ let p = new Proxy(o, {
+ getPrototypeOf(target) {
+ if (target.foo) return { bar: 1 };
+ return { bar: 2 };
+ },
+ });
+
+ expect(Object.getPrototypeOf(p).bar).toBe(2);
+ o.foo = 20;
+ expect(Object.getPrototypeOf(p).bar).toBe(1);
+ });
+
+ test("non-extensible target", () => {
+ let o = {};
+ let proto = { foo: "bar" };
+ Object.setPrototypeOf(o, proto);
+ Object.preventExtensions(o);
+
+ p = new Proxy(o, {
+ getPrototypeOf() {
+ return proto;
+ },
+ });
+
+ expect(Object.getPrototypeOf(p).foo).toBe("bar");
+ });
+});
+
+describe("[[GetPrototypeOf]] invariants", () => {
+ test("must return an object or null", () => {
+ expect(() => {
+ Object.getPrototypeOf(
+ new Proxy(
+ {},
+ {
+ getPrototypeOf() {
+ return 1;
+ },
+ }
+ )
+ );
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's getPrototypeOf trap violates invariant: must return an object or null"
+ );
+ });
+
+ test("different return object for non-extensible target", () => {
+ let o = {};
+ let proto = {};
+ Object.setPrototypeOf(o, proto);
+ Object.preventExtensions(o);
+
+ let p = new Proxy(o, {
+ getPrototypeOf(target) {
+ return { baz: "qux" };
+ },
+ });
+
+ expect(() => {
+ Object.getPrototypeOf(p);
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's getPrototypeOf trap violates invariant: cannot return a different prototype object for a non-extensible target"
+ );
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-has.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-has.js
new file mode 100644
index 0000000000..b9e2df94ee
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-has.js
@@ -0,0 +1,74 @@
+describe("[[Has]] trap normal behavior", () => {
+ test("forwarding when not defined in handler", () => {
+ expect("foo" in new Proxy({}, { has: null })).toBeFalse();
+ expect("foo" in new Proxy({}, { has: undefined })).toBeFalse();
+ expect("foo" in new Proxy({}, {})).toBeFalse();
+ });
+
+ test("correct arguments supplied to trap", () => {
+ let o = {};
+ let p = new Proxy(o, {
+ has(target, prop) {
+ expect(target).toBe(o);
+ expect(prop).toBe("foo");
+ return true;
+ },
+ });
+
+ "foo" in p;
+ });
+
+ test("conditional return", () => {
+ let o = {};
+ let p = new Proxy(o, {
+ has(target, prop) {
+ if (target.checkedFoo) return true;
+ if (prop === "foo") target.checkedFoo = true;
+ return false;
+ },
+ });
+
+ expect("foo" in p).toBeFalse();
+ expect("foo" in p).toBeTrue();
+ });
+});
+
+describe("[[Has]] invariants", () => {
+ test("cannot return false if the property exists and is non-configurable", () => {
+ let o = {};
+ Object.defineProperty(o, "foo", { configurable: false });
+
+ p = new Proxy(o, {
+ has() {
+ return false;
+ },
+ });
+
+ expect(() => {
+ "foo" in p;
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's has trap violates invariant: a property cannot be reported as non-existent if it exists on the target as a non-configurable property"
+ );
+ });
+
+ test("cannot return false if the property exists and the target is non-extensible", () => {
+ let o = {};
+ Object.defineProperty(o, "foo", { value: 10, configurable: true });
+
+ let p = new Proxy(o, {
+ has() {
+ return false;
+ },
+ });
+
+ Object.preventExtensions(o);
+
+ expect(() => {
+ "foo" in p;
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's has trap violates invariant: a property cannot be reported as non-existent if it exists on the target and the target is non-extensible"
+ );
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-isExtensible.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-isExtensible.js
new file mode 100644
index 0000000000..430b0fde56
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-isExtensible.js
@@ -0,0 +1,39 @@
+describe("[[IsExtensible]] trap normal behavior", () => {
+ test("forwarding when not defined in handler", () => {
+ expect(Object.isExtensible(new Proxy({}, { isExtensible: null }))).toBeTrue();
+ expect(Object.isExtensible(new Proxy({}, { isExtensible: undefined }))).toBeTrue();
+ expect(Object.isExtensible(new Proxy({}, {}))).toBeTrue();
+ });
+
+ test("correct arguments supplied to trap", () => {
+ let o = {};
+ let p = new Proxy(o, {
+ isExtensible(target) {
+ expect(target).toBe(o);
+ return true;
+ },
+ });
+
+ expect(Object.isExtensible(p)).toBeTrue();
+ });
+});
+
+describe("[[Call]] invariants", () => {
+ test("return value must match the target's extensibility", () => {
+ let o = {};
+ Object.preventExtensions(o);
+
+ let p = new Proxy(o, {
+ isExtensible() {
+ return true;
+ },
+ });
+
+ expect(() => {
+ Object.isExtensible(p);
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's isExtensible trap violates invariant: return value must match the target's extensibility"
+ );
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-preventExtensions.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-preventExtensions.js
new file mode 100644
index 0000000000..13a960faea
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-preventExtensions.js
@@ -0,0 +1,59 @@
+describe("[[PreventExtension]] trap normal behavior", () => {
+ test("forwarding when not defined in handler", () => {
+ let p = new Proxy({}, { preventExtensions: null });
+ expect(Object.preventExtensions(p)).toBe(p);
+ p = new Proxy({}, { preventExtensions: undefined });
+ expect(Object.preventExtensions(p)).toBe(p);
+ p = new Proxy({}, {});
+ expect(Object.preventExtensions(p)).toBe(p);
+ });
+
+ test("correct arguments supplied to trap", () => {
+ let o = {};
+ p = new Proxy(o, {
+ preventExtensions(target) {
+ expect(target).toBe(o);
+ return true;
+ },
+ });
+
+ Object.preventExtensions(o);
+ Object.preventExtensions(p);
+ });
+});
+
+describe("[[PreventExtensions]] invariants", () => {
+ test("cannot return false", () => {
+ let p = new Proxy(
+ {},
+ {
+ preventExtensions() {
+ return false;
+ },
+ }
+ );
+
+ expect(() => {
+ Object.preventExtensions(p);
+ }).toThrowWithMessage(TypeError, "Object's [[PreventExtensions]] method returned false");
+ });
+
+ test("cannot return true if the target is extensible", () => {
+ let o = {};
+ let p = new Proxy(o, {
+ preventExtensions() {
+ return true;
+ },
+ });
+
+ expect(() => {
+ Object.preventExtensions(p);
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's preventExtensions trap violates invariant: cannot return true if the target object is extensible"
+ );
+
+ Object.preventExtensions(o);
+ expect(Object.preventExtensions(p)).toBe(p);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-set.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-set.js
new file mode 100644
index 0000000000..dd45c51b4b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-set.js
@@ -0,0 +1,99 @@
+describe("[[Set]] trap normal behavior", () => {
+ test("forwarding when not defined in handler", () => {
+ expect((new Proxy({}, { set: undefined }).foo = 1)).toBe(1);
+ expect((new Proxy({}, { set: null }).foo = 1)).toBe(1);
+ expect((new Proxy({}, {}).foo = 1)).toBe(1);
+ });
+
+ test("correct arguments supplied to trap", () => {
+ let o = {};
+ let p = new Proxy(o, {
+ set(target, prop, value, receiver) {
+ expect(target).toBe(o);
+ expect(prop).toBe("foo");
+ expect(value).toBe(10);
+ expect(receiver).toBe(p);
+ return true;
+ },
+ });
+
+ p.foo = 10;
+ });
+
+ test("conditional return value", () => {
+ let p = new Proxy(
+ {},
+ {
+ set(target, prop, value) {
+ if (target[prop] === value) {
+ target[prop] *= 2;
+ } else {
+ target[prop] = value;
+ }
+ },
+ }
+ );
+
+ p.foo = 10;
+ expect(p.foo).toBe(10);
+ p.foo = 10;
+ expect(p.foo).toBe(20);
+ p.foo = 10;
+ expect(p.foo).toBe(10);
+ p[Symbol.hasInstance] = "foo";
+ expect(p[Symbol.hasInstance]).toBe("foo");
+ });
+
+ test("custom receiver value", () => {
+ const o = {};
+ const r = {};
+ let p = new Proxy(o, {
+ set(target, property, value, receiver) {
+ receiver[property] = value;
+ return true;
+ },
+ });
+
+ expect(Reflect.set(p, "foo", 42, r)).toBe(true);
+ expect(o.foo).toBeUndefined();
+ expect(r.foo).toBe(42);
+ });
+});
+
+describe("[[Set]] invariants", () => {
+ test("cannot return true for a non-configurable, non-writable property", () => {
+ let o = {};
+ Object.defineProperty(o, "foo", { value: 10 });
+
+ let p = new Proxy(o, {
+ set() {
+ return true;
+ },
+ });
+
+ expect(() => {
+ p.foo = 12;
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's set trap violates invariant: cannot return true for a property on the target which is a non-configurable, non-writable own data property"
+ );
+ });
+
+ test("cannot return true for a non-configurable accessor property with no setter", () => {
+ let o = {};
+ Object.defineProperty(o, "foo", { get() {} });
+
+ let p = new Proxy(o, {
+ set() {
+ return true;
+ },
+ });
+
+ expect(() => {
+ p.foo = 12;
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's set trap violates invariant: cannot return true for a property on the target which is a non-configurable own accessor property with an undefined set attribute"
+ );
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-setPrototypeOf.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-setPrototypeOf.js
new file mode 100644
index 0000000000..30909c90f5
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-setPrototypeOf.js
@@ -0,0 +1,96 @@
+describe("[[SetPrototypeOf]] trap normal behavior", () => {
+ test("forwarding when not defined in handler", () => {
+ const o = {};
+ const proto = { foo: "bar" };
+ Object.setPrototypeOf(o, proto);
+
+ let p = new Proxy(o, { setPrototypeOf: null });
+ expect(Object.setPrototypeOf(p, proto)).toBe(p);
+ let p = new Proxy(o, { setPrototypeOf: undefined });
+ expect(Object.setPrototypeOf(p, proto)).toBe(p);
+ let p = new Proxy(o, {});
+ expect(Object.setPrototypeOf(p, proto)).toBe(p);
+ });
+
+ test("correct arguments supplied to trap", () => {
+ let o = {};
+ let theNewProto = { foo: "bar" };
+
+ let p = new Proxy(o, {
+ setPrototypeOf(target, newProto) {
+ expect(target).toBe(o);
+ expect(newProto).toBe(theNewProto);
+ return true;
+ },
+ });
+
+ Object.setPrototypeOf(p, theNewProto);
+ });
+
+ test("conditional setting", () => {
+ let o = {};
+
+ let p = new Proxy(o, {
+ setPrototypeOf(target, newProto) {
+ if (target.shouldSet) Object.setPrototypeOf(target, newProto);
+ return true;
+ },
+ });
+
+ Object.setPrototypeOf(p, { foo: 1 });
+ expect(Object.getPrototypeOf(p).foo).toBeUndefined();
+ p.shouldSet = true;
+ expect(o.shouldSet).toBeTrue();
+ Object.setPrototypeOf(p, { foo: 1 });
+ expect(Object.getPrototypeOf(p).foo).toBe(1);
+ });
+
+ test("non-extensible targets", () => {
+ let o = {};
+ let proto = {};
+ Object.setPrototypeOf(o, proto);
+ Object.preventExtensions(o);
+
+ p = new Proxy(o, {
+ setPrototypeOf() {
+ return true;
+ },
+ });
+
+ expect(Object.setPrototypeOf(p, proto)).toBe(p);
+ expect(Object.getPrototypeOf(p)).toBe(proto);
+ });
+});
+
+describe("[[SetPrototypeOf]] invariants", () => {
+ test("cannot return false", () => {
+ let o = {};
+ p = new Proxy(o, {
+ setPrototypeOf() {
+ return false;
+ },
+ });
+
+ expect(() => {
+ Object.setPrototypeOf(p, {});
+ }).toThrowWithMessage(TypeError, "Object's [[SetPrototypeOf]] method returned false");
+ });
+
+ test("the argument must match the target's prototype if the target is non-extensible", () => {
+ let o = {};
+ Object.preventExtensions(o);
+
+ let p = new Proxy(o, {
+ setPrototypeOf() {
+ return true;
+ },
+ });
+
+ expect(() => {
+ Object.setPrototypeOf(p, {});
+ }).toThrowWithMessage(
+ TypeError,
+ "Proxy handler's setPrototypeOf trap violates invariant: the argument must match the prototype of the target if the target is non-extensible"
+ );
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.js
new file mode 100644
index 0000000000..9e336c2694
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.js
@@ -0,0 +1,37 @@
+test("constructs properly", () => {
+ expect(() => {
+ new Proxy({}, {});
+ }).not.toThrow();
+});
+
+test("constructor argument count", () => {
+ expect(() => {
+ new Proxy();
+ }).toThrowWithMessage(TypeError, "Proxy constructor requires at least two arguments");
+
+ expect(() => {
+ new Proxy({});
+ }).toThrowWithMessage(TypeError, "Proxy constructor requires at least two arguments");
+});
+
+test("constructor requires objects", () => {
+ expect(() => {
+ new Proxy(1, {});
+ }).toThrowWithMessage(
+ TypeError,
+ "Expected target argument of Proxy constructor to be object, got 1"
+ );
+
+ expect(() => {
+ new Proxy({}, 1);
+ }).toThrowWithMessage(
+ TypeError,
+ "Expected handler argument of Proxy constructor to be object, got 1"
+ );
+});
+
+test("constructor must be invoked with 'new'", () => {
+ expect(() => {
+ Proxy({}, {});
+ }).toThrowWithMessage(TypeError, "Proxy constructor must be called with 'new'");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.apply.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.apply.js
new file mode 100644
index 0000000000..7e6a67d16d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.apply.js
@@ -0,0 +1,44 @@
+test("length is 3", () => {
+ expect(Reflect.apply).toHaveLength(3);
+});
+
+describe("errors", () => {
+ test("target must be a function", () => {
+ [null, undefined, "foo", 123, NaN, Infinity, {}].forEach(value => {
+ expect(() => {
+ Reflect.apply(value);
+ }).toThrowWithMessage(
+ TypeError,
+ "First argument of Reflect.apply() must be a function"
+ );
+ });
+ });
+
+ test("arguments list must be an object", () => {
+ [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
+ expect(() => {
+ Reflect.apply(() => {}, undefined, value);
+ }).toThrowWithMessage(TypeError, "Arguments list must be an object");
+ });
+ });
+});
+
+describe("normal behavior", () => {
+ test("calling built-in functions", () => {
+ expect(Reflect.apply(String.prototype.charAt, "foo", [0])).toBe("f");
+ expect(Reflect.apply(Array.prototype.indexOf, ["hello", 123, "foo", "bar"], ["foo"])).toBe(
+ 2
+ );
+ });
+
+ test("|this| argument is forwarded to called function", () => {
+ function Foo(foo) {
+ this.foo = foo;
+ }
+
+ var o = {};
+ expect(o.foo).toBeUndefined();
+ Reflect.apply(Foo, o, ["bar"]);
+ expect(o.foo).toBe("bar");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.construct.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.construct.js
new file mode 100644
index 0000000000..a14a016ee0
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.construct.js
@@ -0,0 +1,75 @@
+test("length is 2", () => {
+ expect(Reflect.construct).toHaveLength(2);
+});
+
+describe("errors", () => {
+ test("target must be a function", () => {
+ [null, undefined, "foo", 123, NaN, Infinity, {}].forEach(value => {
+ expect(() => {
+ Reflect.construct(value);
+ }).toThrowWithMessage(
+ TypeError,
+ "First argument of Reflect.construct() must be a function"
+ );
+ });
+ });
+
+ test("arguments list must be an object", () => {
+ [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
+ expect(() => {
+ Reflect.construct(() => {}, value);
+ }).toThrowWithMessage(TypeError, "Arguments list must be an object");
+ });
+ });
+
+ test("new target must be a function", () => {
+ [null, undefined, "foo", 123, NaN, Infinity, {}].forEach(value => {
+ expect(() => {
+ Reflect.construct(() => {}, [], value);
+ }).toThrowWithMessage(
+ TypeError,
+ "Optional third argument of Reflect.construct() must be a constructor"
+ );
+ });
+ });
+});
+
+describe("normal behavior", () => {
+ test("built-in Array function", () => {
+ var a = Reflect.construct(Array, [5]);
+ expect(a instanceof Array).toBeTrue();
+ expect(a).toHaveLength(5);
+ });
+
+ test("built-in String function", () => {
+ var s = Reflect.construct(String, [123]);
+ expect(s instanceof String).toBeTrue();
+ expect(s).toHaveLength(3);
+ expect(s.toString()).toBe("123");
+ });
+
+ test("user-defined function", () => {
+ function Foo() {
+ this.name = "foo";
+ }
+
+ var o = Reflect.construct(Foo, []);
+ expect(o.name).toBe("foo");
+ expect(o instanceof Foo).toBeTrue();
+ });
+
+ test("user-defined function with different new target", () => {
+ function Foo() {
+ this.name = "foo";
+ }
+
+ function Bar() {
+ this.name = "bar";
+ }
+
+ var o = Reflect.construct(Foo, [], Bar);
+ expect(o.name).toBe("foo");
+ expect(o instanceof Foo).toBeFalse();
+ expect(o instanceof Bar).toBeTrue();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.defineProperty.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.defineProperty.js
new file mode 100644
index 0000000000..786d4c0178
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.defineProperty.js
@@ -0,0 +1,82 @@
+test("length is 3", () => {
+ expect(Reflect.defineProperty).toHaveLength(3);
+});
+
+describe("errors", () => {
+ test("target must be an object", () => {
+ [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
+ expect(() => {
+ Reflect.defineProperty(value);
+ }).toThrowWithMessage(
+ TypeError,
+ "First argument of Reflect.defineProperty() must be an object"
+ );
+ });
+ });
+
+ test("descriptor must be an object", () => {
+ [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
+ expect(() => {
+ Reflect.defineProperty({}, "foo", value);
+ }).toThrowWithMessage(TypeError, "Descriptor argument is not an object");
+ });
+ });
+});
+
+describe("normal behavior", () => {
+ test("initial value and non-writable", () => {
+ var o = {};
+
+ expect(o.foo).toBeUndefined();
+ expect(Reflect.defineProperty(o, "foo", { value: 1, writable: false })).toBeTrue();
+ expect(o.foo).toBe(1);
+ o.foo = 2;
+ expect(o.foo).toBe(1);
+ });
+
+ test("initial value and writable", () => {
+ var o = {};
+
+ expect(o.foo).toBeUndefined();
+ expect(Reflect.defineProperty(o, "foo", { value: 1, writable: true })).toBeTrue();
+ expect(o.foo).toBe(1);
+ o.foo = 2;
+ expect(o.foo).toBe(2);
+ });
+
+ test("can redefine value of configurable, writable property", () => {
+ var o = {};
+
+ expect(o.foo).toBeUndefined();
+ expect(
+ Reflect.defineProperty(o, "foo", { value: 1, configurable: true, writable: true })
+ ).toBeTrue();
+ expect(o.foo).toBe(1);
+ expect(Reflect.defineProperty(o, "foo", { value: 2 })).toBeTrue();
+ expect(o.foo).toBe(2);
+ });
+
+ test("can redefine value of configurable, non-writable property", () => {
+ var o = {};
+
+ expect(o.foo).toBeUndefined();
+ expect(
+ Reflect.defineProperty(o, "foo", { value: 1, configurable: true, writable: false })
+ ).toBeTrue();
+ expect(o.foo).toBe(1);
+ expect(Reflect.defineProperty(o, "foo", { value: 2 })).toBeTrue();
+ expect(o.foo).toBe(2);
+ });
+
+ test("cannot redefine value of non-configurable, non-writable property", () => {
+ var o = {};
+
+ expect(o.foo).toBeUndefined();
+ expect(
+ Reflect.defineProperty(o, "foo", { value: 1, configurable: false, writable: false })
+ ).toBeTrue();
+ expect(o.foo).toBe(1);
+ expect(Reflect.defineProperty(o, "foo", { value: 2 })).toBeFalse();
+ expect(o.foo).toBe(1);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.deleteProperty.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.deleteProperty.js
new file mode 100644
index 0000000000..a0dcbbada7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.deleteProperty.js
@@ -0,0 +1,66 @@
+test("length is 2", () => {
+ expect(Reflect.deleteProperty).toHaveLength(2);
+});
+
+describe("errors", () => {
+ test("target must be an object", () => {
+ [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
+ expect(() => {
+ Reflect.deleteProperty(value);
+ }).toThrowWithMessage(
+ TypeError,
+ "First argument of Reflect.deleteProperty() must be an object"
+ );
+ });
+ });
+});
+
+describe("normal behavior", () => {
+ test("deleting non-existent property", () => {
+ expect(Reflect.deleteProperty({})).toBeTrue();
+ expect(Reflect.deleteProperty({}, "foo")).toBeTrue();
+ });
+
+ test("deleting existent property", () => {
+ var o = { foo: 1 };
+ expect(o.foo).toBe(1);
+ expect(Reflect.deleteProperty(o, "foo")).toBeTrue();
+ expect(o.foo).toBeUndefined();
+ expect(Reflect.deleteProperty(o, "foo")).toBeTrue();
+ expect(o.foo).toBeUndefined();
+ });
+
+ test("deleting existent, configurable, non-writable property", () => {
+ var o = {};
+ Object.defineProperty(o, "foo", { value: 1, configurable: true, writable: false });
+ expect(Reflect.deleteProperty(o, "foo")).toBeTrue();
+ expect(o.foo).toBeUndefined();
+ });
+
+ test("deleting existent, non-configurable, writable property", () => {
+ var o = {};
+ Object.defineProperty(o, "foo", { value: 1, configurable: false, writable: true });
+ expect(Reflect.deleteProperty(o, "foo")).toBeFalse();
+ expect(o.foo).toBe(1);
+ });
+
+ test("deleting existent, non-configurable, non-writable property", () => {
+ var o = {};
+ Object.defineProperty(o, "foo", { value: 1, configurable: false, writable: false });
+ expect(Reflect.deleteProperty(o, "foo")).toBeFalse();
+ expect(o.foo).toBe(1);
+ });
+
+ test("deleting array index", () => {
+ var a = [1, 2, 3];
+ expect(a).toHaveLength(3);
+ expect(a[0]).toBe(1);
+ expect(a[1]).toBe(2);
+ expect(a[2]).toBe(3);
+ expect(Reflect.deleteProperty(a, 1)).toBeTrue();
+ expect(a).toHaveLength(3);
+ expect(a[0]).toBe(1);
+ expect(a[1]).toBeUndefined();
+ expect(a[2]).toBe(3);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.get.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.get.js
new file mode 100644
index 0000000000..fc9b5f172e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.get.js
@@ -0,0 +1,67 @@
+test("length is 2", () => {
+ expect(Reflect.get).toHaveLength(2);
+});
+
+describe("errors", () => {
+ test("target must be an object", () => {
+ [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
+ expect(() => {
+ Reflect.get(value);
+ }).toThrowWithMessage(TypeError, "First argument of Reflect.get() must be an object");
+ });
+ });
+});
+
+describe("normal behavior", () => {
+ test("regular object", () => {
+ expect(Reflect.get({})).toBeUndefined();
+ expect(Reflect.get({ undefined: 1 })).toBe(1);
+ expect(Reflect.get({ foo: 1 })).toBeUndefined();
+ expect(Reflect.get({ foo: 1 }, "foo")).toBe(1);
+ });
+
+ test("array", () => {
+ expect(Reflect.get([])).toBeUndefined();
+ expect(Reflect.get([1, 2, 3])).toBeUndefined();
+ expect(Reflect.get([1, 2, 3], "0")).toBe(1);
+ expect(Reflect.get([1, 2, 3], 0)).toBe(1);
+ expect(Reflect.get([1, 2, 3], 1)).toBe(2);
+ expect(Reflect.get([1, 2, 3], 2)).toBe(3);
+ expect(Reflect.get([1, 2, 3], 4)).toBeUndefined();
+ });
+
+ test("string object", () => {
+ expect(Reflect.get(new String())).toBeUndefined();
+ expect(Reflect.get(new String(), 0)).toBeUndefined();
+ expect(Reflect.get(new String("foo"), "0")).toBe("f");
+ expect(Reflect.get(new String("foo"), 0)).toBe("f");
+ expect(Reflect.get(new String("foo"), 1)).toBe("o");
+ expect(Reflect.get(new String("foo"), 2)).toBe("o");
+ expect(Reflect.get(new String("foo"), 3)).toBeUndefined();
+ });
+
+ test("getter function", () => {
+ const foo = {
+ get prop() {
+ this.getPropCalled = true;
+ },
+ };
+ const bar = {};
+ Object.setPrototypeOf(bar, foo);
+
+ expect(foo.getPropCalled).toBeUndefined();
+ expect(bar.getPropCalled).toBeUndefined();
+
+ Reflect.get(bar, "prop");
+ expect(foo.getPropCalled).toBeUndefined();
+ expect(bar.getPropCalled).toBeTrue();
+
+ Reflect.get(bar, "prop", foo);
+ expect(foo.getPropCalled).toBeTrue();
+ expect(bar.getPropCalled).toBeTrue();
+ });
+
+ test("native getter function", () => {
+ expect(Reflect.get(String.prototype, "length", "foo")).toBe(3);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getOwnPropertyDescriptor.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getOwnPropertyDescriptor.js
new file mode 100644
index 0000000000..e343a3f6fc
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getOwnPropertyDescriptor.js
@@ -0,0 +1,41 @@
+test("length is 2", () => {
+ expect(Reflect.getOwnPropertyDescriptor).toHaveLength(2);
+});
+
+describe("errors", () => {
+ test("target must be an object", () => {
+ [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
+ expect(() => {
+ Reflect.getOwnPropertyDescriptor(value);
+ }).toThrowWithMessage(
+ TypeError,
+ "First argument of Reflect.getOwnPropertyDescriptor() must be an object"
+ );
+ });
+ });
+});
+
+describe("normal behavior", () => {
+ test("get descriptor of undefined object property", () => {
+ expect(Reflect.getOwnPropertyDescriptor({})).toBeUndefined();
+ expect(Reflect.getOwnPropertyDescriptor({}, "foo")).toBeUndefined();
+ });
+
+ test("get descriptor of defined object property", () => {
+ var o = { foo: "bar" };
+ var d = Reflect.getOwnPropertyDescriptor(o, "foo");
+ expect(d.value).toBe("bar");
+ expect(d.writable).toBeTrue();
+ expect(d.enumerable).toBeTrue();
+ expect(d.configurable).toBeTrue();
+ });
+
+ test("get descriptor of array length property", () => {
+ var a = [];
+ d = Reflect.getOwnPropertyDescriptor(a, "length");
+ expect(d.value).toBe(0);
+ expect(d.writable).toBeTrue();
+ expect(d.enumerable).toBeFalse();
+ expect(d.configurable).toBeFalse();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getPrototypeOf.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getPrototypeOf.js
new file mode 100644
index 0000000000..87c9a63e87
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getPrototypeOf.js
@@ -0,0 +1,37 @@
+test("length is 1", () => {
+ expect(Reflect.getPrototypeOf).toHaveLength(1);
+});
+
+describe("errors", () => {
+ test("target must be an object", () => {
+ [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
+ expect(() => {
+ Reflect.getPrototypeOf(value);
+ }).toThrowWithMessage(
+ TypeError,
+ "First argument of Reflect.getPrototypeOf() must be an object"
+ );
+ });
+ });
+});
+
+describe("normal behavior", () => {
+ test("get prototype of regular object", () => {
+ expect(Reflect.getPrototypeOf({})).toBe(Object.prototype);
+ });
+
+ test("get prototype of array", () => {
+ expect(Reflect.getPrototypeOf([])).toBe(Array.prototype);
+ });
+
+ test("get prototype of string object", () => {
+ expect(Reflect.getPrototypeOf(new String())).toBe(String.prototype);
+ });
+
+ test("get user-defined prototype of regular object", () => {
+ var o = {};
+ var p = { foo: "bar" };
+ Reflect.setPrototypeOf(o, p);
+ expect(Reflect.getPrototypeOf(o)).toBe(p);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.has.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.has.js
new file mode 100644
index 0000000000..f343daeafa
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.has.js
@@ -0,0 +1,45 @@
+test("length is 2", () => {
+ expect(Reflect.has).toHaveLength(2);
+});
+
+describe("errors", () => {
+ test("target must be an object", () => {
+ [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
+ expect(() => {
+ Reflect.has(value);
+ }).toThrowWithMessage(TypeError, "First argument of Reflect.has() must be an object");
+ });
+ });
+});
+
+describe("normal behavior", () => {
+ test("regular object has property", () => {
+ expect(Reflect.has({})).toBeFalse();
+ expect(Reflect.has({ undefined })).toBeTrue();
+ expect(Reflect.has({ 0: "1" }, "0")).toBeTrue();
+ expect(Reflect.has({ foo: "bar" }, "foo")).toBeTrue();
+ expect(Reflect.has({ bar: "baz" }, "foo")).toBeFalse();
+ expect(Reflect.has({}, "toString")).toBeTrue();
+ });
+
+ test("array has property", () => {
+ expect(Reflect.has([])).toBeFalse();
+ expect(Reflect.has([], 0)).toBeFalse();
+ expect(Reflect.has([1, 2, 3], "0")).toBeTrue();
+ expect(Reflect.has([1, 2, 3], 0)).toBeTrue();
+ expect(Reflect.has([1, 2, 3], 1)).toBeTrue();
+ expect(Reflect.has([1, 2, 3], 2)).toBeTrue();
+ expect(Reflect.has([1, 2, 3], 3)).toBeFalse();
+ expect(Reflect.has([], "pop")).toBeTrue();
+ });
+
+ test("string object has property", () => {
+ expect(Reflect.has(new String())).toBeFalse();
+ expect(Reflect.has(new String("foo"), "0")).toBeTrue();
+ expect(Reflect.has(new String("foo"), 0)).toBeTrue();
+ expect(Reflect.has(new String("foo"), 1)).toBeTrue();
+ expect(Reflect.has(new String("foo"), 2)).toBeTrue();
+ expect(Reflect.has(new String("foo"), 3)).toBeFalse();
+ expect(Reflect.has(new String("foo"), "charAt")).toBeTrue();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.isExtensible.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.isExtensible.js
new file mode 100644
index 0000000000..2f873da66c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.isExtensible.js
@@ -0,0 +1,33 @@
+test("length is 1", () => {
+ expect(Reflect.isExtensible).toHaveLength(1);
+});
+
+describe("errors", () => {
+ test("target must be an object", () => {
+ [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
+ expect(() => {
+ Reflect.isExtensible(value);
+ }).toThrowWithMessage(
+ TypeError,
+ "First argument of Reflect.isExtensible() must be an object"
+ );
+ });
+ });
+});
+
+describe("normal behavior", () => {
+ test("regular object is extensible", () => {
+ expect(Reflect.isExtensible({})).toBeTrue();
+ });
+
+ test("global object is extensible", () => {
+ expect(Reflect.isExtensible(globalThis)).toBeTrue();
+ });
+
+ test("regular object is not extensible after preventExtensions()", () => {
+ var o = {};
+ expect(Reflect.isExtensible(o)).toBeTrue();
+ Reflect.preventExtensions(o);
+ expect(Reflect.isExtensible(o)).toBeFalse();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.ownKeys.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.ownKeys.js
new file mode 100644
index 0000000000..b262775f1a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.ownKeys.js
@@ -0,0 +1,51 @@
+test("length is 1", () => {
+ expect(Reflect.ownKeys).toHaveLength(1);
+});
+
+describe("errors", () => {
+ test("target must be an object", () => {
+ [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
+ expect(() => {
+ Reflect.ownKeys(value);
+ }).toThrowWithMessage(
+ TypeError,
+ "First argument of Reflect.ownKeys() must be an object"
+ );
+ });
+ });
+});
+
+describe("normal behavior", () => {
+ test("regular empty object has no own keys", () => {
+ var objectOwnKeys = Reflect.ownKeys({});
+ expect(objectOwnKeys instanceof Array).toBeTrue();
+ expect(objectOwnKeys).toHaveLength(0);
+ });
+
+ test("regular object with some properties has own keys", () => {
+ var objectOwnKeys = Reflect.ownKeys({ foo: "bar", bar: "baz", 0: 42 });
+ expect(objectOwnKeys instanceof Array).toBeTrue();
+ expect(objectOwnKeys).toHaveLength(3);
+ expect(objectOwnKeys[0]).toBe("0");
+ expect(objectOwnKeys[1]).toBe("foo");
+ expect(objectOwnKeys[2]).toBe("bar");
+ });
+
+ test("empty array has only 'length' own key", () => {
+ var arrayOwnKeys = Reflect.ownKeys([]);
+ expect(arrayOwnKeys instanceof Array).toBeTrue();
+ expect(arrayOwnKeys).toHaveLength(1);
+ expect(arrayOwnKeys[0]).toBe("length");
+ });
+
+ test("array with some values has 'lenght' and indices own keys", () => {
+ var arrayOwnKeys = Reflect.ownKeys(["foo", [], 123, undefined]);
+ expect(arrayOwnKeys instanceof Array).toBeTrue();
+ expect(arrayOwnKeys).toHaveLength(5);
+ expect(arrayOwnKeys[0]).toBe("0");
+ expect(arrayOwnKeys[1]).toBe("1");
+ expect(arrayOwnKeys[2]).toBe("2");
+ expect(arrayOwnKeys[3]).toBe("3");
+ expect(arrayOwnKeys[4]).toBe("length");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.preventExtensions.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.preventExtensions.js
new file mode 100644
index 0000000000..6b0bfdc1bc
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.preventExtensions.js
@@ -0,0 +1,42 @@
+describe("errors", () => {
+ test("target must be an object", () => {
+ [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
+ expect(() => {
+ Reflect.preventExtensions(value);
+ }).toThrowWithMessage(
+ TypeError,
+ "First argument of Reflect.preventExtensions() must be an object"
+ );
+ });
+ });
+});
+
+describe("normal behavior", () => {
+ test("length is 1", () => {
+ expect(Reflect.preventExtensions).toHaveLength(1);
+ });
+
+ test("properties cannot be added", () => {
+ var o = {};
+ o.foo = "foo";
+ expect(Reflect.preventExtensions(o)).toBeTrue();
+ o.bar = "bar";
+ expect(o.foo).toBe("foo");
+ expect(o.bar).toBeUndefined();
+ });
+
+ test("modifying existing properties", () => {
+ const o = {};
+ o.foo = "foo";
+ expect(Reflect.preventExtensions(o)).toBeTrue();
+ o.foo = "bar";
+ expect(o.foo).toBe("bar");
+ });
+
+ test("deleting existing properties", () => {
+ const o = { foo: "bar" };
+ Reflect.preventExtensions(o);
+ delete o.foo;
+ expect(o).not.toHaveProperty("foo");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.set.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.set.js
new file mode 100644
index 0000000000..854fb23888
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.set.js
@@ -0,0 +1,102 @@
+test("length is 3", () => {
+ expect(Reflect.set).toHaveLength(3);
+});
+
+describe("errors", () => {
+ test("target must be an object", () => {
+ [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
+ expect(() => {
+ Reflect.set(value);
+ }).toThrowWithMessage(TypeError, "First argument of Reflect.set() must be an object");
+ });
+ });
+});
+
+describe("normal behavior", () => {
+ test("setting properties of regular object", () => {
+ var o = {};
+
+ expect(Reflect.set(o)).toBeTrue();
+ expect(o.undefined).toBeUndefined();
+
+ expect(Reflect.set(o, "foo")).toBeTrue();
+ expect(o.foo).toBeUndefined();
+
+ expect(Reflect.set(o, "foo", "bar")).toBeTrue();
+ expect(o.foo).toBe("bar");
+
+ expect(Reflect.set(o, "foo", 42)).toBeTrue();
+ expect(o.foo).toBe(42);
+ });
+
+ test("setting configurable, non-writable property of regular object", () => {
+ var o = {};
+ Object.defineProperty(o, "foo", { value: 1, configurable: true, writable: false });
+ expect(Reflect.set(o, "foo", 2)).toBeFalse();
+ expect(o.foo).toBe(1);
+ });
+
+ test("setting non-configurable, writable property of regular object", () => {
+ var o = {};
+ Object.defineProperty(o, "foo", { value: 1, configurable: false, writable: true });
+ expect(Reflect.set(o, "foo", 2)).toBeTrue();
+ expect(o.foo).toBe(2);
+ });
+
+ test("", () => {
+ var a = [];
+ expect(a.length === 0);
+ expect(Reflect.set(a, "0")).toBeTrue();
+ expect(a.length === 1);
+ expect(a[0]).toBeUndefined();
+ expect(Reflect.set(a, 1, "foo")).toBeTrue();
+ expect(a.length === 2);
+ expect(a[0]).toBeUndefined();
+ expect(a[1] === "foo");
+ expect(Reflect.set(a, 4, "bar")).toBeTrue();
+ expect(a.length === 5);
+ expect(a[0]).toBeUndefined();
+ expect(a[1] === "foo");
+ expect(a[2]).toBeUndefined();
+ expect(a[3]).toBeUndefined();
+ expect(a[4] === "bar");
+ });
+
+ test("setting setter property of regular object", () => {
+ const foo = {
+ set prop(value) {
+ this.setPropCalled = true;
+ },
+ };
+ expect(foo.setPropCalled).toBeUndefined();
+ Reflect.set(foo, "prop", 42);
+ expect(foo.setPropCalled).toBeTrue();
+ });
+
+ test("setting setter property of regular object with different receiver", () => {
+ const foo = {
+ set prop(value) {
+ this.setPropCalled = true;
+ },
+ };
+ const bar = {};
+ Object.setPrototypeOf(bar, foo);
+
+ expect(foo.setPropCalled).toBeUndefined();
+ expect(bar.setPropCalled).toBeUndefined();
+
+ Reflect.set(bar, "prop", 42);
+ expect(foo.setPropCalled).toBeUndefined();
+ expect(bar.setPropCalled).toBeTrue();
+
+ Reflect.set(bar, "prop", 42, foo);
+ expect(foo.setPropCalled).toBeTrue();
+ expect(bar.setPropCalled).toBeTrue();
+ });
+
+ test("native setter function", () => {
+ const e = new Error();
+ Reflect.set(Error.prototype, "name", "Foo", e);
+ expect(e.name).toBe("Foo");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.setPrototypeOf.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.setPrototypeOf.js
new file mode 100644
index 0000000000..70bbdf9d9c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.setPrototypeOf.js
@@ -0,0 +1,57 @@
+test("length is 2", () => {
+ expect(Reflect.setPrototypeOf).toHaveLength(2);
+});
+
+describe("errors", () => {
+ test("target must be an object", () => {
+ [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
+ expect(() => {
+ Reflect.setPrototypeOf(value);
+ }).toThrowWithMessage(
+ TypeError,
+ "First argument of Reflect.setPrototypeOf() must be an object"
+ );
+ });
+ });
+
+ test("prototype must be an object or null", () => {
+ [undefined, "foo", 123, NaN, Infinity].forEach(value => {
+ expect(() => {
+ Reflect.setPrototypeOf({}, value);
+ }).toThrowWithMessage(TypeError, "Prototype must be an object or null");
+ });
+ });
+});
+
+describe("normal behavior", () => {
+ test("setting prototype of regular object", () => {
+ expect(Reflect.setPrototypeOf({}, null)).toBeTrue();
+ expect(Reflect.setPrototypeOf({}, {})).toBeTrue();
+ expect(Reflect.setPrototypeOf({}, Object.prototype)).toBeTrue();
+ expect(Reflect.setPrototypeOf({}, Array.prototype)).toBeTrue();
+ expect(Reflect.setPrototypeOf({}, String.prototype)).toBeTrue();
+ expect(Reflect.setPrototypeOf({}, Reflect.getPrototypeOf({}))).toBeTrue();
+ });
+
+ test("setting user-defined prototype of regular object", () => {
+ var o = {};
+ var p = { foo: "bar" };
+ expect(o.foo).toBeUndefined();
+ expect(Reflect.setPrototypeOf(o, p)).toBeTrue();
+ expect(o.foo).toBe("bar");
+ });
+
+ test("setting prototype of non-extensible object", () => {
+ var o = {};
+ Reflect.preventExtensions(o);
+ expect(Reflect.setPrototypeOf(o, {})).toBeFalse();
+ });
+
+ test("setting same prototype of non-extensible object", () => {
+ var o = {};
+ var p = { foo: "bar" };
+ expect(Reflect.setPrototypeOf(o, p)).toBeTrue();
+ Reflect.preventExtensions(o);
+ expect(Reflect.setPrototypeOf(o, p)).toBeTrue();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.js b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.js
new file mode 100644
index 0000000000..9ae8ab97c0
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.js
@@ -0,0 +1,8 @@
+test("basic functionality", () => {
+ expect(RegExp().toString()).toBe("/(?:)/");
+ expect(RegExp(undefined).toString()).toBe("/(?:)/");
+ expect(RegExp("foo").toString()).toBe("/foo/");
+ expect(RegExp("foo", undefined).toString()).toBe("/foo/");
+ expect(RegExp("foo", "g").toString()).toBe("/foo/g");
+ expect(RegExp(undefined, "g").toString()).toBe("/(?:)/g");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.exec.js b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.exec.js
new file mode 100644
index 0000000000..7fcbf3582a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.exec.js
@@ -0,0 +1,58 @@
+test("basic functionality", () => {
+ let re = /foo/;
+ expect(re.exec.length).toBe(1);
+
+ let res = re.exec("foo");
+ expect(res.length).toBe(1);
+ expect(res[0]).toBe("foo");
+ expect(res.groups).toBe(undefined);
+ expect(res.index).toBe(0);
+});
+
+test("basic unnamed captures", () => {
+ let re = /f(o.*)/;
+ let res = re.exec("fooooo");
+
+ expect(res.length).toBe(2);
+ expect(res[0]).toBe("fooooo");
+ expect(res[1]).toBe("ooooo");
+ expect(res.groups).toBe(undefined);
+ expect(res.index).toBe(0);
+});
+
+test("basic named captures", () => {
+ let re = /f(?<os>o.*)/;
+ let res = re.exec("fooooo");
+
+ expect(res.length).toBe(1);
+ expect(res.index).toBe(0);
+ expect(res[0]).toBe("fooooo");
+ expect(res.groups).not.toBe(undefined);
+ expect(res.groups.os).toBe("ooooo");
+});
+
+test("basic index", () => {
+ let re = /foo/;
+ let res = re.exec("abcfoo");
+
+ expect(res.length).toBe(1);
+ expect(res.index).toBe(3);
+ expect(res[0]).toBe("foo");
+});
+
+test("basic index with global and initial offset", () => {
+ let re = /foo/g;
+ re.lastIndex = 2;
+ let res = re.exec("abcfoo");
+
+ expect(res.length).toBe(1);
+ expect(res.index).toBe(3);
+ expect(res[0]).toBe("foo");
+});
+
+test("not matching", () => {
+ let re = /foo/;
+ let res = re.exec("bar");
+
+ expect(res).toBe(null);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.flags.js b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.flags.js
new file mode 100644
index 0000000000..9ccc0b614e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.flags.js
@@ -0,0 +1,11 @@
+test("basic functionality", () => {
+ expect(/foo/.flags).toBe("");
+ expect(/foo/g.flags).toBe("g");
+ expect(/foo/i.flags).toBe("i");
+ expect(/foo/m.flags).toBe("m");
+ expect(/foo/s.flags).toBe("s");
+ expect(/foo/u.flags).toBe("u");
+ expect(/foo/y.flags).toBe("y");
+ // prettier-ignore
+ expect(/foo/sgimyu.flags).toBe("gimsuy");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.source.js b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.source.js
new file mode 100644
index 0000000000..4f0e14297f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.source.js
@@ -0,0 +1,8 @@
+test("basic functionality", () => {
+ expect(RegExp.prototype.source).toBe("(?:)");
+ expect(RegExp().source).toBe("(?:)");
+ expect(/test/.source).toBe("test");
+ expect(/\n/.source).toBe("\\n");
+ // FIXME: RegExp parse doesn't parse \/ :(
+ // expect(/foo\/bar/.source).toBe("foo\\/bar");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.test.js b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.test.js
new file mode 100644
index 0000000000..df3e8b9771
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.test.js
@@ -0,0 +1,58 @@
+test("basic functionality", () => {
+ expect(RegExp.prototype.test).toHaveLength(1);
+});
+
+test("simple test", () => {
+ let re = /test/;
+ expect(re.test("test")).toBe(true);
+ expect(re.test("test")).toBe(true);
+});
+
+test("simple global test", () => {
+ let re = /test/g;
+ expect(re.test("testtest")).toBe(true);
+ expect(re.test("testtest")).toBe(true);
+ expect(re.test("testtest")).toBe(false);
+ expect(re.test("testtest")).toBe(true);
+ expect(re.test("testtest")).toBe(true);
+});
+
+test("global test with offset lastIndex", () => {
+ let re = /test/g;
+ re.lastIndex = 2;
+ expect(re.test("testtest")).toBe(true);
+ expect(re.test("testtest")).toBe(false);
+ expect(re.test("testtest")).toBe(true);
+ expect(re.test("testtest")).toBe(true);
+ expect(re.test("testtest")).toBe(false);
+});
+
+test("sticky test with offset lastIndex", () => {
+ let re = /test/y;
+ re.lastIndex = 2;
+ expect(re.test("aatest")).toBe(true);
+ expect(re.test("aatest")).toBe(false);
+ expect(re.test("aatest")).toBe(false);
+});
+
+test("flag and options", () => {
+ expect(/foo/gi.flags).toBe("gi");
+ expect(/foo/mu.flags).toBe("mu");
+ expect(/foo/gimsuy.flags).toBe("gimsuy");
+
+ let re = /foo/gim;
+ expect(re.dotAll).toBe(false);
+ expect(re.global).toBe(true);
+ expect(re.ignoreCase).toBe(true);
+ expect(re.multiline).toBe(true);
+ expect(re.sticky).toBe(false);
+ expect(re.unicode).toBe(false);
+
+ expect(() => {
+ /foo/gg;
+ }).toThrowWithMessage(SyntaxError, "Repeated RegExp flag 'g'");
+
+ expect(() => {
+ /foo/x;
+ }).toThrowWithMessage(SyntaxError, "Invalid RegExp flag 'x'");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.toString.js b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.toString.js
new file mode 100644
index 0000000000..fcf132862d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.toString.js
@@ -0,0 +1,6 @@
+test("basic functionality", () => {
+ expect(RegExp.prototype.toString).toHaveLength(0);
+
+ expect(/test/g.toString()).toBe("/test/g");
+ expect(RegExp.prototype.toString.call({ source: "test", flags: "g" })).toBe("/test/g");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.fromCharCode.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.fromCharCode.js
new file mode 100644
index 0000000000..c6b52fd2e1
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.fromCharCode.js
@@ -0,0 +1,17 @@
+test("basic functionality", () => {
+ expect(String.fromCharCode).toHaveLength(1);
+
+ expect(String.fromCharCode()).toBe("");
+ expect(String.fromCharCode(0)).toBe("\u0000");
+ expect(String.fromCharCode(false)).toBe("\u0000");
+ expect(String.fromCharCode(null)).toBe("\u0000");
+ expect(String.fromCharCode(undefined)).toBe("\u0000");
+ expect(String.fromCharCode(1)).toBe("\u0001");
+ expect(String.fromCharCode(true)).toBe("\u0001");
+ expect(String.fromCharCode(-1)).toBe("\uffff");
+ expect(String.fromCharCode(0xffff)).toBe("\uffff");
+ expect(String.fromCharCode(0x123ffff)).toBe("\uffff");
+ expect(String.fromCharCode(65)).toBe("A");
+ expect(String.fromCharCode(65, 66, 67)).toBe("ABC");
+ expect(String.fromCharCode(228, 246, 252)).toBe("äöü");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.js
new file mode 100644
index 0000000000..687e0929d4
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.js
@@ -0,0 +1,9 @@
+test("constructor properties", () => {
+ expect(String).toHaveLength(1);
+ expect(String.name).toBe("String");
+});
+
+test("typeof", () => {
+ expect(typeof String()).toBe("string");
+ expect(typeof new String()).toBe("object");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype-generic-functions.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype-generic-functions.js
new file mode 100644
index 0000000000..a05ca53d8c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype-generic-functions.js
@@ -0,0 +1,39 @@
+test("basic functionality", () => {
+ const genericStringPrototypeFunctions = [
+ "charAt",
+ "charCodeAt",
+ "repeat",
+ "startsWith",
+ "endsWith",
+ "indexOf",
+ "toLowerCase",
+ "toUpperCase",
+ "padStart",
+ "padEnd",
+ "trim",
+ "trimStart",
+ "trimEnd",
+ "concat",
+ "substring",
+ "includes",
+ "slice",
+ ];
+
+ genericStringPrototypeFunctions.forEach(name => {
+ String.prototype[name].call({ toString: () => "hello friends" });
+ String.prototype[name].call({ toString: () => 123 });
+ String.prototype[name].call({ toString: () => undefined });
+
+ expect(() => {
+ String.prototype[name].call({ toString: () => new String() });
+ }).toThrowWithMessage(TypeError, "Cannot convert object to string");
+
+ expect(() => {
+ String.prototype[name].call({ toString: () => [] });
+ }).toThrowWithMessage(TypeError, "Cannot convert object to string");
+
+ expect(() => {
+ String.prototype[name].call({ toString: () => ({}) });
+ }).toThrowWithMessage(TypeError, "Cannot convert object to string");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.charAt.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.charAt.js
new file mode 100644
index 0000000000..7747331885
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.charAt.js
@@ -0,0 +1,20 @@
+test("basic functionality", () => {
+ expect(String.prototype.charAt).toHaveLength(1);
+
+ var s = "foobar";
+ expect(typeof s).toBe("string");
+ expect(s).toHaveLength(6);
+
+ expect(s.charAt(0)).toBe("f");
+ expect(s.charAt(1)).toBe("o");
+ expect(s.charAt(2)).toBe("o");
+ expect(s.charAt(3)).toBe("b");
+ expect(s.charAt(4)).toBe("a");
+ expect(s.charAt(5)).toBe("r");
+ expect(s.charAt(6)).toBe("");
+
+ expect(s.charAt()).toBe("f");
+ expect(s.charAt(NaN)).toBe("f");
+ expect(s.charAt("foo")).toBe("f");
+ expect(s.charAt(undefined)).toBe("f");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.charCodeAt.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.charCodeAt.js
new file mode 100644
index 0000000000..e7ebd9d0d3
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.charCodeAt.js
@@ -0,0 +1,21 @@
+test("basic functionality", () => {
+ expect(String.prototype.charAt).toHaveLength(1);
+
+ var s = "Foobar";
+ expect(typeof s).toBe("string");
+ expect(s).toHaveLength(6);
+
+ expect(s.charCodeAt(0)).toBe(70);
+ expect(s.charCodeAt(1)).toBe(111);
+ expect(s.charCodeAt(2)).toBe(111);
+ expect(s.charCodeAt(3)).toBe(98);
+ expect(s.charCodeAt(4)).toBe(97);
+ expect(s.charCodeAt(5)).toBe(114);
+ expect(s.charCodeAt(6)).toBe(NaN);
+ expect(s.charCodeAt(-1)).toBe(NaN);
+
+ expect(s.charCodeAt()).toBe(70);
+ expect(s.charCodeAt(NaN)).toBe(70);
+ expect(s.charCodeAt("foo")).toBe(70);
+ expect(s.charCodeAt(undefined)).toBe(70);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.concat.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.concat.js
new file mode 100644
index 0000000000..7a38877e9e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.concat.js
@@ -0,0 +1,17 @@
+test("basic functionality", () => {
+ expect(String.prototype.concat).toHaveLength(1);
+
+ expect("".concat(1)).toBe("1");
+ expect("".concat(3, 2, 1)).toBe("321");
+ expect("hello".concat(" ", "friends")).toBe("hello friends");
+ expect("".concat(null)).toBe("null");
+ expect("".concat(false)).toBe("false");
+ expect("".concat(true)).toBe("true");
+ expect("".concat([])).toBe("");
+ expect("".concat([1, 2, 3, "hello"])).toBe("1,2,3,hello");
+ expect("".concat(true, [])).toBe("true");
+ expect("".concat(true, false)).toBe("truefalse");
+ expect("".concat({})).toBe("[object Object]");
+ expect("".concat(1, {})).toBe("1[object Object]");
+ expect("".concat(1, {}, false)).toBe("1[object Object]false");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.endsWith.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.endsWith.js
new file mode 100644
index 0000000000..286cc6b1b2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.endsWith.js
@@ -0,0 +1,42 @@
+test("basic functionality", () => {
+ expect(String.prototype.endsWith).toHaveLength(1);
+
+ var s = "foobar";
+ expect(s.endsWith("r")).toBeTrue();
+ expect(s.endsWith("ar")).toBeTrue();
+ expect(s.endsWith("bar")).toBeTrue();
+ expect(s.endsWith("obar")).toBeTrue();
+ expect(s.endsWith("oobar")).toBeTrue();
+ expect(s.endsWith("foobar")).toBeTrue();
+ expect(s.endsWith("1foobar")).toBeFalse();
+ expect(s.endsWith("r", 6)).toBeTrue();
+ expect(s.endsWith("ar", 6)).toBeTrue();
+ expect(s.endsWith("bar", 6)).toBeTrue();
+ expect(s.endsWith("obar", 6)).toBeTrue();
+ expect(s.endsWith("oobar", 6)).toBeTrue();
+ expect(s.endsWith("foobar", 6)).toBeTrue();
+ expect(s.endsWith("1foobar", 6)).toBeFalse();
+ expect(s.endsWith("bar", [])).toBeFalse();
+ expect(s.endsWith("bar", null)).toBeFalse();
+ expect(s.endsWith("bar", false)).toBeFalse();
+ expect(s.endsWith("bar", true)).toBeFalse();
+ expect(s.endsWith("f", true)).toBeTrue();
+ expect(s.endsWith("bar", -1)).toBeFalse();
+ expect(s.endsWith("bar", 42)).toBeTrue();
+ expect(s.endsWith("foo", 3)).toBeTrue();
+ expect(s.endsWith("foo", "3")).toBeTrue();
+ expect(s.endsWith("foo1", 3)).toBeFalse();
+ expect(s.endsWith("foo", 3.7)).toBeTrue();
+ expect(s.endsWith()).toBeFalse();
+ expect(s.endsWith("")).toBeTrue();
+ expect(s.endsWith("", 0)).toBeTrue();
+ expect(s.endsWith("", 1)).toBeTrue();
+ expect(s.endsWith("", -1)).toBeTrue();
+ expect(s.endsWith("", 42)).toBeTrue();
+ expect("12undefined".endsWith()).toBeTrue();
+ expect(() => s.endsWith(/foobar/)).toThrowWithMessage(
+ TypeError,
+ "searchString is not a string, but a regular expression"
+ );
+ expect(s.endsWith("bar", undefined)).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.includes.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.includes.js
new file mode 100644
index 0000000000..14a2230530
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.includes.js
@@ -0,0 +1,12 @@
+test("basic functionality", () => {
+ expect(String.prototype.includes).toHaveLength(1);
+
+ expect("hello friends".includes("hello")).toBeTrue();
+ expect("hello friends".includes("hello", 100)).toBeFalse();
+ expect("hello friends".includes("hello", -10)).toBeTrue();
+ expect("hello friends".includes("friends", 6)).toBeTrue();
+ expect("hello friends".includes("hello", 6)).toBeFalse();
+ expect("hello friends false".includes(false)).toBeTrue();
+ expect("hello 10 friends".includes(10)).toBeTrue();
+ expect("hello friends undefined".includes()).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.indexOf.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.indexOf.js
new file mode 100644
index 0000000000..8e2ca2f892
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.indexOf.js
@@ -0,0 +1,8 @@
+test("basic functionality", () => {
+ expect(String.prototype.indexOf).toHaveLength(1);
+
+ var s = "hello friends";
+
+ expect(s.indexOf("friends")).toBe(6);
+ expect(s.indexOf("enemies")).toBe(-1);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.js
new file mode 100644
index 0000000000..a66207e96c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.js
@@ -0,0 +1,9 @@
+test("basic functionality", () => {
+ expect(String.prototype).toHaveLength(0);
+
+ expect(typeof Object.getPrototypeOf("")).toBe("object");
+ expect(Object.getPrototypeOf("").valueOf()).toBe("");
+
+ expect(typeof String.prototype).toBe("object");
+ expect(String.prototype.valueOf()).toBe("");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.lastIndexOf.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.lastIndexOf.js
new file mode 100644
index 0000000000..ce69fc3214
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.lastIndexOf.js
@@ -0,0 +1,22 @@
+test("basic functionality", () => {
+ expect(String.prototype.lastIndexOf).toHaveLength(1);
+
+ expect("hello friends".lastIndexOf()).toBe(-1);
+ expect("hello friends".lastIndexOf("e")).toBe(9);
+ expect("hello friends".lastIndexOf("e", -7)).toBe(-1);
+ expect("hello friends".lastIndexOf("e", 100)).toBe(9);
+ expect("hello friends".lastIndexOf("")).toBe(13);
+ expect("hello friends".lastIndexOf("Z")).toBe(-1);
+ expect("hello friends".lastIndexOf("serenity")).toBe(-1);
+ expect("hello friends".lastIndexOf("", 4)).toBe(4);
+ expect("hello serenity friends".lastIndexOf("serenity")).toBe(6);
+ expect("hello serenity friends serenity".lastIndexOf("serenity")).toBe(23);
+ expect("hello serenity friends serenity".lastIndexOf("serenity", 14)).toBe(6);
+ expect("".lastIndexOf("")).toBe(0);
+ expect("".lastIndexOf("", 1)).toBe(0);
+ expect("".lastIndexOf("", -1)).toBe(0);
+ expect("hello friends serenity".lastIndexOf("h", 10)).toBe(0);
+ expect("hello friends serenity".lastIndexOf("l", 4)).toBe(3);
+ expect("hello friends serenity".lastIndexOf("s", 13)).toBe(12);
+ expect("hello".lastIndexOf("serenity")).toBe(-1);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.padEnd.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.padEnd.js
new file mode 100644
index 0000000000..a103084c3a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.padEnd.js
@@ -0,0 +1,18 @@
+test("basic functionality", () => {
+ expect(String.prototype.padEnd).toHaveLength(1);
+
+ var s = "foo";
+ expect(s.padEnd(-1)).toBe("foo");
+ expect(s.padEnd(0)).toBe("foo");
+ expect(s.padEnd(3)).toBe("foo");
+ expect(s.padEnd(5)).toBe("foo ");
+ expect(s.padEnd(10)).toBe("foo ");
+ expect(s.padEnd("5")).toBe("foo ");
+ expect(s.padEnd([[["5"]]])).toBe("foo ");
+ expect(s.padEnd(2, "+")).toBe("foo");
+ expect(s.padEnd(5, "+")).toBe("foo++");
+ expect(s.padEnd(5, 1)).toBe("foo11");
+ expect(s.padEnd(10, null)).toBe("foonullnul");
+ expect(s.padEnd(10, "bar")).toBe("foobarbarb");
+ expect(s.padEnd(10, "123456789")).toBe("foo1234567");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.padStart.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.padStart.js
new file mode 100644
index 0000000000..a2f0f0b710
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.padStart.js
@@ -0,0 +1,18 @@
+test("basic functionality", () => {
+ expect(String.prototype.padStart).toHaveLength(1);
+
+ var s = "foo";
+ expect(s.padStart(-1)).toBe("foo");
+ expect(s.padStart(0)).toBe("foo");
+ expect(s.padStart(3)).toBe("foo");
+ expect(s.padStart(5)).toBe(" foo");
+ expect(s.padStart(10)).toBe(" foo");
+ expect(s.padStart("5")).toBe(" foo");
+ expect(s.padStart([[["5"]]])).toBe(" foo");
+ expect(s.padStart(2, "+")).toBe("foo");
+ expect(s.padStart(5, "+")).toBe("++foo");
+ expect(s.padStart(5, 1)).toBe("11foo");
+ expect(s.padStart(10, null)).toBe("nullnulfoo");
+ expect(s.padStart(10, "bar")).toBe("barbarbfoo");
+ expect(s.padStart(10, "123456789")).toBe("1234567foo");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.repeat.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.repeat.js
new file mode 100644
index 0000000000..8427698509
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.repeat.js
@@ -0,0 +1,25 @@
+test("basic functionality", () => {
+ expect(String.prototype.repeat).toHaveLength(1);
+
+ expect("foo".repeat(0)).toBe("");
+ expect("foo".repeat(1)).toBe("foo");
+ expect("foo".repeat(2)).toBe("foofoo");
+ expect("foo".repeat(3)).toBe("foofoofoo");
+ expect("foo".repeat(3.1)).toBe("foofoofoo");
+ expect("foo".repeat(3.5)).toBe("foofoofoo");
+ expect("foo".repeat(3.9)).toBe("foofoofoo");
+ expect("foo".repeat(null)).toBe("");
+ expect("foo".repeat(undefined)).toBe("");
+ expect("foo".repeat([])).toBe("");
+ expect("foo".repeat("")).toBe("");
+});
+
+test("throws correct range errors", () => {
+ expect(() => {
+ "foo".repeat(-1);
+ }).toThrowWithMessage(RangeError, "repeat count must be a positive number");
+
+ expect(() => {
+ "foo".repeat(Infinity);
+ }).toThrowWithMessage(RangeError, "repeat count must be a finite number");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.slice.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.slice.js
new file mode 100644
index 0000000000..2141e0f6f3
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.slice.js
@@ -0,0 +1,17 @@
+test("basic functionality", () => {
+ expect(String.prototype.slice).toHaveLength(2);
+
+ expect("hello friends".slice()).toBe("hello friends");
+ expect("hello friends".slice(1)).toBe("ello friends");
+ expect("hello friends".slice(0, 5)).toBe("hello");
+ expect("hello friends".slice(13, 6)).toBe("");
+ expect("hello friends".slice("", 5)).toBe("hello");
+ expect("hello friends".slice(3, 3)).toBe("");
+ expect("hello friends".slice(-1, 13)).toBe("s");
+ expect("hello friends".slice(0, 50)).toBe("hello friends");
+ expect("hello friends".slice(0, "5")).toBe("hello");
+ expect("hello friends".slice("6", "13")).toBe("friends");
+ expect("hello friends".slice(-7)).toBe("friends");
+ expect("hello friends".slice(1000)).toBe("");
+ expect("hello friends".slice(-1000)).toBe("hello friends");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.split.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.split.js
new file mode 100644
index 0000000000..63d6c69a97
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.split.js
@@ -0,0 +1,35 @@
+test("basic functionality", () => {
+ expect(String.prototype.split).toHaveLength(2);
+
+ expect("hello friends".split()).toEqual(["hello friends"]);
+ expect("hello friends".split("")).toEqual([
+ "h",
+ "e",
+ "l",
+ "l",
+ "o",
+ " ",
+ "f",
+ "r",
+ "i",
+ "e",
+ "n",
+ "d",
+ "s",
+ ]);
+ expect("hello friends".split(" ")).toEqual(["hello", "friends"]);
+
+ expect("a,b,c,d".split(",")).toEqual(["a", "b", "c", "d"]);
+ expect(",a,b,c,d".split(",")).toEqual(["", "a", "b", "c", "d"]);
+ expect("a,b,c,d,".split(",")).toEqual(["a", "b", "c", "d", ""]);
+ expect("a,b,,c,d".split(",")).toEqual(["a", "b", "", "c", "d"]);
+ expect(",a,b,,c,d,".split(",")).toEqual(["", "a", "b", "", "c", "d", ""]);
+ expect(",a,b,,,c,d,".split(",,")).toEqual([",a,b", ",c,d,"]);
+});
+
+test("limits", () => {
+ expect("a b c d".split(" ", 0)).toEqual([]);
+ expect("a b c d".split(" ", 1)).toEqual(["a"]);
+ expect("a b c d".split(" ", 3)).toEqual(["a", "b", "c"]);
+ expect("a b c d".split(" ", 100)).toEqual(["a", "b", "c", "d"]);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.startsWith.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.startsWith.js
new file mode 100644
index 0000000000..5114da78f9
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.startsWith.js
@@ -0,0 +1,36 @@
+test("basic functionality", () => {
+ expect(String.prototype.startsWith).toHaveLength(1);
+
+ var s = "foobar";
+ expect(s.startsWith("f")).toBeTrue();
+ expect(s.startsWith("fo")).toBeTrue();
+ expect(s.startsWith("foo")).toBeTrue();
+ expect(s.startsWith("foob")).toBeTrue();
+ expect(s.startsWith("fooba")).toBeTrue();
+ expect(s.startsWith("foobar")).toBeTrue();
+ expect(s.startsWith("foobar1")).toBeFalse();
+ expect(s.startsWith("f", 0)).toBeTrue();
+ expect(s.startsWith("fo", 0)).toBeTrue();
+ expect(s.startsWith("foo", 0)).toBeTrue();
+ expect(s.startsWith("foob", 0)).toBeTrue();
+ expect(s.startsWith("fooba", 0)).toBeTrue();
+ expect(s.startsWith("foobar", 0)).toBeTrue();
+ expect(s.startsWith("foobar1", 0)).toBeFalse();
+ expect(s.startsWith("foo", [])).toBeTrue();
+ expect(s.startsWith("foo", null)).toBeTrue();
+ expect(s.startsWith("foo", undefined)).toBeTrue();
+ expect(s.startsWith("foo", false)).toBeTrue();
+ expect(s.startsWith("foo", true)).toBeFalse();
+ expect(s.startsWith("foo", "foo")).toBeTrue();
+ expect(s.startsWith("foo", -1)).toBeTrue();
+ expect(s.startsWith("foo", 42)).toBeFalse();
+ expect(s.startsWith("bar", 3)).toBeTrue();
+ expect(s.startsWith("bar", "3")).toBeTrue();
+ expect(s.startsWith("bar1", 3)).toBeFalse();
+ expect(s.startsWith()).toBeFalse();
+ expect(s.startsWith("")).toBeTrue();
+ expect(s.startsWith("", 0)).toBeTrue();
+ expect(s.startsWith("", 1)).toBeTrue();
+ expect(s.startsWith("", -1)).toBeTrue();
+ expect(s.startsWith("", 42)).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.substr.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.substr.js
new file mode 100644
index 0000000000..ade6c3c60a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.substr.js
@@ -0,0 +1,16 @@
+test("basic functionality", () => {
+ expect(String.prototype.substr).toHaveLength(2);
+
+ expect("hello friends".substr()).toBe("hello friends");
+ expect("hello friends".substr(1)).toBe("ello friends");
+ expect("hello friends".substr(0, 5)).toBe("hello");
+ expect("hello friends".substr(5, 6)).toBe(" frien");
+ expect("hello friends".substr("", 5)).toBe("hello");
+ expect("hello friends".substr(3, 3)).toBe("lo ");
+ expect("hello friends".substr(-1, 13)).toBe("s");
+ expect("hello friends".substr(0, 50)).toBe("hello friends");
+ expect("hello friends".substr(0, "5")).toBe("hello");
+ expect("hello friends".substr("2", "2")).toBe("ll");
+ expect("hello friends".substr(-7)).toBe("friends");
+ expect("hello friends".substr(-3, -5)).toBe("");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.substring.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.substring.js
new file mode 100644
index 0000000000..49a9888d7f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.substring.js
@@ -0,0 +1,14 @@
+test("basic functionality", () => {
+ expect(String.prototype.substring).toHaveLength(2);
+
+ expect("hello friends".substring()).toBe("hello friends");
+ expect("hello friends".substring(1)).toBe("ello friends");
+ expect("hello friends".substring(0, 5)).toBe("hello");
+ expect("hello friends".substring(13, 6)).toBe("friends");
+ expect("hello friends".substring("", 5)).toBe("hello");
+ expect("hello friends".substring(3, 3)).toBe("");
+ expect("hello friends".substring(-1, 13)).toBe("hello friends");
+ expect("hello friends".substring(0, 50)).toBe("hello friends");
+ expect("hello friends".substring(0, "5")).toBe("hello");
+ expect("hello friends".substring("6", "13")).toBe("friends");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toLowerCase.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toLowerCase.js
new file mode 100644
index 0000000000..4c7cbad50a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toLowerCase.js
@@ -0,0 +1,9 @@
+test("basic functionality", () => {
+ expect(String.prototype.toLowerCase).toHaveLength(0);
+
+ expect("foo".toLowerCase()).toBe("foo");
+ expect("Foo".toLowerCase()).toBe("foo");
+ expect("FOO".toLowerCase()).toBe("foo");
+
+ expect(("b" + "a" + +"a" + "a").toLowerCase()).toBe("banana");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toString.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toString.js
new file mode 100644
index 0000000000..418f2da5f8
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toString.js
@@ -0,0 +1,6 @@
+test("basic functionality", () => {
+ expect(String.prototype.toString).toHaveLength(0);
+
+ expect("".toString()).toBe("");
+ expect("hello friends".toString()).toBe("hello friends");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toUpperCase.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toUpperCase.js
new file mode 100644
index 0000000000..03f463c6e1
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.toUpperCase.js
@@ -0,0 +1,9 @@
+test("basic functionality", () => {
+ expect(String.prototype.toUpperCase).toHaveLength(0);
+
+ expect("foo".toUpperCase()).toBe("FOO");
+ expect("Foo".toUpperCase()).toBe("FOO");
+ expect("FOO".toUpperCase()).toBe("FOO");
+
+ expect(("b" + "a" + +"n" + "a").toUpperCase()).toBe("BANANA");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.trim.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.trim.js
new file mode 100644
index 0000000000..d60522cad4
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.trim.js
@@ -0,0 +1,58 @@
+test("trim", () => {
+ expect(String.prototype.trim).toHaveLength(0);
+
+ expect(" hello friends ".trim()).toBe("hello friends");
+ expect("hello friends ".trim()).toBe("hello friends");
+ expect(" hello friends".trim()).toBe("hello friends");
+
+ expect("\thello friends\t".trim()).toBe("hello friends");
+ expect("\thello friends".trim()).toBe("hello friends");
+ expect("hello friends\t".trim()).toBe("hello friends");
+
+ expect("\rhello friends\r".trim()).toBe("hello friends");
+ expect("\rhello friends".trim()).toBe("hello friends");
+ expect("hello friends\r".trim()).toBe("hello friends");
+
+ expect("\rhello friends\n".trim()).toBe("hello friends");
+ expect("\r\thello friends".trim()).toBe("hello friends");
+ expect("hello friends\r\n".trim()).toBe("hello friends");
+ expect(" \thello friends\r\n".trim()).toBe("hello friends");
+ expect("\n\t\thello friends\r\n".trim()).toBe("hello friends");
+ expect("\n\t\thello friends\t\t".trim()).toBe("hello friends");
+});
+
+test("trimStart", () => {
+ expect(String.prototype.trimStart).toHaveLength(0);
+
+ expect(" hello friends".trimStart()).toBe("hello friends");
+ expect("hello friends ".trimStart()).toBe("hello friends ");
+ expect(" hello friends ".trimStart()).toBe("hello friends ");
+
+ expect("\thello friends".trimStart()).toBe("hello friends");
+ expect("hello friends\t".trimStart()).toBe("hello friends\t");
+ expect("\thello friends\t".trimStart()).toBe("hello friends\t");
+
+ expect("\rhello friends".trimStart()).toBe("hello friends");
+ expect("hello friends\r".trimStart()).toBe("hello friends\r");
+ expect("\rhello friends\r".trimStart()).toBe("hello friends\r");
+});
+
+test("trimEnd", () => {
+ expect(String.prototype.trimEnd).toHaveLength(0);
+
+ expect("hello friends ".trimEnd()).toBe("hello friends");
+ expect(" hello friends".trimEnd()).toBe(" hello friends");
+ expect(" hello friends ".trimEnd()).toBe(" hello friends");
+
+ expect("hello friends\t".trimEnd()).toBe("hello friends");
+ expect("\thello friends".trimEnd()).toBe("\thello friends");
+ expect("\thello friends\t".trimEnd()).toBe("\thello friends");
+
+ expect("hello friends\r".trimEnd()).toBe("hello friends");
+ expect("\rhello friends".trimEnd()).toBe("\rhello friends");
+ expect("\rhello friends\r".trimEnd()).toBe("\rhello friends");
+
+ expect("hello friends\n".trimEnd()).toBe("hello friends");
+ expect("\r\nhello friends".trimEnd()).toBe("\r\nhello friends");
+ expect("\rhello friends\r\n".trimEnd()).toBe("\rhello friends");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.valueOf.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.valueOf.js
new file mode 100644
index 0000000000..87fb50fa21
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.valueOf.js
@@ -0,0 +1,12 @@
+test("basic functionality", () => {
+ expect(String.prototype.valueOf).toHaveLength(0);
+
+ expect(String()).toBe("");
+ expect(new String().valueOf()).toBe("");
+ expect(String("foo")).toBe("foo");
+ expect(new String("foo").valueOf()).toBe("foo");
+ expect(String(123)).toBe("123");
+ expect(new String(123).valueOf()).toBe("123");
+ expect(String(123)).toBe("123");
+ expect(new String(123).valueOf()).toBe("123");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.raw.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.raw.js
new file mode 100644
index 0000000000..a866276034
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.raw.js
@@ -0,0 +1,31 @@
+test("basic functionality", () => {
+ expect(String.raw).toHaveLength(1);
+
+ let str = String.raw`foo\nbar`;
+ expect(str).toHaveLength(8);
+ expect(str).toBe("foo\\nbar");
+
+ str = String.raw`foo ${1 + 9}\nbar${"hf!"}`;
+ expect(str).toBe("foo 10\\nbarhf!");
+
+ str = String.raw`${10}${20}${30}`;
+ expect(str).toBe("102030");
+
+ str = String.raw({ raw: ["foo ", "\\nbar"] }, 10, "hf!");
+ expect(str).toBe("foo 10\\nbar");
+
+ str = String.raw({ raw: ["foo ", "\\nbar"] });
+ expect(str).toBe("foo \\nbar");
+
+ str = String.raw({ raw: [] }, 10, "hf!");
+ expect(str).toBe("");
+
+ str = String.raw({ raw: 1 });
+ expect(str).toBe("");
+});
+
+test("passing object with no 'raw' property", () => {
+ expect(() => {
+ String.raw({});
+ }).toThrowWithMessage(TypeError, "Cannot convert property 'raw' to object from undefined");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.for.js b/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.for.js
new file mode 100644
index 0000000000..c905be3c72
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.for.js
@@ -0,0 +1,23 @@
+test("basic functionality", () => {
+ const localSym = Symbol("foo");
+ const globalSym = Symbol.for("foo");
+
+ expect(localSym).not.toBe(globalSym);
+ expect(localSym).not.toBe(Symbol("foo"));
+ expect(globalSym).not.toBe(Symbol("foo"));
+ expect(globalSym).toBe(Symbol.for("foo"));
+ expect(localSym.toString()).toBe("Symbol(foo)");
+ expect(globalSym.toString()).toBe("Symbol(foo)");
+
+ expect(Symbol.for(1).description).toBe("1");
+ expect(Symbol.for(true).description).toBe("true");
+ expect(Symbol.for({}).description).toBe("[object Object]");
+ expect(Symbol.for().description).toBe("undefined");
+ expect(Symbol.for(null).description).toBe("null");
+});
+
+test("symbol argument throws an error", () => {
+ expect(() => {
+ Symbol.for(Symbol());
+ }).toThrowWithMessage(TypeError, "Cannot convert symbol to string");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.js b/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.js
new file mode 100644
index 0000000000..2533f593c7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.js
@@ -0,0 +1,19 @@
+test("basic functionality", () => {
+ const s1 = Symbol("foo");
+ const s2 = Symbol("foo");
+
+ expect(s1).not.toBe(s2);
+ expect(s1.description).toBe("foo");
+ expect(s2.description).toBe("foo");
+
+ s1.description = "bar";
+ expect(s1.description).toBe("foo");
+
+ expect(typeof s1).toBe("symbol");
+});
+
+test("constructing symbol from symbol is an error", () => {
+ expect(() => {
+ Symbol(Symbol("foo"));
+ }).toThrowWithMessage(TypeError, "Cannot convert symbol to string");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.keyFor.js b/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.keyFor.js
new file mode 100644
index 0000000000..8b04f652bc
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.keyFor.js
@@ -0,0 +1,24 @@
+test("basic functionality", () => {
+ const localSym = Symbol("foo");
+ const globalSym = Symbol.for("foo");
+
+ expect(Symbol.keyFor(localSym)).toBeUndefined();
+ expect(Symbol.keyFor(globalSym)).toBe("foo");
+});
+
+test("bad argument values", () => {
+ [
+ [1, "1"],
+ [null, "null"],
+ [undefined, "undefined"],
+ [[], "[object Array]"],
+ [{}, "[object Object]"],
+ [true, "true"],
+ ["foobar", "foobar"],
+ [function () {}, "[object ScriptFunction]"], // FIXME: Better function stringification
+ ].forEach(testCase => {
+ expect(() => {
+ Symbol.keyFor(testCase[0]);
+ }).toThrowWithMessage(TypeError, `${testCase[1]} is not a symbol`);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.prototype.@@toStringTag.js b/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.prototype.@@toStringTag.js
new file mode 100644
index 0000000000..c73e637fc4
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.prototype.@@toStringTag.js
@@ -0,0 +1,3 @@
+test("basic functionality", () => {
+ expect(Symbol.prototype[Symbol.toStringTag]).toBe("Symbol");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.prototype.toString.js b/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.prototype.toString.js
new file mode 100644
index 0000000000..4a95fe2397
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.prototype.toString.js
@@ -0,0 +1,25 @@
+describe("correct behavior", () => {
+ test("basic functionality", () => {
+ const s1 = Symbol("baz");
+ const s2 = Symbol.for("qux");
+
+ // Explicit conversions to string are fine, but implicit toString via concatenation throws.
+ expect(s1.toString()).toBe("Symbol(baz)");
+ expect(String(s1)).toBe("Symbol(baz)");
+ expect(s2.toString()).toBe("Symbol(qux)");
+ });
+});
+
+describe("errors", () => {
+ test("convert to string", () => {
+ expect(() => {
+ Symbol() + "";
+ }).toThrowWithMessage(TypeError, "Cannot convert symbol to string");
+ });
+
+ test("convert to number", () => {
+ expect(() => {
+ Symbol() + 1;
+ }).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.prototype.valueOf.js b/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.prototype.valueOf.js
new file mode 100644
index 0000000000..0bfbfef912
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Symbol/Symbol.prototype.valueOf.js
@@ -0,0 +1,15 @@
+test("basic functionality", () => {
+ const local = Symbol("foo");
+ // const global = Symbol.for("foo");
+ expect(local.valueOf()).toBe(local);
+ // expect(global.valueOf()).toBe(global);
+
+ expect(Symbol.prototype.valueOf.call(local)).toBe(local);
+ // expect(Symbol.prototype.valueOf.call(global)).toBe(global);
+});
+
+test("|this| must be a symbol", () => {
+ expect(() => {
+ Symbol.prototype.valueOf.call("foo");
+ }).toThrowWithMessage(TypeError, "Not a Symbol object");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Symbol/well-known-symbol-existence.js b/Userland/Libraries/LibJS/Tests/builtins/Symbol/well-known-symbol-existence.js
new file mode 100644
index 0000000000..204e69882c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Symbol/well-known-symbol-existence.js
@@ -0,0 +1,15 @@
+test("basic functionality", () => {
+ expect(Symbol).toHaveProperty("iterator");
+ expect(Symbol).toHaveProperty("asyncIterator");
+ expect(Symbol).toHaveProperty("match");
+ expect(Symbol).toHaveProperty("matchAll");
+ expect(Symbol).toHaveProperty("replace");
+ expect(Symbol).toHaveProperty("search");
+ expect(Symbol).toHaveProperty("split");
+ expect(Symbol).toHaveProperty("hasInstance");
+ expect(Symbol).toHaveProperty("isConcatSpreadable");
+ expect(Symbol).toHaveProperty("unscopables");
+ expect(Symbol).toHaveProperty("species");
+ expect(Symbol).toHaveProperty("toPrimitive");
+ expect(Symbol).toHaveProperty("toStringTag");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.BYTES_PER_ELEMENT.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.BYTES_PER_ELEMENT.js
new file mode 100644
index 0000000000..c53213ac07
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.BYTES_PER_ELEMENT.js
@@ -0,0 +1,10 @@
+test("basic functionality", () => {
+ expect(Uint8Array.BYTES_PER_ELEMENT).toBe(1);
+ expect(Uint16Array.BYTES_PER_ELEMENT).toBe(2);
+ expect(Uint32Array.BYTES_PER_ELEMENT).toBe(4);
+ expect(Int8Array.BYTES_PER_ELEMENT).toBe(1);
+ expect(Int16Array.BYTES_PER_ELEMENT).toBe(2);
+ expect(Int32Array.BYTES_PER_ELEMENT).toBe(4);
+ expect(Float32Array.BYTES_PER_ELEMENT).toBe(4);
+ expect(Float64Array.BYTES_PER_ELEMENT).toBe(8);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.js
new file mode 100644
index 0000000000..d443d13cd6
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.js
@@ -0,0 +1,133 @@
+// Update when more typed arrays get added
+const TYPED_ARRAYS = [
+ Uint8Array,
+ Uint16Array,
+ Uint32Array,
+ Int8Array,
+ Int16Array,
+ Int32Array,
+ Float32Array,
+ Float64Array,
+];
+
+const getTypedArrayConstructor = () => Object.getPrototypeOf(TYPED_ARRAYS[0]);
+
+test("basic functionality", () => {
+ const TypedArray = getTypedArrayConstructor();
+ expect(TypedArray).toHaveLength(0);
+ expect(TypedArray.name).toBe("TypedArray");
+ expect(TypedArray.prototype.constructor).toBe(TypedArray);
+ TYPED_ARRAYS.forEach(T => {
+ expect(T.prototype.constructor).toBe(T);
+ });
+ const FunctionPrototype = Object.getPrototypeOf(() => {});
+ expect(Object.getPrototypeOf(TypedArray)).toBe(FunctionPrototype);
+});
+
+test("typed array constructors must be invoked with 'new'", () => {
+ TYPED_ARRAYS.forEach(T => {
+ expect(() => {
+ T();
+ }).toThrowWithMessage(TypeError, `${T.name} constructor must be called with 'new'`);
+ });
+});
+
+test("typed array constructors have TypedArray as prototype", () => {
+ const TypedArray = getTypedArrayConstructor();
+ TYPED_ARRAYS.forEach(T => {
+ expect(Object.getPrototypeOf(T)).toBe(TypedArray);
+ });
+});
+
+test("typed array prototypes have TypedArray.prototype as prototype", () => {
+ const TypedArray = getTypedArrayConstructor();
+ TYPED_ARRAYS.forEach(T => {
+ const TPrototype = Object.getPrototypeOf(new T());
+ expect(Object.getPrototypeOf(TPrototype)).toBe(TypedArray.prototype);
+ });
+});
+
+test("typed arrays inherit from TypedArray", () => {
+ const TypedArray = getTypedArrayConstructor();
+ TYPED_ARRAYS.forEach(T => {
+ expect(new T()).toBeInstanceOf(TypedArray);
+ });
+});
+
+test("typed array can share the same ArrayBuffer", () => {
+ const arrayBuffer = new ArrayBuffer(2);
+ const uint8Array = new Uint8Array(arrayBuffer);
+ const uint16Array = new Uint16Array(arrayBuffer);
+ expect(uint8Array[0]).toBe(0);
+ expect(uint8Array[1]).toBe(0);
+ expect(uint16Array[0]).toBe(0);
+ expect(uint16Array[1]).toBeUndefined();
+ uint16Array[0] = 54321;
+ expect(uint8Array[0]).toBe(0x31);
+ expect(uint8Array[1]).toBe(0xd4);
+ expect(uint16Array[0]).toBe(54321);
+ expect(uint16Array[1]).toBeUndefined();
+});
+
+test("typed array from ArrayBuffer with custom length and offset", () => {
+ const arrayBuffer = new ArrayBuffer(10);
+ const uint8ArrayAll = new Uint8Array(arrayBuffer);
+ const uint16ArrayPartial = new Uint16Array(arrayBuffer, 2, 4);
+ // Affects two bytes of the buffer, beginning at offset
+ uint16ArrayPartial[0] = 52651;
+ // Out of relative bounds, doesn't affect buffer
+ uint16ArrayPartial[4] = 123;
+ expect(uint8ArrayAll[0]).toBe(0);
+ expect(uint8ArrayAll[1]).toBe(0);
+ expect(uint8ArrayAll[2]).toBe(0xab);
+ expect(uint8ArrayAll[3]).toBe(0xcd);
+ expect(uint8ArrayAll[5]).toBe(0);
+ expect(uint8ArrayAll[6]).toBe(0);
+ expect(uint8ArrayAll[7]).toBe(0);
+ expect(uint8ArrayAll[8]).toBe(0);
+ expect(uint8ArrayAll[9]).toBe(0);
+});
+
+test("typed array from ArrayBuffer errors", () => {
+ expect(() => {
+ new Uint16Array(new ArrayBuffer(1));
+ }).toThrowWithMessage(
+ RangeError,
+ "Invalid buffer length for Uint16Array: must be a multiple of 2, got 1"
+ );
+
+ expect(() => {
+ new Uint16Array(new ArrayBuffer(), 1);
+ }).toThrowWithMessage(
+ RangeError,
+ "Invalid byte offset for Uint16Array: must be a multiple of 2, got 1"
+ );
+
+ expect(() => {
+ new Uint16Array(new ArrayBuffer(), 2);
+ }).toThrowWithMessage(
+ RangeError,
+ "Typed array byte offset 2 is out of range for buffer with length 0"
+ );
+
+ expect(() => {
+ new Uint16Array(new ArrayBuffer(7), 2, 3);
+ }).toThrowWithMessage(
+ RangeError,
+ "Typed array range 2:8 is out of range for buffer with length 7"
+ );
+});
+
+test("TypedArray is not exposed on the global object", () => {
+ expect(globalThis.TypedArray).toBeUndefined();
+});
+
+test("TypedArray is abstract", () => {
+ const TypedArray = getTypedArrayConstructor();
+ expect(() => {
+ TypedArray();
+ }).toThrowWithMessage(TypeError, "Abstract class TypedArray cannot be constructed directly");
+ expect(() => {
+ new TypedArray();
+ }).toThrowWithMessage(TypeError, "Abstract class TypedArray cannot be constructed directly");
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.length.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.length.js
new file mode 100644
index 0000000000..ce02c72391
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.length.js
@@ -0,0 +1,19 @@
+// Update when more typed arrays get added
+const TYPED_ARRAYS = [
+ Uint8Array,
+ Uint16Array,
+ Uint32Array,
+ Int8Array,
+ Int16Array,
+ Int32Array,
+ Float32Array,
+ Float64Array,
+];
+
+test("basic functionality", () => {
+ TYPED_ARRAYS.forEach(T => {
+ const typedArray = new T(42);
+ expect(Object.hasOwnProperty(typedArray, "length")).toBeFalse();
+ expect(typedArray.length).toBe(42);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/typed-array-basic.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/typed-array-basic.js
new file mode 100644
index 0000000000..aeefb5bb54
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/typed-array-basic.js
@@ -0,0 +1,86 @@
+test("basic Uint8Array", () => {
+ var a = new Uint8Array(1);
+ expect(typeof a).toBe("object");
+ expect(a instanceof Uint8Array).toBe(true);
+ expect(a.length).toBe(1);
+ a[0] = 1;
+ expect(a[0]).toBe(1);
+ a[0] -= 2;
+ expect(a[0]).toBe(0xff);
+ ++a[0];
+ expect(a[0]).toBe(0);
+});
+
+test("basic Uint16Array", () => {
+ var a = new Uint16Array(1);
+ expect(typeof a).toBe("object");
+ expect(a instanceof Uint16Array).toBe(true);
+ expect(a.length).toBe(1);
+ a[0] = 1;
+ expect(a[0]).toBe(1);
+ a[0] -= 2;
+ expect(a[0]).toBe(0xffff);
+ ++a[0];
+ expect(a[0]).toBe(0);
+});
+
+test("basic Uint32Array", () => {
+ var a = new Uint32Array(1);
+ expect(typeof a).toBe("object");
+ expect(a instanceof Uint32Array).toBe(true);
+ expect(a.length).toBe(1);
+ a[0] = 1;
+ expect(a[0]).toBe(1);
+ a[0] -= 2;
+ expect(a[0]).toBe(0xffffffff);
+ ++a[0];
+ expect(a[0]).toBe(0);
+});
+
+test("basic Int8Array", () => {
+ var a = new Int8Array(1);
+ expect(typeof a).toBe("object");
+ expect(a instanceof Int8Array).toBe(true);
+ expect(a.length).toBe(1);
+ a[0] = 1;
+ expect(a[0]).toBe(1);
+ a[0] -= 2;
+ expect(a[0]).toBe(-1);
+ ++a[0];
+ expect(a[0]).toBe(0);
+ a[0] = 127;
+ a[0]++;
+ expect(a[0]).toBe(-128);
+});
+
+test("basic Int16Array", () => {
+ var a = new Int16Array(1);
+ expect(typeof a).toBe("object");
+ expect(a instanceof Int16Array).toBe(true);
+ expect(a.length).toBe(1);
+ a[0] = 1;
+ expect(a[0]).toBe(1);
+ a[0] -= 2;
+ expect(a[0]).toBe(-1);
+ ++a[0];
+ expect(a[0]).toBe(0);
+ a[0] = 32767;
+ a[0]++;
+ expect(a[0]).toBe(-32768);
+});
+
+test("basic Int32Array", () => {
+ var a = new Int32Array(1);
+ expect(typeof a).toBe("object");
+ expect(a instanceof Int32Array).toBe(true);
+ expect(a.length).toBe(1);
+ a[0] = 1;
+ expect(a[0]).toBe(1);
+ a[0] -= 2;
+ expect(a[0]).toBe(-1);
+ ++a[0];
+ expect(a[0]).toBe(0);
+ a[0] = 0x7fffffff;
+ a[0]++;
+ expect(a[0]).toBe(-0x80000000);
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/functions/isFinite.js b/Userland/Libraries/LibJS/Tests/builtins/functions/isFinite.js
new file mode 100644
index 0000000000..71f4f70655
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/functions/isFinite.js
@@ -0,0 +1,23 @@
+test("basic functionality", () => {
+ expect(isFinite).toHaveLength(1);
+
+ expect(isFinite(0)).toBeTrue();
+ expect(isFinite(1.23)).toBeTrue();
+ expect(isFinite(42)).toBeTrue();
+ expect(isFinite("")).toBeTrue();
+ expect(isFinite("0")).toBeTrue();
+ expect(isFinite("42")).toBeTrue();
+ expect(isFinite(true)).toBeTrue();
+ expect(isFinite(false)).toBeTrue();
+ expect(isFinite(null)).toBeTrue();
+ expect(isFinite([])).toBeTrue();
+
+ expect(isFinite()).toBeFalse();
+ expect(isFinite(NaN)).toBeFalse();
+ expect(isFinite(undefined)).toBeFalse();
+ expect(isFinite(Infinity)).toBeFalse();
+ expect(isFinite(-Infinity)).toBeFalse();
+ expect(isFinite("foo")).toBeFalse();
+ expect(isFinite({})).toBeFalse();
+ expect(isFinite([1, 2, 3])).toBeFalse();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/functions/isNaN.js b/Userland/Libraries/LibJS/Tests/builtins/functions/isNaN.js
new file mode 100644
index 0000000000..bb0d418bb5
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/functions/isNaN.js
@@ -0,0 +1,26 @@
+test("length is 1", () => {
+ expect(isNaN).toHaveLength(1);
+});
+
+test("arguments that evaluate to false", () => {
+ expect(isNaN(0)).toBeFalse();
+ expect(isNaN(42)).toBeFalse();
+ expect(isNaN("")).toBeFalse();
+ expect(isNaN("0")).toBeFalse();
+ expect(isNaN("42")).toBeFalse();
+ expect(isNaN(true)).toBeFalse();
+ expect(isNaN(false)).toBeFalse();
+ expect(isNaN(null)).toBeFalse();
+ expect(isNaN([])).toBeFalse();
+ expect(isNaN(Infinity)).toBeFalse();
+ expect(isNaN(-Infinity)).toBeFalse();
+});
+
+test("arguments that evaluate to true", () => {
+ expect(isNaN()).toBeTrue();
+ expect(isNaN(NaN)).toBeTrue();
+ expect(isNaN(undefined)).toBeTrue();
+ expect(isNaN("foo")).toBeTrue();
+ expect(isNaN({})).toBeTrue();
+ expect(isNaN([1, 2, 3])).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/functions/parseFloat.js b/Userland/Libraries/LibJS/Tests/builtins/functions/parseFloat.js
new file mode 100644
index 0000000000..20ed2633dd
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/functions/parseFloat.js
@@ -0,0 +1,59 @@
+test("parsing numbers", () => {
+ [
+ [0, 0],
+ [1, 1],
+ [0.23, 0.23],
+ [1.23, 1.23],
+ [0.0123e2, 1.23],
+ [1.23e4, 12300],
+ [Infinity, Infinity],
+ ].forEach(test => {
+ expect(parseFloat(test[0])).toBe(test[1]);
+ expect(parseFloat(+test[0])).toBe(test[1]);
+ expect(parseFloat(-test[0])).toBe(-test[1]);
+ });
+});
+
+test("parsing strings", () => {
+ [
+ ["0", 0],
+ ["1", 1],
+ [".23", 0.23],
+ ["1.23", 1.23],
+ ["0.0123E+2", 1.23],
+ ["1.23e4", 12300],
+ ["Infinity", Infinity],
+ ].forEach(test => {
+ expect(parseFloat(test[0])).toBe(test[1]);
+ expect(parseFloat(`+${test[0]}`)).toBe(test[1]);
+ expect(parseFloat(`-${test[0]}`)).toBe(-test[1]);
+ expect(parseFloat(`${test[0]}foo`)).toBe(test[1]);
+ expect(parseFloat(`+${test[0]}foo`)).toBe(test[1]);
+ expect(parseFloat(`-${test[0]}foo`)).toBe(-test[1]);
+ expect(parseFloat(` \n \t ${test[0]} \v foo `)).toBe(test[1]);
+ expect(parseFloat(` \r -${test[0]} \f \n\n foo `)).toBe(-test[1]);
+ expect(parseFloat({ toString: () => test[0] })).toBe(test[1]);
+ });
+});
+
+test("parsing NaN", () => {
+ [
+ "",
+ [],
+ [],
+ true,
+ false,
+ null,
+ undefined,
+ NaN,
+ "foo123",
+ "foo+123",
+ "fooInfinity",
+ "foo+Infinity",
+ ].forEach(value => {
+ expect(parseFloat(value)).toBeNaN();
+ });
+
+ expect(parseFloat()).toBeNaN();
+ expect(parseFloat("", 123, Infinity)).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/classes/class-advanced-extends.js b/Userland/Libraries/LibJS/Tests/classes/class-advanced-extends.js
new file mode 100644
index 0000000000..5cbeb90404
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/classes/class-advanced-extends.js
@@ -0,0 +1,35 @@
+test("extending function", () => {
+ class A extends function () {
+ this.foo = 10;
+ } {}
+
+ expect(new A().foo).toBe(10);
+});
+
+test("extending null", () => {
+ class A extends null {}
+
+ expect(Object.getPrototypeOf(A.prototype)).toBeNull();
+
+ expect(() => {
+ new A();
+ }).toThrowWithMessage(ReferenceError, "|this| has not been initialized");
+});
+
+test("extending String", () => {
+ class MyString extends String {}
+
+ const ms = new MyString("abc");
+ expect(ms).toBeInstanceOf(MyString);
+ expect(ms).toBeInstanceOf(String);
+ expect(ms.charAt(1)).toBe("b");
+
+ class MyString2 extends MyString {
+ charAt(i) {
+ return `#${super.charAt(i)}`;
+ }
+ }
+
+ const ms2 = new MyString2("abc");
+ expect(ms2.charAt(1)).toBe("#b");
+});
diff --git a/Userland/Libraries/LibJS/Tests/classes/class-basic.js b/Userland/Libraries/LibJS/Tests/classes/class-basic.js
new file mode 100644
index 0000000000..0fae88c033
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/classes/class-basic.js
@@ -0,0 +1,5 @@
+test("class properties", () => {
+ class A {}
+ expect(A.name).toBe("A");
+ expect(A).toHaveLength(0);
+});
diff --git a/Userland/Libraries/LibJS/Tests/classes/class-constructor.js b/Userland/Libraries/LibJS/Tests/classes/class-constructor.js
new file mode 100644
index 0000000000..54a601e10c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/classes/class-constructor.js
@@ -0,0 +1,67 @@
+test("property initialization", () => {
+ class A {
+ constructor() {
+ this.x = 3;
+ }
+ }
+
+ expect(new A().x).toBe(3);
+});
+
+test("method initialization", () => {
+ class A {
+ constructor() {
+ this.x = () => 10;
+ }
+ }
+
+ expect(new A().x()).toBe(10);
+});
+
+test("initialize to class method", () => {
+ class A {
+ constructor() {
+ this.x = this.method;
+ }
+
+ method() {
+ return 10;
+ }
+ }
+
+ expect(new A().x()).toBe(10);
+});
+
+test("constructor length affects class length", () => {
+ class A {
+ constructor() {}
+ }
+
+ expect(A).toHaveLength(0);
+
+ class B {
+ constructor(a, b, c = 2) {}
+ }
+
+ expect(B).toHaveLength(2);
+});
+
+test("must be invoked with 'new'", () => {
+ class A {
+ constructor() {}
+ }
+
+ expect(() => {
+ A();
+ }).toThrowWithMessage(TypeError, "Class constructor A must be called with 'new'");
+
+ expect(() => {
+ A.prototype.constructor();
+ }).toThrowWithMessage(TypeError, "Class constructor A must be called with 'new'");
+});
+
+test("implicit constructor", () => {
+ class A {}
+
+ expect(new A()).toBeInstanceOf(A);
+});
diff --git a/Userland/Libraries/LibJS/Tests/classes/class-errors.js b/Userland/Libraries/LibJS/Tests/classes/class-errors.js
new file mode 100644
index 0000000000..fb479a0af2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/classes/class-errors.js
@@ -0,0 +1,100 @@
+describe("non-syntax errors", () => {
+ test("super reference inside nested-but-same |this| scope with no base class", () => {
+ expect(`
+ class A {
+ foo() {
+ () => { super.bar; }
+ }
+ }`).toEval();
+ });
+
+ test("super reference property lookup with no base class", () => {
+ expect(`
+ class A {
+ constructor() {
+ super.foo;
+ }
+ }`).toEval();
+ });
+});
+
+describe("reference errors", () => {
+ test("derived class doesn't call super in constructor before using this", () => {
+ class Parent {}
+ class Child extends Parent {
+ constructor() {
+ this;
+ }
+ }
+
+ expect(() => {
+ new Child();
+ }).toThrowWithMessage(ReferenceError, "|this| has not been initialized");
+ });
+
+ test("derived class calls super twice in constructor", () => {
+ class Parent {}
+ class Child extends Parent {
+ constructor() {
+ super();
+ super();
+ }
+ }
+
+ expect(() => {
+ new Child();
+ }).toThrowWithMessage(ReferenceError, "|this| is already initialized");
+ });
+});
+
+describe("syntax errors", () => {
+ test("getter with argument", () => {
+ expect(`
+ class A {
+ get foo(v) {
+ return 0;
+ }
+ }`).not.toEval();
+ });
+
+ test("setter with no arguments", () => {
+ expect(`
+ class A {
+ set foo() {
+ }
+ }`).not.toEval();
+ });
+
+ test("setter with more than one argument", () => {
+ expect(`
+ class A {
+ set foo(bar, baz) {
+ }
+ }`).not.toEval();
+ expect(`
+ class A {
+ set foo(...bar) {
+ }
+ }`).not.toEval();
+ });
+
+ test("super reference inside different |this| scope", () => {
+ expect(`
+ class Parent {}
+
+ class Child extends Parent {
+ foo() {
+ function f() { super.foo; }
+ }
+ }`).not.toEval();
+ });
+
+ test("super reference call with no base class", () => {
+ expect(`
+ class A {
+ constructor() {
+ super();
+ }
+ }`).not.toEval();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/classes/class-expressions.js b/Userland/Libraries/LibJS/Tests/classes/class-expressions.js
new file mode 100644
index 0000000000..f67f54ff7f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/classes/class-expressions.js
@@ -0,0 +1,68 @@
+test("basic functionality", () => {
+ const A = class {
+ constructor(x) {
+ this.x = x;
+ }
+
+ getX() {
+ return this.x * 2;
+ }
+ };
+
+ expect(new A(10).getX()).toBe(20);
+});
+
+test("inline instantiation", () => {
+ // prettier-ignore
+ const a = new class {
+ constructor() {
+ this.x = 10;
+ }
+
+ getX() {
+ return this.x * 2;
+ }
+ };
+
+ expect(a.getX()).toBe(20);
+});
+
+test("inline instantiation with argument", () => {
+ // prettier-ignore
+ const a = new class {
+ constructor(x) {
+ this.x = x;
+ }
+
+ getX() {
+ return this.x * 2;
+ }
+ }(10);
+
+ expect(a.getX()).toBe(20);
+});
+
+test("extending class expressions", () => {
+ class A extends class {
+ constructor(x) {
+ this.x = x;
+ }
+ } {
+ constructor(y) {
+ super(y);
+ this.y = y * 2;
+ }
+ }
+
+ const a = new A(10);
+ expect(a.x).toBe(10);
+ expect(a.y).toBe(20);
+});
+
+test("class expression name", () => {
+ let A = class {};
+ expect(A.name).toBe("A");
+
+ let B = class C {};
+ expect(B.name).toBe("C");
+});
diff --git a/Userland/Libraries/LibJS/Tests/classes/class-getters.js b/Userland/Libraries/LibJS/Tests/classes/class-getters.js
new file mode 100644
index 0000000000..5b51910b3e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/classes/class-getters.js
@@ -0,0 +1,75 @@
+test("basic functionality", () => {
+ class A {
+ get x() {
+ return 10;
+ }
+ }
+
+ expect(A).not.toHaveProperty("x");
+ expect(new A().x).toBe(10);
+});
+test("name", () => {
+ class A {
+ get x() {}
+ }
+
+ const d = Object.getOwnPropertyDescriptor(A.prototype, "x");
+ expect(d.get.name).toBe("get x");
+});
+
+test("extended name syntax", () => {
+ const s = Symbol("foo");
+
+ class A {
+ get "method with space"() {
+ return 1;
+ }
+
+ get 12() {
+ return 2;
+ }
+
+ get [`he${"llo"}`]() {
+ return 3;
+ }
+
+ get [s]() {
+ return 4;
+ }
+ }
+
+ const a = new A();
+ expect(a["method with space"]).toBe(1);
+ expect(a[12]).toBe(2);
+ expect(a.hello).toBe(3);
+ expect(a[s]).toBe(4);
+});
+
+test("inherited getter", () => {
+ class Parent {
+ get x() {
+ return 3;
+ }
+ }
+
+ class Child extends Parent {}
+
+ expect(Child).not.toHaveProperty("x");
+ expect(new Child().x).toBe(3);
+});
+
+test("inherited getter overriding", () => {
+ class Parent {
+ get x() {
+ return 3;
+ }
+ }
+
+ class Child extends Parent {
+ get x() {
+ return 10;
+ }
+ }
+
+ expect(new Child().x).toBe(10);
+});
diff --git a/Userland/Libraries/LibJS/Tests/classes/class-inheritance.js b/Userland/Libraries/LibJS/Tests/classes/class-inheritance.js
new file mode 100644
index 0000000000..0e6778dbf5
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/classes/class-inheritance.js
@@ -0,0 +1,133 @@
+test("method inheritance", () => {
+ class Parent {
+ method() {
+ return 3;
+ }
+ }
+
+ class Child extends Parent {}
+
+ const p = new Parent();
+ const c = new Child();
+ expect(p.method()).toBe(3);
+ expect(c.method()).toBe(3);
+});
+
+test("method overriding", () => {
+ class Parent {
+ method() {
+ return 3;
+ }
+ }
+
+ class Child extends Parent {
+ method() {
+ return 10;
+ }
+ }
+
+ const p = new Parent();
+ const c = new Child();
+ expect(p.method()).toBe(3);
+ expect(c.method()).toBe(10);
+});
+
+test("parent method reference with super", () => {
+ class Parent {
+ method() {
+ return 3;
+ }
+ }
+
+ class Child extends Parent {
+ method() {
+ return super.method() * 2;
+ }
+ }
+
+ const p = new Parent();
+ const c = new Child();
+ expect(p.method()).toBe(3);
+ expect(c.method()).toBe(6);
+});
+
+test("child class access to parent class initialized properties", () => {
+ class Parent {
+ constructor() {
+ this.x = 3;
+ }
+ }
+
+ class Child extends Parent {}
+
+ const p = new Parent();
+ const c = new Child();
+ expect(p.x).toBe(3);
+ expect(c.x).toBe(3);
+});
+
+test("child class modification of parent class properties", () => {
+ class Parent {
+ constructor() {
+ this.x = 3;
+ }
+ }
+
+ class Child extends Parent {
+ change() {
+ this.x = 10;
+ }
+ }
+
+ const p = new Parent();
+ const c = new Child();
+ expect(p.x).toBe(3);
+ expect(c.x).toBe(3);
+
+ c.change();
+ expect(c.x).toBe(10);
+});
+
+test("inheritance and hasOwnProperty", () => {
+ class Parent {
+ constructor() {
+ this.x = 3;
+ }
+ }
+
+ class Child extends Parent {
+ method() {
+ this.y = 10;
+ }
+ }
+
+ const p = new Parent();
+ const c = new Child();
+ expect(p.hasOwnProperty("x")).toBeTrue();
+ expect(p.hasOwnProperty("y")).toBeFalse();
+ expect(c.hasOwnProperty("x")).toBeTrue();
+ expect(c.hasOwnProperty("y")).toBeFalse();
+
+ c.method();
+ expect(c.hasOwnProperty("x")).toBeTrue();
+ expect(c.hasOwnProperty("y")).toBeTrue();
+});
+
+test("super constructor call from child class with argument", () => {
+ class Parent {
+ constructor(x) {
+ this.x = x;
+ }
+ }
+
+ class Child extends Parent {
+ constructor() {
+ super(10);
+ }
+ }
+
+ const p = new Parent(3);
+ const c = new Child(3);
+ expect(p.x).toBe(3);
+ expect(c.x).toBe(10);
+});
diff --git a/Userland/Libraries/LibJS/Tests/classes/class-methods.js b/Userland/Libraries/LibJS/Tests/classes/class-methods.js
new file mode 100644
index 0000000000..0add75635c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/classes/class-methods.js
@@ -0,0 +1,51 @@
+test("basic functionality", () => {
+ class A {
+ number() {
+ return 2;
+ }
+
+ string() {
+ return "foo";
+ }
+ }
+
+ const a = new A();
+ expect(a.number()).toBe(2);
+ expect(a.string()).toBe("foo");
+});
+
+test("length", () => {
+ class A {
+ method1() {}
+
+ method2(a, b, c, d) {}
+
+ method3(a, b, ...c) {}
+ }
+
+ const a = new A();
+ expect(a.method1).toHaveLength(0);
+ expect(a.method2).toHaveLength(4);
+ expect(a.method3).toHaveLength(2);
+});
+
+test("extended name syntax", () => {
+ class A {
+ "method with space"() {
+ return 1;
+ }
+
+ 12() {
+ return 2;
+ }
+
+ [`he${"llo"}`]() {
+ return 3;
+ }
+ }
+
+ const a = new A();
+ expect(a["method with space"]()).toBe(1);
+ expect(a[12]()).toBe(2);
+ expect(a.hello()).toBe(3);
+});
diff --git a/Userland/Libraries/LibJS/Tests/classes/class-setters.js b/Userland/Libraries/LibJS/Tests/classes/class-setters.js
new file mode 100644
index 0000000000..99c307994b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/classes/class-setters.js
@@ -0,0 +1,108 @@
+test("basic functionality", () => {
+ class A {
+ get x() {
+ return this._x;
+ }
+
+ set x(value) {
+ this._x = value * 2;
+ }
+ }
+
+ expect(A.x).toBeUndefined();
+ expect(A).not.toHaveProperty("_x");
+ const a = new A();
+ expect(a.x).toBeUndefined();
+ expect(a).not.toHaveProperty("_x");
+ a.x = 3;
+ expect(a.x).toBe(6);
+ expect(a).toHaveProperty("_x", 6);
+});
+
+test("name", () => {
+ class A {
+ set x(v) {}
+ }
+
+ const d = Object.getOwnPropertyDescriptor(A.prototype, "x");
+ expect(d.set.name).toBe("set x");
+});
+
+test("extended name syntax", () => {
+ const s = Symbol("foo");
+
+ class A {
+ set "method with space"(value) {
+ this.a = value;
+ }
+
+ set 12(value) {
+ this.b = value;
+ }
+
+ set [`he${"llo"}`](value) {
+ this.c = value;
+ }
+
+ set [s](value) {
+ this.d = value;
+ }
+ }
+
+ const a = new A();
+ a["method with space"] = 1;
+ a[12] = 2;
+ a.hello = 3;
+ a[s] = 4;
+ expect(a.a).toBe(1);
+ expect(a.b).toBe(2);
+ expect(a.c).toBe(3);
+ expect(a.d).toBe(4);
+});
+
+test("inherited setter", () => {
+ class Parent {
+ get x() {
+ return this._x;
+ }
+
+ set x(value) {
+ this._x = value * 2;
+ }
+ }
+
+ class Child extends Parent {}
+
+ const c = new Child();
+
+ expect(c.x).toBeUndefined();
+ c.x = 10;
+ expect(c.x).toBe(20);
+});
+
+test("inherited static setter overriding", () => {
+ class Parent {
+ get x() {
+ return this._x;
+ }
+
+ set x(value) {
+ this._x = value * 2;
+ }
+ }
+
+ class Child extends Parent {
+ get x() {
+ return this._x;
+ }
+
+ set x(value) {
+ this._x = value * 3;
+ }
+ }
+
+ const c = new Child();
+ expect(c.x).toBeUndefined();
+ c.x = 10;
+ expect(c.x).toBe(30);
+});
diff --git a/Userland/Libraries/LibJS/Tests/classes/class-static-getters.js b/Userland/Libraries/LibJS/Tests/classes/class-static-getters.js
new file mode 100644
index 0000000000..c158a00595
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/classes/class-static-getters.js
@@ -0,0 +1,87 @@
+describe("correct behavior", () => {
+ test("basic functionality", () => {
+ class A {
+ static get x() {
+ return 10;
+ }
+ }
+
+ expect(A.x).toBe(10);
+ expect(new A()).not.toHaveProperty("x");
+ });
+
+ test("name", () => {
+ class A {
+ static get x() {}
+ }
+
+ const d = Object.getOwnPropertyDescriptor(A, "x");
+ expect(d.get.name).toBe("get x");
+ });
+
+ test("extended name syntax", () => {
+ const s = Symbol("foo");
+
+ class A {
+ static get "method with space"() {
+ return 1;
+ }
+
+ static get 12() {
+ return 2;
+ }
+
+ static get [`he${"llo"}`]() {
+ return 3;
+ }
+
+ static get [s]() {
+ return 4;
+ }
+ }
+
+ expect(A["method with space"]).toBe(1);
+ expect(A[12]).toBe(2);
+ expect(A.hello).toBe(3);
+ expect(A[s]).toBe(4);
+ });
+
+ test("inherited static getter", () => {
+ class Parent {
+ static get x() {
+ return 3;
+ }
+ }
+
+ class Child extends Parent {}
+
+ expect(Parent.x).toBe(3);
+ expect(Child.x).toBe(3);
+ });
+
+ test("inherited static getter overriding", () => {
+ class Parent {
+ static get x() {
+ return 3;
+ }
+ }
+
+ class Child extends Parent {
+ static get x() {
+ return 10;
+ }
+ }
+
+ expect(Parent.x).toBe(3);
+ expect(Child.x).toBe(10);
+ });
+});
+
+describe("errors", () => {
+ test('"get static" is a syntax error', () => {
+ expect(`
+ class A {
+ get static foo() {}
+ }`).not.toEval();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/classes/class-static-setters.js b/Userland/Libraries/LibJS/Tests/classes/class-static-setters.js
new file mode 100644
index 0000000000..38cbbe2394
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/classes/class-static-setters.js
@@ -0,0 +1,112 @@
+describe("correct behavior", () => {
+ test("basic functionality", () => {
+ class A {
+ static get x() {
+ return this._x;
+ }
+
+ static set x(value) {
+ this._x = value * 2;
+ }
+ }
+
+ expect(A.x).toBeUndefined();
+ expect(A).not.toHaveProperty("_x");
+ A.x = 3;
+ expect(A.x).toBe(6);
+ expect(A).toHaveProperty("_x", 6);
+ });
+
+ test("name", () => {
+ class A {
+ static set x(v) {}
+ }
+
+ const d = Object.getOwnPropertyDescriptor(A, "x");
+ expect(d.set.name).toBe("set x");
+ });
+
+ test("extended name syntax", () => {
+ const s = Symbol("foo");
+
+ class A {
+ static set "method with space"(value) {
+ this.a = value;
+ }
+
+ static set 12(value) {
+ this.b = value;
+ }
+
+ static set [`he${"llo"}`](value) {
+ this.c = value;
+ }
+
+ static set [s](value) {
+ this.d = value;
+ }
+ }
+
+ A["method with space"] = 1;
+ A[12] = 2;
+ A.hello = 3;
+ A[s] = 4;
+ expect(A.a).toBe(1);
+ expect(A.b).toBe(2);
+ expect(A.c).toBe(3);
+ expect(A.d).toBe(4);
+ });
+
+ test("inherited static setter", () => {
+ class Parent {
+ static get x() {
+ return this._x;
+ }
+
+ static set x(value) {
+ this._x = value * 2;
+ }
+ }
+
+ class Child extends Parent {}
+
+ expect(Child.x).toBeUndefined();
+ Child.x = 10;
+ expect(Child.x).toBe(20);
+ });
+
+ test("inherited static setter overriding", () => {
+ class Parent {
+ static get x() {
+ return this._x;
+ }
+
+ static set x(value) {
+ this._x = value * 2;
+ }
+ }
+
+ class Child extends Parent {
+ static get x() {
+ return this._x;
+ }
+
+ static set x(value) {
+ this._x = value * 3;
+ }
+ }
+
+ expect(Child.x).toBeUndefined();
+ Child.x = 10;
+ expect(Child.x).toBe(30);
+ });
+});
+
+describe("errors", () => {
+ test('"set static" is a syntax error', () => {
+ expect(`
+ class A {
+ set static foo(value) {}
+ }`).not.toEval();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/classes/class-static.js b/Userland/Libraries/LibJS/Tests/classes/class-static.js
new file mode 100644
index 0000000000..5bfd28c3da
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/classes/class-static.js
@@ -0,0 +1,72 @@
+test("basic functionality", () => {
+ class A {
+ static method() {
+ return 10;
+ }
+ }
+
+ expect(A.method()).toBe(10);
+ expect(new A().method).toBeUndefined();
+});
+
+test("extended name syntax", () => {
+ class A {
+ static method() {
+ return 1;
+ }
+
+ static 12() {
+ return 2;
+ }
+
+ static [`he${"llo"}`]() {
+ return 3;
+ }
+ }
+
+ expect(A.method()).toBe(1);
+ expect(A[12]()).toBe(2);
+ expect(A.hello()).toBe(3);
+});
+
+test("bound |this|", () => {
+ class A {
+ static method() {
+ expect(this).toBe(A);
+ }
+ }
+
+ A.method();
+});
+
+test("inherited static methods", () => {
+ class Parent {
+ static method() {
+ return 3;
+ }
+ }
+
+ class Child extends Parent {}
+
+ expect(Parent.method()).toBe(3);
+ expect(Child.method()).toBe(3);
+ expect(new Parent()).not.toHaveProperty("method");
+ expect(new Child()).not.toHaveProperty("method");
+});
+
+test("static method overriding", () => {
+ class Parent {
+ static method() {
+ return 3;
+ }
+ }
+
+ class Child extends Parent {
+ static method() {
+ return 10;
+ }
+ }
+
+ expect(Parent.method()).toBe(3);
+ expect(Child.method()).toBe(10);
+});
diff --git a/Userland/Libraries/LibJS/Tests/classes/class-strict-mode.js b/Userland/Libraries/LibJS/Tests/classes/class-strict-mode.js
new file mode 100644
index 0000000000..016d2d2eba
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/classes/class-strict-mode.js
@@ -0,0 +1,19 @@
+test("constructors are always strict mode", () => {
+ class A {
+ constructor() {
+ expect(isStrictMode()).toBeTrue();
+ }
+ }
+
+ new A();
+});
+
+test("methods are always strict mode", () => {
+ class A {
+ method() {
+ expect(isStrictMode()).toBeTrue();
+ }
+ }
+
+ new A().method();
+});
diff --git a/Userland/Libraries/LibJS/Tests/comments-basic.js b/Userland/Libraries/LibJS/Tests/comments-basic.js
new file mode 100644
index 0000000000..3dbe6aec1e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/comments-basic.js
@@ -0,0 +1,35 @@
+test("regular comments", () => {
+ const source = `
+var i = 0;
+// i++;
+/* i++; */
+/*
+i++;
+*/
+/**/ i++;
+return i;`;
+
+ expect(source).toEvalTo(1);
+});
+
+test("html comments", () => {
+ const source = `
+var i = 0;
+var j = 0;
+<!-- i++; --> i++;
+<!-- i++;
+i++;
+--> i++;
+/**/ --> i++;
+j --> i++;
+return i;`;
+ expect(source).toEvalTo(2);
+});
+
+test("unterminated multi-line comment", () => {
+ expect("/*").not.toEval();
+ expect("/**").not.toEval();
+ expect("/*/").not.toEval();
+ expect("/* foo").not.toEval();
+ expect("foo /*").not.toEval();
+});
diff --git a/Userland/Libraries/LibJS/Tests/computed-property-throws.js b/Userland/Libraries/LibJS/Tests/computed-property-throws.js
new file mode 100644
index 0000000000..2f8e35eb63
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/computed-property-throws.js
@@ -0,0 +1,19 @@
+test("Issue #3459, exception in computed property expression", () => {
+ expect(() => {
+ "foo"[bar];
+ }).toThrow(ReferenceError);
+ expect(() => {
+ "foo"[bar]();
+ }).toThrow(ReferenceError);
+});
+
+test("Issue #3941, exception in computed property's toString()", () => {
+ expect(() => {
+ const o = {
+ toString() {
+ throw Error();
+ },
+ };
+ "foo"[o];
+ }).toThrow(Error);
+});
diff --git a/Userland/Libraries/LibJS/Tests/const-declaration-missing-initializer.js b/Userland/Libraries/LibJS/Tests/const-declaration-missing-initializer.js
new file mode 100644
index 0000000000..5bbf2ef9e2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/const-declaration-missing-initializer.js
@@ -0,0 +1,5 @@
+test("missing initializer in 'const' variable declaration is syntax error", () => {
+ expect("const foo").not.toEval();
+ expect("const foo = 1, bar").not.toEval();
+ expect("const foo = 1, bar, baz = 2").not.toEval();
+});
diff --git a/Userland/Libraries/LibJS/Tests/const-reassignment.js b/Userland/Libraries/LibJS/Tests/const-reassignment.js
new file mode 100644
index 0000000000..1c66fc24ce
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/const-reassignment.js
@@ -0,0 +1,17 @@
+// I'm not sure how this test passed before the refactor, but it definitely doesn't work at all
+test.skip("reassignment to const", () => {
+ const constantValue = 1;
+ expect(() => {
+ constantValue = 2;
+ }).toThrowWithMessage(TypeError, "Invalid assignment to const variable");
+ expect(constantValue).toBe(1);
+});
+
+test("const creation in inner scope", () => {
+ const constantValue = 1;
+ do {
+ const constantValue = 2;
+ expect(constantValue).toBe(2);
+ } while (false);
+ expect(constantValue).toBe(1);
+});
diff --git a/Userland/Libraries/LibJS/Tests/custom-@@hasInstance.js b/Userland/Libraries/LibJS/Tests/custom-@@hasInstance.js
new file mode 100644
index 0000000000..2540beaf68
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/custom-@@hasInstance.js
@@ -0,0 +1,9 @@
+test("basic functionality", () => {
+ function Foo() {}
+ Foo[Symbol.hasInstance] = value => {
+ return value === 2;
+ };
+
+ expect(new Foo() instanceof Foo).toBeFalse();
+ expect(2 instanceof Foo).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/custom-@@toStringTag.js b/Userland/Libraries/LibJS/Tests/custom-@@toStringTag.js
new file mode 100644
index 0000000000..e297961e55
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/custom-@@toStringTag.js
@@ -0,0 +1,26 @@
+test("inside objects", () => {
+ const o = {
+ [Symbol.toStringTag]: "hello friends",
+ };
+
+ expect(o.toString()).toBe("[object hello friends]");
+});
+
+test("inside classes", () => {
+ class A {
+ constructor() {
+ this[Symbol.toStringTag] = "hello friends";
+ }
+ }
+
+ const a = new A();
+ expect(a.toString()).toBe("[object hello friends]");
+});
+
+test("non-string values are ignored", () => {
+ const o = {
+ [Symbol.toStringTag]: [1, 2, 3],
+ };
+
+ expect(o.toString()).toBe("[object Object]");
+});
diff --git a/Userland/Libraries/LibJS/Tests/debugger-statement.js b/Userland/Libraries/LibJS/Tests/debugger-statement.js
new file mode 100644
index 0000000000..e5b0b14761
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/debugger-statement.js
@@ -0,0 +1,3 @@
+test("debugger keyword", () => {
+ expect("debugger").toEval();
+});
diff --git a/Userland/Libraries/LibJS/Tests/empty-statements.js b/Userland/Libraries/LibJS/Tests/empty-statements.js
new file mode 100644
index 0000000000..1c8fe467b8
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/empty-statements.js
@@ -0,0 +1,19 @@
+test("empty semicolon statements", () => {
+ expect(";;;").toEval();
+});
+
+test("if with no body", () => {
+ expect("if (true);").toEval();
+});
+
+test("chained ifs with no bodies", () => {
+ expect("if (false); else if (false); else;").toEval();
+});
+
+test("while with no body", () => {
+ expect("while (false);").toEval();
+});
+
+test("do while with no body", () => {
+ expect("do; while (false);").toEval();
+});
diff --git a/Userland/Libraries/LibJS/Tests/exception-ReferenceError.js b/Userland/Libraries/LibJS/Tests/exception-ReferenceError.js
new file mode 100644
index 0000000000..5cd436d0bf
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/exception-ReferenceError.js
@@ -0,0 +1,3 @@
+test("unknown variable produces ReferenceError", () => {
+ expect(new Function("i < 3")).toThrow(ReferenceError);
+});
diff --git a/Userland/Libraries/LibJS/Tests/exception-in-catch-block.js b/Userland/Libraries/LibJS/Tests/exception-in-catch-block.js
new file mode 100644
index 0000000000..2a0fa6e1fc
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/exception-in-catch-block.js
@@ -0,0 +1,25 @@
+test("Issue #3437, exception thrown in catch {} block", () => {
+ var tryHasBeenExecuted = false;
+ var catchHasBeenExecuted = false;
+ var finallyHasBeenExecuted = false;
+ expect(() => {
+ try {
+ tryHasBeenExecuted = true;
+ foo();
+ // execution must not reach this step
+ expect().fail();
+ } catch (e) {
+ catchHasBeenExecuted = true;
+ bar();
+ // ...also not this step
+ expect().fail();
+ } finally {
+ finallyHasBeenExecuted = true;
+ }
+ // ...or this step
+ expect().fail();
+ }).toThrow(ReferenceError, "'bar' is not defined");
+ expect(tryHasBeenExecuted).toBeTrue();
+ expect(catchHasBeenExecuted).toBeTrue();
+ expect(finallyHasBeenExecuted).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/exponentiation-basic.js b/Userland/Libraries/LibJS/Tests/exponentiation-basic.js
new file mode 100644
index 0000000000..d260f43a1b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/exponentiation-basic.js
@@ -0,0 +1,37 @@
+test("regular exponentiation", () => {
+ expect(2 ** 0).toBe(1);
+ expect(2 ** 1).toBe(2);
+ expect(2 ** 2).toBe(4);
+ expect(2 ** 3).toBe(8);
+ expect(3 ** 2).toBe(9);
+ expect(0 ** 0).toBe(1);
+ expect(2 ** (3 ** 2)).toBe(512);
+ expect(2 ** (3 ** 2)).toBe(512);
+ expect((2 ** 3) ** 2).toBe(64);
+});
+
+test("exponentiation with negatives", () => {
+ expect(2 ** -3).toBe(0.125);
+ expect((-2) ** 3).toBe(-8);
+
+ // FIXME: This should fail :)
+ // expect("-2 ** 3").not.toEval();
+});
+
+test("exponentiation with non-numeric primitives", () => {
+ expect("2" ** "3").toBe(8);
+ expect("" ** []).toBe(1);
+ expect([] ** null).toBe(1);
+ expect(null ** null).toBe(1);
+ expect(undefined ** null).toBe(1);
+});
+
+test("exponentiation that produces NaN", () => {
+ expect(NaN ** 2).toBeNaN();
+ expect(2 ** NaN).toBeNaN();
+ expect(undefined ** 2).toBeNaN();
+ expect(2 ** undefined).toBeNaN();
+ expect(null ** undefined).toBeNaN();
+ expect(2 ** "foo").toBeNaN();
+ expect("foo" ** 2).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/functions/arrow-functions.js b/Userland/Libraries/LibJS/Tests/functions/arrow-functions.js
new file mode 100644
index 0000000000..6af8790514
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/functions/arrow-functions.js
@@ -0,0 +1,164 @@
+test("no arguments", () => {
+ let getNumber = () => {
+ return 42;
+ };
+ expect(getNumber()).toBe(42);
+
+ getNumber = () => 42;
+ expect(getNumber()).toBe(42);
+
+ getNumber = () => {
+ return 99;
+ };
+ expect(getNumber()).toBe(99);
+ getNumber = () => 99;
+ expect(getNumber()).toBe(99);
+});
+
+test("arguments", () => {
+ let add = (a, b) => a + b;
+ expect(add(2, 3)).toBe(5);
+
+ const addBlock = (a, b) => {
+ let res = a + b;
+ return res;
+ };
+ expect(addBlock(5, 4)).toBe(9);
+});
+
+test("inside an array", () => {
+ let chompy = [x => x, 2];
+ expect(chompy).toHaveLength(2);
+ expect(chompy[0](1)).toBe(1);
+});
+
+test("return object literal", () => {
+ const makeObject = (a, b) => ({ a, b });
+ const obj = makeObject(33, 44);
+ expect(typeof obj).toBe("object");
+ expect(obj.a).toBe(33);
+ expect(obj.b).toBe(44);
+});
+
+test("return undefined", () => {
+ let returnUndefined = () => {};
+ expect(returnUndefined()).toBeUndefined();
+});
+
+test("return array literal", () => {
+ const makeArray = (a, b) => [a, b];
+ const array = makeArray("3", { foo: 4 });
+ expect(array[0]).toBe("3");
+ expect(array[1].foo).toBe(4);
+});
+
+test("return numeric expression", () => {
+ let square = x => x * x;
+ expect(square(3)).toBe(9);
+
+ let squareBlock = x => {
+ return x * x;
+ };
+ expect(squareBlock(4)).toBe(16);
+});
+
+test("return called arrow function expression", () => {
+ const message = (who => "Hello " + who)("friends!");
+ expect(message).toBe("Hello friends!");
+
+ const sum = ((x, y, z) => x + y + z)(1, 2, 3);
+ expect(sum).toBe(6);
+
+ const product = ((x, y, z) => {
+ let res = x * y * z;
+ return res;
+ })(5, 4, 2);
+ expect(product).toBe(40);
+
+ const half = (x => {
+ return x / 2;
+ })(10);
+ expect(half).toBe(5);
+});
+
+test("currying", () => {
+ let add = a => b => a + b;
+ expect(typeof add(1)).toBe("function");
+ expect(typeof add(1, 2)).toBe("function");
+ expect(add(1)(2)).toBe(3);
+});
+
+test("with comma operator", () => {
+ let foo, bar;
+ (foo = bar), baz => {};
+ expect(foo).toBe(undefined);
+ expect(bar).toBe(undefined);
+});
+
+test("arrow functions in objects", () => {
+ function FooBar() {
+ this.x = {
+ y: () => this,
+ z: function () {
+ return (() => this)();
+ },
+ };
+ }
+
+ const foobar = new FooBar();
+ expect(foobar.x.y()).toBe(foobar);
+ expect(foobar.x.z()).toBe(foobar.x);
+});
+
+test("strict mode propagation", () => {
+ (() => {
+ "use strict";
+ expect(isStrictMode()).toBeTrue();
+
+ (() => {
+ expect(isStrictMode()).toBeTrue();
+ })();
+ })();
+
+ (() => {
+ "use strict";
+ expect(isStrictMode()).toBeTrue();
+ })();
+
+ (() => {
+ expect(isStrictMode()).toBeFalse();
+
+ (() => {
+ "use strict";
+ expect(isStrictMode()).toBeTrue();
+ })();
+
+ expect(isStrictMode()).toBeFalse();
+ })();
+});
+
+test("no prototype", () => {
+ let foo = () => {};
+ expect(foo).not.toHaveProperty("prototype");
+});
+
+test("cannot be constructed", () => {
+ let foo = () => {};
+ expect(() => {
+ new foo();
+ }).toThrowWithMessage(TypeError, "foo is not a constructor");
+});
+
+test("syntax errors", () => {
+ expect("a, => {}").not.toEval();
+ expect("(a, => {}").not.toEval();
+ expect("(,) => {}").not.toEval();
+ expect("(,,) => {}").not.toEval();
+ expect("(a,,) => {}").not.toEval();
+ expect("(a,,b) => {}").not.toEval();
+ expect("(a, ...b, ...c) => {}").not.toEval();
+ expect("(a b) => {}").not.toEval();
+ expect("(a ...b) => {}").not.toEval();
+ expect("(a = 1 = 2) => {}").not.toEval();
+ expect("()\n=> {}").not.toEval();
+});
diff --git a/Userland/Libraries/LibJS/Tests/functions/constructor-basic.js b/Userland/Libraries/LibJS/Tests/functions/constructor-basic.js
new file mode 100644
index 0000000000..cf58525634
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/functions/constructor-basic.js
@@ -0,0 +1,11 @@
+test("basic functionality", () => {
+ function Foo() {
+ this.x = 123;
+ }
+
+ expect(Foo.prototype.constructor).toBe(Foo);
+
+ const foo = new Foo();
+ expect(foo.constructor).toBe(Foo);
+ expect(foo.x).toBe(123);
+});
diff --git a/Userland/Libraries/LibJS/Tests/functions/function-TypeError.js b/Userland/Libraries/LibJS/Tests/functions/function-TypeError.js
new file mode 100644
index 0000000000..41fb6ae196
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/functions/function-TypeError.js
@@ -0,0 +1,47 @@
+test("calling non-function", () => {
+ expect(() => {
+ const a = true;
+ a();
+ }).toThrowWithMessage(TypeError, "true is not a function (evaluated from 'a')");
+});
+
+test("calling number", () => {
+ expect(() => {
+ const a = 100 + 20 + 3;
+ a();
+ }).toThrowWithMessage(TypeError, "123 is not a function (evaluated from 'a')");
+});
+
+test("calling undefined object key", () => {
+ expect(() => {
+ const o = {};
+ o.a();
+ }).toThrowWithMessage(TypeError, "undefined is not a function (evaluated from 'o.a')");
+});
+
+test("calling object", () => {
+ expect(() => {
+ Math();
+ }).toThrowWithMessage(
+ TypeError,
+ "[object MathObject] is not a function (evaluated from 'Math')"
+ );
+});
+
+test("constructing object", () => {
+ expect(() => {
+ new Math();
+ }).toThrowWithMessage(
+ TypeError,
+ "[object MathObject] is not a constructor (evaluated from 'Math')"
+ );
+});
+
+test("constructing native function", () => {
+ expect(() => {
+ new isNaN();
+ }).toThrowWithMessage(
+ TypeError,
+ "[object NativeFunction] is not a constructor (evaluated from 'isNaN')"
+ );
+});
diff --git a/Userland/Libraries/LibJS/Tests/functions/function-default-parameters.js b/Userland/Libraries/LibJS/Tests/functions/function-default-parameters.js
new file mode 100644
index 0000000000..5a3cafc3b3
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/functions/function-default-parameters.js
@@ -0,0 +1,143 @@
+test("single default parameter", () => {
+ function func(a = 6) {
+ return typeof a;
+ }
+
+ const arrowFunc = (a = 6) => typeof a;
+
+ expect(func()).toBe("number");
+ expect(func(5)).toBe("number");
+ expect(func(undefined)).toBe("number");
+ expect(func(false)).toBe("boolean");
+ expect(func(null)).toBe("object");
+ expect(func({})).toBe("object");
+
+ expect(arrowFunc()).toBe("number");
+ expect(arrowFunc(5)).toBe("number");
+ expect(arrowFunc(undefined)).toBe("number");
+ expect(arrowFunc(false)).toBe("boolean");
+ expect(arrowFunc(null)).toBe("object");
+ expect(arrowFunc({})).toBe("object");
+});
+
+test("two parameters, second one is default", () => {
+ function func(a, b = 1) {
+ return a + b;
+ }
+
+ const arrowFunc = (a, b = 1) => a + b;
+
+ expect(func(4, 5)).toBe(9);
+ expect(func(4)).toBe(5);
+ expect(func(4, undefined)).toBe(5);
+ expect(func()).toBeNaN();
+
+ expect(arrowFunc(4, 5)).toBe(9);
+ expect(arrowFunc(4)).toBe(5);
+ expect(arrowFunc(4, undefined)).toBe(5);
+ expect(arrowFunc()).toBeNaN();
+});
+
+test("two parameters, first one is default", () => {
+ function func(a = 5, b) {
+ return a + b;
+ }
+
+ const arrowFunc = (a = 5, b) => a + b;
+
+ expect(func(4, 5)).toBe(9);
+ expect(func(undefined, 4)).toBe(9);
+ expect(func()).toBeNaN();
+
+ expect(arrowFunc(4, 5)).toBe(9);
+ expect(arrowFunc(undefined, 4)).toBe(9);
+ expect(arrowFunc()).toBeNaN();
+});
+
+test("default parameter references a previous parameter", () => {
+ function func(a, b = a) {
+ return a + b;
+ }
+
+ const arrowFunc = (a, b = a) => a + b;
+
+ expect(func(4, 5)).toBe(9);
+ expect(func(4)).toBe(8);
+ expect(func("hf")).toBe("hfhf");
+ expect(func(true)).toBe(2);
+ expect(func()).toBeNaN();
+
+ expect(arrowFunc(4, 5)).toBe(9);
+ expect(arrowFunc(4)).toBe(8);
+ expect(arrowFunc("hf")).toBe("hfhf");
+ expect(arrowFunc(true)).toBe(2);
+ expect(arrowFunc()).toBeNaN();
+});
+
+test("parameter with a function default value", () => {
+ function func(
+ a = function () {
+ return 5;
+ }
+ ) {
+ return a();
+ }
+
+ const arrowFunc = (
+ a = function () {
+ return 5;
+ }
+ ) => a();
+
+ expect(func()).toBe(5);
+ expect(
+ func(function () {
+ return 10;
+ })
+ ).toBe(10);
+ expect(func(() => 10)).toBe(10);
+
+ expect(arrowFunc()).toBe(5);
+ expect(
+ arrowFunc(function () {
+ return 10;
+ })
+ ).toBe(10);
+ expect(arrowFunc(() => 10)).toBe(10);
+});
+
+test("parameter with an arrow function default value", () => {
+ function func(a = () => 5) {
+ return a();
+ }
+
+ const arrowFunc = (a = () => 5) => a();
+
+ expect(func()).toBe(5);
+ expect(
+ func(function () {
+ return 10;
+ })
+ ).toBe(10);
+ expect(func(() => 10)).toBe(10);
+ expect(arrowFunc()).toBe(5);
+ expect(
+ arrowFunc(function () {
+ return 10;
+ })
+ ).toBe(10);
+ expect(arrowFunc(() => 10)).toBe(10);
+});
+
+test("parameter with an object default value", () => {
+ function func(a = { foo: "bar" }) {
+ return a.foo;
+ }
+
+ const arrowFunc = (a = { foo: "bar" }) => a.foo;
+
+ expect(func()).toBe("bar");
+ expect(func({ foo: "baz" })).toBe("baz");
+ expect(arrowFunc()).toBe("bar");
+ expect(arrowFunc({ foo: "baz" })).toBe("baz");
+});
diff --git a/Userland/Libraries/LibJS/Tests/functions/function-duplicate-parameters.js b/Userland/Libraries/LibJS/Tests/functions/function-duplicate-parameters.js
new file mode 100644
index 0000000000..8a44337ba3
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/functions/function-duplicate-parameters.js
@@ -0,0 +1,45 @@
+test("function with duplicate parameter names", () => {
+ function foo(bar, _, bar) {
+ return bar;
+ }
+ expect(foo(1, 2, 3)).toBe(3);
+});
+
+test("syntax errors", () => {
+ // Regular function in strict mode
+ expect(`
+ "use strict";
+ function foo(bar, bar) {}
+ `).not.toEval();
+
+ // Arrow function in strict mode
+ expect(`
+ "use strict";
+ const foo = (bar, bar) => {};
+ `).not.toEval();
+
+ // Arrow function in non-strict mode
+ expect(`
+ const foo = (bar, bar) => {};
+ `).not.toEval();
+
+ // Regular function with rest parameter
+ expect(`
+ function foo(bar, ...bar) {}
+ `).not.toEval();
+
+ // Arrow function with rest parameter
+ expect(`
+ const foo = (bar, ...bar) => {};
+ `).not.toEval();
+
+ // Regular function with default parameter
+ expect(`
+ function foo(bar, bar = 1) {}
+ `).not.toEval();
+
+ // Arrow function with default parameter
+ expect(`
+ const foo = (bar, bar = 1) => {};
+ `).not.toEval();
+});
diff --git a/Userland/Libraries/LibJS/Tests/functions/function-hoisting.js b/Userland/Libraries/LibJS/Tests/functions/function-hoisting.js
new file mode 100644
index 0000000000..3345cc4d5f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/functions/function-hoisting.js
@@ -0,0 +1,39 @@
+test("basic functionality", () => {
+ let callHoisted = hoisted();
+ function hoisted() {
+ return "foo";
+ }
+ expect(hoisted()).toBe("foo");
+ expect(callHoisted).toBe("foo");
+});
+
+// First two calls produce a ReferenceError, but the declarations should be hoisted
+test.skip("functions are hoisted across non-lexical scopes", () => {
+ expect(scopedHoisted).toBeUndefined();
+ expect(callScopedHoisted).toBeUndefined();
+ {
+ var callScopedHoisted = scopedHoisted();
+ function scopedHoisted() {
+ return "foo";
+ }
+ expect(scopedHoisted()).toBe("foo");
+ expect(callScopedHoisted).toBe("foo");
+ }
+ expect(scopedHoisted()).toBe("foo");
+ expect(callScopedHoisted).toBe("foo");
+});
+
+test("functions are not hoisted across lexical scopes", () => {
+ const test = () => {
+ var iife = (function () {
+ return declaredLater();
+ })();
+ function declaredLater() {
+ return "yay";
+ }
+ return iife;
+ };
+
+ expect(() => declaredLater).toThrow(ReferenceError);
+ expect(test()).toBe("yay");
+});
diff --git a/Userland/Libraries/LibJS/Tests/functions/function-length.js b/Userland/Libraries/LibJS/Tests/functions/function-length.js
new file mode 100644
index 0000000000..d57b773c09
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/functions/function-length.js
@@ -0,0 +1,23 @@
+test("basic functionality", () => {
+ function foo() {}
+ expect(foo).toHaveLength(0);
+ expect((foo.length = 5)).toBe(5);
+ expect(foo).toHaveLength(0);
+
+ function bar(a, b, c) {}
+ expect(bar).toHaveLength(3);
+ expect((bar.length = 5)).toBe(5);
+ expect(bar).toHaveLength(3);
+});
+
+test("functions with special parameter lists", () => {
+ function baz(a, b = 1, c) {}
+ expect(baz).toHaveLength(1);
+ expect((baz.length = 5)).toBe(5);
+ expect(baz).toHaveLength(1);
+
+ function qux(a, b, ...c) {}
+ expect(qux).toHaveLength(2);
+ expect((qux.length = 2)).toBe(2);
+ expect(qux).toHaveLength(2);
+});
diff --git a/Userland/Libraries/LibJS/Tests/functions/function-missing-arg.js b/Userland/Libraries/LibJS/Tests/functions/function-missing-arg.js
new file mode 100644
index 0000000000..f599d05ced
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/functions/function-missing-arg.js
@@ -0,0 +1,10 @@
+test("basic functionality", () => {
+ function foo(a, b) {
+ return a + b;
+ }
+
+ expect(foo()).toBeNaN();
+ expect(foo(1)).toBeNaN();
+ expect(foo(2, 3)).toBe(5);
+ expect(foo(2, 3, 4)).toBe(5);
+});
diff --git a/Userland/Libraries/LibJS/Tests/functions/function-name.js b/Userland/Libraries/LibJS/Tests/functions/function-name.js
new file mode 100644
index 0000000000..90520187d9
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/functions/function-name.js
@@ -0,0 +1,57 @@
+test("basic functionality", () => {
+ expect(function () {}.name).toBe("");
+
+ function bar() {}
+ expect(bar.name).toBe("bar");
+ expect((bar.name = "baz")).toBe("baz");
+ expect(bar.name).toBe("bar");
+});
+
+test("function assigned to variable", () => {
+ let foo = function () {};
+ expect(foo.name).toBe("foo");
+ expect((foo.name = "bar")).toBe("bar");
+ expect(foo.name).toBe("foo");
+
+ let a, b;
+ a = b = function () {};
+ expect(a.name).toBe("b");
+ expect(b.name).toBe("b");
+});
+
+test("functions in array assigned to variable", () => {
+ const arr = [function () {}, function () {}, function () {}];
+ expect(arr[0].name).toBe("arr");
+ expect(arr[1].name).toBe("arr");
+ expect(arr[2].name).toBe("arr");
+});
+
+test("functions in objects", () => {
+ let f;
+ let o = { a: function () {} };
+
+ expect(o.a.name).toBe("a");
+ f = o.a;
+ expect(f.name).toBe("a");
+ expect(o.a.name).toBe("a");
+
+ o = { ...o, b: f };
+ expect(o.a.name).toBe("a");
+ expect(o.b.name).toBe("a");
+
+ o.c = function () {};
+ expect(o.c.name).toBe("c");
+});
+
+test("names of native functions", () => {
+ expect(console.debug.name).toBe("debug");
+ expect((console.debug.name = "warn")).toBe("warn");
+ expect(console.debug.name).toBe("debug");
+});
+
+test("cyclic members should not cause infinite recursion (#3471)", () => {
+ let a = [() => 4];
+ a[1] = a;
+ a = a;
+ expect(a[0].name).toBe("a");
+});
diff --git a/Userland/Libraries/LibJS/Tests/functions/function-new-target.js b/Userland/Libraries/LibJS/Tests/functions/function-new-target.js
new file mode 100644
index 0000000000..9eb7659391
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/functions/function-new-target.js
@@ -0,0 +1,25 @@
+test("basic functionality", () => {
+ function foo() {
+ return new.target;
+ }
+ expect(foo()).toBeUndefined();
+ expect(new foo()).toEqual(foo);
+
+ function bar() {
+ const baz = () => new.target;
+ return baz();
+ }
+ expect(bar()).toBeUndefined();
+ expect(new bar()).toEqual(bar);
+
+ class baz {
+ constructor() {
+ this.newTarget = new.target;
+ }
+ }
+ expect(new baz().newTarget).toEqual(baz);
+});
+
+test("syntax error outside of function", () => {
+ expect("new.target").not.toEval();
+});
diff --git a/Userland/Libraries/LibJS/Tests/functions/function-prototype-writable.js b/Userland/Libraries/LibJS/Tests/functions/function-prototype-writable.js
new file mode 100644
index 0000000000..ba8be23f43
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/functions/function-prototype-writable.js
@@ -0,0 +1,10 @@
+test("a function's prototype property should be writable", () => {
+ function x() {}
+ var desc = Object.getOwnPropertyDescriptor(x, "prototype");
+ expect(desc.writable).toBe(true);
+ expect(desc.enumerable).toBe(false);
+ expect(desc.configurable).toBe(false);
+
+ x.prototype = 1;
+ expect(x.prototype).toBe(1);
+});
diff --git a/Userland/Libraries/LibJS/Tests/functions/function-rest-params.js b/Userland/Libraries/LibJS/Tests/functions/function-rest-params.js
new file mode 100644
index 0000000000..83ed411675
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/functions/function-rest-params.js
@@ -0,0 +1,47 @@
+test("rest parameter with no arguments", () => {
+ function foo(...a) {
+ expect(a).toBeInstanceOf(Array);
+ expect(a).toHaveLength(0);
+ }
+ foo();
+});
+
+test("rest parameter with arguments", () => {
+ function foo(...a) {
+ expect(a).toEqual(["foo", 123, undefined, { foo: "bar" }]);
+ }
+ foo("foo", 123, undefined, { foo: "bar" });
+});
+
+test("rest parameter after normal parameters with no arguments", () => {
+ function foo(a, b, ...c) {
+ expect(a).toBe("foo");
+ expect(b).toBe(123);
+ expect(c).toEqual([]);
+ }
+ foo("foo", 123);
+});
+
+test("rest parameter after normal parameters with arguments", () => {
+ function foo(a, b, ...c) {
+ expect(a).toBe("foo");
+ expect(b).toBe(123);
+ expect(c).toEqual([undefined, { foo: "bar" }]);
+ }
+ foo("foo", 123, undefined, { foo: "bar" });
+});
+
+test("basic arrow function rest parameters", () => {
+ let foo = (...a) => {
+ expect(a).toBeInstanceOf(Array);
+ expect(a).toHaveLength(0);
+ };
+ foo();
+
+ foo = (a, b, ...c) => {
+ expect(a).toBe("foo");
+ expect(b).toBe(123);
+ expect(c).toEqual([undefined, { foo: "bar" }]);
+ };
+ foo("foo", 123, undefined, { foo: "bar" });
+});
diff --git a/Userland/Libraries/LibJS/Tests/functions/function-spread.js b/Userland/Libraries/LibJS/Tests/functions/function-spread.js
new file mode 100644
index 0000000000..fcfcd9cd48
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/functions/function-spread.js
@@ -0,0 +1,38 @@
+test("basic functionality", () => {
+ const sum = (a, b, c) => a + b + c;
+ const a = [1, 2, 3];
+
+ expect(sum(...a)).toBe(6);
+ expect(sum(1, ...a)).toBe(4);
+ expect(sum(...a, 10)).toBe(6);
+
+ const foo = (a, b, c) => c;
+
+ const o = { bar: [1, 2, 3] };
+ expect(foo(...o.bar)).toBe(3);
+ expect(foo(..."abc")).toBe("c");
+});
+
+test("spreading custom iterable", () => {
+ let o = {
+ [Symbol.iterator]() {
+ return {
+ i: 0,
+ next() {
+ if (this.i++ === 3) {
+ return { done: true };
+ }
+ return { value: this.i };
+ },
+ };
+ },
+ };
+
+ expect(Math.max(...o)).toBe(3);
+});
+
+test("spreading non iterable", () => {
+ expect(() => {
+ [...1];
+ }).toThrowWithMessage(TypeError, "1 is not iterable");
+});
diff --git a/Userland/Libraries/LibJS/Tests/functions/function-strict-mode.js b/Userland/Libraries/LibJS/Tests/functions/function-strict-mode.js
new file mode 100644
index 0000000000..99ec10b9a7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/functions/function-strict-mode.js
@@ -0,0 +1,60 @@
+test("non strict-mode by default", () => {
+ expect(isStrictMode()).toBeFalse();
+});
+
+test("use strict with double quotes", () => {
+ "use strict";
+ expect(isStrictMode()).toBeTrue();
+});
+
+// prettier-ignore
+test("use strict with single quotes", () => {
+ 'use strict';
+ expect(isStrictMode()).toBeTrue();
+});
+
+// prettier-ignore
+test("use strict with backticks does not yield strict mode", () => {
+ `use strict`;
+ expect(isStrictMode()).toBeFalse();
+});
+
+// prettier-ignore
+test("use strict with single quotes after statement does not yield strict mode code", () => {
+ ;'use strict';
+ expect(isStrictMode()).toBeFalse();
+});
+
+// prettier-ignore
+test("use strict with double quotes after statement does not yield strict mode code", () => {
+ ;"use strict";
+ expect(isStrictMode()).toBeFalse();
+});
+
+test("use strict interrupted by a line continuation does not yield strict mode code", () => {
+ "use \
+strict";
+ expect(isStrictMode()).toBeFalse();
+});
+
+test("strict mode propagates down the scope chain", () => {
+ "use strict";
+ expect(isStrictMode()).toBeTrue();
+ (function () {
+ expect(isStrictMode()).toBeTrue();
+ })();
+});
+
+test("strict mode does not propagate up the scope chain", () => {
+ expect(isStrictMode()).toBeFalse();
+ (function () {
+ "use strict";
+ expect(isStrictMode()).toBeTrue();
+ })();
+ expect(isStrictMode()).toBeFalse();
+});
+
+test('only the string "use strict" yields strict mode code', () => {
+ "use stric";
+ expect(isStrictMode()).toBeFalse();
+});
diff --git a/Userland/Libraries/LibJS/Tests/functions/function-this-in-arguments.js b/Userland/Libraries/LibJS/Tests/functions/function-this-in-arguments.js
new file mode 100644
index 0000000000..cf24b6f8aa
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/functions/function-this-in-arguments.js
@@ -0,0 +1,16 @@
+test("basic functionality", () => {
+ expect(typeof this).toBe("object");
+ expect(this).toBe(globalThis);
+});
+
+test("this inside instantiated functions is not globalThis", () => {
+ let functionThis;
+ function Foo() {
+ this.x = 5;
+ functionThis = this;
+ }
+
+ new Foo();
+ expect(typeof functionThis).toBe("object");
+ expect(functionThis.x).toBe(5);
+});
diff --git a/Userland/Libraries/LibJS/Tests/if-statement-function-declaration.js b/Userland/Libraries/LibJS/Tests/if-statement-function-declaration.js
new file mode 100644
index 0000000000..583585ebe5
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/if-statement-function-declaration.js
@@ -0,0 +1,41 @@
+describe("function declarations in if statement clauses", () => {
+ test("if clause", () => {
+ if (true) function foo() {}
+ if (false) function bar() {}
+ expect(typeof globalThis.foo).toBe("function");
+ expect(typeof globalThis.bar).toBe("undefined");
+ });
+
+ test("else clause", () => {
+ if (false);
+ else function foo() {}
+ if (true);
+ else function bar() {}
+ expect(typeof globalThis.foo).toBe("function");
+ expect(typeof globalThis.bar).toBe("undefined");
+ });
+
+ test("if and else clause", () => {
+ if (true) function foo() {}
+ else function bar() {}
+ expect(typeof globalThis.foo).toBe("function");
+ expect(typeof globalThis.bar).toBe("undefined");
+ });
+
+ test("syntax error in strict mode", () => {
+ expect(`
+ "use strict";
+ if (true) function foo() {}
+ `).not.toEval();
+ expect(`
+ "use strict";
+ if (false);
+ else function foo() {}
+ `).not.toEval();
+ expect(`
+ "use strict";
+ if (false) function foo() {}
+ else function bar() {}
+ `).not.toEval();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/indexed-access-string-object.js b/Userland/Libraries/LibJS/Tests/indexed-access-string-object.js
new file mode 100644
index 0000000000..7946cbfbf4
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/indexed-access-string-object.js
@@ -0,0 +1,15 @@
+test("string literal indexing", () => {
+ var s = "foo";
+ expect(s[0]).toBe("f");
+ expect(s[1]).toBe("o");
+ expect(s[2]).toBe("o");
+ expect(s[3]).toBeUndefined();
+});
+
+test("string object indexing", () => {
+ var s = new String("bar");
+ expect(s[0]).toBe("b");
+ expect(s[1]).toBe("a");
+ expect(s[2]).toBe("r");
+ expect(s[3]).toBeUndefined();
+});
diff --git a/Userland/Libraries/LibJS/Tests/invalid-lhs-in-assignment.js b/Userland/Libraries/LibJS/Tests/invalid-lhs-in-assignment.js
new file mode 100644
index 0000000000..ec5024f8ec
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/invalid-lhs-in-assignment.js
@@ -0,0 +1,32 @@
+test("assignment to function call", () => {
+ expect(() => {
+ function foo() {}
+ foo() = "foo";
+ }).toThrowWithMessage(ReferenceError, "Invalid left-hand side in assignment");
+});
+
+test("assignment to function call in strict mode", () => {
+ expect("'use strict'; foo() = 'foo'").not.toEval();
+});
+
+test("assignment to inline function call", () => {
+ expect(() => {
+ (function () {})() = "foo";
+ }).toThrowWithMessage(ReferenceError, "Invalid left-hand side in assignment");
+});
+
+test("assignment to invalid LHS is syntax error", () => {
+ expect("1 += 1").not.toEval();
+ expect("1 -= 1").not.toEval();
+ expect("1 *= 1").not.toEval();
+ expect("1 /= 1").not.toEval();
+ expect("1 %= 1").not.toEval();
+ expect("1 **= 1").not.toEval();
+ expect("1 &= 1").not.toEval();
+ expect("1 |= 1").not.toEval();
+ expect("1 ^= 1").not.toEval();
+ expect("1 <<= 1").not.toEval();
+ expect("1 >>= 1").not.toEval();
+ expect("1 >>>= 1").not.toEval();
+ expect("1 = 1").not.toEval();
+});
diff --git a/Userland/Libraries/LibJS/Tests/iterators/%IteratorPrototype%.js b/Userland/Libraries/LibJS/Tests/iterators/%IteratorPrototype%.js
new file mode 100644
index 0000000000..efdb32e51a
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/iterators/%IteratorPrototype%.js
@@ -0,0 +1,12 @@
+const getIteratorPrototype = () =>
+ Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
+
+test("prototype of %IteratorPrototype% is %ObjectPrototype%", () => {
+ let itProto = getIteratorPrototype();
+ expect(Object.getPrototypeOf(itProto)).toBe(Object.getPrototypeOf({}));
+});
+
+test("@@iterator of %IteratorPrototype% is itself", () => {
+ let itProto = getIteratorPrototype();
+ expect(itProto[Symbol.iterator]()).toBe(itProto);
+});
diff --git a/Userland/Libraries/LibJS/Tests/iterators/array-iterator.js b/Userland/Libraries/LibJS/Tests/iterators/array-iterator.js
new file mode 100644
index 0000000000..82af3e942d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/iterators/array-iterator.js
@@ -0,0 +1,53 @@
+test("length", () => {
+ expect(Array.prototype[Symbol.iterator]).toHaveLength(0);
+});
+
+test("@@toStringTag", () => {
+ expect([].values()[Symbol.toStringTag]).toBe("Array Iterator");
+ expect([].values().toString()).toBe("[object Array Iterator]");
+});
+
+test("same function as Array.prototype.values", () => {
+ expect(Array.prototype[Symbol.iterator]).toBe(Array.prototype.values);
+});
+
+test("basic functionality", () => {
+ const a = [1, 2, 3];
+ const it = a[Symbol.iterator]();
+ expect(it.next()).toEqual({ value: 1, done: false });
+ expect(it.next()).toEqual({ value: 2, done: false });
+ expect(it.next()).toEqual({ value: 3, done: false });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+});
+
+test("works when applied to non-object", () => {
+ [true, false, 9, 2n, Symbol()].forEach(primitive => {
+ const it = [][Symbol.iterator].call(primitive);
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ });
+});
+
+test("item added to array before exhaustion is accessible", () => {
+ const a = [1, 2];
+ const it = a[Symbol.iterator]();
+ expect(it.next()).toEqual({ value: 1, done: false });
+ expect(it.next()).toEqual({ value: 2, done: false });
+ a.push(3);
+ expect(it.next()).toEqual({ value: 3, done: false });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+});
+
+test("item added to array after exhaustion is inaccessible", () => {
+ const a = [1, 2];
+ const it = a[Symbol.iterator]();
+ expect(it.next()).toEqual({ value: 1, done: false });
+ expect(it.next()).toEqual({ value: 2, done: false });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ a.push(3);
+ expect(it.next()).toEqual({ value: undefined, done: true });
+});
diff --git a/Userland/Libraries/LibJS/Tests/iterators/string-iterator.js b/Userland/Libraries/LibJS/Tests/iterators/string-iterator.js
new file mode 100644
index 0000000000..379f6f05fc
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/iterators/string-iterator.js
@@ -0,0 +1,48 @@
+test("length", () => {
+ expect(String.prototype[Symbol.iterator]).toHaveLength(0);
+});
+
+test("basic functionality", () => {
+ const s = "abcd";
+ const it = s[Symbol.iterator]();
+ expect(it.next()).toEqual({ value: "a", done: false });
+ expect(it.next()).toEqual({ value: "b", done: false });
+ expect(it.next()).toEqual({ value: "c", done: false });
+ expect(it.next()).toEqual({ value: "d", done: false });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+});
+
+test("casts |this| to string", () => {
+ const it = String.prototype[Symbol.iterator].call(45);
+ expect(it.next()).toEqual({ value: "4", done: false });
+ expect(it.next()).toEqual({ value: "5", done: false });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+
+ const it = String.prototype[Symbol.iterator].call(false);
+ expect(it.next()).toEqual({ value: "f", done: false });
+ expect(it.next()).toEqual({ value: "a", done: false });
+ expect(it.next()).toEqual({ value: "l", done: false });
+ expect(it.next()).toEqual({ value: "s", done: false });
+ expect(it.next()).toEqual({ value: "e", done: false });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+
+ expect(() => {
+ String.prototype[Symbol.iterator].call(null);
+ }).toThrowWithMessage(TypeError, "ToObject on null or undefined");
+ expect(() => {
+ String.prototype[Symbol.iterator].call(undefined);
+ }).toThrowWithMessage(TypeError, "ToObject on null or undefined");
+});
+
+test("utf8 compatible", () => {
+ const it = "ab\u{1f41e}cde"[Symbol.iterator]();
+ expect(it.next()).toEqual({ value: "a", done: false });
+ expect(it.next()).toEqual({ value: "b", done: false });
+ expect(it.next()).toEqual({ value: "🐞", done: false });
+ expect(it.next()).toEqual({ value: "c", done: false });
+ expect(it.next()).toEqual({ value: "d", done: false });
+ expect(it.next()).toEqual({ value: "e", done: false });
+ expect(it.next()).toEqual({ value: undefined, done: true });
+});
diff --git a/Userland/Libraries/LibJS/Tests/labels.js b/Userland/Libraries/LibJS/Tests/labels.js
new file mode 100644
index 0000000000..1747e8d0bb
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/labels.js
@@ -0,0 +1,47 @@
+test("labeled plain scope", () => {
+ test: {
+ let o = 1;
+ expect(o).toBe(1);
+ break test;
+ expect().fail();
+ }
+});
+
+test("break on plain scope from inner scope", () => {
+ outer: {
+ {
+ break outer;
+ }
+ expect().fail();
+ }
+});
+
+test("labeled for loop with break", () => {
+ let counter = 0;
+ outer: for (a of [1, 2, 3]) {
+ for (b of [4, 5, 6]) {
+ if (a === 2 && b === 5) break outer;
+ counter++;
+ }
+ }
+ expect(counter).toBe(4);
+});
+
+test("labeled for loop with continue", () => {
+ let counter = 0;
+ outer: for (a of [1, 2, 3]) {
+ for (b of [4, 5, 6]) {
+ if (b === 6) continue outer;
+ counter++;
+ }
+ }
+ expect(counter).toBe(6);
+});
+
+test("invalid label across scope", () => {
+ expect(`
+ label: {
+ (() => { break label; });
+ }
+ `).not.toEval();
+});
diff --git a/Userland/Libraries/LibJS/Tests/let-scoping.js b/Userland/Libraries/LibJS/Tests/let-scoping.js
new file mode 100644
index 0000000000..27c92e23f9
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/let-scoping.js
@@ -0,0 +1,14 @@
+test("let scoping", () => {
+ let i = 1;
+ {
+ let i = 3;
+ expect(i).toBe(3);
+ }
+ expect(i).toBe(1);
+
+ {
+ const i = 2;
+ expect(i).toBe(2);
+ }
+ expect(i).toBe(1);
+});
diff --git a/Userland/Libraries/LibJS/Tests/loops/break-basic.js b/Userland/Libraries/LibJS/Tests/loops/break-basic.js
new file mode 100644
index 0000000000..1bb141c618
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/loops/break-basic.js
@@ -0,0 +1,30 @@
+test("Toplevel break inside loop", () => {
+ var j = 0;
+ for (var i = 0; i < 9; ++i) {
+ break;
+ ++j;
+ }
+ expect(j).toBe(0);
+});
+
+test("break inside sub-blocks", () => {
+ var j = 0;
+ for (var i = 0; i < 9; ++i) {
+ if (j == 4) {
+ break;
+ }
+ ++j;
+ }
+ expect(j).toBe(4);
+});
+
+test("break inside curly sub-blocks", () => {
+ var j = 0;
+ for (var i = 0; i < 9; ++i) {
+ if (j == 4) {
+ break;
+ }
+ ++j;
+ }
+ expect(j).toBe(4);
+});
diff --git a/Userland/Libraries/LibJS/Tests/loops/continue-basic.js b/Userland/Libraries/LibJS/Tests/loops/continue-basic.js
new file mode 100644
index 0000000000..765a9ead18
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/loops/continue-basic.js
@@ -0,0 +1,10 @@
+test("basic functionality", () => {
+ var j = 0;
+ for (var i = 0; i < 9; ++i) {
+ if (i == 3) {
+ continue;
+ }
+ ++j;
+ }
+ expect(j).toBe(8);
+});
diff --git a/Userland/Libraries/LibJS/Tests/loops/do-while-basic.js b/Userland/Libraries/LibJS/Tests/loops/do-while-basic.js
new file mode 100644
index 0000000000..c935c5f0a6
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/loops/do-while-basic.js
@@ -0,0 +1,24 @@
+test("basic functionality", () => {
+ let number = 0;
+ do {
+ number++;
+ } while (number < 9);
+ expect(number).toBe(9);
+});
+
+test("no braces", () => {
+ let number = 0;
+ do number++;
+ while (number < 3);
+ expect(number).toBe(3);
+});
+
+test("exception in test expression", () => {
+ expect(() => {
+ do {} while (foo);
+ }).toThrow(ReferenceError);
+});
+
+test("automatic semicolon insertion", () => {
+ expect("do {} while (false) foo").toEval();
+});
diff --git a/Userland/Libraries/LibJS/Tests/loops/for-basic.js b/Userland/Libraries/LibJS/Tests/loops/for-basic.js
new file mode 100644
index 0000000000..8a270ddee6
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/loops/for-basic.js
@@ -0,0 +1,15 @@
+test("basic functionality", () => {
+ let a = [];
+ for (let i = 0; i < 3; ++i) {
+ a.push(i);
+ }
+ expect(a).toEqual([0, 1, 2]);
+});
+
+test("only condition", () => {
+ let a = [];
+ for (; a.length < 3; ) {
+ a.push("x");
+ }
+ expect(a).toEqual(["x", "x", "x"]);
+});
diff --git a/Userland/Libraries/LibJS/Tests/loops/for-head-errors.js b/Userland/Libraries/LibJS/Tests/loops/for-head-errors.js
new file mode 100644
index 0000000000..c1b96014b8
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/loops/for-head-errors.js
@@ -0,0 +1,23 @@
+test("using undefined variable in initializer", () => {
+ expect(() => {
+ for (let i = foo; i < 100; ++i) {}
+ }).toThrowWithMessage(ReferenceError, "'foo' is not defined");
+});
+
+test("using undefined variable in condition", () => {
+ expect(() => {
+ for (let i = 0; i < foo; ++i) {}
+ }).toThrowWithMessage(ReferenceError, "'foo' is not defined");
+});
+
+test("using undefined variable in updater", () => {
+ let loopCount = 0;
+
+ expect(() => {
+ for (let i = 0; i < 100; ++foo) {
+ loopCount++;
+ }
+ }).toThrowWithMessage(ReferenceError, "'foo' is not defined");
+
+ expect(loopCount).toBe(1);
+});
diff --git a/Userland/Libraries/LibJS/Tests/loops/for-in-basic.js b/Userland/Libraries/LibJS/Tests/loops/for-in-basic.js
new file mode 100644
index 0000000000..5a35ee1c91
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/loops/for-in-basic.js
@@ -0,0 +1,45 @@
+test("iterate through empty string", () => {
+ const a = [];
+ for (const property in "") {
+ a.push(property);
+ }
+ expect(a).toEqual([]);
+});
+
+test("iterate through number", () => {
+ const a = [];
+ for (const property in 123) {
+ a.push(property);
+ }
+ expect(a).toEqual([]);
+});
+
+test("iterate through empty object", () => {
+ const a = [];
+ for (const property in {}) {
+ a.push(property);
+ }
+ expect(a).toEqual([]);
+});
+
+test("iterate through string", () => {
+ const a = [];
+ for (const property in "hello") {
+ a.push(property);
+ }
+ expect(a).toEqual(["0", "1", "2", "3", "4"]);
+});
+
+test("iterate through object", () => {
+ const a = [];
+ for (const property in { a: 1, b: 2, c: 2 }) {
+ a.push(property);
+ }
+ expect(a).toEqual(["a", "b", "c"]);
+});
+
+test("use already-declared variable", () => {
+ var property;
+ for (property in "abc");
+ expect(property).toBe("2");
+});
diff --git a/Userland/Libraries/LibJS/Tests/loops/for-no-curlies.js b/Userland/Libraries/LibJS/Tests/loops/for-no-curlies.js
new file mode 100644
index 0000000000..913347c395
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/loops/for-no-curlies.js
@@ -0,0 +1,5 @@
+test("basic functionality", () => {
+ let number = 0;
+ for (let i = 0; i < 3; ++i) for (let j = 0; j < 3; ++j) number++;
+ expect(number).toBe(9);
+});
diff --git a/Userland/Libraries/LibJS/Tests/loops/for-of-basic.js b/Userland/Libraries/LibJS/Tests/loops/for-of-basic.js
new file mode 100644
index 0000000000..e08d096c68
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/loops/for-of-basic.js
@@ -0,0 +1,100 @@
+describe("correct behavior", () => {
+ test("iterate through array", () => {
+ const a = [];
+ for (const num of [1, 2, 3]) {
+ a.push(num);
+ }
+ expect(a).toEqual([1, 2, 3]);
+ });
+
+ test("iterate through string", () => {
+ const a = [];
+ for (const char of "hello") {
+ a.push(char);
+ }
+ expect(a).toEqual(["h", "e", "l", "l", "o"]);
+ });
+
+ test("iterate through string object", () => {
+ const a = [];
+ for (const char of new String("hello")) {
+ a.push(char);
+ }
+ expect(a).toEqual(["h", "e", "l", "l", "o"]);
+ });
+
+ test("use already-declared variable", () => {
+ var char;
+ for (char of "abc");
+ expect(char).toBe("c");
+ });
+
+ test("respects custom Symbol.iterator method", () => {
+ const o = {
+ [Symbol.iterator]() {
+ return {
+ i: 0,
+ next() {
+ if (this.i++ == 3) {
+ return { done: true };
+ }
+ return { value: this.i, done: false };
+ },
+ };
+ },
+ };
+
+ const a = [];
+ for (const k of o) {
+ a.push(k);
+ }
+
+ expect(a).toEqual([1, 2, 3]);
+ });
+
+ test("loops through custom iterator if there is an exception thrown part way through", () => {
+ // This tests against the way custom iterators used to be implemented, where the values
+ // were all collected at once before the for-of body was executed, instead of getting
+ // the values one at a time
+ const o = {
+ [Symbol.iterator]() {
+ return {
+ i: 0,
+ next() {
+ if (this.i++ === 3) {
+ throw new Error();
+ }
+ return { value: this.i };
+ },
+ };
+ },
+ };
+
+ const a = [];
+
+ try {
+ for (let k of o) {
+ a.push(k);
+ }
+ expect().fail();
+ } catch (e) {
+ expect(a).toEqual([1, 2, 3]);
+ }
+ });
+});
+
+describe("errors", () => {
+ test("right hand side is a primitive", () => {
+ expect(() => {
+ for (const _ of 123) {
+ }
+ }).toThrowWithMessage(TypeError, "123 is not iterable");
+ });
+
+ test("right hand side is an object", () => {
+ expect(() => {
+ for (const _ of { foo: 1, bar: 2 }) {
+ }
+ }).toThrowWithMessage(TypeError, "[object Object] is not iterable");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/loops/for-scopes.js b/Userland/Libraries/LibJS/Tests/loops/for-scopes.js
new file mode 100644
index 0000000000..2f89bd1229
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/loops/for-scopes.js
@@ -0,0 +1,18 @@
+test("var in for head", () => {
+ for (var v = 5; false; );
+ expect(v).toBe(5);
+});
+
+test("let in for head", () => {
+ for (let l = 5; false; );
+ expect(() => {
+ l;
+ }).toThrowWithMessage(ReferenceError, "'l' is not defined");
+});
+
+test("const in for head", () => {
+ for (const c = 5; false; );
+ expect(() => {
+ c;
+ }).toThrowWithMessage(ReferenceError, "'c' is not defined");
+});
diff --git a/Userland/Libraries/LibJS/Tests/loops/while-basic.js b/Userland/Libraries/LibJS/Tests/loops/while-basic.js
new file mode 100644
index 0000000000..f3a4a2da88
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/loops/while-basic.js
@@ -0,0 +1,25 @@
+test("basic functionality", () => {
+ let number = 0;
+ while (number < 9) {
+ number++;
+ }
+ expect(number).toBe(9);
+});
+
+test("no braces", () => {
+ let number = 0;
+ while (number < 3) number++;
+ expect(number).toBe(3);
+});
+
+test("does not loop when initially false", () => {
+ while (false) {
+ expect().fail();
+ }
+});
+
+test("exception in test expression", () => {
+ expect(() => {
+ while (foo);
+ }).toThrow(ReferenceError);
+});
diff --git a/Userland/Libraries/LibJS/Tests/new-expression.js b/Userland/Libraries/LibJS/Tests/new-expression.js
new file mode 100644
index 0000000000..827b156910
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/new-expression.js
@@ -0,0 +1,53 @@
+// prettier-ignore
+test("new-expression parsing", () => {
+ function Foo() {
+ this.x = 1;
+ }
+
+ let foo = new Foo();
+ expect(foo.x).toBe(1);
+
+ foo = new Foo
+ expect(foo.x).toBe(1);
+
+ foo = new
+ Foo
+ ();
+ expect(foo.x).toBe(1);
+
+ foo = new Foo + 2
+ expect(foo).toBe("[object Object]2");
+});
+
+// prettier-ignore
+test("new-expressions with object keys", () => {
+ let a = {
+ b: function () {
+ this.x = 2;
+ },
+ };
+
+ foo = new a.b();
+ expect(foo.x).toBe(2);
+
+ foo = new a.b;
+ expect(foo.x).toBe(2);
+
+ foo = new
+ a.b();
+ expect(foo.x).toBe(2);
+});
+
+test("new-expressions with function calls", () => {
+ function funcGetter() {
+ return function (a, b) {
+ this.x = a + b;
+ };
+ }
+
+ foo = new funcGetter()(1, 5);
+ expect(foo).toBeUndefined();
+
+ foo = new (funcGetter())(1, 5);
+ expect(foo.x).toBe(6);
+});
diff --git a/Userland/Libraries/LibJS/Tests/numeric-literals-basic.js b/Userland/Libraries/LibJS/Tests/numeric-literals-basic.js
new file mode 100644
index 0000000000..b4b9f75181
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/numeric-literals-basic.js
@@ -0,0 +1,52 @@
+// FIXME: Some of the test cases below are duplicated, presumably to test
+// uppercase as well which then got lowercased by Prettier at some point.
+
+test("hex literals", () => {
+ expect(0xff).toBe(255);
+ expect(0xff).toBe(255);
+});
+
+test("octal literals", () => {
+ expect(0o10).toBe(8);
+ expect(0o10).toBe(8);
+ expect(010).toBe(8);
+ expect(089).toBe(89);
+});
+
+test("binary literals", () => {
+ expect(0b10).toBe(2);
+ expect(0b10).toBe(2);
+});
+
+test("exponential literals", () => {
+ expect(1e3).toBe(1000);
+ expect(1e3).toBe(1000);
+ expect(1e-3).toBe(0.001);
+ expect(1e1).toBe(10);
+ expect(0.1e1).toBe(1);
+ expect(0.1e1).toBe(1);
+ expect(0.1e1).toBe(1);
+ expect(0.1e1).toBe(1);
+});
+
+test("decimal numbers", () => {
+ expect(1).toBe(1);
+ expect(0.1).toBe(0.1);
+});
+
+test("accessing properties of decimal numbers", () => {
+ Number.prototype.foo = "foo";
+ expect((1).foo).toBe("foo");
+ expect((1.1).foo).toBe("foo");
+ expect((0.1).foo).toBe("foo");
+});
+
+test("invalid numeric literals", () => {
+ expect("1e").not.toEval();
+ expect("0x").not.toEval();
+ expect("0b").not.toEval();
+ expect("0o").not.toEval();
+ expect("'use strict'; 0755").not.toEval();
+ expect("1in[]").not.toEval();
+ expect("2instanceof foo").not.toEval();
+});
diff --git a/Userland/Libraries/LibJS/Tests/object-basic.js b/Userland/Libraries/LibJS/Tests/object-basic.js
new file mode 100644
index 0000000000..5b3b78fb19
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/object-basic.js
@@ -0,0 +1,169 @@
+describe("correct behavior", () => {
+ test("numeric indexing", () => {
+ const o = { 1: 23 };
+
+ expect(o[1]).toBe(23);
+ expect(o[1n]).toBe(23);
+ expect(o["1"]).toBe(23);
+
+ o[10] = "123";
+ expect(o[10]).toBe("123");
+ expect(o["10"]).toBe("123");
+
+ o[10n] = "1234";
+ expect(o[10]).toBe("1234");
+ expect(o["10"]).toBe("1234");
+ });
+
+ test("string indexing", () => {
+ let foo = "bar";
+
+ const o = {
+ foo,
+ bar: "baz",
+ qux: true ? 10 : 20,
+ hello: "friends",
+ };
+
+ expect(o.foo).toBe("bar");
+ expect(o["foo"]).toBe("bar");
+ expect(o.qux).toBe(10), expect(o.hello).toBe("friends");
+ expect(o["hello"]).toBe("friends");
+ });
+
+ test("symbol keys", () => {
+ let object = {};
+ let symbol = Symbol("foo");
+
+ object[symbol] = 2;
+ expect(object[symbol]).toBe(2);
+ });
+
+ test("numeric keys", () => {
+ const hex = { 0x10: "16" };
+ const oct = { 0o10: "8" };
+ const bin = { 0b10: "2" };
+ const float = { 0.5: "0.5" };
+
+ expect(hex["16"]).toBe("16");
+ expect(oct["8"]).toBe("8");
+ expect(bin["2"]).toBe("2");
+ expect(float["0.5"]).toBe("0.5");
+ });
+
+ test("computed properties", () => {
+ const foo = "bar";
+ const computed = "computed";
+ const o = {
+ [1 + 2]: 42,
+ [`I am a ${computed} key`]: foo,
+ };
+
+ expect(o[3]).toBe(42);
+ expect(o["I am a computed key"]).toBe("bar");
+ });
+
+ test("duplicate keys", () => {
+ const o = {
+ duplicate: "hello",
+ duplicate: "world",
+ };
+ expect(o.duplicate).toBe("world");
+ });
+
+ test("assigning after creation", () => {
+ const o = {};
+ o.baz = "test";
+
+ expect(o.baz).toBe("test");
+ expect(o["baz"]).toBe("test");
+
+ expect(o[-1]).toBeUndefined();
+ o[-1] = "hello friends";
+ expect(o[-1]).toBe("hello friends");
+ expect(o["-1"]).toBe("hello friends");
+ });
+
+ test("floating point keys", () => {
+ const math = { 3.14: "pi" };
+ expect(math["3.14"]).toBe("pi");
+ expect(math[3.14]).toBe("pi");
+ });
+
+ test("keywords as property keys", () => {
+ const o2 = {
+ return: 1,
+ yield: 1,
+ for: 1,
+ catch: 1,
+ break: 1,
+ };
+
+ expect(o2.return).toBe(1);
+ expect(o2.yield).toBe(1);
+ expect(o2.for).toBe(1);
+ expect(o2.catch).toBe(1);
+ expect(o2.break).toBe(1);
+ });
+
+ test("prototypical inheritance", () => {
+ var base = {
+ getNumber() {
+ return 10;
+ },
+ };
+
+ var derived = {
+ getNumber() {
+ return 20 + super.getNumber();
+ },
+ };
+
+ Object.setPrototypeOf(derived, base);
+ expect(derived.getNumber()).toBe(30);
+ });
+});
+
+describe("side effects", () => {
+ let a;
+ const append = x => {
+ a.push(x);
+ };
+
+ test("computed key side effects", () => {
+ a = [];
+ const o3 = { [append(1)]: 1, [append(2)]: 2, [append(3)]: 3 };
+ expect(a).toHaveLength(3);
+ expect(a[0]).toBe(1);
+ expect(a[1]).toBe(2);
+ expect(a[2]).toBe(3);
+ expect(o3.undefined).toBe(3);
+ });
+
+ test("value side effects", () => {
+ a = [];
+ const o4 = { test: append(1), test: append(2), test: append(3) };
+ expect(a).toHaveLength(3);
+ expect(a[0]).toBe(1);
+ expect(a[1]).toBe(2);
+ expect(a[2]).toBe(3);
+ expect(o4.test).toBeUndefined();
+ });
+});
+
+describe("errors", () => {
+ test("syntax errors", () => {
+ expect("({ foo: function() { super.bar; } })").not.toEval();
+ expect("({ get ...foo })").not.toEval();
+ expect("({ get... foo })").not.toEval();
+ expect("({ get foo })").not.toEval();
+ expect("({ get foo: bar })").not.toEval();
+ expect("({ get [foo]: bar })").not.toEval();
+ expect("({ get ...[foo] })").not.toEval();
+ expect("({ get foo(bar) {} })").not.toEval();
+ expect("({ set foo() {} })").not.toEval();
+ expect("({ set foo(...bar) {} })").not.toEval();
+ expect("({ set foo(bar, baz) {} })").not.toEval();
+ expect("({ ...foo: bar })").not.toEval();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/object-expression-computed-property.js b/Userland/Libraries/LibJS/Tests/object-expression-computed-property.js
new file mode 100644
index 0000000000..7fa579d21e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/object-expression-computed-property.js
@@ -0,0 +1,12 @@
+test("Issue #3712, negative/non-int computed property in object expression", () => {
+ const o = {
+ [1.23]: "foo",
+ [-1]: "foo",
+ [NaN]: "foo",
+ [Infinity]: "foo",
+ };
+ expect(o[1.23]).toBe("foo");
+ expect(o[-1]).toBe("foo");
+ expect(o[NaN]).toBe("foo");
+ expect(o[Infinity]).toBe("foo");
+});
diff --git a/Userland/Libraries/LibJS/Tests/object-getter-setter-shorthand.js b/Userland/Libraries/LibJS/Tests/object-getter-setter-shorthand.js
new file mode 100644
index 0000000000..02b513800b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/object-getter-setter-shorthand.js
@@ -0,0 +1,62 @@
+test("normal methods named get and set", () => {
+ let o = {
+ get() {
+ return 5;
+ },
+ set() {
+ return 10;
+ },
+ };
+ expect(o.get()).toBe(5);
+ expect(o.set()).toBe(10);
+});
+
+test("basic get and set", () => {
+ let o = {
+ get x() {
+ return 5;
+ },
+ set x(_) {},
+ };
+ expect(o.x).toBe(5);
+ o.x = 10;
+ expect(o.x).toBe(5);
+});
+
+test("get and set with 'this'", () => {
+ let o = {
+ get x() {
+ return this._x + 1;
+ },
+ set x(value) {
+ this._x = value + 1;
+ },
+ };
+
+ expect(o.x).toBeNaN();
+ o.x = 10;
+ expect(o.x).toBe(12);
+ o.x = 20;
+ expect(o.x).toBe(22);
+});
+
+test("multiple getters", () => {
+ let o = {
+ get x() {
+ return 5;
+ },
+ get x() {
+ return 10;
+ },
+ };
+ expect(o.x).toBe(10);
+});
+
+test("setter return value", () => {
+ o = {
+ set x(value) {
+ return 10;
+ },
+ };
+ expect((o.x = 20)).toBe(20);
+});
diff --git a/Userland/Libraries/LibJS/Tests/object-method-shorthand.js b/Userland/Libraries/LibJS/Tests/object-method-shorthand.js
new file mode 100644
index 0000000000..8d8dfd512f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/object-method-shorthand.js
@@ -0,0 +1,50 @@
+test("basic method shorthand", () => {
+ const o = {
+ foo: "bar",
+ getFoo() {
+ return this.foo;
+ },
+ };
+ expect(o.getFoo()).toBe("bar");
+});
+
+test("numeric literal method shorthand", () => {
+ const o = {
+ foo: "bar",
+ 12() {
+ return this.foo;
+ },
+ };
+ expect(o[12]()).toBe("bar");
+});
+
+test("string literal method shorthand", () => {
+ const o = {
+ foo: "bar",
+ "hello friends"() {
+ return this.foo;
+ },
+ };
+ expect(o["hello friends"]()).toBe("bar");
+});
+
+test("computed property method shorthand", () => {
+ const o = {
+ foo: "bar",
+ [4 + 10]() {
+ return this.foo;
+ },
+ };
+ expect(o[14]()).toBe("bar");
+});
+
+test("symbol computed property shorthand", () => {
+ const s = Symbol("foo");
+ const o = {
+ foo: "bar",
+ [s]() {
+ return this.foo;
+ },
+ };
+ expect(o[s]()).toBe("bar");
+});
diff --git a/Userland/Libraries/LibJS/Tests/object-spread.js b/Userland/Libraries/LibJS/Tests/object-spread.js
new file mode 100644
index 0000000000..93008a8352
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/object-spread.js
@@ -0,0 +1,117 @@
+const testObjSpread = obj => {
+ expect(obj).toEqual({
+ foo: 0,
+ bar: 1,
+ baz: 2,
+ qux: 3,
+ });
+};
+
+const testObjStrSpread = obj => {
+ expect(obj).toEqual(["a", "b", "c", "d"]);
+};
+
+test("spread object literal inside object literal", () => {
+ const obj = {
+ foo: 0,
+ ...{ bar: 1, baz: 2 },
+ qux: 3,
+ };
+ testObjSpread(obj);
+});
+
+test("spread object with assigned property inside object literal", () => {
+ const obj = { foo: 0, bar: 1, baz: 2 };
+ obj.qux = 3;
+ testObjSpread({ ...obj });
+});
+
+test("spread object inside object literal", () => {
+ let a = { bar: 1, baz: 2 };
+ const obj = { foo: 0, ...a, qux: 3 };
+ testObjSpread(obj);
+});
+
+test("complex nested object spreading", () => {
+ const obj = {
+ ...{},
+ ...{
+ ...{ foo: 0, bar: 1, baz: 2 },
+ },
+ qux: 3,
+ };
+ testObjSpread(obj);
+});
+
+test("spread string in object literal", () => {
+ const obj = { ..."abcd" };
+ testObjStrSpread(obj);
+});
+
+test("spread array in object literal", () => {
+ const obj = { ...["a", "b", "c", "d"] };
+ testObjStrSpread(obj);
+});
+
+test("spread array with holes in object literal", () => {
+ const obj = { ...[, , "a", , , , "b", "c", , "d", , ,] };
+ expect(obj).toEqual({ 2: "a", 6: "b", 7: "c", 9: "d" });
+});
+
+test("spread string object in object literal", () => {
+ const obj = { ...String("abcd") };
+ testObjStrSpread(obj);
+});
+
+test("spread object with non-enumerable property", () => {
+ const a = { foo: 0 };
+ Object.defineProperty(a, "bar", {
+ value: 1,
+ enumerable: false,
+ });
+ const obj = { ...a };
+ expect(obj.foo).toBe(0);
+ expect(obj).not.toHaveProperty("bar");
+});
+
+test("spread object with symbol keys", () => {
+ const s = Symbol("baz");
+ const a = {
+ foo: "bar",
+ [s]: "qux",
+ };
+ const obj = { ...a };
+ expect(obj.foo).toBe("bar");
+ expect(obj[s]).toBe("qux");
+});
+
+test("spreading non-spreadable values", () => {
+ let empty = {
+ ...undefined,
+ ...null,
+ ...1,
+ ...true,
+ ...function () {},
+ ...Date,
+ };
+ expect(Object.getOwnPropertyNames(empty)).toHaveLength(0);
+});
+
+test("respects custom Symbol.iterator method", () => {
+ let o = {
+ [Symbol.iterator]() {
+ return {
+ i: 0,
+ next() {
+ if (this.i++ == 3) {
+ return { done: true };
+ }
+ return { value: this.i, done: false };
+ },
+ };
+ },
+ };
+
+ let a = [...o];
+ expect(a).toEqual([1, 2, 3]);
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/assignment-operators.js b/Userland/Libraries/LibJS/Tests/operators/assignment-operators.js
new file mode 100644
index 0000000000..3ab2d01a5b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/assignment-operators.js
@@ -0,0 +1,135 @@
+let x, o;
+
+test("basic functionality", () => {
+ x = 1;
+ expect((x = 2)).toBe(2);
+ expect(x).toBe(2);
+
+ x = 1;
+ expect((x += 2)).toBe(3);
+ expect(x).toBe(3);
+
+ x = 3;
+ expect((x -= 2)).toBe(1);
+ expect(x).toBe(1);
+
+ x = 3;
+ expect((x *= 2)).toBe(6);
+ expect(x).toBe(6);
+
+ x = 6;
+ expect((x /= 2)).toBe(3);
+ expect(x).toBe(3);
+
+ x = 6;
+ expect((x %= 4)).toBe(2);
+ expect(x).toBe(2);
+
+ x = 2;
+ expect((x **= 3)).toBe(8);
+ expect(x).toBe(8);
+
+ x = 3;
+ expect((x &= 2)).toBe(2);
+ expect(x).toBe(2);
+
+ x = 3;
+ expect((x |= 4)).toBe(7);
+ expect(x).toBe(7);
+
+ x = 6;
+ expect((x ^= 2)).toBe(4);
+ expect(x).toBe(4);
+
+ x = 2;
+ expect((x <<= 2)).toBe(8);
+ expect(x).toBe(8);
+
+ x = 8;
+ expect((x >>= 2)).toBe(2);
+ expect(x).toBe(2);
+
+ x = -(2 ** 32 - 10);
+ expect((x >>>= 2)).toBe(2);
+ expect(x).toBe(2);
+});
+
+test("logical assignment operators", () => {
+ // short circuiting evaluation
+ x = false;
+ expect((x &&= expect.fail())).toBeFalse();
+
+ x = true;
+ expect((x ||= expect.fail())).toBeTrue();
+
+ x = "foo";
+ expect((x ??= expect.fail())).toBe("foo");
+
+ const prepareObject = (shortCircuitValue, assignmentValue) => ({
+ get shortCircuit() {
+ return shortCircuitValue;
+ },
+ set shortCircuit(_) {
+ // assignment will short circuit in all test cases
+ // so its setter must never be called
+ expect().fail();
+ },
+ assignment: assignmentValue,
+ });
+
+ o = prepareObject(false, true);
+ expect((o.shortCircuit &&= "foo")).toBeFalse();
+ expect(o.shortCircuit).toBeFalse();
+ expect((o.assignment &&= "bar")).toBe("bar");
+ expect(o.assignment).toBe("bar");
+
+ o = prepareObject(true, false);
+ expect((o.shortCircuit ||= "foo")).toBeTrue();
+ expect(o.shortCircuit).toBeTrue();
+ expect((o.assignment ||= "bar")).toBe("bar");
+ expect(o.assignment).toBe("bar");
+
+ o = prepareObject("test", null);
+ expect((o.shortCircuit ??= "foo")).toBe("test");
+ expect(o.shortCircuit).toBe("test");
+ expect((o.assignment ??= "bar")).toBe("bar");
+ expect(o.assignment).toBe("bar");
+});
+
+test("evaluation order", () => {
+ for (const op of [
+ "=",
+ "+=",
+ "-=",
+ "*=",
+ "/=",
+ "%=",
+ "**=",
+ "&=",
+ "|=",
+ "^=",
+ "<<=",
+ ">>=",
+ ">>>=",
+ "&&=",
+ "||=",
+ "??=",
+ ]) {
+ var a = [];
+ function b() {
+ b.hasBeenCalled = true;
+ throw Error();
+ }
+ function c() {
+ c.hasBeenCalled = true;
+ throw Error();
+ }
+ b.hasBeenCalled = false;
+ c.hasBeenCalled = false;
+ expect(() => {
+ new Function(`a[b()] ${op} c()`)();
+ }).toThrow(Error);
+ expect(b.hasBeenCalled).toBeTrue();
+ expect(c.hasBeenCalled).toBeFalse();
+ }
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-and.js b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-and.js
new file mode 100644
index 0000000000..5eb4f19cc3
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-and.js
@@ -0,0 +1,60 @@
+test("basic numeric and", () => {
+ expect(0 & 0).toBe(0);
+ expect(0 & 1).toBe(0);
+ expect(0 & 2).toBe(0);
+ expect(0 & 3).toBe(0);
+ expect(0 & 4).toBe(0);
+ expect(0 & 5).toBe(0);
+
+ expect(1 & 0).toBe(0);
+ expect(1 & 1).toBe(1);
+ expect(1 & 2).toBe(0);
+ expect(1 & 3).toBe(1);
+ expect(1 & 4).toBe(0);
+ expect(1 & 5).toBe(1);
+
+ expect(2 & 0).toBe(0);
+ expect(2 & 1).toBe(0);
+ expect(2 & 2).toBe(2);
+ expect(2 & 3).toBe(2);
+ expect(2 & 4).toBe(0);
+ expect(2 & 5).toBe(0);
+
+ expect(3 & 0).toBe(0);
+ expect(3 & 1).toBe(1);
+ expect(3 & 2).toBe(2);
+ expect(3 & 3).toBe(3);
+ expect(3 & 4).toBe(0);
+ expect(3 & 5).toBe(1);
+
+ expect(4 & 0).toBe(0);
+ expect(4 & 1).toBe(0);
+ expect(4 & 2).toBe(0);
+ expect(4 & 3).toBe(0);
+ expect(4 & 4).toBe(4);
+ expect(4 & 5).toBe(4);
+
+ expect(5 & 0).toBe(0);
+ expect(5 & 1).toBe(1);
+ expect(5 & 2).toBe(0);
+ expect(5 & 3).toBe(1);
+ expect(5 & 4).toBe(4);
+ expect(5 & 5).toBe(5);
+});
+
+test("and with non-numeric values", () => {
+ let x = 3;
+ let y = 7;
+
+ expect("42" & 6).toBe(2);
+ expect(x & y).toBe(3);
+ expect(x & [[[[13]]]]).toBe(1);
+ expect(undefined & y).toBe(0);
+ expect("a" & "b").toBe(0);
+ expect(null & null).toBe(0);
+ expect(undefined & undefined).toBe(0);
+ expect(NaN & NaN).toBe(0);
+ expect(NaN & 6).toBe(0);
+ expect(Infinity & Infinity).toBe(0);
+ expect(-Infinity & Infinity).toBe(0);
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-left-shift.js b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-left-shift.js
new file mode 100644
index 0000000000..d0c9d8d5f9
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-left-shift.js
@@ -0,0 +1,60 @@
+test("basic numeric shifting", () => {
+ expect(0 << 0).toBe(0);
+ expect(0 << 1).toBe(0);
+ expect(0 << 2).toBe(0);
+ expect(0 << 3).toBe(0);
+ expect(0 << 4).toBe(0);
+ expect(0 << 5).toBe(0);
+
+ expect(1 << 0).toBe(1);
+ expect(1 << 1).toBe(2);
+ expect(1 << 2).toBe(4);
+ expect(1 << 3).toBe(8);
+ expect(1 << 4).toBe(16);
+ expect(1 << 5).toBe(32);
+
+ expect(2 << 0).toBe(2);
+ expect(2 << 1).toBe(4);
+ expect(2 << 2).toBe(8);
+ expect(2 << 3).toBe(16);
+ expect(2 << 4).toBe(32);
+ expect(2 << 5).toBe(64);
+
+ expect(3 << 0).toBe(3);
+ expect(3 << 1).toBe(6);
+ expect(3 << 2).toBe(12);
+ expect(3 << 3).toBe(24);
+ expect(3 << 4).toBe(48);
+ expect(3 << 5).toBe(96);
+
+ expect(4 << 0).toBe(4);
+ expect(4 << 1).toBe(8);
+ expect(4 << 2).toBe(16);
+ expect(4 << 3).toBe(32);
+ expect(4 << 4).toBe(64);
+ expect(4 << 5).toBe(128);
+
+ expect(5 << 0).toBe(5);
+ expect(5 << 1).toBe(10);
+ expect(5 << 2).toBe(20);
+ expect(5 << 3).toBe(40);
+ expect(5 << 4).toBe(80);
+ expect(5 << 5).toBe(160);
+});
+
+test("shifting with non-numeric values", () => {
+ let x = 3;
+ let y = 7;
+
+ expect("42" << 6).toBe(2688);
+ expect(x << y).toBe(384);
+ expect(x << [[[[12]]]]).toBe(12288);
+ expect(undefined << y).toBe(0);
+ expect("a" << "b").toBe(0);
+ expect(null << null).toBe(0);
+ expect(undefined << undefined).toBe(0);
+ expect(NaN << NaN).toBe(0);
+ expect(NaN << 6).toBe(0);
+ expect(Infinity << Infinity).toBe(0);
+ expect(-Infinity << Infinity).toBe(0);
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-or.js b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-or.js
new file mode 100644
index 0000000000..485d1226d2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-or.js
@@ -0,0 +1,60 @@
+test("basic numeric or", () => {
+ expect(0 | 0).toBe(0);
+ expect(0 | 1).toBe(1);
+ expect(0 | 2).toBe(2);
+ expect(0 | 3).toBe(3);
+ expect(0 | 4).toBe(4);
+ expect(0 | 5).toBe(5);
+
+ expect(1 | 0).toBe(1);
+ expect(1 | 1).toBe(1);
+ expect(1 | 2).toBe(3);
+ expect(1 | 3).toBe(3);
+ expect(1 | 4).toBe(5);
+ expect(1 | 5).toBe(5);
+
+ expect(2 | 0).toBe(2);
+ expect(2 | 1).toBe(3);
+ expect(2 | 2).toBe(2);
+ expect(2 | 3).toBe(3);
+ expect(2 | 4).toBe(6);
+ expect(2 | 5).toBe(7);
+
+ expect(3 | 0).toBe(3);
+ expect(3 | 1).toBe(3);
+ expect(3 | 2).toBe(3);
+ expect(3 | 3).toBe(3);
+ expect(3 | 4).toBe(7);
+ expect(3 | 5).toBe(7);
+
+ expect(4 | 0).toBe(4);
+ expect(4 | 1).toBe(5);
+ expect(4 | 2).toBe(6);
+ expect(4 | 3).toBe(7);
+ expect(4 | 4).toBe(4);
+ expect(4 | 5).toBe(5);
+
+ expect(5 | 0).toBe(5);
+ expect(5 | 1).toBe(5);
+ expect(5 | 2).toBe(7);
+ expect(5 | 3).toBe(7);
+ expect(5 | 4).toBe(5);
+ expect(5 | 5).toBe(5);
+});
+
+test("or with non-numeric values", () => {
+ let x = 3;
+ let y = 7;
+
+ expect("42" | 6).toBe(46);
+ expect(x | y).toBe(7);
+ expect(x | [[[[12]]]]).toBe(15);
+ expect(undefined | y).toBe(7);
+ expect("a" | "b").toBe(0);
+ expect(null | null).toBe(0);
+ expect(undefined | undefined).toBe(0);
+ expect(NaN | NaN).toBe(0);
+ expect(NaN | 6).toBe(6);
+ expect(Infinity | Infinity).toBe(0);
+ expect(-Infinity | Infinity).toBe(0);
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-right-shift.js b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-right-shift.js
new file mode 100644
index 0000000000..f071c5da92
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-right-shift.js
@@ -0,0 +1,62 @@
+test("basic numeric shifting", () => {
+ expect(0 >> 0).toBe(0);
+ expect(0 >> 1).toBe(0);
+ expect(0 >> 2).toBe(0);
+ expect(0 >> 3).toBe(0);
+ expect(0 >> 4).toBe(0);
+ expect(0 >> 5).toBe(0);
+
+ expect(1 >> 0).toBe(1);
+ expect(1 >> 1).toBe(0);
+ expect(1 >> 2).toBe(0);
+ expect(1 >> 3).toBe(0);
+ expect(1 >> 4).toBe(0);
+ expect(1 >> 5).toBe(0);
+
+ expect(5 >> 0).toBe(5);
+ expect(5 >> 1).toBe(2);
+ expect(5 >> 2).toBe(1);
+ expect(5 >> 3).toBe(0);
+ expect(5 >> 4).toBe(0);
+ expect(5 >> 5).toBe(0);
+
+ expect(42 >> 0).toBe(42);
+ expect(42 >> 1).toBe(21);
+ expect(42 >> 2).toBe(10);
+ expect(42 >> 3).toBe(5);
+ expect(42 >> 4).toBe(2);
+ expect(42 >> 5).toBe(1);
+});
+
+test("numeric shifting with negative lhs values", () => {
+ expect(-1 >> 0).toBe(-1);
+ expect(-1 >> 1).toBe(-1);
+ expect(-1 >> 2).toBe(-1);
+ expect(-1 >> 3).toBe(-1);
+ expect(-1 >> 4).toBe(-1);
+ expect(-1 >> 5).toBe(-1);
+
+ expect(-5 >> 0).toBe(-5);
+ expect(-5 >> 1).toBe(-3);
+ expect(-5 >> 2).toBe(-2);
+ expect(-5 >> 3).toBe(-1);
+ expect(-5 >> 4).toBe(-1);
+ expect(-5 >> 5).toBe(-1);
+});
+
+test("shifting with non-numeric values", () => {
+ let x = 67;
+ let y = 4;
+
+ expect("42" >> 3).toBe(5);
+ expect(x >> y).toBe(4);
+ expect(x >> [[[[5]]]]).toBe(2);
+ expect(undefined >> y).toBe(0);
+ expect("a" >> "b").toBe(0);
+ expect(null >> null).toBe(0);
+ expect(undefined >> undefined).toBe(0);
+ expect(NaN >> NaN).toBe(0);
+ expect(6 >> NaN).toBe(6);
+ expect(Infinity >> Infinity).toBe(0);
+ expect(-Infinity >> Infinity).toBe(0);
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-unsigned-right-shift.js b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-unsigned-right-shift.js
new file mode 100644
index 0000000000..4a950e18ef
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-unsigned-right-shift.js
@@ -0,0 +1,62 @@
+test("basic numeric shifting", () => {
+ expect(0 >>> 0).toBe(0);
+ expect(0 >>> 1).toBe(0);
+ expect(0 >>> 2).toBe(0);
+ expect(0 >>> 3).toBe(0);
+ expect(0 >>> 4).toBe(0);
+ expect(0 >>> 5).toBe(0);
+
+ expect(1 >>> 0).toBe(1);
+ expect(1 >>> 1).toBe(0);
+ expect(1 >>> 2).toBe(0);
+ expect(1 >>> 3).toBe(0);
+ expect(1 >>> 4).toBe(0);
+ expect(1 >>> 5).toBe(0);
+
+ expect(5 >>> 0).toBe(5);
+ expect(5 >>> 1).toBe(2);
+ expect(5 >>> 2).toBe(1);
+ expect(5 >>> 3).toBe(0);
+ expect(5 >>> 4).toBe(0);
+ expect(5 >>> 5).toBe(0);
+
+ expect(42 >>> 0).toBe(42);
+ expect(42 >>> 1).toBe(21);
+ expect(42 >>> 2).toBe(10);
+ expect(42 >>> 3).toBe(5);
+ expect(42 >>> 4).toBe(2);
+ expect(42 >>> 5).toBe(1);
+});
+
+test("numeric shifting with negative lhs values", () => {
+ expect(-1 >>> 0).toBe(4294967295);
+ expect(-1 >>> 1).toBe(2147483647);
+ expect(-1 >>> 2).toBe(1073741823);
+ expect(-1 >>> 3).toBe(536870911);
+ expect(-1 >>> 4).toBe(268435455);
+ expect(-1 >>> 5).toBe(134217727);
+
+ expect(-5 >>> 0).toBe(4294967291);
+ expect(-5 >>> 1).toBe(2147483645);
+ expect(-5 >>> 2).toBe(1073741822);
+ expect(-5 >>> 3).toBe(536870911);
+ expect(-5 >>> 4).toBe(268435455);
+ expect(-5 >>> 5).toBe(134217727);
+});
+
+test("shifting with non-numeric values", () => {
+ let x = -67;
+ let y = 4;
+
+ expect("-42" >>> 3).toBe(536870906);
+ expect(x >>> y).toBe(268435451);
+ expect(x >>> [[[[5]]]]).toBe(134217725);
+ expect(undefined >>> y).toBe(0);
+ expect("a" >>> "b").toBe(0);
+ expect(null >>> null).toBe(0);
+ expect(undefined >>> undefined).toBe(0);
+ expect(NaN >>> NaN).toBe(0);
+ expect(6 >>> NaN).toBe(6);
+ expect(Infinity >>> Infinity).toBe(0);
+ expect(-Infinity >>> Infinity).toBe(0);
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-xor.js b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-xor.js
new file mode 100644
index 0000000000..356ad01ca4
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-xor.js
@@ -0,0 +1,60 @@
+test("basic numeric xor", () => {
+ expect(0 ^ 0).toBe(0);
+ expect(0 ^ 1).toBe(1);
+ expect(0 ^ 2).toBe(2);
+ expect(0 ^ 3).toBe(3);
+ expect(0 ^ 4).toBe(4);
+ expect(0 ^ 5).toBe(5);
+
+ expect(1 ^ 0).toBe(1);
+ expect(1 ^ 1).toBe(0);
+ expect(1 ^ 2).toBe(3);
+ expect(1 ^ 3).toBe(2);
+ expect(1 ^ 4).toBe(5);
+ expect(1 ^ 5).toBe(4);
+
+ expect(2 ^ 0).toBe(2);
+ expect(2 ^ 1).toBe(3);
+ expect(2 ^ 2).toBe(0);
+ expect(2 ^ 3).toBe(1);
+ expect(2 ^ 4).toBe(6);
+ expect(2 ^ 5).toBe(7);
+
+ expect(3 ^ 0).toBe(3);
+ expect(3 ^ 1).toBe(2);
+ expect(3 ^ 2).toBe(1);
+ expect(3 ^ 3).toBe(0);
+ expect(3 ^ 4).toBe(7);
+ expect(3 ^ 5).toBe(6);
+
+ expect(4 ^ 0).toBe(4);
+ expect(4 ^ 1).toBe(5);
+ expect(4 ^ 2).toBe(6);
+ expect(4 ^ 3).toBe(7);
+ expect(4 ^ 4).toBe(0);
+ expect(4 ^ 5).toBe(1);
+
+ expect(5 ^ 0).toBe(5);
+ expect(5 ^ 1).toBe(4);
+ expect(5 ^ 2).toBe(7);
+ expect(5 ^ 3).toBe(6);
+ expect(5 ^ 4).toBe(1);
+ expect(5 ^ 5).toBe(0);
+});
+
+test("xor with non-numeric values", () => {
+ let x = 3;
+ let y = 7;
+
+ expect("42" ^ 6).toBe(44);
+ expect(x ^ y).toBe(4);
+ expect(x ^ [[[[12]]]]).toBe(15);
+ expect(undefined ^ y).toBe(7);
+ expect("a" ^ "b").toBe(0);
+ expect(null ^ null).toBe(0);
+ expect(undefined ^ undefined).toBe(0);
+ expect(NaN ^ NaN).toBe(0);
+ expect(NaN ^ 6).toBe(6);
+ expect(Infinity ^ Infinity).toBe(0);
+ expect(-Infinity ^ Infinity).toBe(0);
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/binary-relational.js b/Userland/Libraries/LibJS/Tests/operators/binary-relational.js
new file mode 100644
index 0000000000..307f7164e4
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/binary-relational.js
@@ -0,0 +1,729 @@
+test("basic functionality", () => {
+ const vals = [
+ 1,
+ 2,
+ 1.23,
+ 2.34567,
+ 1n,
+ 2n,
+ [],
+ [1],
+ [2],
+ [1, 2],
+ "foo",
+ "fooo",
+ "🔥",
+ "❤️",
+ "bar",
+ {},
+ { a: 1 },
+ { a: 2 },
+ { b: 1 },
+ true,
+ false,
+ undefined,
+ null,
+ NaN,
+ +Infinity,
+ -Infinity,
+ ];
+
+ // Each row contains: xIndex, yIndex, (x < y), (x > y), (x <= y), (x >= y)
+ // where x = vals[xIndex], y = vals[yIndex]
+ // Table can be generated using a mature engine and the following code:
+ // for (var xIndex = 0; xIndex < vals.length; ++xIndex) {
+ // for (var yIndex = 0; yIndex < vals.length; ++yIndex) {
+ // var x = vals[xIndex];
+ // var y = vals[yIndex];
+ // console.log(`[${xIndex}, ${yIndex}, ${x < y}, ${x > y}, ${x <= y}, ${x >= y}]`);
+ // }
+ // }
+ const table = [
+ [0, 0, false, false, true, true],
+ [0, 1, true, false, true, false],
+ [0, 2, true, false, true, false],
+ [0, 3, true, false, true, false],
+ [0, 4, false, false, true, true],
+ [0, 5, true, false, true, false],
+ [0, 6, false, true, false, true],
+ [0, 7, false, false, true, true],
+ [0, 8, true, false, true, false],
+ [0, 9, false, false, false, false],
+ [0, 10, false, false, false, false],
+ [0, 11, false, false, false, false],
+ [0, 12, false, false, false, false],
+ [0, 13, false, false, false, false],
+ [0, 14, false, false, false, false],
+ [0, 15, false, false, false, false],
+ [0, 16, false, false, false, false],
+ [0, 17, false, false, false, false],
+ [0, 18, false, false, false, false],
+ [0, 19, false, false, true, true],
+ [0, 20, false, true, false, true],
+ [0, 21, false, false, false, false],
+ [0, 22, false, true, false, true],
+ [0, 23, false, false, false, false],
+ [0, 24, true, false, true, false],
+ [0, 25, false, true, false, true],
+ [1, 0, false, true, false, true],
+ [1, 1, false, false, true, true],
+ [1, 2, false, true, false, true],
+ [1, 3, true, false, true, false],
+ [1, 4, false, true, false, true],
+ [1, 5, false, false, true, true],
+ [1, 6, false, true, false, true],
+ [1, 7, false, true, false, true],
+ [1, 8, false, false, true, true],
+ [1, 9, false, false, false, false],
+ [1, 10, false, false, false, false],
+ [1, 11, false, false, false, false],
+ [1, 12, false, false, false, false],
+ [1, 13, false, false, false, false],
+ [1, 14, false, false, false, false],
+ [1, 15, false, false, false, false],
+ [1, 16, false, false, false, false],
+ [1, 17, false, false, false, false],
+ [1, 18, false, false, false, false],
+ [1, 19, false, true, false, true],
+ [1, 20, false, true, false, true],
+ [1, 21, false, false, false, false],
+ [1, 22, false, true, false, true],
+ [1, 23, false, false, false, false],
+ [1, 24, true, false, true, false],
+ [1, 25, false, true, false, true],
+ [2, 0, false, true, false, true],
+ [2, 1, true, false, true, false],
+ [2, 2, false, false, true, true],
+ [2, 3, true, false, true, false],
+ [2, 4, false, true, false, true],
+ [2, 5, true, false, true, false],
+ [2, 6, false, true, false, true],
+ [2, 7, false, true, false, true],
+ [2, 8, true, false, true, false],
+ [2, 9, false, false, false, false],
+ [2, 10, false, false, false, false],
+ [2, 11, false, false, false, false],
+ [2, 12, false, false, false, false],
+ [2, 13, false, false, false, false],
+ [2, 14, false, false, false, false],
+ [2, 15, false, false, false, false],
+ [2, 16, false, false, false, false],
+ [2, 17, false, false, false, false],
+ [2, 18, false, false, false, false],
+ [2, 19, false, true, false, true],
+ [2, 20, false, true, false, true],
+ [2, 21, false, false, false, false],
+ [2, 22, false, true, false, true],
+ [2, 23, false, false, false, false],
+ [2, 24, true, false, true, false],
+ [2, 25, false, true, false, true],
+ [3, 0, false, true, false, true],
+ [3, 1, false, true, false, true],
+ [3, 2, false, true, false, true],
+ [3, 3, false, false, true, true],
+ [3, 4, false, true, false, true],
+ [3, 5, false, true, false, true],
+ [3, 6, false, true, false, true],
+ [3, 7, false, true, false, true],
+ [3, 8, false, true, false, true],
+ [3, 9, false, false, false, false],
+ [3, 10, false, false, false, false],
+ [3, 11, false, false, false, false],
+ [3, 12, false, false, false, false],
+ [3, 13, false, false, false, false],
+ [3, 14, false, false, false, false],
+ [3, 15, false, false, false, false],
+ [3, 16, false, false, false, false],
+ [3, 17, false, false, false, false],
+ [3, 18, false, false, false, false],
+ [3, 19, false, true, false, true],
+ [3, 20, false, true, false, true],
+ [3, 21, false, false, false, false],
+ [3, 22, false, true, false, true],
+ [3, 23, false, false, false, false],
+ [3, 24, true, false, true, false],
+ [3, 25, false, true, false, true],
+ [4, 0, false, false, true, true],
+ [4, 1, true, false, true, false],
+ [4, 2, true, false, true, false],
+ [4, 3, true, false, true, false],
+ [4, 4, false, false, true, true],
+ [4, 5, true, false, true, false],
+ [4, 6, false, true, false, true],
+ [4, 7, false, false, true, true],
+ [4, 8, true, false, true, false],
+ [4, 9, false, false, false, false],
+ [4, 10, false, false, false, false],
+ [4, 11, false, false, false, false],
+ [4, 12, false, false, false, false],
+ [4, 13, false, false, false, false],
+ [4, 14, false, false, false, false],
+ [4, 15, false, false, false, false],
+ [4, 16, false, false, false, false],
+ [4, 17, false, false, false, false],
+ [4, 18, false, false, false, false],
+ [4, 19, false, false, true, true],
+ [4, 20, false, true, false, true],
+ [4, 21, false, false, false, false],
+ [4, 22, false, true, false, true],
+ [4, 23, false, false, false, false],
+ [4, 24, true, false, true, false],
+ [4, 25, false, true, false, true],
+ [5, 0, false, true, false, true],
+ [5, 1, false, false, true, true],
+ [5, 2, false, true, false, true],
+ [5, 3, true, false, true, false],
+ [5, 4, false, true, false, true],
+ [5, 5, false, false, true, true],
+ [5, 6, false, true, false, true],
+ [5, 7, false, true, false, true],
+ [5, 8, false, false, true, true],
+ [5, 9, false, false, false, false],
+ [5, 10, false, false, false, false],
+ [5, 11, false, false, false, false],
+ [5, 12, false, false, false, false],
+ [5, 13, false, false, false, false],
+ [5, 14, false, false, false, false],
+ [5, 15, false, false, false, false],
+ [5, 16, false, false, false, false],
+ [5, 17, false, false, false, false],
+ [5, 18, false, false, false, false],
+ [5, 19, false, true, false, true],
+ [5, 20, false, true, false, true],
+ [5, 21, false, false, false, false],
+ [5, 22, false, true, false, true],
+ [5, 23, false, false, false, false],
+ [5, 24, true, false, true, false],
+ [5, 25, false, true, false, true],
+ [6, 0, true, false, true, false],
+ [6, 1, true, false, true, false],
+ [6, 2, true, false, true, false],
+ [6, 3, true, false, true, false],
+ [6, 4, true, false, true, false],
+ [6, 5, true, false, true, false],
+ [6, 6, false, false, true, true],
+ [6, 7, true, false, true, false],
+ [6, 8, true, false, true, false],
+ [6, 9, true, false, true, false],
+ [6, 10, true, false, true, false],
+ [6, 11, true, false, true, false],
+ [6, 12, true, false, true, false],
+ [6, 13, true, false, true, false],
+ [6, 14, true, false, true, false],
+ [6, 15, true, false, true, false],
+ [6, 16, true, false, true, false],
+ [6, 17, true, false, true, false],
+ [6, 18, true, false, true, false],
+ [6, 19, true, false, true, false],
+ [6, 20, false, false, true, true],
+ [6, 21, false, false, false, false],
+ [6, 22, false, false, true, true],
+ [6, 23, false, false, false, false],
+ [6, 24, true, false, true, false],
+ [6, 25, false, true, false, true],
+ [7, 0, false, false, true, true],
+ [7, 1, true, false, true, false],
+ [7, 2, true, false, true, false],
+ [7, 3, true, false, true, false],
+ [7, 4, false, false, true, true],
+ [7, 5, true, false, true, false],
+ [7, 6, false, true, false, true],
+ [7, 7, false, false, true, true],
+ [7, 8, true, false, true, false],
+ [7, 9, true, false, true, false],
+ [7, 10, true, false, true, false],
+ [7, 11, true, false, true, false],
+ [7, 12, true, false, true, false],
+ [7, 13, true, false, true, false],
+ [7, 14, true, false, true, false],
+ [7, 15, true, false, true, false],
+ [7, 16, true, false, true, false],
+ [7, 17, true, false, true, false],
+ [7, 18, true, false, true, false],
+ [7, 19, false, false, true, true],
+ [7, 20, false, true, false, true],
+ [7, 21, false, false, false, false],
+ [7, 22, false, true, false, true],
+ [7, 23, false, false, false, false],
+ [7, 24, true, false, true, false],
+ [7, 25, false, true, false, true],
+ [8, 0, false, true, false, true],
+ [8, 1, false, false, true, true],
+ [8, 2, false, true, false, true],
+ [8, 3, true, false, true, false],
+ [8, 4, false, true, false, true],
+ [8, 5, false, false, true, true],
+ [8, 6, false, true, false, true],
+ [8, 7, false, true, false, true],
+ [8, 8, false, false, true, true],
+ [8, 9, false, true, false, true],
+ [8, 10, true, false, true, false],
+ [8, 11, true, false, true, false],
+ [8, 12, true, false, true, false],
+ [8, 13, true, false, true, false],
+ [8, 14, true, false, true, false],
+ [8, 15, true, false, true, false],
+ [8, 16, true, false, true, false],
+ [8, 17, true, false, true, false],
+ [8, 18, true, false, true, false],
+ [8, 19, false, true, false, true],
+ [8, 20, false, true, false, true],
+ [8, 21, false, false, false, false],
+ [8, 22, false, true, false, true],
+ [8, 23, false, false, false, false],
+ [8, 24, true, false, true, false],
+ [8, 25, false, true, false, true],
+ [9, 0, false, false, false, false],
+ [9, 1, false, false, false, false],
+ [9, 2, false, false, false, false],
+ [9, 3, false, false, false, false],
+ [9, 4, false, false, false, false],
+ [9, 5, false, false, false, false],
+ [9, 6, false, true, false, true],
+ [9, 7, false, true, false, true],
+ [9, 8, true, false, true, false],
+ [9, 9, false, false, true, true],
+ [9, 10, true, false, true, false],
+ [9, 11, true, false, true, false],
+ [9, 12, true, false, true, false],
+ [9, 13, true, false, true, false],
+ [9, 14, true, false, true, false],
+ [9, 15, true, false, true, false],
+ [9, 16, true, false, true, false],
+ [9, 17, true, false, true, false],
+ [9, 18, true, false, true, false],
+ [9, 19, false, false, false, false],
+ [9, 20, false, false, false, false],
+ [9, 21, false, false, false, false],
+ [9, 22, false, false, false, false],
+ [9, 23, false, false, false, false],
+ [9, 24, false, false, false, false],
+ [9, 25, false, false, false, false],
+ [10, 0, false, false, false, false],
+ [10, 1, false, false, false, false],
+ [10, 2, false, false, false, false],
+ [10, 3, false, false, false, false],
+ [10, 4, false, false, false, false],
+ [10, 5, false, false, false, false],
+ [10, 6, false, true, false, true],
+ [10, 7, false, true, false, true],
+ [10, 8, false, true, false, true],
+ [10, 9, false, true, false, true],
+ [10, 10, false, false, true, true],
+ [10, 11, true, false, true, false],
+ [10, 12, true, false, true, false],
+ [10, 13, true, false, true, false],
+ [10, 14, false, true, false, true],
+ [10, 15, false, true, false, true],
+ [10, 16, false, true, false, true],
+ [10, 17, false, true, false, true],
+ [10, 18, false, true, false, true],
+ [10, 19, false, false, false, false],
+ [10, 20, false, false, false, false],
+ [10, 21, false, false, false, false],
+ [10, 22, false, false, false, false],
+ [10, 23, false, false, false, false],
+ [10, 24, false, false, false, false],
+ [10, 25, false, false, false, false],
+ [11, 0, false, false, false, false],
+ [11, 1, false, false, false, false],
+ [11, 2, false, false, false, false],
+ [11, 3, false, false, false, false],
+ [11, 4, false, false, false, false],
+ [11, 5, false, false, false, false],
+ [11, 6, false, true, false, true],
+ [11, 7, false, true, false, true],
+ [11, 8, false, true, false, true],
+ [11, 9, false, true, false, true],
+ [11, 10, false, true, false, true],
+ [11, 11, false, false, true, true],
+ [11, 12, true, false, true, false],
+ [11, 13, true, false, true, false],
+ [11, 14, false, true, false, true],
+ [11, 15, false, true, false, true],
+ [11, 16, false, true, false, true],
+ [11, 17, false, true, false, true],
+ [11, 18, false, true, false, true],
+ [11, 19, false, false, false, false],
+ [11, 20, false, false, false, false],
+ [11, 21, false, false, false, false],
+ [11, 22, false, false, false, false],
+ [11, 23, false, false, false, false],
+ [11, 24, false, false, false, false],
+ [11, 25, false, false, false, false],
+ [12, 0, false, false, false, false],
+ [12, 1, false, false, false, false],
+ [12, 2, false, false, false, false],
+ [12, 3, false, false, false, false],
+ [12, 4, false, false, false, false],
+ [12, 5, false, false, false, false],
+ [12, 6, false, true, false, true],
+ [12, 7, false, true, false, true],
+ [12, 8, false, true, false, true],
+ [12, 9, false, true, false, true],
+ [12, 10, false, true, false, true],
+ [12, 11, false, true, false, true],
+ [12, 12, false, false, true, true],
+ [12, 13, false, true, false, true],
+ [12, 14, false, true, false, true],
+ [12, 15, false, true, false, true],
+ [12, 16, false, true, false, true],
+ [12, 17, false, true, false, true],
+ [12, 18, false, true, false, true],
+ [12, 19, false, false, false, false],
+ [12, 20, false, false, false, false],
+ [12, 21, false, false, false, false],
+ [12, 22, false, false, false, false],
+ [12, 23, false, false, false, false],
+ [12, 24, false, false, false, false],
+ [12, 25, false, false, false, false],
+ [13, 0, false, false, false, false],
+ [13, 1, false, false, false, false],
+ [13, 2, false, false, false, false],
+ [13, 3, false, false, false, false],
+ [13, 4, false, false, false, false],
+ [13, 5, false, false, false, false],
+ [13, 6, false, true, false, true],
+ [13, 7, false, true, false, true],
+ [13, 8, false, true, false, true],
+ [13, 9, false, true, false, true],
+ [13, 10, false, true, false, true],
+ [13, 11, false, true, false, true],
+ [13, 12, true, false, true, false],
+ [13, 13, false, false, true, true],
+ [13, 14, false, true, false, true],
+ [13, 15, false, true, false, true],
+ [13, 16, false, true, false, true],
+ [13, 17, false, true, false, true],
+ [13, 18, false, true, false, true],
+ [13, 19, false, false, false, false],
+ [13, 20, false, false, false, false],
+ [13, 21, false, false, false, false],
+ [13, 22, false, false, false, false],
+ [13, 23, false, false, false, false],
+ [13, 24, false, false, false, false],
+ [13, 25, false, false, false, false],
+ [14, 0, false, false, false, false],
+ [14, 1, false, false, false, false],
+ [14, 2, false, false, false, false],
+ [14, 3, false, false, false, false],
+ [14, 4, false, false, false, false],
+ [14, 5, false, false, false, false],
+ [14, 6, false, true, false, true],
+ [14, 7, false, true, false, true],
+ [14, 8, false, true, false, true],
+ [14, 9, false, true, false, true],
+ [14, 10, true, false, true, false],
+ [14, 11, true, false, true, false],
+ [14, 12, true, false, true, false],
+ [14, 13, true, false, true, false],
+ [14, 14, false, false, true, true],
+ [14, 15, false, true, false, true],
+ [14, 16, false, true, false, true],
+ [14, 17, false, true, false, true],
+ [14, 18, false, true, false, true],
+ [14, 19, false, false, false, false],
+ [14, 20, false, false, false, false],
+ [14, 21, false, false, false, false],
+ [14, 22, false, false, false, false],
+ [14, 23, false, false, false, false],
+ [14, 24, false, false, false, false],
+ [14, 25, false, false, false, false],
+ [15, 0, false, false, false, false],
+ [15, 1, false, false, false, false],
+ [15, 2, false, false, false, false],
+ [15, 3, false, false, false, false],
+ [15, 4, false, false, false, false],
+ [15, 5, false, false, false, false],
+ [15, 6, false, true, false, true],
+ [15, 7, false, true, false, true],
+ [15, 8, false, true, false, true],
+ [15, 9, false, true, false, true],
+ [15, 10, true, false, true, false],
+ [15, 11, true, false, true, false],
+ [15, 12, true, false, true, false],
+ [15, 13, true, false, true, false],
+ [15, 14, true, false, true, false],
+ [15, 15, false, false, true, true],
+ [15, 16, false, false, true, true],
+ [15, 17, false, false, true, true],
+ [15, 18, false, false, true, true],
+ [15, 19, false, false, false, false],
+ [15, 20, false, false, false, false],
+ [15, 21, false, false, false, false],
+ [15, 22, false, false, false, false],
+ [15, 23, false, false, false, false],
+ [15, 24, false, false, false, false],
+ [15, 25, false, false, false, false],
+ [16, 0, false, false, false, false],
+ [16, 1, false, false, false, false],
+ [16, 2, false, false, false, false],
+ [16, 3, false, false, false, false],
+ [16, 4, false, false, false, false],
+ [16, 5, false, false, false, false],
+ [16, 6, false, true, false, true],
+ [16, 7, false, true, false, true],
+ [16, 8, false, true, false, true],
+ [16, 9, false, true, false, true],
+ [16, 10, true, false, true, false],
+ [16, 11, true, false, true, false],
+ [16, 12, true, false, true, false],
+ [16, 13, true, false, true, false],
+ [16, 14, true, false, true, false],
+ [16, 15, false, false, true, true],
+ [16, 16, false, false, true, true],
+ [16, 17, false, false, true, true],
+ [16, 18, false, false, true, true],
+ [16, 19, false, false, false, false],
+ [16, 20, false, false, false, false],
+ [16, 21, false, false, false, false],
+ [16, 22, false, false, false, false],
+ [16, 23, false, false, false, false],
+ [16, 24, false, false, false, false],
+ [16, 25, false, false, false, false],
+ [17, 0, false, false, false, false],
+ [17, 1, false, false, false, false],
+ [17, 2, false, false, false, false],
+ [17, 3, false, false, false, false],
+ [17, 4, false, false, false, false],
+ [17, 5, false, false, false, false],
+ [17, 6, false, true, false, true],
+ [17, 7, false, true, false, true],
+ [17, 8, false, true, false, true],
+ [17, 9, false, true, false, true],
+ [17, 10, true, false, true, false],
+ [17, 11, true, false, true, false],
+ [17, 12, true, false, true, false],
+ [17, 13, true, false, true, false],
+ [17, 14, true, false, true, false],
+ [17, 15, false, false, true, true],
+ [17, 16, false, false, true, true],
+ [17, 17, false, false, true, true],
+ [17, 18, false, false, true, true],
+ [17, 19, false, false, false, false],
+ [17, 20, false, false, false, false],
+ [17, 21, false, false, false, false],
+ [17, 22, false, false, false, false],
+ [17, 23, false, false, false, false],
+ [17, 24, false, false, false, false],
+ [17, 25, false, false, false, false],
+ [18, 0, false, false, false, false],
+ [18, 1, false, false, false, false],
+ [18, 2, false, false, false, false],
+ [18, 3, false, false, false, false],
+ [18, 4, false, false, false, false],
+ [18, 5, false, false, false, false],
+ [18, 6, false, true, false, true],
+ [18, 7, false, true, false, true],
+ [18, 8, false, true, false, true],
+ [18, 9, false, true, false, true],
+ [18, 10, true, false, true, false],
+ [18, 11, true, false, true, false],
+ [18, 12, true, false, true, false],
+ [18, 13, true, false, true, false],
+ [18, 14, true, false, true, false],
+ [18, 15, false, false, true, true],
+ [18, 16, false, false, true, true],
+ [18, 17, false, false, true, true],
+ [18, 18, false, false, true, true],
+ [18, 19, false, false, false, false],
+ [18, 20, false, false, false, false],
+ [18, 21, false, false, false, false],
+ [18, 22, false, false, false, false],
+ [18, 23, false, false, false, false],
+ [18, 24, false, false, false, false],
+ [18, 25, false, false, false, false],
+ [19, 0, false, false, true, true],
+ [19, 1, true, false, true, false],
+ [19, 2, true, false, true, false],
+ [19, 3, true, false, true, false],
+ [19, 4, false, false, true, true],
+ [19, 5, true, false, true, false],
+ [19, 6, false, true, false, true],
+ [19, 7, false, false, true, true],
+ [19, 8, true, false, true, false],
+ [19, 9, false, false, false, false],
+ [19, 10, false, false, false, false],
+ [19, 11, false, false, false, false],
+ [19, 12, false, false, false, false],
+ [19, 13, false, false, false, false],
+ [19, 14, false, false, false, false],
+ [19, 15, false, false, false, false],
+ [19, 16, false, false, false, false],
+ [19, 17, false, false, false, false],
+ [19, 18, false, false, false, false],
+ [19, 19, false, false, true, true],
+ [19, 20, false, true, false, true],
+ [19, 21, false, false, false, false],
+ [19, 22, false, true, false, true],
+ [19, 23, false, false, false, false],
+ [19, 24, true, false, true, false],
+ [19, 25, false, true, false, true],
+ [20, 0, true, false, true, false],
+ [20, 1, true, false, true, false],
+ [20, 2, true, false, true, false],
+ [20, 3, true, false, true, false],
+ [20, 4, true, false, true, false],
+ [20, 5, true, false, true, false],
+ [20, 6, false, false, true, true],
+ [20, 7, true, false, true, false],
+ [20, 8, true, false, true, false],
+ [20, 9, false, false, false, false],
+ [20, 10, false, false, false, false],
+ [20, 11, false, false, false, false],
+ [20, 12, false, false, false, false],
+ [20, 13, false, false, false, false],
+ [20, 14, false, false, false, false],
+ [20, 15, false, false, false, false],
+ [20, 16, false, false, false, false],
+ [20, 17, false, false, false, false],
+ [20, 18, false, false, false, false],
+ [20, 19, true, false, true, false],
+ [20, 20, false, false, true, true],
+ [20, 21, false, false, false, false],
+ [20, 22, false, false, true, true],
+ [20, 23, false, false, false, false],
+ [20, 24, true, false, true, false],
+ [20, 25, false, true, false, true],
+ [21, 0, false, false, false, false],
+ [21, 1, false, false, false, false],
+ [21, 2, false, false, false, false],
+ [21, 3, false, false, false, false],
+ [21, 4, false, false, false, false],
+ [21, 5, false, false, false, false],
+ [21, 6, false, false, false, false],
+ [21, 7, false, false, false, false],
+ [21, 8, false, false, false, false],
+ [21, 9, false, false, false, false],
+ [21, 10, false, false, false, false],
+ [21, 11, false, false, false, false],
+ [21, 12, false, false, false, false],
+ [21, 13, false, false, false, false],
+ [21, 14, false, false, false, false],
+ [21, 15, false, false, false, false],
+ [21, 16, false, false, false, false],
+ [21, 17, false, false, false, false],
+ [21, 18, false, false, false, false],
+ [21, 19, false, false, false, false],
+ [21, 20, false, false, false, false],
+ [21, 21, false, false, false, false],
+ [21, 22, false, false, false, false],
+ [21, 23, false, false, false, false],
+ [21, 24, false, false, false, false],
+ [21, 25, false, false, false, false],
+ [22, 0, true, false, true, false],
+ [22, 1, true, false, true, false],
+ [22, 2, true, false, true, false],
+ [22, 3, true, false, true, false],
+ [22, 4, true, false, true, false],
+ [22, 5, true, false, true, false],
+ [22, 6, false, false, true, true],
+ [22, 7, true, false, true, false],
+ [22, 8, true, false, true, false],
+ [22, 9, false, false, false, false],
+ [22, 10, false, false, false, false],
+ [22, 11, false, false, false, false],
+ [22, 12, false, false, false, false],
+ [22, 13, false, false, false, false],
+ [22, 14, false, false, false, false],
+ [22, 15, false, false, false, false],
+ [22, 16, false, false, false, false],
+ [22, 17, false, false, false, false],
+ [22, 18, false, false, false, false],
+ [22, 19, true, false, true, false],
+ [22, 20, false, false, true, true],
+ [22, 21, false, false, false, false],
+ [22, 22, false, false, true, true],
+ [22, 23, false, false, false, false],
+ [22, 24, true, false, true, false],
+ [22, 25, false, true, false, true],
+ [23, 0, false, false, false, false],
+ [23, 1, false, false, false, false],
+ [23, 2, false, false, false, false],
+ [23, 3, false, false, false, false],
+ [23, 4, false, false, false, false],
+ [23, 5, false, false, false, false],
+ [23, 6, false, false, false, false],
+ [23, 7, false, false, false, false],
+ [23, 8, false, false, false, false],
+ [23, 9, false, false, false, false],
+ [23, 10, false, false, false, false],
+ [23, 11, false, false, false, false],
+ [23, 12, false, false, false, false],
+ [23, 13, false, false, false, false],
+ [23, 14, false, false, false, false],
+ [23, 15, false, false, false, false],
+ [23, 16, false, false, false, false],
+ [23, 17, false, false, false, false],
+ [23, 18, false, false, false, false],
+ [23, 19, false, false, false, false],
+ [23, 20, false, false, false, false],
+ [23, 21, false, false, false, false],
+ [23, 22, false, false, false, false],
+ [23, 23, false, false, false, false],
+ [23, 24, false, false, false, false],
+ [23, 25, false, false, false, false],
+ [24, 0, false, true, false, true],
+ [24, 1, false, true, false, true],
+ [24, 2, false, true, false, true],
+ [24, 3, false, true, false, true],
+ [24, 4, false, true, false, true],
+ [24, 5, false, true, false, true],
+ [24, 6, false, true, false, true],
+ [24, 7, false, true, false, true],
+ [24, 8, false, true, false, true],
+ [24, 9, false, false, false, false],
+ [24, 10, false, false, false, false],
+ [24, 11, false, false, false, false],
+ [24, 12, false, false, false, false],
+ [24, 13, false, false, false, false],
+ [24, 14, false, false, false, false],
+ [24, 15, false, false, false, false],
+ [24, 16, false, false, false, false],
+ [24, 17, false, false, false, false],
+ [24, 18, false, false, false, false],
+ [24, 19, false, true, false, true],
+ [24, 20, false, true, false, true],
+ [24, 21, false, false, false, false],
+ [24, 22, false, true, false, true],
+ [24, 23, false, false, false, false],
+ [24, 24, false, false, true, true],
+ [24, 25, false, true, false, true],
+ [25, 0, true, false, true, false],
+ [25, 1, true, false, true, false],
+ [25, 2, true, false, true, false],
+ [25, 3, true, false, true, false],
+ [25, 4, true, false, true, false],
+ [25, 5, true, false, true, false],
+ [25, 6, true, false, true, false],
+ [25, 7, true, false, true, false],
+ [25, 8, true, false, true, false],
+ [25, 9, false, false, false, false],
+ [25, 10, false, false, false, false],
+ [25, 11, false, false, false, false],
+ [25, 12, false, false, false, false],
+ [25, 13, false, false, false, false],
+ [25, 14, false, false, false, false],
+ [25, 15, false, false, false, false],
+ [25, 16, false, false, false, false],
+ [25, 17, false, false, false, false],
+ [25, 18, false, false, false, false],
+ [25, 19, true, false, true, false],
+ [25, 20, true, false, true, false],
+ [25, 21, false, false, false, false],
+ [25, 22, true, false, true, false],
+ [25, 23, false, false, false, false],
+ [25, 24, true, false, true, false],
+ [25, 25, false, false, true, true],
+ ];
+
+ for (let test of table) {
+ let x = vals[test[0]];
+ let y = vals[test[1]];
+
+ expect(x < y).toBe(test[2]);
+ expect(x > y).toBe(test[3]);
+ expect(x <= y).toBe(test[4]);
+ expect(x >= y).toBe(test[5]);
+ }
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/bitwise-not.js b/Userland/Libraries/LibJS/Tests/operators/bitwise-not.js
new file mode 100644
index 0000000000..1ad75d5c92
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/bitwise-not.js
@@ -0,0 +1,23 @@
+test("basic functionality", () => {
+ expect(~0).toBe(-1);
+ expect(~1).toBe(-2);
+ expect(~2).toBe(-3);
+ expect(~3).toBe(-4);
+ expect(~4).toBe(-5);
+ expect(~5).toBe(-6);
+ expect(~-1).toBe(0);
+ expect(~42).toBe(-43);
+ expect(~9999).toBe(-10000);
+});
+
+test("non-numeric values", () => {
+ expect(~"42").toBe(-43);
+ expect(~"foo").toBe(-1);
+ expect(~[]).toBe(-1);
+ expect(~{}).toBe(-1);
+ expect(~undefined).toBe(-1);
+ expect(~null).toBe(-1);
+ expect(~NaN).toBe(-1);
+ expect(~Infinity).toBe(-1);
+ expect(~-Infinity).toBe(-1);
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/comma-operator.js b/Userland/Libraries/LibJS/Tests/operators/comma-operator.js
new file mode 100644
index 0000000000..368a43fff5
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/comma-operator.js
@@ -0,0 +1,24 @@
+test("inside parenthesis", () => {
+ expect((1, 2, 3)).toBe(3);
+ expect((1, 2 + 3, 4)).toBe(4);
+});
+
+test("with post-increment operator", () => {
+ let foo = 0;
+ foo = (foo++, foo);
+ expect(foo).toBe(1);
+});
+
+test("with assignment operator", () => {
+ var a, b, c;
+ expect(((a = b = 3), (c = 4))).toBe(4);
+ expect(a).toBe(3);
+ expect(b).toBe(3);
+ expect(c).toBe(4);
+
+ var x, y, z;
+ expect((x = ((y = 5), (z = 6)))).toBe(6);
+ expect(x).toBe(6);
+ expect(y).toBe(5);
+ expect(z).toBe(6);
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/delete-basic.js b/Userland/Libraries/LibJS/Tests/operators/delete-basic.js
new file mode 100644
index 0000000000..e619e711a2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/delete-basic.js
@@ -0,0 +1,61 @@
+test("deleting object properties", () => {
+ const o = {};
+ o.x = 1;
+ o.y = 2;
+ o.z = 3;
+ expect(Object.getOwnPropertyNames(o)).toHaveLength(3);
+
+ expect(delete o.x).toBeTrue();
+ expect(o.hasOwnProperty("x")).toBeFalse();
+ expect(o.hasOwnProperty("y")).toBeTrue();
+ expect(o.hasOwnProperty("z")).toBeTrue();
+ expect(Object.getOwnPropertyNames(o)).toHaveLength(2);
+
+ expect(delete o.y).toBeTrue();
+ expect(o.hasOwnProperty("x")).toBeFalse();
+ expect(o.hasOwnProperty("y")).toBeFalse();
+ expect(o.hasOwnProperty("z")).toBeTrue();
+ expect(Object.getOwnPropertyNames(o)).toHaveLength(1);
+
+ expect(delete o.z).toBeTrue();
+ expect(o.hasOwnProperty("x")).toBeFalse();
+ expect(o.hasOwnProperty("y")).toBeFalse();
+ expect(o.hasOwnProperty("z")).toBeFalse();
+ expect(Object.getOwnPropertyNames(o)).toHaveLength(0);
+});
+
+test("deleting array indices", () => {
+ const a = [3, 5, 7];
+
+ expect(Object.getOwnPropertyNames(a)).toHaveLength(4);
+
+ expect(delete a[0]).toBeTrue();
+ expect(a.hasOwnProperty(0)).toBeFalse();
+ expect(a.hasOwnProperty(1)).toBeTrue();
+ expect(a.hasOwnProperty(2)).toBeTrue();
+ expect(Object.getOwnPropertyNames(a)).toHaveLength(3);
+
+ expect(delete a[1]).toBeTrue();
+ expect(a.hasOwnProperty(0)).toBeFalse();
+ expect(a.hasOwnProperty(1)).toBeFalse();
+ expect(a.hasOwnProperty(2)).toBeTrue();
+ expect(Object.getOwnPropertyNames(a)).toHaveLength(2);
+
+ expect(delete a[2]).toBeTrue();
+ expect(a.hasOwnProperty(0)).toBeFalse();
+ expect(a.hasOwnProperty(1)).toBeFalse();
+ expect(a.hasOwnProperty(2)).toBeFalse();
+ expect(Object.getOwnPropertyNames(a)).toHaveLength(1);
+
+ expect(delete a["42"]).toBeTrue();
+ expect(Object.getOwnPropertyNames(a)).toHaveLength(1);
+});
+
+test("deleting non-configurable property", () => {
+ const q = {};
+ Object.defineProperty(q, "foo", { value: 1, writable: false, enumerable: false });
+ expect(q.foo).toBe(1);
+
+ expect(delete q.foo).toBeFalse();
+ expect(q.hasOwnProperty("foo")).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/delete-global-variable.js b/Userland/Libraries/LibJS/Tests/operators/delete-global-variable.js
new file mode 100644
index 0000000000..81c5ccac66
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/delete-global-variable.js
@@ -0,0 +1,9 @@
+a = 1;
+
+test("basic functionality", () => {
+ expect(delete a).toBeTrue();
+
+ expect(() => {
+ a;
+ }).toThrowWithMessage(ReferenceError, "'a' is not defined");
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/delete-globalThis-property-crash.js b/Userland/Libraries/LibJS/Tests/operators/delete-globalThis-property-crash.js
new file mode 100644
index 0000000000..27a219c5d6
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/delete-globalThis-property-crash.js
@@ -0,0 +1,8 @@
+a = 1;
+
+test("basic functionality", () => {
+ expect(delete globalThis.a).toBeTrue();
+ expect(() => {
+ a = 2;
+ }).not.toThrow();
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/in-operator-basic.js b/Userland/Libraries/LibJS/Tests/operators/in-operator-basic.js
new file mode 100644
index 0000000000..1deec41f76
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/in-operator-basic.js
@@ -0,0 +1,32 @@
+test("in operator with objects", () => {
+ const o = { foo: "bar", bar: undefined };
+ expect("" in o).toBeFalse();
+ expect("foo" in o).toBeTrue();
+ expect("bar" in o).toBeTrue();
+ expect("baz" in o).toBeFalse();
+ expect("toString" in o).toBeTrue();
+});
+
+test("in operator with arrays", () => {
+ const a = ["hello", "friends"];
+ expect(0 in a).toBeTrue();
+ expect(1 in a).toBeTrue();
+ expect(2 in a).toBeFalse();
+ expect("0" in a).toBeTrue();
+ expect("hello" in a).toBeFalse();
+ expect("friends" in a).toBeFalse();
+ expect("length" in a).toBeTrue();
+});
+
+test("in operator with string object", () => {
+ const s = new String("foo");
+ expect("length" in s).toBeTrue();
+});
+
+test("error when used with primitives", () => {
+ ["foo", 123, null, undefined].forEach(value => {
+ expect(() => {
+ "prop" in value;
+ }).toThrowWithMessage(TypeError, "'in' operator must be used on an object");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/instanceof-basic.js b/Userland/Libraries/LibJS/Tests/operators/instanceof-basic.js
new file mode 100644
index 0000000000..3d2b38c496
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/instanceof-basic.js
@@ -0,0 +1,36 @@
+test("basic functionality", () => {
+ function Foo() {
+ this.x = 123;
+ }
+
+ const foo = new Foo();
+ expect(foo instanceof Foo).toBeTrue();
+});
+
+test("derived ES5 classes", () => {
+ function Base() {
+ this.is_base = true;
+ }
+
+ function Derived() {
+ this.is_derived = true;
+ }
+
+ Object.setPrototypeOf(Derived.prototype, Base.prototype);
+
+ const d = new Derived();
+ expect(d instanceof Derived).toBeTrue();
+ expect(d instanceof Base).toBeTrue();
+});
+
+test("issue #3930, instanceof on arrow function", () => {
+ function f() {}
+ const a = () => {};
+
+ expect(() => {
+ f instanceof a;
+ }).toThrow(TypeError);
+ expect(() => {
+ a instanceof a;
+ }).toThrow(TypeError);
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/logical-and.js b/Userland/Libraries/LibJS/Tests/operators/logical-and.js
new file mode 100644
index 0000000000..6d916a5b0e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/logical-and.js
@@ -0,0 +1,50 @@
+test("booleans", () => {
+ expect(true && true).toBeTrue();
+ expect(false && false).toBeFalse();
+ expect(true && false).toBeFalse();
+ expect(false && true).toBeFalse();
+});
+
+test("strings", () => {
+ expect("" && "").toBe("");
+ expect("" && false).toBe("");
+ expect("" && true).toBe("");
+ expect(false && "").toBeFalse();
+ expect(true && "").toBe("");
+ expect("foo" && "bar").toBe("bar");
+ expect("foo" && false).toBeFalse();
+ expect("foo" && true).toBeTrue();
+ expect(false && "bar").toBeFalse();
+ expect(true && "bar").toBe("bar");
+});
+
+test("numbers", () => {
+ expect(false && 1 === 2).toBeFalse();
+ expect(true && 1 === 2).toBeFalse();
+ expect(0 && false).toBe(0);
+ expect(0 && true).toBe(0);
+ expect(42 && false).toBeFalse();
+ expect(42 && true).toBeTrue();
+ expect(false && 0).toBeFalse();
+ expect(true && 0).toBe(0);
+ expect(false && 42).toBeFalse();
+ expect(true && 42).toBe(42);
+});
+
+test("objects", () => {
+ expect([] && false).toBeFalse();
+ expect([] && true).toBeTrue();
+ expect(false && []).toBeFalse();
+ expect(true && []).toHaveLength(0);
+});
+
+test("null & undefined", () => {
+ expect(null && false).toBeNull();
+ expect(null && true).toBeNull();
+ expect(false && null).toBeFalse();
+ expect(true && null).toBeNull();
+ expect(undefined && false).toBeUndefined();
+ expect(undefined && true).toBeUndefined();
+ expect(false && undefined).toBeFalse();
+ expect(true && undefined).toBeUndefined();
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/logical-expressions-short-circuit.js b/Userland/Libraries/LibJS/Tests/operators/logical-expressions-short-circuit.js
new file mode 100644
index 0000000000..28ad1b3d81
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/logical-expressions-short-circuit.js
@@ -0,0 +1,13 @@
+test("basic functionality", () => {
+ let foo = 1;
+ false && (foo = 2);
+ expect(foo).toBe(1);
+
+ foo = 1;
+ true || (foo = 2);
+ expect(foo).toBe(1);
+
+ foo = 1;
+ true ?? (foo = 2);
+ expect(foo).toBe(1);
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/logical-nullish-coalescing.js b/Userland/Libraries/LibJS/Tests/operators/logical-nullish-coalescing.js
new file mode 100644
index 0000000000..f8d778208f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/logical-nullish-coalescing.js
@@ -0,0 +1,50 @@
+test("booleans", () => {
+ expect(true ?? true).toBeTrue();
+ expect(false ?? false).toBeFalse();
+ expect(true ?? false).toBeTrue();
+ expect(false ?? true).toBeFalse();
+});
+
+test("strings", () => {
+ expect("" ?? "").toBe("");
+ expect("" ?? false).toBe("");
+ expect("" ?? true).toBe("");
+ expect(false ?? "").toBeFalse();
+ expect(true ?? "").toBeTrue();
+ expect("foo" ?? "bar").toBe("foo");
+ expect("foo" ?? false).toBe("foo");
+ expect("foo" ?? true).toBe("foo");
+ expect(false ?? "bar").toBeFalse();
+ expect(true ?? "bar").toBeTrue();
+});
+
+test("numbers", () => {
+ expect(false ?? 1 === 2).toBeFalse();
+ expect(true ?? 1 === 2).toBeTrue();
+ expect(0 ?? false).toBe(0);
+ expect(0 ?? true).toBe(0);
+ expect(42 ?? false).toBe(42);
+ expect(42 ?? true).toBe(42);
+ expect(false ?? 0).toBeFalse();
+ expect(true ?? 0).toBeTrue();
+ expect(false ?? 42).toBeFalse();
+ expect(true ?? 42).toBeTrue();
+});
+
+test("objects", () => {
+ expect([] ?? false).toHaveLength(0);
+ expect([] ?? true).toHaveLength(0);
+ expect(false ?? []).toBeFalse();
+ expect(true ?? []).toBeTrue();
+});
+
+test("null & undefined", () => {
+ expect(null ?? false).toBeFalse();
+ expect(null ?? true).toBeTrue();
+ expect(false ?? null).toBeFalse();
+ expect(true ?? null).toBeTrue();
+ expect(undefined ?? false).toBeFalse();
+ expect(undefined ?? true).toBeTrue();
+ expect(false ?? undefined).toBeFalse();
+ expect(true ?? undefined).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/logical-or.js b/Userland/Libraries/LibJS/Tests/operators/logical-or.js
new file mode 100644
index 0000000000..9d5d71c5a0
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/logical-or.js
@@ -0,0 +1,50 @@
+test("booleans", () => {
+ expect(true || true).toBeTrue();
+ expect(false || false).toBeFalse();
+ expect(true || false).toBeTrue();
+ expect(false || true).toBeTrue();
+});
+
+test("strings", () => {
+ expect("" || "").toBe("");
+ expect("" || false).toBeFalse();
+ expect("" || true).toBeTrue();
+ expect(false || "").toBe("");
+ expect(true || "").toBeTrue();
+ expect("foo" || "bar").toBe("foo");
+ expect("foo" || false).toBe("foo");
+ expect("foo" || true).toBe("foo");
+ expect(false || "bar").toBe("bar");
+ expect(true || "bar").toBeTrue();
+});
+
+test("numbers", () => {
+ expect(false || 1 === 2).toBeFalse();
+ expect(true || 1 === 2).toBeTrue();
+ expect(0 || false).toBeFalse();
+ expect(0 || true).toBeTrue();
+ expect(42 || false).toBe(42);
+ expect(42 || true).toBe(42);
+ expect(false || 0).toBe(0);
+ expect(true || 0).toBeTrue();
+ expect(false || 42).toBe(42);
+ expect(true || 42).toBeTrue();
+});
+
+test("objects", () => {
+ expect([] || false).toHaveLength(0);
+ expect([] || true).toHaveLength(0);
+ expect(false || []).toHaveLength(0);
+ expect(true || []).toBeTrue();
+});
+
+test("null & undefined", () => {
+ expect(null || false).toBeFalse();
+ expect(null || true).toBeTrue();
+ expect(false || null).toBeNull();
+ expect(true || null).toBeTrue();
+ expect(undefined || false).toBeFalse();
+ expect(undefined || true).toBeTrue();
+ expect(false || undefined).toBeUndefined();
+ expect(true || undefined).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/modulo-basic.js b/Userland/Libraries/LibJS/Tests/operators/modulo-basic.js
new file mode 100644
index 0000000000..22b60ea256
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/modulo-basic.js
@@ -0,0 +1,16 @@
+test("basic functionality", () => {
+ expect(10 % 3).toBe(1);
+ expect(10.5 % 2.5).toBe(0.5);
+ expect(-0.99 % 0.99).toBe(-0);
+
+ // Examples from MDN:
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators
+ expect(12 % 5).toBe(2);
+ expect(-1 % 2).toBe(-1);
+ expect(1 % -2).toBe(1);
+ expect(1 % 2).toBe(1);
+ expect(2 % 3).toBe(2);
+ expect(-4 % 2).toBe(-0);
+ expect(5.5 % 2).toBe(1.5);
+ expect(NaN % 2).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/ternary-basic.js b/Userland/Libraries/LibJS/Tests/operators/ternary-basic.js
new file mode 100644
index 0000000000..c0f68fdf83
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/ternary-basic.js
@@ -0,0 +1,20 @@
+test("basic functionality", () => {
+ const x = 1;
+
+ expect(x === 1 ? true : false).toBeTrue();
+ expect(x ? x : 0).toBe(x);
+ expect(1 < 2 ? true : false).toBeTrue();
+ expect(0 ? 1 : 1 ? 10 : 20).toBe(10);
+ expect(0 ? (1 ? 1 : 10) : 20).toBe(20);
+});
+
+test("object values", () => {
+ const o = {};
+ o.f = true;
+ expect(o.f ? true : false).toBeTrue();
+ expect(1 ? o.f : null).toBeTrue();
+});
+
+test("issue #4409, '?.' followed by decimal digit", () => {
+ expect("return false?.1:.2").toEvalTo(0.2);
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/typeof-basic.js b/Userland/Libraries/LibJS/Tests/operators/typeof-basic.js
new file mode 100644
index 0000000000..8a84b5a72b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/typeof-basic.js
@@ -0,0 +1,27 @@
+test("basic functionality", () => {
+ expect(typeof "foo").toBe("string");
+ expect(typeof (1 + 2)).toBe("number");
+ expect(typeof {}).toBe("object");
+ expect(typeof null).toBe("object");
+ expect(typeof undefined).toBe("undefined");
+ expect(typeof 1n).toBe("bigint");
+ expect(typeof Symbol()).toBe("symbol");
+ expect(typeof function () {}).toBe("function");
+
+ var iExist = 1;
+ expect(typeof iExist).toBe("number");
+ expect(typeof iDontExist).toBe("undefined");
+});
+
+test("typeof calls property getter", () => {
+ var calls = 0;
+ Object.defineProperty(globalThis, "foo", {
+ get() {
+ calls++;
+ return 10;
+ },
+ });
+
+ expect(typeof foo).toBe("number");
+ expect(calls).toBe(1);
+});
diff --git a/Userland/Libraries/LibJS/Tests/operators/void-basic.js b/Userland/Libraries/LibJS/Tests/operators/void-basic.js
new file mode 100644
index 0000000000..a23becae96
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/operators/void-basic.js
@@ -0,0 +1,14 @@
+test("basic functionality", () => {
+ expect(void "").toBeUndefined();
+ expect(void "foo").toBeUndefined();
+ expect(void 1).toBeUndefined();
+ expect(void 42).toBeUndefined();
+ expect(void true).toBeUndefined();
+ expect(void false).toBeUndefined();
+ expect(void null).toBeUndefined();
+ expect(void undefined).toBeUndefined();
+ expect(void function () {}).toBeUndefined();
+ expect(void (() => {})).toBeUndefined();
+ expect(void (() => "hello friends")()).toBeUndefined();
+ expect((() => void "hello friends")()).toBeUndefined();
+});
diff --git a/Userland/Libraries/LibJS/Tests/ordinary-to-primitive.js b/Userland/Libraries/LibJS/Tests/ordinary-to-primitive.js
new file mode 100644
index 0000000000..176beadb76
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/ordinary-to-primitive.js
@@ -0,0 +1,23 @@
+test("object with custom toString", () => {
+ const o = { toString: () => "foo" };
+ expect(o + "bar").toBe("foobar");
+ expect([o, "bar"].toString()).toBe("foo,bar");
+});
+
+test("object with uncallable toString and custom valueOf", () => {
+ const o = { toString: undefined, valueOf: () => "foo" };
+ expect(o + "bar").toBe("foobar");
+ expect([o, "bar"].toString()).toBe("foo,bar");
+});
+
+test("object with custom valueOf", () => {
+ const o = { valueOf: () => 42 };
+ expect(Number(o)).toBe(42);
+ expect(o + 1).toBe(43);
+});
+
+test("object with uncallable valueOf and custom toString", () => {
+ const o = { valueOf: undefined, toString: () => "42" };
+ expect(Number(o)).toBe(42);
+ expect(o + 1).toBe("421");
+});
diff --git a/Userland/Libraries/LibJS/Tests/parseInt.js b/Userland/Libraries/LibJS/Tests/parseInt.js
new file mode 100644
index 0000000000..e4115affd0
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/parseInt.js
@@ -0,0 +1,59 @@
+test("basic parseInt() functionality", () => {
+ expect(parseInt("0")).toBe(0);
+ expect(parseInt("100")).toBe(100);
+ expect(parseInt("1000", 16)).toBe(4096);
+ expect(parseInt("0xF", 16)).toBe(15);
+ expect(parseInt("F", 16)).toBe(15);
+ expect(parseInt("17", 8)).toBe(15);
+ expect(parseInt(021, 8)).toBe(15);
+ expect(parseInt("015", 10)).toBe(15);
+ expect(parseInt(15.99, 10)).toBe(15);
+ expect(parseInt("15,123", 10)).toBe(15);
+ expect(parseInt("FXX123", 16)).toBe(15);
+ expect(parseInt("1111", 2)).toBe(15);
+ expect(parseInt("15 * 3", 10)).toBe(15);
+ expect(parseInt("15e2", 10)).toBe(15);
+ expect(parseInt("15px", 10)).toBe(15);
+ expect(parseInt("12", 13)).toBe(15);
+ expect(parseInt("Hello", 8)).toBeNaN();
+ expect(parseInt("546", 2)).toBeNaN();
+ expect(parseInt("-F", 16)).toBe(-15);
+ expect(parseInt("-0F", 16)).toBe(-15);
+ expect(parseInt("-0XF", 16)).toBe(-15);
+ expect(parseInt(-15.1, 10)).toBe(-15);
+ expect(parseInt("-17", 8)).toBe(-15);
+ expect(parseInt("-15", 10)).toBe(-15);
+ expect(parseInt("-1111", 2)).toBe(-15);
+ expect(parseInt("-15e1", 10)).toBe(-15);
+ expect(parseInt("-12", 13)).toBe(-15);
+ expect(parseInt(4.7, 10)).toBe(4);
+ expect(parseInt("0e0", 16)).toBe(224);
+ expect(parseInt("123_456")).toBe(123);
+
+ // FIXME: expect(parseInt(4.7 * 1e22, 10)).toBe(4);
+ // FIXME: expect(parseInt(0.00000000000434, 10)).toBe(4);
+ // FIXME: expect(parseInt(0.0000001,11)).toBe(1);
+ // FIXME: expect(parseInt(0.000000124,10)).toBe(1);
+ // FIXME: expect(parseInt(1e-7,10)).toBe(1);
+ // FIXME: expect(parseInt(1000000000000100000000,10)).toBe(1);
+ // FIXME: expect(parseInt(123000000000010000000000,10)).toBe(1);
+ // FIXME: expect(parseInt(1e+21,10)).toBe(1);
+ // FIXME: expect(parseInt('900719925474099267n')).toBe(900719925474099300)
+});
+
+test("parseInt() radix is coerced to a number", () => {
+ const obj = {
+ valueOf() {
+ return 8;
+ },
+ };
+ expect(parseInt("11", obj)).toBe(9);
+ obj.valueOf = function () {
+ return 1;
+ };
+ expect(parseInt("11", obj)).toBeNaN();
+ obj.valueOf = function () {
+ return Infinity;
+ };
+ expect(parseInt("11", obj)).toBe(11);
+});
diff --git a/Userland/Libraries/LibJS/Tests/parser-declaration-in-single-statement-context.js b/Userland/Libraries/LibJS/Tests/parser-declaration-in-single-statement-context.js
new file mode 100644
index 0000000000..5e263427bc
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/parser-declaration-in-single-statement-context.js
@@ -0,0 +1,6 @@
+test("Declaration in single-statement context is a syntax error", () => {
+ expect("if (0) const foo = 1").not.toEval();
+ expect("while (0) function foo() {}").not.toEval();
+ expect("for (var 0;;) class foo() {}").not.toEval();
+ expect("do let foo = 1 while (0)").not.toEval();
+});
diff --git a/Userland/Libraries/LibJS/Tests/parser-line-terminators.js b/Userland/Libraries/LibJS/Tests/parser-line-terminators.js
new file mode 100644
index 0000000000..ab88b0eb44
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/parser-line-terminators.js
@@ -0,0 +1,66 @@
+/*
+These tests deliberately produce syntax errors to check what line the parser thinks we're on.
+Note that line numbers are higher than you might expect as the parsed code is:
+
+function anonymous(
+) {
+<code>
+}
+
+⚠ PLEASE MAKE SURE TO NOT LET YOUR EDITOR REMOVE THE LS/PS LINE TERMINATORS!
+*/
+
+test("LINE FEED is a line terminator", () => {
+ expect(() => {
+ Function("\n\n@");
+ }).toThrowWithMessage(SyntaxError, "line: 5, column: 1");
+});
+
+test("CARRIAGE RETURN is a line terminator", () => {
+ expect(() => {
+ Function("\r\r@");
+ }).toThrowWithMessage(SyntaxError, "line: 5, column: 1");
+});
+
+test("LINE SEPARATOR is a line terminator", () => {
+ expect(() => {
+ Function("

@");
+ }).toThrowWithMessage(SyntaxError, "line: 5, column: 1");
+});
+
+test("PARAGRAPH SEPARATOR is a line terminator", () => {
+ expect(() => {
+ Function("

@");
+ }).toThrowWithMessage(SyntaxError, "line: 5, column: 1");
+});
+
+test("CR LF is counted as only one line terminator", () => {
+ expect(() => {
+ Function("\r\n\r\n@");
+ }).toThrowWithMessage(SyntaxError, "line: 5, column: 1");
+});
+
+test("LF/CR are not allowed in string literal", () => {
+ expect(() => {
+ Function(`"
+ "`);
+ }).toThrowWithMessage(SyntaxError, "Unexpected token UnterminatedStringLiteral");
+});
+
+test("LS/PS are allowed in string literal", () => {
+ expect(`"
"`).toEval();
+ expect(`"
"`).toEval();
+});
+
+test("line terminators can be mixed (but please don't)", () => {
+ expect(() => {
+ Function("\r
\r\n
\n\r@");
+ }).toThrowWithMessage(SyntaxError, "line: 9, column: 1");
+});
+
+test("all line terminators are valid for line continuations", () => {
+ expect(Function('return "a\\\nb"')()).toBe("ab");
+ expect(Function('return "a\\\rb"')()).toBe("ab");
+ expect(Function('return "a\\
b"')()).toBe("ab");
+ expect(Function('return "a\\
b"')()).toBe("ab");
+});
diff --git a/Userland/Libraries/LibJS/Tests/parser-unary-associativity.js b/Userland/Libraries/LibJS/Tests/parser-unary-associativity.js
new file mode 100644
index 0000000000..25763eda6c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/parser-unary-associativity.js
@@ -0,0 +1,14 @@
+test("basic functionality", () => {
+ const o = {};
+ o.a = 1;
+
+ expect(o.a === 1).toBeTrue();
+ expect(!o.a === false).toBeTrue();
+ expect(!o.a === !o.a).toBeTrue();
+ expect(~o.a === ~o.a).toBeTrue();
+ expect(+o.a === +o.a).toBeTrue();
+ expect(-o.a === -o.a).toBeTrue();
+
+ expect((typeof "x" === "string") === true).toBeTrue();
+ expect(!(typeof "x" === "string") === false).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/program-strict-mode.js b/Userland/Libraries/LibJS/Tests/program-strict-mode.js
new file mode 100644
index 0000000000..d8f8941d0f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/program-strict-mode.js
@@ -0,0 +1,18 @@
+"use strict";
+
+test("basic functionality", () => {
+ expect(isStrictMode()).toBeTrue();
+
+ (function () {
+ expect(isStrictMode()).toBeTrue();
+ })();
+
+ (() => {
+ expect(isStrictMode()).toBeTrue();
+ })();
+
+ (() => {
+ "use strict";
+ expect(isStrictMode()).toBeTrue();
+ })();
+});
diff --git a/Userland/Libraries/LibJS/Tests/return.js b/Userland/Libraries/LibJS/Tests/return.js
new file mode 100644
index 0000000000..2c5b8683f6
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/return.js
@@ -0,0 +1,53 @@
+describe("returning from loops", () => {
+ test("returning from while loops", () => {
+ function foo() {
+ while (true) {
+ return 10;
+ }
+ }
+
+ expect(foo()).toBe(10);
+ });
+
+ test("returning from do-while loops", () => {
+ function foo() {
+ do {
+ return 10;
+ } while (true);
+ }
+
+ expect(foo()).toBe(10);
+ });
+
+ test("returning from for loops", () => {
+ function foo() {
+ for (let i = 0; i < 5; i++) {
+ return 10;
+ }
+ }
+
+ expect(foo()).toBe(10);
+ });
+
+ test("returning from for-in loops", () => {
+ function foo() {
+ const o = { a: 1, b: 2 };
+ for (let a in o) {
+ return 10;
+ }
+ }
+
+ expect(foo()).toBe(10);
+ });
+
+ test("returning from for-of loops", () => {
+ function foo() {
+ const o = [1, 2, 3];
+ for (let a of o) {
+ return 10;
+ }
+ }
+
+ expect(foo()).toBe(10);
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/runtime-error-call-stack-size.js b/Userland/Libraries/LibJS/Tests/runtime-error-call-stack-size.js
new file mode 100644
index 0000000000..177bb2d675
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/runtime-error-call-stack-size.js
@@ -0,0 +1,21 @@
+test("infinite recursion", () => {
+ function infiniteRecursion() {
+ infiniteRecursion();
+ }
+
+ try {
+ infiniteRecursion();
+ } catch (e) {
+ expect(e).toBeInstanceOf(Error);
+ expect(e.name).toBe("RuntimeError");
+ expect(e.message).toBe("Call stack size limit exceeded");
+ }
+
+ expect(() => {
+ JSON.stringify({}, () => ({ foo: "bar" }));
+ }).toThrowWithMessage(Error, "Call stack size limit exceeded");
+
+ expect(() => {
+ new Proxy({}, { get: (_, __, p) => p.foo }).foo;
+ }).toThrowWithMessage(Error, "Call stack size limit exceeded");
+});
diff --git a/Userland/Libraries/LibJS/Tests/strict-mode-blocks.js b/Userland/Libraries/LibJS/Tests/strict-mode-blocks.js
new file mode 100644
index 0000000000..35d0264664
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/strict-mode-blocks.js
@@ -0,0 +1,25 @@
+test("Issue #3641, strict mode should be function- or program-level, not block-level", () => {
+ function func() {
+ expect(isStrictMode()).toBeFalse();
+
+ // prettier-ignore
+ {
+ "use strict";
+ expect(isStrictMode()).toBeFalse();
+ }
+
+ // prettier-ignore
+ if (true) {
+ "use strict";
+ expect(isStrictMode()).toBeFalse();
+ }
+
+ // prettier-ignore
+ do {
+ "use strict";
+ expect(isStrictMode()).toBeFalse();
+ } while (false);
+ }
+
+ func();
+});
diff --git a/Userland/Libraries/LibJS/Tests/strict-mode-errors.js b/Userland/Libraries/LibJS/Tests/strict-mode-errors.js
new file mode 100644
index 0000000000..ada52e359f
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/strict-mode-errors.js
@@ -0,0 +1,15 @@
+"use strict";
+
+test("basic functionality", () => {
+ [true, false, "foo", 123].forEach(primitive => {
+ expect(() => {
+ primitive.foo = "bar";
+ }).toThrowWithMessage(TypeError, "Cannot assign property foo to primitive value");
+ expect(() => {
+ primitive[Symbol.hasInstance] = 123;
+ }).toThrowWithMessage(
+ TypeError,
+ "Cannot assign property Symbol(Symbol.hasInstance) to primitive value"
+ );
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/string-escapes.js b/Userland/Libraries/LibJS/Tests/string-escapes.js
new file mode 100644
index 0000000000..8a0920361e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/string-escapes.js
@@ -0,0 +1,63 @@
+test("hex escapes", () => {
+ expect("\x55").toBe("U");
+ expect("X55").toBe("X55");
+ expect(`\x55`).toBe("U");
+ expect(`\X55`).toBe("X55");
+ expect("\xff").toBe(String.fromCharCode(0xff));
+ expect("'\\x'").not.toEval();
+ expect("'\\x1'").not.toEval();
+ expect("'\\xz'").not.toEval();
+ expect("'\\xzz'").not.toEval();
+ expect("'\\x🐞'").not.toEval();
+});
+
+test("unicode escapes", () => {
+ expect("\u26a0").toBe("⚠");
+ expect(`\u26a0`).toBe("⚠");
+ expect("\u{1f41e}").toBe("🐞");
+ expect(`\u{1f41e}`).toBe("🐞");
+ expect("\u00ff").toBe(String.fromCharCode(0xff));
+ expect("'\\u'").not.toEval();
+ expect("'\\u1'").not.toEval();
+ expect("'\\uf'").not.toEval();
+ expect("'\\u123'").not.toEval();
+ expect("'\\u123z'").not.toEval();
+ expect("'\\uz'").not.toEval();
+ expect("'\\uzz'").not.toEval();
+ expect("'\\uzzzz'").not.toEval();
+ expect("'\\u{'").not.toEval();
+ expect("'\\u{}'").not.toEval();
+ expect("'\\u{z}'").not.toEval();
+ expect("'\\u🐞'").not.toEval();
+});
+
+describe("octal escapes", () => {
+ test("basic functionality", () => {
+ expect("\1").toBe("\u0001");
+ expect("\2").toBe("\u0002");
+ expect("\3").toBe("\u0003");
+ expect("\4").toBe("\u0004");
+ expect("\5").toBe("\u0005");
+ expect("\6").toBe("\u0006");
+ expect("\7").toBe("\u0007");
+ // prettier-ignore
+ expect("\8").toBe("8");
+ // prettier-ignore
+ expect("\9").toBe("9");
+ expect("\128").toBe("\n8");
+ expect("\141bc").toBe("abc");
+ expect("f\157o\142a\162").toBe("foobar");
+ expect("\123\145\162\145\156\151\164\171\117\123").toBe("SerenityOS");
+ });
+
+ test("syntax error in template literal", () => {
+ expect("`\\123`").not.toEval();
+ });
+
+ test("syntax error in strict mode", () => {
+ expect("'use strict'; '\\123'").not.toEval();
+ expect('"use strict"; "\\123"').not.toEval();
+ // Special case, string literal precedes use strict directive
+ expect("'\\123'; somethingElse; 'use strict'").not.toEval();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/string-spread.js b/Userland/Libraries/LibJS/Tests/string-spread.js
new file mode 100644
index 0000000000..507bab7710
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/string-spread.js
@@ -0,0 +1,25 @@
+function testArray(arr) {
+ return arr.length === 4 && arr[0] === "a" && arr[1] === "b" && arr[2] === "c" && arr[3] === "d";
+}
+
+test("spreading string literal", () => {
+ expect(["a", ..."bc", "d"]).toEqual(["a", "b", "c", "d"]);
+});
+
+test("spreading string variable", () => {
+ const s = "bc";
+ expect(["a", ...s, "d"]).toEqual(["a", "b", "c", "d"]);
+});
+
+test("spreading string in object", () => {
+ const obj = { a: "bc" };
+ expect(["a", ...obj.a, "d"]).toEqual(["a", "b", "c", "d"]);
+});
+
+test("spreading empty string", () => {
+ expect([..."", "a", ..."bc", ..."", "d", ...""]).toEqual(["a", "b", "c", "d"]);
+});
+
+test("spreading string objects", () => {
+ expect([..."", ...[...new String("abc")], "d"]).toEqual(["a", "b", "c", "d"]);
+});
diff --git a/Userland/Libraries/LibJS/Tests/switch-basic.js b/Userland/Libraries/LibJS/Tests/switch-basic.js
new file mode 100644
index 0000000000..36210dda17
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/switch-basic.js
@@ -0,0 +1,78 @@
+describe("basic switch tests", () => {
+ test("string case does not match number target", () => {
+ switch (1 + 2) {
+ case "3":
+ expect().fail();
+ case 3:
+ return;
+ case 5:
+ case 1:
+ break;
+ default:
+ break;
+ }
+
+ expect().fail();
+ });
+
+ test("string concatenation in target", () => {
+ var a = "foo";
+
+ switch (a + "bar") {
+ case 1:
+ expect().fail();
+ case "foobar":
+ case 2:
+ return;
+ }
+ expect().fail();
+ });
+
+ test("default", () => {
+ switch (100) {
+ default:
+ return;
+ }
+
+ expect().fail();
+ });
+
+ test("return from switch statement", () => {
+ function foo(value) {
+ switch (value) {
+ case 42:
+ return "return from 'case 42'";
+ default:
+ return "return from 'default'";
+ }
+ }
+ expect(foo(42)).toBe("return from 'case 42'");
+ expect(foo(43)).toBe("return from 'default'");
+ });
+
+ test("continue from switch statement", () => {
+ let i = 0;
+ for (; i < 5; ++i) {
+ switch (i) {
+ case 0:
+ continue;
+ expect().fail();
+ case 0:
+ expect().fail();
+ default:
+ continue;
+ expect().fail();
+ }
+ expect().fail();
+ }
+ expect(i).toBe(5);
+ });
+});
+
+describe("errors", () => {
+ test("syntax errors", () => {
+ expect("switch () {}").not.toEval();
+ expect("switch (foo) { bar }").not.toEval();
+ expect("switch (foo) { default: default: }").not.toEval();
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/switch-break.js b/Userland/Libraries/LibJS/Tests/switch-break.js
new file mode 100644
index 0000000000..c8f30aba7e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/switch-break.js
@@ -0,0 +1,20 @@
+test("basic functionality", () => {
+ let i = 0;
+ let three;
+ let five;
+
+ for (; i < 9; ) {
+ switch (i) {
+ case 3:
+ three = i;
+ break;
+ case 5:
+ five = i;
+ break;
+ }
+ ++i;
+ }
+
+ expect(three).toBe(3);
+ expect(five).toBe(5);
+});
diff --git a/Userland/Libraries/LibJS/Tests/tagged-template-literals.js b/Userland/Libraries/LibJS/Tests/tagged-template-literals.js
new file mode 100644
index 0000000000..8372333ff6
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/tagged-template-literals.js
@@ -0,0 +1,108 @@
+describe("tagged template literal errors", () => {
+ test("undefined variables in template expression throw a ReferenceError", () => {
+ expect(() => {
+ foo`bar${baz}`;
+ }).toThrowWithMessage(ReferenceError, "'foo' is not defined");
+
+ expect(() => {
+ function foo() {}
+ foo`bar${baz}`;
+ }).toThrowWithMessage(ReferenceError, "'baz' is not defined");
+ });
+
+ test("cannot tag a non-function", () => {
+ expect(() => {
+ undefined``;
+ }).toThrowWithMessage(TypeError, "undefined is not a function");
+ });
+});
+
+describe("tagged template literal functionality", () => {
+ test("empty template tag", () => {
+ function test1(strings) {
+ expect(strings).toBeInstanceOf(Array);
+ expect(strings).toHaveLength(1);
+ expect(strings[0]).toBe("");
+ return 42;
+ }
+ expect(test1``).toBe(42);
+ });
+
+ test("tagging a template literal", () => {
+ function test2(s) {
+ return function (strings) {
+ expect(strings).toBeInstanceOf(Array);
+ expect(strings).toHaveLength(1);
+ expect(strings[0]).toBe("bar");
+ return s + strings[0];
+ };
+ }
+ expect(test2("foo")`bar`).toBe("foobar");
+ });
+
+ test("tagging an object function key", () => {
+ var test3 = {
+ foo(strings, p1) {
+ expect(strings).toBeInstanceOf(Array);
+ expect(strings).toHaveLength(2);
+ expect(strings[0]).toBe("");
+ expect(strings[1]).toBe("");
+ expect(p1).toBe("bar");
+ },
+ };
+ test3.foo`${"bar"}`;
+ });
+
+ test("tagging with a variable in a template expression", () => {
+ function test4(strings, p1) {
+ expect(strings).toBeInstanceOf(Array);
+ expect(strings).toHaveLength(2);
+ expect(strings[0]).toBe("foo");
+ expect(strings[1]).toBe("");
+ expect(p1).toBe(42);
+ }
+ var bar = 42;
+ test4`foo${bar}`;
+ });
+
+ test("template tag result of another template tag", () => {
+ function test5(strings, p1, p2) {
+ expect(strings).toBeInstanceOf(Array);
+ expect(strings).toHaveLength(3);
+ expect(strings[0]).toBe("foo");
+ expect(strings[1]).toBe("baz");
+ expect(strings[2]).toBe("");
+ expect(p1).toBe(42);
+ expect(p2).toBe("qux");
+ return (strings, value) => `${value}${strings[0]}`;
+ }
+ var bar = 42;
+ expect(test5`foo${bar}baz${"qux"}``test${123}`).toBe("123test");
+ });
+
+ test("general test", () => {
+ function review(strings, name, rating) {
+ return `${strings[0]}**${name}**${strings[1]}_${rating}_${strings[2]}`;
+ }
+ var name = "SerenityOS";
+ var rating = "great";
+ expect(review`${name} is a ${rating} project!`).toBe(
+ "**SerenityOS** is a _great_ project!"
+ );
+ });
+
+ test("template object structure", () => {
+ const getTemplateObject = (...rest) => rest;
+ const getRawTemplateStrings = arr => arr.raw;
+
+ let o = getTemplateObject`foo\nbar`;
+ expect(Object.getOwnPropertyNames(o[0])).toContain("raw");
+
+ let raw = getRawTemplateStrings`foo${1 + 3}\nbar`;
+ expect(Object.getOwnPropertyNames(raw)).not.toContain("raw");
+ expect(raw).toHaveLength(2);
+ expect(raw[0]).toBe("foo");
+ expect(raw[1]).toHaveLength(5);
+ expect(raw[1]).toBe("\\nbar");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/template-literals.js b/Userland/Libraries/LibJS/Tests/template-literals.js
new file mode 100644
index 0000000000..07d81509a8
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/template-literals.js
@@ -0,0 +1,65 @@
+test("plain literals with expression-like characters", () => {
+ expect(`foo`).toBe("foo");
+ expect(`foo{`).toBe("foo{");
+ expect(`foo}`).toBe("foo}");
+ expect(`foo$`).toBe("foo$");
+});
+
+test("plain literals with escaped special characters", () => {
+ expect(`foo\``).toBe("foo`");
+ expect(`foo\$`).toBe("foo$");
+ expect(`foo \${"bar"}`).toBe('foo ${"bar"}');
+});
+
+test("literals in expressions", () => {
+ expect(`foo ${undefined}`).toBe("foo undefined");
+ expect(`foo ${null}`).toBe("foo null");
+ expect(`foo ${5}`).toBe("foo 5");
+ expect(`foo ${true}`).toBe("foo true");
+ expect(`foo ${"bar"}`).toBe("foo bar");
+});
+
+test("objects in expressions", () => {
+ expect(`foo ${{}}`).toBe("foo [object Object]");
+ expect(`foo ${{ bar: { baz: "qux" } }}`).toBe("foo [object Object]");
+});
+
+test("expressions at beginning of template literal", () => {
+ expect(`${"foo"} bar baz`).toBe("foo bar baz");
+ expect(`${"foo bar baz"}`).toBe("foo bar baz");
+});
+
+test("multiple template literals", () => {
+ expect(`foo ${"bar"} ${"baz"}`).toBe("foo bar baz");
+});
+
+test("variables in expressions", () => {
+ let a = 27;
+ expect(`${a}`).toBe("27");
+ expect(`foo ${a}`).toBe("foo 27");
+ expect(`foo ${a ? "bar" : "baz"}`).toBe("foo bar");
+ expect(`foo ${(() => a)()}`).toBe("foo 27");
+});
+
+test("template literals in expressions", () => {
+ expect(`foo ${`bar`}`).toBe("foo bar");
+ expect(`${`${`${`${"foo"}`} bar`}`}`).toBe("foo bar");
+});
+
+test("newline literals (not characters)", () => {
+ expect(
+ `foo
+ bar`
+ ).toBe("foo\n bar");
+});
+
+test("line continuation in literals (not characters)", () => {
+ expect(
+ `foo\
+ bar`
+ ).toBe("foo bar");
+});
+
+test("reference error from expressions", () => {
+ expect(() => `${b}`).toThrowWithMessage(ReferenceError, "'b' is not defined");
+});
diff --git a/Userland/Libraries/LibJS/Tests/test-common-tests.js b/Userland/Libraries/LibJS/Tests/test-common-tests.js
new file mode 100644
index 0000000000..a857d327bb
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/test-common-tests.js
@@ -0,0 +1,417 @@
+test("toBe", () => {
+ expect(null).toBe(null);
+ expect(undefined).toBe(undefined);
+ expect(null).not.toBe(undefined);
+
+ expect(1).toBe(1);
+ expect(1).not.toBe(2);
+
+ expect("1").toBe("1");
+ expect("1").not.toBe("2");
+
+ expect(true).toBeTrue();
+ expect(true).not.toBeFalse();
+
+ expect({}).not.toBe({});
+ expect([]).not.toBe([]);
+
+ function foo() {}
+ expect(foo).toBe(foo);
+ expect(function () {}).not.toBe(function () {});
+
+ let s = Symbol("foo");
+ expect(s).toBe(s);
+ expect(Symbol("foo")).not.toBe(Symbol("foo"));
+
+ expect(1n).toBe(1n);
+ expect(1n).not.toBe(1);
+});
+
+test("toBeCloseTo", () => {
+ expect(1).toBeCloseTo(1);
+ expect(1).not.toBeCloseTo(1.1);
+ expect(1).not.toBeCloseTo(1.01);
+ expect(1).not.toBeCloseTo(1.001);
+ expect(1).not.toBeCloseTo(1.0001);
+ expect(1).not.toBeCloseTo(1.00001);
+ expect(1).toBeCloseTo(1.000001);
+
+ [
+ ["foo", 1],
+ [1, "foo"],
+ [1n, 1],
+ ].forEach(arr => {
+ expect(() => {
+ expect(arr[0]).toBeCloseTo(arr[1]);
+ }).toThrow(ExpectationError);
+ });
+});
+
+test("toHaveLength", () => {
+ expect([]).toHaveLength(0);
+ expect([]).not.toHaveLength(1);
+ expect([1]).toHaveLength(1);
+ expect({ length: 1 }).toHaveLength(1);
+
+ expect("a\
+b").toHaveLength(2);
+
+ expect(() => {
+ expect(1).toHaveLength();
+ }).toThrow(ExpectationError);
+});
+
+test("toHaveProperty", () => {
+ expect([]).toHaveProperty("length");
+ expect([]).toHaveProperty("length", 0);
+ expect([1]).not.toHaveProperty("length", 0);
+ expect({ foo: "bar" }).toHaveProperty("foo");
+ expect({ foo: "bar" }).toHaveProperty("foo", "bar");
+
+ expect({ foo: { bar: "baz" } }).toHaveProperty(["foo", "bar"]);
+ expect({ foo: { bar: "baz" } }).toHaveProperty(["foo", "bar"], "baz");
+ expect({ foo: { bar: "baz" } }).toHaveProperty("foo.bar");
+ expect({ foo: { bar: "baz" } }).toHaveProperty("foo.bar", "baz");
+
+ expect({ foo: { bar: "baz" } }).toHaveProperty(["foo", "bar"]);
+ expect({ foo: { bar: "baz" } }).toHaveProperty(["foo", "bar"], "baz");
+ expect({ foo: { bar: "baz" } }).not.toHaveProperty(["foo", "baz"]);
+ expect({ foo: { bar: "baz" } }).not.toHaveProperty(["foo", "baz"], "qux");
+ expect({ foo: { bar: "baz" } }).not.toHaveProperty("foo.baz");
+ expect({ foo: { bar: "baz" } }).not.toHaveProperty("foo.baz", "qux");
+});
+
+test("toBeDefined", () => {
+ expect(1).toBeDefined();
+ expect(true).toBeDefined();
+ expect(false).toBeDefined();
+ expect({}).toBeDefined();
+ expect([]).toBeDefined();
+ expect("a").toBeDefined();
+ expect(null).toBeDefined();
+ expect(undefined).not.toBeDefined();
+});
+
+test("toBeInstanceOf", () => {
+ expect(new Error()).toBeInstanceOf(Error);
+ expect(Error).not.toBeInstanceOf(Error);
+
+ class Parent {}
+ class Child extends Parent {}
+
+ expect(new Child()).toBeInstanceOf(Child);
+ expect(new Child()).toBeInstanceOf(Parent);
+ expect(new Parent()).toBeInstanceOf(Parent);
+ expect(new Parent()).not.toBeInstanceOf(Child);
+});
+
+test("toBeNull", () => {
+ expect(null).toBeNull();
+ expect(undefined).not.toBeNull();
+ expect(5).not.toBeNull();
+});
+
+test("toBeUndefined", () => {
+ expect(undefined).toBeUndefined();
+ expect(null).not.toBeUndefined();
+ expect().toBeUndefined();
+ expect(5).not.toBeUndefined();
+});
+
+test("toBeNaN", () => {
+ expect(NaN).toBeNaN();
+ expect(5).not.toBeNaN();
+});
+
+test("toBeTrue", () => {
+ expect(true).toBeTrue();
+ expect(false).not.toBeTrue();
+ expect(null).not.toBeTrue();
+ expect(undefined).not.toBeTrue();
+ expect(0).not.toBeTrue();
+});
+
+test("toBeFalse", () => {
+ expect(true).not.toBeFalse();
+ expect(false).toBeFalse();
+ expect(null).not.toBeFalse();
+ expect(undefined).not.toBeFalse();
+ expect(0).not.toBeFalse();
+});
+
+test("toBeLessThan", () => {
+ expect(0).toBeLessThan(1);
+ expect(0).toBeLessThan(0.1);
+ expect(1).not.toBeLessThan(1);
+ expect(1).not.toBeLessThan(0);
+
+ expect(0n).toBeLessThan(1n);
+ expect(1n).not.toBeLessThan(1n);
+ expect(1n).not.toBeLessThan(0n);
+
+ [
+ ["foo", 0],
+ [0, "foo"],
+ [0, 0n],
+ [0n, 0],
+ ].forEach(arr => {
+ expect(() => {
+ expect(arr[0]).toBeLessThan(arr[1]);
+ }).toThrow(ExpectationError);
+ });
+});
+
+test("toBeLessThanOrEqual", () => {
+ expect(0).toBeLessThanOrEqual(1);
+ expect(0).toBeLessThanOrEqual(0.1);
+ expect(1).toBeLessThanOrEqual(1);
+ expect(1).not.toBeLessThanOrEqual(0);
+
+ expect(0n).toBeLessThanOrEqual(1n);
+ expect(1n).toBeLessThanOrEqual(1n);
+ expect(1n).not.toBeLessThanOrEqual(0n);
+
+ [
+ ["foo", 0],
+ [0, "foo"],
+ [0, 0n],
+ [0n, 0],
+ ].forEach(arr => {
+ expect(() => {
+ expect(arr[0]).toBeLessThanOrEqual(arr[1]);
+ }).toThrow(ExpectationError);
+ });
+});
+
+test("toBeGreaterThan", () => {
+ expect(1).toBeGreaterThan(0);
+ expect(0.1).toBeGreaterThan(0);
+ expect(1).not.toBeGreaterThan(1);
+ expect(0).not.toBeGreaterThan(1);
+
+ expect(1n).toBeGreaterThan(0n);
+ expect(1n).not.toBeGreaterThan(1n);
+ expect(0n).not.toBeGreaterThan(1n);
+
+ [
+ ["foo", 0],
+ [0, "foo"],
+ [0, 0n],
+ [0n, 0],
+ ].forEach(arr => {
+ expect(() => {
+ expect(arr[0]).toBeGreaterThan(arr[1]);
+ }).toThrow(ExpectationError);
+ });
+});
+
+test("toBeGreaterThanOrEqual", () => {
+ expect(1).toBeGreaterThanOrEqual(0);
+ expect(0.1).toBeGreaterThanOrEqual(0);
+ expect(1).toBeGreaterThanOrEqual(1);
+ expect(0).not.toBeGreaterThanOrEqual(1);
+
+ expect(1n).toBeGreaterThanOrEqual(0n);
+ expect(1n).toBeGreaterThanOrEqual(1n);
+ expect(0n).not.toBeGreaterThanOrEqual(1n);
+
+ [
+ ["foo", 0],
+ [0, "foo"],
+ [0, 0n],
+ [0n, 0],
+ ].forEach(arr => {
+ expect(() => {
+ expect(arr[0]).toBeGreaterThanOrEqual(arr[1]);
+ }).toThrow(ExpectationError);
+ });
+});
+
+test("toContain", () => {
+ expect([1, 2, 3]).toContain(1);
+ expect([1, 2, 3]).toContain(2);
+ expect([1, 2, 3]).toContain(3);
+ expect([{ foo: 1 }]).not.toContain({ foo: 1 });
+});
+
+test("toContainEqual", () => {
+ expect([1, 2, 3]).toContainEqual(1);
+ expect([1, 2, 3]).toContainEqual(2);
+ expect([1, 2, 3]).toContainEqual(3);
+ expect([{ foo: 1 }]).toContainEqual({ foo: 1 });
+});
+
+test("toEqual", () => {
+ expect(undefined).toEqual(undefined);
+ expect(null).toEqual(null);
+ expect(undefined).not.toEqual(null);
+ expect(null).not.toEqual(undefined);
+ expect(NaN).toEqual(NaN);
+
+ expect(1).toEqual(1);
+ expect("abcd").toEqual("abcd");
+
+ let s = Symbol();
+ expect(s).toEqual(s);
+ expect(Symbol()).not.toEqual(Symbol());
+ expect(Symbol.for("foo")).toEqual(Symbol.for("foo"));
+
+ expect({ foo: 1, bar: { baz: [1, 2, 3] } }).toEqual({ foo: 1, bar: { baz: [1, 2, 3] } });
+ expect([1, 2, { foo: 1 }, [3, [4, 5]]]).toEqual([1, 2, { foo: 1 }, [3, [4, 5]]]);
+
+ function foo() {}
+ expect(foo).toEqual(foo);
+ expect(function () {}).not.toEqual(function () {});
+});
+
+test("toThrow", () => {
+ expect(() => {}).not.toThrow();
+ expect(() => {}).not.toThrow("foo");
+ expect(() => {}).not.toThrow(TypeError);
+ expect(() => {}).not.toThrow(new TypeError("foo"));
+
+ let thrower = () => {
+ throw new TypeError("foo bar");
+ };
+
+ expect(thrower).toThrow();
+ expect(thrower).toThrow(TypeError);
+ expect(thrower).toThrow("o ba");
+ expect(thrower).toThrow("foo bar");
+ expect(thrower).not.toThrow("baz");
+ expect(thrower).not.toThrow(ReferenceError);
+ expect(thrower).toThrow(new TypeError("foo bar"));
+ expect(thrower).not.toThrow(new TypeError("o ba"));
+ expect(thrower).toThrow(new ReferenceError("foo bar"));
+ expect(thrower).toThrow({ message: "foo bar" });
+});
+
+test("pass", () => {
+ expect().pass();
+ expect({}).pass();
+});
+
+test("fail", () => {
+ // FIXME: Doesn't really make sense; this is a great candidate
+ // for expect.assertions()
+ try {
+ expect().fail();
+ } catch (e) {
+ expect(e.name).toBe("ExpectationError");
+ }
+});
+
+test("toThrowWithMessage", () => {
+ let incorrectUsages = [
+ [1, undefined, undefined],
+ [() => {}, undefined, undefined],
+ [() => {}, function () {}, undefined],
+ [() => {}, undefined, "test"],
+ ];
+
+ incorrectUsages.forEach(arr => {
+ expect(() => {
+ expect(arr[0]).toThrowWithMessage(arr[1], arr[2]);
+ }).toThrow();
+ });
+
+ let thrower = () => {
+ throw new TypeError("foo bar");
+ };
+
+ expect(thrower).toThrowWithMessage(TypeError, "foo bar");
+ expect(thrower).toThrowWithMessage(TypeError, "foo");
+ expect(thrower).toThrowWithMessage(TypeError, "o ba");
+ expect(thrower).not.toThrowWithMessage(ReferenceError, "foo bar");
+ expect(thrower).not.toThrowWithMessage(TypeError, "foo baz");
+});
+
+test("toEval", () => {
+ expect("let a = 1").toEval();
+ expect("a < 1").toEval();
+ expect("&&*^%#%@").not.toEval();
+ expect("function foo() { return 1; }; foo();").toEval();
+});
+
+// FIXME: Will have to change when this matcher changes to use the
+// "eval" function
+test("toEvalTo", () => {
+ expect("let a = 1").toEvalTo();
+ expect("let a = 1").toEvalTo(undefined);
+ expect("return 10").toEvalTo(10);
+ expect("return 10").not.toEvalTo(5);
+
+ expect(() => {
+ expect("*^&%%").not.toEvalTo();
+ }).toThrow();
+});
+
+test("toHaveConfigurableProperty", () => {
+ expect({ foo: 1 }).toHaveConfigurableProperty("foo");
+
+ expect(() => {
+ expect({ foo: 1 }).not.toHaveConfigurableProperty("bar");
+ }).toThrow();
+
+ let o = {};
+ Object.defineProperty(o, "foo", { configurable: true, value: 1 });
+ Object.defineProperty(o, "bar", { configurable: false, value: 1 });
+ expect(o).toHaveConfigurableProperty("foo");
+ expect(o).not.toHaveConfigurableProperty("bar");
+});
+
+test("toHaveEnumerableProperty", () => {
+ expect({ foo: 1 }).toHaveEnumerableProperty("foo");
+
+ expect(() => {
+ expect({ foo: 1 }).not.toHaveEnumerableProperty("bar");
+ }).toThrow();
+
+ let o = {};
+ Object.defineProperty(o, "foo", { enumerable: true, value: 1 });
+ Object.defineProperty(o, "bar", { enumerable: false, value: 1 });
+ expect(o).toHaveEnumerableProperty("foo");
+ expect(o).not.toHaveEnumerableProperty("bar");
+});
+
+test("toHaveWritableProperty", () => {
+ expect({ foo: 1 }).toHaveWritableProperty("foo");
+
+ expect(() => {
+ expect({ foo: 1 }).not.toHaveWritableProperty("bar");
+ }).toThrow();
+
+ let o = {};
+ Object.defineProperty(o, "foo", { writable: true, value: 1 });
+ Object.defineProperty(o, "bar", { writable: false, value: 1 });
+ expect(o).toHaveWritableProperty("foo");
+ expect(o).not.toHaveWritableProperty("bar");
+});
+
+test("toHaveGetterProperty", () => {
+ expect(() => {
+ expect({ foo: 1 }).not.toHaveGetterProperty("bar");
+ }).toThrow();
+
+ let o = {};
+ Object.defineProperty(o, "foo", {
+ get() {
+ return 1;
+ },
+ });
+ Object.defineProperty(o, "bar", { value: 1 });
+ expect(o).toHaveGetterProperty("foo");
+ expect(o).not.toHaveGetterProperty("bar");
+});
+
+test("toHaveSetterProperty", () => {
+ expect(() => {
+ expect({ foo: 1 }).not.toHaveSetterProperty("bar");
+ }).toThrow();
+
+ let o = {};
+ Object.defineProperty(o, "foo", { set(_) {} });
+ Object.defineProperty(o, "bar", { value: 1 });
+ expect(o).toHaveSetterProperty("foo");
+ expect(o).not.toHaveSetterProperty("bar");
+});
diff --git a/Userland/Libraries/LibJS/Tests/test-common.js b/Userland/Libraries/LibJS/Tests/test-common.js
new file mode 100644
index 0000000000..4e6d0e62e0
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/test-common.js
@@ -0,0 +1,462 @@
+let describe;
+let test;
+let expect;
+
+// Stores the results of each test and suite. Has a terrible
+// name to avoid name collision.
+let __TestResults__ = {};
+
+// So test names like "toString" don't automatically produce an error
+Object.setPrototypeOf(__TestResults__, null);
+
+// This array is used to communicate with the C++ program. It treats
+// each message in this array as a separate message. Has a terrible
+// name to avoid name collision.
+let __UserOutput__ = [];
+
+// We also rebind console.log here to use the array above
+console.log = (...args) => {
+ __UserOutput__.push(args.join(" "));
+};
+
+class ExpectationError extends Error {
+ constructor(message, fileName, lineNumber) {
+ super(message, fileName, lineNumber);
+ this.name = "ExpectationError";
+ }
+}
+
+// Use an IIFE to avoid polluting the global namespace as much as possible
+(() => {
+ // FIXME: This is a very naive deepEquals algorithm
+ const deepEquals = (a, b) => {
+ if (Array.isArray(a)) return Array.isArray(b) && deepArrayEquals(a, b);
+ if (typeof a === "object") return typeof b === "object" && deepObjectEquals(a, b);
+ return Object.is(a, b);
+ };
+
+ const deepArrayEquals = (a, b) => {
+ if (a.length !== b.length) return false;
+ for (let i = 0; i < a.length; ++i) {
+ if (!deepEquals(a[i], b[i])) return false;
+ }
+ return true;
+ };
+
+ const deepObjectEquals = (a, b) => {
+ if (a === null) return b === null;
+ for (let key of Reflect.ownKeys(a)) {
+ if (!deepEquals(a[key], b[key])) return false;
+ }
+ return true;
+ };
+
+ class Expector {
+ constructor(target, inverted) {
+ this.target = target;
+ this.inverted = !!inverted;
+ }
+
+ get not() {
+ return new Expector(this.target, !this.inverted);
+ }
+
+ toBe(value) {
+ this.__doMatcher(() => {
+ this.__expect(
+ Object.is(this.target, value),
+ () =>
+ "toBe: expected _" + String(value) + "_, got _" + String(this.target) + "_"
+ );
+ });
+ }
+
+ // FIXME: Take a precision argument like jest's toBeCloseTo matcher
+ toBeCloseTo(value) {
+ this.__expect(
+ typeof this.target === "number",
+ () => "toBeCloseTo: target not of type number"
+ );
+ this.__expect(
+ typeof value === "number",
+ () => "toBeCloseTo: argument not of type number"
+ );
+
+ this.__doMatcher(() => {
+ this.__expect(Math.abs(this.target - value) < 0.000001);
+ });
+ }
+
+ toHaveLength(length) {
+ this.__expect(
+ typeof this.target.length === "number",
+ () => "toHaveLength: target.length not of type number"
+ );
+
+ this.__doMatcher(() => {
+ this.__expect(Object.is(this.target.length, length));
+ });
+ }
+
+ toHaveProperty(property, value) {
+ this.__doMatcher(() => {
+ let object = this.target;
+
+ if (typeof property === "string" && property.includes(".")) {
+ let propertyArray = [];
+
+ while (property.includes(".")) {
+ let index = property.indexOf(".");
+ propertyArray.push(property.substring(0, index));
+ if (index + 1 >= property.length) break;
+ property = property.substring(index + 1, property.length);
+ }
+
+ propertyArray.push(property);
+
+ property = propertyArray;
+ }
+
+ if (Array.isArray(property)) {
+ for (let key of property) {
+ this.__expect(object !== undefined && object !== null);
+ object = object[key];
+ }
+ } else {
+ object = object[property];
+ }
+
+ this.__expect(object !== undefined);
+ if (value !== undefined) this.__expect(deepEquals(object, value));
+ });
+ }
+
+ toBeDefined() {
+ this.__doMatcher(() => {
+ this.__expect(this.target !== undefined, () => "toBeDefined: target was undefined");
+ });
+ }
+
+ toBeInstanceOf(class_) {
+ this.__doMatcher(() => {
+ this.__expect(this.target instanceof class_);
+ });
+ }
+
+ toBeNull() {
+ this.__doMatcher(() => {
+ this.__expect(this.target === null);
+ });
+ }
+
+ toBeUndefined() {
+ this.__doMatcher(() => {
+ this.__expect(
+ this.target === undefined,
+ () => "toBeUndefined: target was not undefined"
+ );
+ });
+ }
+
+ toBeNaN() {
+ this.__doMatcher(() => {
+ this.__expect(
+ isNaN(this.target),
+ () => "toBeNaN: target was _" + String(this.target) + "_, not NaN"
+ );
+ });
+ }
+
+ toBeTrue() {
+ this.__doMatcher(() => {
+ this.__expect(this.target === true);
+ });
+ }
+
+ toBeFalse() {
+ this.__doMatcher(() => {
+ this.__expect(this.target === false);
+ });
+ }
+
+ __validateNumericComparisonTypes(value) {
+ this.__expect(typeof this.target === "number" || typeof this.target === "bigint");
+ this.__expect(typeof value === "number" || typeof value === "bigint");
+ this.__expect(typeof this.target === typeof value);
+ }
+
+ toBeLessThan(value) {
+ this.__validateNumericComparisonTypes(value);
+
+ this.__doMatcher(() => {
+ this.__expect(this.target < value);
+ });
+ }
+
+ toBeLessThanOrEqual(value) {
+ this.__validateNumericComparisonTypes(value);
+
+ this.__doMatcher(() => {
+ this.__expect(this.target <= value);
+ });
+ }
+
+ toBeGreaterThan(value) {
+ this.__validateNumericComparisonTypes(value);
+
+ this.__doMatcher(() => {
+ this.__expect(this.target > value);
+ });
+ }
+
+ toBeGreaterThanOrEqual(value) {
+ this.__validateNumericComparisonTypes(value);
+
+ this.__doMatcher(() => {
+ this.__expect(this.target >= value);
+ });
+ }
+
+ toContain(item) {
+ this.__doMatcher(() => {
+ for (let element of this.target) {
+ if (item === element) return;
+ }
+
+ throw new ExpectationError();
+ });
+ }
+
+ toContainEqual(item) {
+ this.__doMatcher(() => {
+ for (let element of this.target) {
+ if (deepEquals(item, element)) return;
+ }
+
+ throw new ExpectationError();
+ });
+ }
+
+ toEqual(value) {
+ this.__doMatcher(() => {
+ this.__expect(deepEquals(this.target, value));
+ });
+ }
+
+ toThrow(value) {
+ this.__expect(typeof this.target === "function");
+ this.__expect(
+ typeof value === "string" ||
+ typeof value === "function" ||
+ typeof value === "object" ||
+ value === undefined
+ );
+
+ this.__doMatcher(() => {
+ let threw = true;
+ try {
+ this.target();
+ threw = false;
+ } catch (e) {
+ if (typeof value === "string") {
+ this.__expect(e.message.includes(value));
+ } else if (typeof value === "function") {
+ this.__expect(e instanceof value);
+ } else if (typeof value === "object") {
+ this.__expect(e.message === value.message);
+ }
+ }
+ this.__expect(threw);
+ });
+ }
+
+ pass(message) {
+ // FIXME: This does nothing. If we want to implement things
+ // like assertion count, this will have to do something
+ }
+
+ // jest-extended
+ fail(message) {
+ this.__doMatcher(() => {
+ this.__expect(false, message);
+ });
+ }
+
+ // jest-extended
+ toThrowWithMessage(class_, message) {
+ this.__expect(typeof this.target === "function");
+ this.__expect(class_ !== undefined);
+ this.__expect(message !== undefined);
+
+ this.__doMatcher(() => {
+ try {
+ this.target();
+ this.__expect(false);
+ } catch (e) {
+ this.__expect(e instanceof class_);
+ this.__expect(e.message.includes(message));
+ }
+ });
+ }
+
+ // Test for syntax errors; target must be a string
+ toEval() {
+ this.__expect(typeof this.target === "string");
+ const success = canParseSource(this.target);
+ this.__expect(this.inverted ? !success : success);
+ }
+
+ // Must compile regardless of inverted-ness
+ toEvalTo(value) {
+ this.__expect(typeof this.target === "string");
+
+ let result;
+
+ try {
+ result = new Function(this.target)();
+ } catch (e) {
+ throw new ExpectationError();
+ }
+
+ this.__doMatcher(() => {
+ this.__expect(deepEquals(value, result));
+ });
+ }
+
+ toHaveConfigurableProperty(property) {
+ this.__expect(this.target !== undefined && this.target !== null);
+ let d = Object.getOwnPropertyDescriptor(this.target, property);
+ this.__expect(d !== undefined);
+
+ this.__doMatcher(() => {
+ this.__expect(d.configurable);
+ });
+ }
+
+ toHaveEnumerableProperty(property) {
+ this.__expect(this.target !== undefined && this.target !== null);
+ let d = Object.getOwnPropertyDescriptor(this.target, property);
+ this.__expect(d !== undefined);
+
+ this.__doMatcher(() => {
+ this.__expect(d.enumerable);
+ });
+ }
+
+ toHaveWritableProperty(property) {
+ this.__expect(this.target !== undefined && this.target !== null);
+ let d = Object.getOwnPropertyDescriptor(this.target, property);
+ this.__expect(d !== undefined);
+
+ this.__doMatcher(() => {
+ this.__expect(d.writable);
+ });
+ }
+
+ toHaveValueProperty(property, value) {
+ this.__expect(this.target !== undefined && this.target !== null);
+ let d = Object.getOwnPropertyDescriptor(this.target, property);
+ this.__expect(d !== undefined);
+
+ this.__doMatcher(() => {
+ this.__expect(d.value !== undefined);
+ if (value !== undefined) this.__expect(deepEquals(value, d.value));
+ });
+ }
+
+ toHaveGetterProperty(property) {
+ this.__expect(this.target !== undefined && this.target !== null);
+ let d = Object.getOwnPropertyDescriptor(this.target, property);
+ this.__expect(d !== undefined);
+
+ this.__doMatcher(() => {
+ this.__expect(d.get !== undefined);
+ });
+ }
+
+ toHaveSetterProperty(property) {
+ this.__expect(this.target !== undefined && this.target !== null);
+ let d = Object.getOwnPropertyDescriptor(this.target, property);
+ this.__expect(d !== undefined);
+
+ this.__doMatcher(() => {
+ this.__expect(d.set !== undefined);
+ });
+ }
+
+ __doMatcher(matcher) {
+ if (!this.inverted) {
+ matcher();
+ } else {
+ let threw = false;
+ try {
+ matcher();
+ } catch (e) {
+ if (e.name === "ExpectationError") threw = true;
+ }
+ if (!threw) throw new ExpectationError("not: test didn't fail");
+ }
+ }
+
+ __expect(value, details) {
+ if (value !== true) {
+ if (details !== undefined) {
+ throw new ExpectationError(details());
+ } else {
+ throw new ExpectationError();
+ }
+ }
+ }
+ }
+
+ expect = value => new Expector(value);
+
+ // describe is able to lump test results inside of it by using this context
+ // variable. Top level tests have the default suite message
+ const defaultSuiteMessage = "__$$TOP_LEVEL$$__";
+ let suiteMessage = defaultSuiteMessage;
+
+ describe = (message, callback) => {
+ suiteMessage = message;
+ callback();
+ suiteMessage = defaultSuiteMessage;
+ };
+
+ test = (message, callback) => {
+ if (!__TestResults__[suiteMessage]) __TestResults__[suiteMessage] = {};
+
+ const suite = __TestResults__[suiteMessage];
+ if (suite[message]) {
+ suite[message] = {
+ result: "fail",
+ };
+ return;
+ }
+
+ try {
+ callback();
+ suite[message] = {
+ result: "pass",
+ };
+ } catch (e) {
+ suite[message] = {
+ result: "fail",
+ details: String(e),
+ };
+ }
+ };
+
+ test.skip = (message, callback) => {
+ if (typeof callback !== "function")
+ throw new Error("test.skip has invalid second argument (must be a function)");
+
+ if (!__TestResults__[suiteMessage]) __TestResults__[suiteMessage] = {};
+
+ const suite = __TestResults__[suiteMessage];
+ if (suite[message]) throw new Error("Duplicate test name: " + message);
+
+ suite[message] = {
+ result: "skip",
+ };
+ };
+})();
diff --git a/Userland/Libraries/LibJS/Tests/throw-basic.js b/Userland/Libraries/LibJS/Tests/throw-basic.js
new file mode 100644
index 0000000000..b393763d92
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/throw-basic.js
@@ -0,0 +1,34 @@
+test("throw literal", () => {
+ try {
+ throw 1;
+ expect().fail();
+ } catch (e) {
+ if (e.name === "ExpectationError") throw e;
+ expect(e).toBe(1);
+ }
+});
+
+test("throw array", () => {
+ try {
+ throw [99];
+ expect().fail();
+ } catch (e) {
+ if (e.name === "ExpectationError") throw e;
+ expect(e).toEqual([99]);
+ }
+});
+
+test("call function that throws", () => {
+ function foo() {
+ throw "hello";
+ expect().fail();
+ }
+
+ try {
+ foo();
+ expect().fail();
+ } catch (e) {
+ if (e.name === "ExpectationError") throw e;
+ expect(e).toBe("hello");
+ }
+});
diff --git a/Userland/Libraries/LibJS/Tests/to-number-basic.js b/Userland/Libraries/LibJS/Tests/to-number-basic.js
new file mode 100644
index 0000000000..4509537839
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/to-number-basic.js
@@ -0,0 +1,75 @@
+test("non-numeric primitives", () => {
+ expect(+false).toBe(0);
+ expect(-false).toBe(-0);
+ expect(+true).toBe(1);
+ expect(-true).toBe(-1);
+ expect(+null).toBe(0);
+ expect(-null).toBe(-0);
+ expect(+undefined).toBeNaN();
+ expect(-undefined).toBeNaN();
+});
+
+test("arrays", () => {
+ expect(+[]).toBe(0);
+ expect(-[]).toBe(-0);
+ expect(+[,]).toBe(0);
+ expect(-[,]).toBe(-0);
+ expect(+[null]).toBe(0);
+ expect(-[null]).toBe(-0);
+ expect(+[undefined]).toBe(0);
+ expect(-[undefined]).toBe(-0);
+ expect(+[[[[[]]]]]).toBe(0);
+ expect(-[[[[[]]]]]).toBe(-0);
+ expect(+[[[[[42]]]]]).toBe(42);
+ expect(-[[[[[42]]]]]).toBe(-42);
+
+ expect(+[, , ,]).toBeNaN();
+ expect(-[, , ,]).toBeNaN();
+ expect(+[undefined, undefined]).toBeNaN();
+ expect(-[undefined, undefined]).toBeNaN();
+ expect(+[1, 2, 3]).toBeNaN();
+ expect(-[1, 2, 3]).toBeNaN();
+ expect(+[[[["foo"]]]]).toBeNaN();
+ expect(-[[[["foo"]]]]).toBeNaN();
+});
+
+test("strings", () => {
+ expect(+"").toBe(0);
+ expect(-"").toBe(-0);
+ expect(+"42").toBe(42);
+ expect(-"42").toBe(-42);
+ expect(+"1.23").toBe(1.23);
+ expect(-"1.23").toBe(-1.23);
+
+ expect(+"foo").toBeNaN();
+ expect(-"foo").toBeNaN();
+});
+
+test("numbers", () => {
+ expect(+42).toBe(42);
+ expect(-42).toBe(-42);
+ expect(+1.23).toBe(1.23);
+ expect(-1.23).toBe(-1.23);
+});
+
+test("infinity", () => {
+ expect(+"Infinity").toBe(Infinity);
+ expect(+"+Infinity").toBe(Infinity);
+ expect(+"-Infinity").toBe(-Infinity);
+ expect(-"Infinity").toBe(-Infinity);
+ expect(-"+Infinity").toBe(-Infinity);
+ expect(-"-Infinity").toBe(Infinity);
+});
+
+test("space and space-like escapes", () => {
+ expect(+" \r \t \n ").toBe(0);
+ expect(+" \n \t Infinity \r ").toBe(Infinity);
+ expect(+"\r \n1.23 \t\t\t \n").toBe(1.23);
+});
+
+test("object literals", () => {
+ expect(+{}).toBeNaN();
+ expect(-{}).toBeNaN();
+ expect(+{ a: 1 }).toBeNaN();
+ expect(-{ a: 1 }).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/to-number-exception.js b/Userland/Libraries/LibJS/Tests/to-number-exception.js
new file mode 100644
index 0000000000..b9247d9636
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/to-number-exception.js
@@ -0,0 +1,25 @@
+const message = "oops, Value::to_number() failed";
+
+const o = {
+ toString() {
+ throw new Error(message);
+ },
+};
+
+test("basic functionality", () => {
+ expect(() => {
+ +o;
+ }).toThrowWithMessage(Error, message);
+
+ expect(() => {
+ o - 1;
+ }).toThrowWithMessage(Error, message);
+
+ expect(() => {
+ "foo".charAt(o);
+ }).toThrowWithMessage(Error, message);
+
+ expect(() => {
+ "bar".repeat(o);
+ }).toThrowWithMessage(Error, message);
+});
diff --git a/Userland/Libraries/LibJS/Tests/try-catch-finally-nested.js b/Userland/Libraries/LibJS/Tests/try-catch-finally-nested.js
new file mode 100644
index 0000000000..da9ad2d438
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/try-catch-finally-nested.js
@@ -0,0 +1,55 @@
+test("Nested try/catch/finally with exceptions", () => {
+ // This test uses a combination of boolean "checkpoint" flags
+ // and expect().fail() to ensure certain code paths have been
+ // reached and others haven't.
+ var level1TryHasBeenExecuted = false;
+ var level1CatchHasBeenExecuted = false;
+ var level1FinallyHasBeenExecuted = false;
+ var level2TryHasBeenExecuted = false;
+ var level2CatchHasBeenExecuted = false;
+ var level3TryHasBeenExecuted = false;
+ var level3CatchHasBeenExecuted = false;
+ var level3FinallyHasBeenExecuted = false;
+ expect(() => {
+ try {
+ level1TryHasBeenExecuted = true;
+ foo();
+ expect().fail();
+ } catch (e) {
+ level1CatchHasBeenExecuted = true;
+ try {
+ level2TryHasBeenExecuted = true;
+ try {
+ level3TryHasBeenExecuted = true;
+ bar();
+ expect().fail();
+ } catch (e) {
+ level3CatchHasBeenExecuted = true;
+ } finally {
+ level3FinallyHasBeenExecuted = true;
+ baz();
+ expect().fail();
+ }
+ expect().fail();
+ } catch (e) {
+ level2CatchHasBeenExecuted = true;
+ qux();
+ expect().fail();
+ }
+ expect().fail();
+ } finally {
+ level1FinallyHasBeenExecuted = true;
+ throw Error("Error in final finally");
+ expect().fail();
+ }
+ expect().fail();
+ }).toThrow(Error, "Error in final finally");
+ expect(level1TryHasBeenExecuted).toBeTrue();
+ expect(level1CatchHasBeenExecuted).toBeTrue();
+ expect(level1FinallyHasBeenExecuted).toBeTrue();
+ expect(level2TryHasBeenExecuted).toBeTrue();
+ expect(level2CatchHasBeenExecuted).toBeTrue();
+ expect(level3TryHasBeenExecuted).toBeTrue();
+ expect(level3CatchHasBeenExecuted).toBeTrue();
+ expect(level3FinallyHasBeenExecuted).toBeTrue();
+});
diff --git a/Userland/Libraries/LibJS/Tests/try-catch-finally.js b/Userland/Libraries/LibJS/Tests/try-catch-finally.js
new file mode 100644
index 0000000000..1ff0104734
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/try-catch-finally.js
@@ -0,0 +1,172 @@
+test("try/catch without exception", () => {
+ var tryHasBeenExecuted = false;
+ var catchHasBeenExecuted = false;
+ try {
+ tryHasBeenExecuted = true;
+ } catch (e) {
+ catchHasBeenExecuted = true;
+ }
+ expect(tryHasBeenExecuted).toBeTrue();
+ expect(catchHasBeenExecuted).toBeFalse();
+});
+
+test("try/catch with exception in try", () => {
+ var tryHasBeenExecuted = false;
+ var catchHasBeenExecuted = false;
+ var tryError = Error("Error in try");
+ try {
+ tryHasBeenExecuted = true;
+ throw tryError;
+ expect().fail();
+ } catch (e) {
+ catchHasBeenExecuted = true;
+ expect(e).toBe(tryError);
+ }
+ expect(tryHasBeenExecuted).toBeTrue();
+ expect(catchHasBeenExecuted).toBeTrue();
+});
+
+test("try/catch with exception in try and catch", () => {
+ var tryHasBeenExecuted = false;
+ var catchHasBeenExecuted = false;
+ var tryError = Error("Error in try");
+ var catchError = Error("Error in catch");
+ expect(() => {
+ try {
+ tryHasBeenExecuted = true;
+ throw tryError;
+ expect().fail();
+ } catch (e) {
+ catchHasBeenExecuted = true;
+ expect(e).toBe(tryError);
+ throw catchError;
+ expect().fail();
+ }
+ }).toThrow(Error, "Error in catch");
+ expect(tryHasBeenExecuted).toBeTrue();
+ expect(catchHasBeenExecuted).toBeTrue();
+});
+
+test("try/catch/finally without exception", () => {
+ var tryHasBeenExecuted = false;
+ var catchHasBeenExecuted = false;
+ var finallyHasBeenExecuted = false;
+ try {
+ tryHasBeenExecuted = true;
+ } catch (e) {
+ catchHasBeenExecuted = true;
+ } finally {
+ finallyHasBeenExecuted = true;
+ }
+ expect(tryHasBeenExecuted).toBeTrue();
+ expect(catchHasBeenExecuted).toBeFalse();
+ expect(finallyHasBeenExecuted).toBeTrue();
+});
+
+test("try/catch/finally with exception in try and catch", () => {
+ var tryHasBeenExecuted = false;
+ var catchHasBeenExecuted = false;
+ var finallyHasBeenExecuted = false;
+ var tryError = Error("Error in try");
+ var catchError = Error("Error in catch");
+ expect(() => {
+ try {
+ tryHasBeenExecuted = true;
+ throw tryError;
+ expect().fail();
+ } catch (e) {
+ catchHasBeenExecuted = true;
+ expect(e).toBe(tryError);
+ throw catchError;
+ expect().fail();
+ } finally {
+ finallyHasBeenExecuted = true;
+ }
+ }).toThrow(Error, "Error in catch");
+ expect(tryHasBeenExecuted).toBeTrue();
+ expect(catchHasBeenExecuted).toBeTrue();
+ expect(finallyHasBeenExecuted).toBeTrue();
+});
+
+test("try/catch/finally with exception in finally", () => {
+ var tryHasBeenExecuted = false;
+ var catchHasBeenExecuted = false;
+ var finallyHasBeenExecuted = false;
+ var finallyError = Error("Error in finally");
+ expect(() => {
+ try {
+ tryHasBeenExecuted = true;
+ } catch (e) {
+ catchHasBeenExecuted = true;
+ } finally {
+ finallyHasBeenExecuted = true;
+ throw finallyError;
+ expect().fail();
+ }
+ }).toThrow(Error, "Error in finally");
+ expect(tryHasBeenExecuted).toBeTrue();
+ expect(catchHasBeenExecuted).toBeFalse();
+ expect(finallyHasBeenExecuted).toBeTrue();
+});
+
+test("try/catch/finally with exception in try and finally", () => {
+ var tryHasBeenExecuted = false;
+ var catchHasBeenExecuted = false;
+ var finallyHasBeenExecuted = false;
+ var tryError = Error("Error in try");
+ var finallyError = Error("Error in finally");
+ expect(() => {
+ try {
+ tryHasBeenExecuted = true;
+ throw tryError;
+ expect().fail();
+ } catch (e) {
+ catchHasBeenExecuted = true;
+ expect(e).toBe(tryError);
+ } finally {
+ finallyHasBeenExecuted = true;
+ throw finallyError;
+ expect().fail();
+ }
+ }).toThrow(Error, "Error in finally");
+ expect(tryHasBeenExecuted).toBeTrue();
+ expect(catchHasBeenExecuted).toBeTrue();
+ expect(finallyHasBeenExecuted).toBeTrue();
+});
+
+test("try/catch/finally with exception in try, catch and finally", () => {
+ var tryHasBeenExecuted = false;
+ var catchHasBeenExecuted = false;
+ var finallyHasBeenExecuted = false;
+ var tryError = Error("Error in try");
+ var catchError = Error("Error in catch");
+ var finallyError = Error("Error in finally");
+ expect(() => {
+ try {
+ tryHasBeenExecuted = true;
+ throw tryError;
+ expect().fail();
+ } catch (e) {
+ catchHasBeenExecuted = true;
+ expect(e).toBe(tryError);
+ throw catchError;
+ expect().fail();
+ } finally {
+ finallyHasBeenExecuted = true;
+ throw finallyError;
+ expect().fail();
+ }
+ }).toThrow(Error, "Error in finally");
+ expect(tryHasBeenExecuted).toBeTrue();
+ expect(catchHasBeenExecuted).toBeTrue();
+ expect(finallyHasBeenExecuted).toBeTrue();
+});
+
+test("try statement must have either 'catch' or 'finally' clause", () => {
+ expect("try {} catch {}").toEval();
+ expect("try {} catch (e) {}").toEval();
+ expect("try {} finally {}").toEval();
+ expect("try {} catch {} finally {}").toEval();
+ expect("try {} catch (e) {} finally {}").toEval();
+ expect("try {}").not.toEval();
+});
diff --git a/Userland/Libraries/LibJS/Tests/update-expression-on-member-expression.js b/Userland/Libraries/LibJS/Tests/update-expression-on-member-expression.js
new file mode 100644
index 0000000000..e9a29d6ea2
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/update-expression-on-member-expression.js
@@ -0,0 +1,8 @@
+test("basic update expression", () => {
+ const o = {};
+ o.f = 1;
+
+ expect(o.f++).toBe(1);
+ expect(++o.f).toBe(3);
+ expect(++o.missing).toBeNaN();
+});
diff --git a/Userland/Libraries/LibJS/Tests/update-expressions-basic.js b/Userland/Libraries/LibJS/Tests/update-expressions-basic.js
new file mode 100644
index 0000000000..5eacc6a069
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/update-expressions-basic.js
@@ -0,0 +1,53 @@
+describe("correct behavior", () => {
+ test("basic functionality", () => {
+ let n = 0;
+ expect(++n).toBe(1);
+ expect(n).toBe(1);
+
+ n = 0;
+ expect(n++).toBe(0);
+ expect(n).toBe(1);
+
+ n = 0;
+ expect(--n).toBe(-1);
+ expect(n).toBe(-1);
+
+ n = 0;
+ expect(n--).toBe(0);
+ expect(n).toBe(-1);
+
+ let a = [];
+ expect(a++).toBe(0);
+ expect(a).toBe(1);
+
+ let b = true;
+ expect(b--).toBe(1);
+ expect(b).toBe(0);
+ });
+
+ test("updates that produce NaN", () => {
+ let s = "foo";
+ expect(++s).toBeNaN();
+ expect(s).toBeNaN();
+
+ s = "foo";
+ expect(s++).toBeNaN();
+ expect(s).toBeNaN();
+
+ s = "foo";
+ expect(--s).toBeNaN();
+ expect(s).toBeNaN();
+
+ s = "foo";
+ expect(s--).toBeNaN();
+ expect(s).toBeNaN();
+ });
+});
+
+describe("errors", () => {
+ test("update expression throws reference error", () => {
+ expect(() => {
+ ++x;
+ }).toThrowWithMessage(ReferenceError, "'x' is not defined");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/use-strict-directive.js b/Userland/Libraries/LibJS/Tests/use-strict-directive.js
new file mode 100644
index 0000000000..c0bbd2ed9e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/use-strict-directive.js
@@ -0,0 +1,61 @@
+test("valid 'use strict; directive", () => {
+ expect(
+ (() => {
+ "use strict";
+ return isStrictMode();
+ })()
+ ).toBeTrue();
+ expect(
+ // prettier-ignore
+ (() => {
+ 'use strict';
+ return isStrictMode();
+ })()
+ ).toBeTrue();
+});
+
+test("invalid 'use strict; directive", () => {
+ expect(
+ (() => {
+ " use strict ";
+ return isStrictMode();
+ })()
+ ).toBeFalse();
+ expect(
+ (() => {
+ `use strict`;
+ return isStrictMode();
+ })()
+ ).toBeFalse();
+ expect(
+ (() => {
+ "use\
+ strict";
+ return isStrictMode();
+ })()
+ ).toBeFalse();
+ expect(
+ (() => {
+ "use\ strict";
+ return isStrictMode();
+ })()
+ ).toBeFalse();
+ expect(
+ (() => {
+ "use \163trict";
+ return isStrictMode();
+ })()
+ ).toBeFalse();
+ expect(
+ (() => {
+ `"use strict"`;
+ return isStrictMode();
+ })()
+ ).toBeFalse();
+ expect(
+ (() => {
+ "use strict" + 1;
+ return isStrictMode();
+ })()
+ ).toBeFalse();
+});
diff --git a/Userland/Libraries/LibJS/Tests/var-multiple-declarator.js b/Userland/Libraries/LibJS/Tests/var-multiple-declarator.js
new file mode 100644
index 0000000000..ba40d09b9e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/var-multiple-declarator.js
@@ -0,0 +1,8 @@
+test("basic functionality", () => {
+ var a = 1,
+ b = 2,
+ c = a + b;
+ expect(a).toBe(1);
+ expect(b).toBe(2);
+ expect(c).toBe(3);
+});
diff --git a/Userland/Libraries/LibJS/Tests/var-scoping.js b/Userland/Libraries/LibJS/Tests/var-scoping.js
new file mode 100644
index 0000000000..86c5f1d5c7
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/var-scoping.js
@@ -0,0 +1,17 @@
+test("basic functionality", () => {
+ function foo() {
+ i = 3;
+ expect(i).toBe(3);
+ var i;
+ }
+
+ foo();
+
+ var caught_exception;
+ try {
+ j = i;
+ } catch (e) {
+ caught_exception = e;
+ }
+ expect(caught_exception).not.toBeUndefined();
+});
diff --git a/Userland/Libraries/LibJS/Tests/variable-undefined.js b/Userland/Libraries/LibJS/Tests/variable-undefined.js
new file mode 100644
index 0000000000..4399d5332d
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/variable-undefined.js
@@ -0,0 +1,14 @@
+test("basic functionality", () => {
+ function foo(a) {
+ return a;
+ }
+
+ var x = undefined;
+ expect(x).toBeUndefined();
+ expect(foo(x)).toBeUndefined();
+
+ var o = {};
+ o.x = x;
+ expect(o.x).toBeUndefined();
+ expect(o.x).toBe(x);
+});
diff --git a/Userland/Libraries/LibJS/Tests/with-basic.js b/Userland/Libraries/LibJS/Tests/with-basic.js
new file mode 100644
index 0000000000..c2ef01698b
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/with-basic.js
@@ -0,0 +1,24 @@
+test("basic with statement functionality", () => {
+ var object = { foo: 5, bar: 6, baz: 7 };
+ var qux = 1;
+
+ var bar = 99;
+
+ with (object) {
+ expect(foo).toBe(5);
+ expect(bar).toBe(6);
+ expect(baz).toBe(7);
+ expect(qux).toBe(1);
+ expect(typeof quz).toBe("undefined");
+
+ bar = 2;
+ }
+
+ expect(object.bar).toBe(2);
+
+ expect(bar).toBe(99);
+});
+
+test("syntax error in strict mode", () => {
+ expect("'use strict'; with (foo) {}").not.toEval();
+});
diff --git a/Userland/Libraries/LibJS/Token.cpp b/Userland/Libraries/LibJS/Token.cpp
new file mode 100644
index 0000000000..638d4b7931
--- /dev/null
+++ b/Userland/Libraries/LibJS/Token.cpp
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Token.h"
+#include <AK/Assertions.h>
+#include <AK/GenericLexer.h>
+#include <AK/StringBuilder.h>
+#include <ctype.h>
+
+namespace JS {
+
+const char* Token::name(TokenType type)
+{
+ switch (type) {
+#define __ENUMERATE_JS_TOKEN(type, category) \
+ case TokenType::type: \
+ return #type;
+ ENUMERATE_JS_TOKENS
+#undef __ENUMERATE_JS_TOKEN
+ default:
+ ASSERT_NOT_REACHED();
+ return "<Unknown>";
+ }
+}
+
+const char* Token::name() const
+{
+ return name(m_type);
+}
+
+TokenCategory Token::category(TokenType type)
+{
+ switch (type) {
+#define __ENUMERATE_JS_TOKEN(type, category) \
+ case TokenType::type: \
+ return TokenCategory::category;
+ ENUMERATE_JS_TOKENS
+#undef __ENUMERATE_JS_TOKEN
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+TokenCategory Token::category() const
+{
+ return category(m_type);
+}
+
+double Token::double_value() const
+{
+ ASSERT(type() == TokenType::NumericLiteral);
+ String value_string(m_value);
+ if (value_string[0] == '0' && value_string.length() >= 2) {
+ if (value_string[1] == 'x' || value_string[1] == 'X') {
+ // hexadecimal
+ return static_cast<double>(strtoul(value_string.characters() + 2, nullptr, 16));
+ } else if (value_string[1] == 'o' || value_string[1] == 'O') {
+ // octal
+ return static_cast<double>(strtoul(value_string.characters() + 2, nullptr, 8));
+ } else if (value_string[1] == 'b' || value_string[1] == 'B') {
+ // binary
+ return static_cast<double>(strtoul(value_string.characters() + 2, nullptr, 2));
+ } else if (isdigit(value_string[1])) {
+ // also octal, but syntax error in strict mode
+ if (!m_value.contains('8') && !m_value.contains('9'))
+ return static_cast<double>(strtoul(value_string.characters() + 1, nullptr, 8));
+ }
+ }
+ return strtod(value_string.characters(), nullptr);
+}
+
+static u32 hex2int(char x)
+{
+ ASSERT(isxdigit(x));
+ if (x >= '0' && x <= '9')
+ return x - '0';
+ return 10u + (tolower(x) - 'a');
+}
+
+String Token::string_value(StringValueStatus& status) const
+{
+ ASSERT(type() == TokenType::StringLiteral || type() == TokenType::TemplateLiteralString);
+
+ auto is_template = type() == TokenType::TemplateLiteralString;
+ GenericLexer lexer(is_template ? m_value : m_value.substring_view(1, m_value.length() - 2));
+
+ auto encoding_failure = [&status](StringValueStatus parse_status) -> String {
+ status = parse_status;
+ return {};
+ };
+
+ StringBuilder builder;
+ while (!lexer.is_eof()) {
+ // No escape, consume one char and continue
+ if (!lexer.next_is('\\')) {
+ builder.append(lexer.consume());
+ continue;
+ }
+
+ lexer.ignore();
+ ASSERT(!lexer.is_eof());
+
+ // Line continuation
+ if (lexer.next_is('\n') || lexer.next_is('\r')) {
+ lexer.ignore();
+ continue;
+ }
+ // Line continuation
+ if (lexer.next_is(LINE_SEPARATOR) || lexer.next_is(PARAGRAPH_SEPARATOR)) {
+ lexer.ignore(3);
+ continue;
+ }
+ // Null-byte escape
+ if (lexer.next_is('0') && !isdigit(lexer.peek(1))) {
+ lexer.ignore();
+ builder.append('\0');
+ continue;
+ }
+ // Hex escape
+ if (lexer.next_is('x')) {
+ lexer.ignore();
+ if (!isxdigit(lexer.peek()) || !isxdigit(lexer.peek(1)))
+ return encoding_failure(StringValueStatus::MalformedHexEscape);
+ auto code_point = hex2int(lexer.consume()) * 16 + hex2int(lexer.consume());
+ ASSERT(code_point <= 255);
+ builder.append_code_point(code_point);
+ continue;
+ }
+ // Unicode escape
+ if (lexer.next_is('u')) {
+ lexer.ignore();
+ u32 code_point = 0;
+ if (lexer.next_is('{')) {
+ lexer.ignore();
+ while (true) {
+ if (!lexer.next_is(isxdigit))
+ return encoding_failure(StringValueStatus::MalformedUnicodeEscape);
+ auto new_code_point = (code_point << 4u) | hex2int(lexer.consume());
+ if (new_code_point < code_point)
+ return encoding_failure(StringValueStatus::UnicodeEscapeOverflow);
+ code_point = new_code_point;
+ if (lexer.next_is('}'))
+ break;
+ }
+ lexer.ignore();
+ } else {
+ for (int j = 0; j < 4; ++j) {
+ if (!lexer.next_is(isxdigit))
+ return encoding_failure(StringValueStatus::MalformedUnicodeEscape);
+ code_point = (code_point << 4u) | hex2int(lexer.consume());
+ }
+ }
+ builder.append_code_point(code_point);
+ continue;
+ }
+
+ // In non-strict mode LegacyOctalEscapeSequence is allowed in strings:
+ // https://tc39.es/ecma262/#sec-additional-syntax-string-literals
+ String octal_str;
+
+ auto is_octal_digit = [](char ch) { return ch >= '0' && ch <= '7'; };
+ auto is_zero_to_three = [](char ch) { return ch >= '0' && ch <= '3'; };
+ auto is_four_to_seven = [](char ch) { return ch >= '4' && ch <= '7'; };
+
+ // OctalDigit [lookahead ∉ OctalDigit]
+ if (is_octal_digit(lexer.peek()) && !is_octal_digit(lexer.peek(1)))
+ octal_str = lexer.consume(1);
+ // ZeroToThree OctalDigit [lookahead ∉ OctalDigit]
+ else if (is_zero_to_three(lexer.peek()) && is_octal_digit(lexer.peek(1)) && !is_octal_digit(lexer.peek(2)))
+ octal_str = lexer.consume(2);
+ // FourToSeven OctalDigit
+ else if (is_four_to_seven(lexer.peek()) && is_octal_digit(lexer.peek(1)))
+ octal_str = lexer.consume(2);
+ // ZeroToThree OctalDigit OctalDigit
+ else if (is_zero_to_three(lexer.peek()) && is_octal_digit(lexer.peek(1)) && is_octal_digit(lexer.peek(2)))
+ octal_str = lexer.consume(3);
+
+ if (!octal_str.is_null()) {
+ status = StringValueStatus::LegacyOctalEscapeSequence;
+ auto code_point = strtoul(octal_str.characters(), nullptr, 8);
+ ASSERT(code_point <= 255);
+ builder.append_code_point(code_point);
+ continue;
+ }
+
+ lexer.retreat();
+ builder.append(lexer.consume_escaped_character('\\', "b\bf\fn\nr\rt\tv\v"));
+ }
+ return builder.to_string();
+}
+
+bool Token::bool_value() const
+{
+ ASSERT(type() == TokenType::BoolLiteral);
+ return m_value == "true";
+}
+
+bool Token::is_identifier_name() const
+{
+ // IdentifierNames are Identifiers + ReservedWords
+ // The standard defines this reversed: Identifiers are IdentifierNames except reserved words
+ // https://www.ecma-international.org/ecma-262/5.1/#sec-7.6
+ return m_type == TokenType::Identifier
+ || m_type == TokenType::Await
+ || m_type == TokenType::BoolLiteral
+ || m_type == TokenType::Break
+ || m_type == TokenType::Case
+ || m_type == TokenType::Catch
+ || m_type == TokenType::Class
+ || m_type == TokenType::Const
+ || m_type == TokenType::Continue
+ || m_type == TokenType::Default
+ || m_type == TokenType::Delete
+ || m_type == TokenType::Do
+ || m_type == TokenType::Else
+ || m_type == TokenType::Enum
+ || m_type == TokenType::Export
+ || m_type == TokenType::Extends
+ || m_type == TokenType::Finally
+ || m_type == TokenType::For
+ || m_type == TokenType::Function
+ || m_type == TokenType::If
+ || m_type == TokenType::Import
+ || m_type == TokenType::In
+ || m_type == TokenType::Instanceof
+ || m_type == TokenType::Interface
+ || m_type == TokenType::Let
+ || m_type == TokenType::New
+ || m_type == TokenType::NullLiteral
+ || m_type == TokenType::Return
+ || m_type == TokenType::Super
+ || m_type == TokenType::Switch
+ || m_type == TokenType::This
+ || m_type == TokenType::Throw
+ || m_type == TokenType::Try
+ || m_type == TokenType::Typeof
+ || m_type == TokenType::Var
+ || m_type == TokenType::Void
+ || m_type == TokenType::While
+ || m_type == TokenType::Yield;
+}
+
+bool Token::trivia_contains_line_terminator() const
+{
+ return m_trivia.contains('\n') || m_trivia.contains('\r') || m_trivia.contains(LINE_SEPARATOR) || m_trivia.contains(PARAGRAPH_SEPARATOR);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Token.h b/Userland/Libraries/LibJS/Token.h
new file mode 100644
index 0000000000..a266cd32ce
--- /dev/null
+++ b/Userland/Libraries/LibJS/Token.h
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/StringView.h>
+
+namespace JS {
+
+// U+2028 LINE SEPARATOR
+constexpr const char line_separator_chars[] { (char)0xe2, (char)0x80, (char)0xa8, 0 };
+constexpr const StringView LINE_SEPARATOR { line_separator_chars };
+
+// U+2029 PARAGRAPH SEPARATOR
+constexpr const char paragraph_separator_chars[] { (char)0xe2, (char)0x80, (char)0xa9, 0 };
+constexpr const StringView PARAGRAPH_SEPARATOR { paragraph_separator_chars };
+
+#define ENUMERATE_JS_TOKENS \
+ __ENUMERATE_JS_TOKEN(Ampersand, Operator) \
+ __ENUMERATE_JS_TOKEN(AmpersandEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(Arrow, Operator) \
+ __ENUMERATE_JS_TOKEN(Asterisk, Operator) \
+ __ENUMERATE_JS_TOKEN(AsteriskEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(Async, Keyword) \
+ __ENUMERATE_JS_TOKEN(Await, Keyword) \
+ __ENUMERATE_JS_TOKEN(BigIntLiteral, Number) \
+ __ENUMERATE_JS_TOKEN(BoolLiteral, Keyword) \
+ __ENUMERATE_JS_TOKEN(BracketClose, Punctuation) \
+ __ENUMERATE_JS_TOKEN(BracketOpen, Punctuation) \
+ __ENUMERATE_JS_TOKEN(Break, Keyword) \
+ __ENUMERATE_JS_TOKEN(Caret, Operator) \
+ __ENUMERATE_JS_TOKEN(CaretEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(Case, ControlKeyword) \
+ __ENUMERATE_JS_TOKEN(Catch, ControlKeyword) \
+ __ENUMERATE_JS_TOKEN(Class, Keyword) \
+ __ENUMERATE_JS_TOKEN(Colon, Punctuation) \
+ __ENUMERATE_JS_TOKEN(Comma, Punctuation) \
+ __ENUMERATE_JS_TOKEN(Const, Keyword) \
+ __ENUMERATE_JS_TOKEN(Continue, ControlKeyword) \
+ __ENUMERATE_JS_TOKEN(CurlyClose, Punctuation) \
+ __ENUMERATE_JS_TOKEN(CurlyOpen, Punctuation) \
+ __ENUMERATE_JS_TOKEN(Debugger, Keyword) \
+ __ENUMERATE_JS_TOKEN(Default, ControlKeyword) \
+ __ENUMERATE_JS_TOKEN(Delete, Keyword) \
+ __ENUMERATE_JS_TOKEN(Do, ControlKeyword) \
+ __ENUMERATE_JS_TOKEN(DoubleAmpersand, Operator) \
+ __ENUMERATE_JS_TOKEN(DoubleAmpersandEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(DoubleAsterisk, Operator) \
+ __ENUMERATE_JS_TOKEN(DoubleAsteriskEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(DoublePipe, Operator) \
+ __ENUMERATE_JS_TOKEN(DoublePipeEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(DoubleQuestionMark, Operator) \
+ __ENUMERATE_JS_TOKEN(DoubleQuestionMarkEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(Else, ControlKeyword) \
+ __ENUMERATE_JS_TOKEN(Enum, Keyword) \
+ __ENUMERATE_JS_TOKEN(Eof, Invalid) \
+ __ENUMERATE_JS_TOKEN(Equals, Operator) \
+ __ENUMERATE_JS_TOKEN(EqualsEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(EqualsEqualsEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(ExclamationMark, Operator) \
+ __ENUMERATE_JS_TOKEN(ExclamationMarkEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(ExclamationMarkEqualsEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(Export, Keyword) \
+ __ENUMERATE_JS_TOKEN(Extends, Keyword) \
+ __ENUMERATE_JS_TOKEN(Finally, ControlKeyword) \
+ __ENUMERATE_JS_TOKEN(For, ControlKeyword) \
+ __ENUMERATE_JS_TOKEN(Function, Keyword) \
+ __ENUMERATE_JS_TOKEN(GreaterThan, Operator) \
+ __ENUMERATE_JS_TOKEN(GreaterThanEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(Identifier, Identifier) \
+ __ENUMERATE_JS_TOKEN(If, ControlKeyword) \
+ __ENUMERATE_JS_TOKEN(Implements, Keyword) \
+ __ENUMERATE_JS_TOKEN(Import, Keyword) \
+ __ENUMERATE_JS_TOKEN(In, Keyword) \
+ __ENUMERATE_JS_TOKEN(Instanceof, Keyword) \
+ __ENUMERATE_JS_TOKEN(Interface, Keyword) \
+ __ENUMERATE_JS_TOKEN(Invalid, Invalid) \
+ __ENUMERATE_JS_TOKEN(LessThan, Operator) \
+ __ENUMERATE_JS_TOKEN(LessThanEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(Let, Keyword) \
+ __ENUMERATE_JS_TOKEN(Minus, Operator) \
+ __ENUMERATE_JS_TOKEN(MinusEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(MinusMinus, Operator) \
+ __ENUMERATE_JS_TOKEN(New, Keyword) \
+ __ENUMERATE_JS_TOKEN(NullLiteral, Keyword) \
+ __ENUMERATE_JS_TOKEN(NumericLiteral, Number) \
+ __ENUMERATE_JS_TOKEN(Package, Keyword) \
+ __ENUMERATE_JS_TOKEN(ParenClose, Punctuation) \
+ __ENUMERATE_JS_TOKEN(ParenOpen, Punctuation) \
+ __ENUMERATE_JS_TOKEN(Percent, Operator) \
+ __ENUMERATE_JS_TOKEN(PercentEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(Period, Operator) \
+ __ENUMERATE_JS_TOKEN(Pipe, Operator) \
+ __ENUMERATE_JS_TOKEN(PipeEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(Plus, Operator) \
+ __ENUMERATE_JS_TOKEN(PlusEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(PlusPlus, Operator) \
+ __ENUMERATE_JS_TOKEN(Private, Keyword) \
+ __ENUMERATE_JS_TOKEN(Protected, Keyword) \
+ __ENUMERATE_JS_TOKEN(Public, Keyword) \
+ __ENUMERATE_JS_TOKEN(QuestionMark, Operator) \
+ __ENUMERATE_JS_TOKEN(QuestionMarkPeriod, Operator) \
+ __ENUMERATE_JS_TOKEN(RegexFlags, String) \
+ __ENUMERATE_JS_TOKEN(RegexLiteral, String) \
+ __ENUMERATE_JS_TOKEN(Return, ControlKeyword) \
+ __ENUMERATE_JS_TOKEN(Semicolon, Punctuation) \
+ __ENUMERATE_JS_TOKEN(ShiftLeft, Operator) \
+ __ENUMERATE_JS_TOKEN(ShiftLeftEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(ShiftRight, Operator) \
+ __ENUMERATE_JS_TOKEN(ShiftRightEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(Slash, Operator) \
+ __ENUMERATE_JS_TOKEN(SlashEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(Static, Keyword) \
+ __ENUMERATE_JS_TOKEN(StringLiteral, String) \
+ __ENUMERATE_JS_TOKEN(Super, Keyword) \
+ __ENUMERATE_JS_TOKEN(Switch, ControlKeyword) \
+ __ENUMERATE_JS_TOKEN(TemplateLiteralEnd, String) \
+ __ENUMERATE_JS_TOKEN(TemplateLiteralExprEnd, Punctuation) \
+ __ENUMERATE_JS_TOKEN(TemplateLiteralExprStart, Punctuation) \
+ __ENUMERATE_JS_TOKEN(TemplateLiteralStart, String) \
+ __ENUMERATE_JS_TOKEN(TemplateLiteralString, String) \
+ __ENUMERATE_JS_TOKEN(This, Keyword) \
+ __ENUMERATE_JS_TOKEN(Throw, ControlKeyword) \
+ __ENUMERATE_JS_TOKEN(Tilde, Operator) \
+ __ENUMERATE_JS_TOKEN(TripleDot, Operator) \
+ __ENUMERATE_JS_TOKEN(Try, ControlKeyword) \
+ __ENUMERATE_JS_TOKEN(Typeof, Keyword) \
+ __ENUMERATE_JS_TOKEN(UnsignedShiftRight, Operator) \
+ __ENUMERATE_JS_TOKEN(UnsignedShiftRightEquals, Operator) \
+ __ENUMERATE_JS_TOKEN(UnterminatedRegexLiteral, String) \
+ __ENUMERATE_JS_TOKEN(UnterminatedStringLiteral, String) \
+ __ENUMERATE_JS_TOKEN(UnterminatedTemplateLiteral, String) \
+ __ENUMERATE_JS_TOKEN(Var, Keyword) \
+ __ENUMERATE_JS_TOKEN(Void, Keyword) \
+ __ENUMERATE_JS_TOKEN(While, ControlKeyword) \
+ __ENUMERATE_JS_TOKEN(With, ControlKeyword) \
+ __ENUMERATE_JS_TOKEN(Yield, ControlKeyword)
+
+enum class TokenType {
+#define __ENUMERATE_JS_TOKEN(type, category) type,
+ ENUMERATE_JS_TOKENS
+#undef __ENUMERATE_JS_TOKEN
+ _COUNT_OF_TOKENS
+};
+constexpr size_t cs_num_of_js_tokens = static_cast<size_t>(TokenType::_COUNT_OF_TOKENS);
+
+enum class TokenCategory {
+ Invalid,
+ Number,
+ String,
+ Punctuation,
+ Operator,
+ Keyword,
+ ControlKeyword,
+ Identifier
+};
+
+class Token {
+public:
+ Token(TokenType type, String message, StringView trivia, StringView value, size_t line_number, size_t line_column)
+ : m_type(type)
+ , m_message(message)
+ , m_trivia(trivia)
+ , m_value(value)
+ , m_line_number(line_number)
+ , m_line_column(line_column)
+ {
+ }
+
+ TokenType type() const { return m_type; }
+ TokenCategory category() const;
+ static TokenCategory category(TokenType);
+ const char* name() const;
+ static const char* name(TokenType);
+
+ const String& message() const { return m_message; }
+ const StringView& trivia() const { return m_trivia; }
+ const StringView& value() const { return m_value; }
+ size_t line_number() const { return m_line_number; }
+ size_t line_column() const { return m_line_column; }
+ double double_value() const;
+ bool bool_value() const;
+
+ enum class StringValueStatus {
+ Ok,
+ MalformedHexEscape,
+ MalformedUnicodeEscape,
+ UnicodeEscapeOverflow,
+ LegacyOctalEscapeSequence,
+ };
+ String string_value(StringValueStatus& status) const;
+
+ bool is_identifier_name() const;
+ bool trivia_contains_line_terminator() const;
+
+private:
+ TokenType m_type;
+ String m_message;
+ StringView m_trivia;
+ StringView m_value;
+ size_t m_line_number;
+ size_t m_line_column;
+};
+
+}
diff --git a/Userland/Libraries/LibKeyboard/CMakeLists.txt b/Userland/Libraries/LibKeyboard/CMakeLists.txt
new file mode 100644
index 0000000000..503720bcc5
--- /dev/null
+++ b/Userland/Libraries/LibKeyboard/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(SOURCES
+ CharacterMap.cpp
+ CharacterMapFile.cpp
+)
+
+serenity_lib(LibKeyboard keyboard)
+target_link_libraries(LibKeyboard LibCore)
diff --git a/Userland/Libraries/LibKeyboard/CharacterMap.cpp b/Userland/Libraries/LibKeyboard/CharacterMap.cpp
new file mode 100644
index 0000000000..9d5b186383
--- /dev/null
+++ b/Userland/Libraries/LibKeyboard/CharacterMap.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CharacterMap.h"
+#include <AK/StringBuilder.h>
+#include <Kernel/API/Syscall.h>
+#ifndef KERNEL
+# include <LibKeyboard/CharacterMapFile.h>
+#endif
+
+namespace Keyboard {
+
+CharacterMap::CharacterMap(const String& file_name)
+{
+#ifdef KERNEL
+ m_character_map_data = default_character_map;
+#else
+ auto result = CharacterMapFile::load_from_file(file_name);
+ ASSERT(result.has_value());
+
+ m_character_map_data = result.value();
+#endif
+ m_character_map_name = file_name;
+}
+
+#ifndef KERNEL
+
+int CharacterMap::set_system_map()
+{
+ Syscall::SC_setkeymap_params params { m_character_map_data.map, m_character_map_data.shift_map, m_character_map_data.alt_map, m_character_map_data.altgr_map, m_character_map_data.shift_altgr_map, { m_character_map_name.characters(), m_character_map_name.length() } };
+ return syscall(SC_setkeymap, &params);
+}
+
+#endif
+
+u32 CharacterMap::get_char(KeyEvent event)
+{
+ auto modifiers = event.modifiers();
+ auto index = event.scancode & 0xFF; // Index is last byte of scan code.
+ auto caps_lock_on = event.caps_lock_on;
+
+ u32 code_point;
+ if (modifiers & Mod_Alt)
+ code_point = m_character_map_data.alt_map[index];
+ else if ((modifiers & Mod_Shift) && (modifiers & Mod_AltGr))
+ code_point = m_character_map_data.shift_altgr_map[index];
+ else if (modifiers & Mod_Shift)
+ code_point = m_character_map_data.shift_map[index];
+ else if (modifiers & Mod_AltGr)
+ code_point = m_character_map_data.altgr_map[index];
+ else
+ code_point = m_character_map_data.map[index];
+
+ if (caps_lock_on && (modifiers == 0 || modifiers == Mod_Shift)) {
+ if (code_point >= 'a' && code_point <= 'z')
+ code_point &= ~0x20;
+ else if (code_point >= 'A' && code_point <= 'Z')
+ code_point |= 0x20;
+ }
+
+ if (event.e0_prefix && event.key == Key_Slash) {
+ // If Key_Slash (scancode = 0x35) mapped to other form "/", we fix num pad key of "/" with this case.
+ code_point = '/';
+ } else if (event.e0_prefix && event.key != Key_Return) {
+ // Except for `keypad-/` and 'keypad-return', all e0 scan codes are not actually characters. i.e., `keypad-0` and
+ // `Insert` have the same scancode except for the prefix, but insert should not have a code_point.
+ code_point = 0;
+ }
+
+ return code_point;
+}
+
+void CharacterMap::set_character_map_data(CharacterMapData character_map_data)
+{
+ m_character_map_data = character_map_data;
+}
+
+void CharacterMap::set_character_map_name(const String& character_map_name)
+{
+ m_character_map_name = character_map_name;
+}
+
+const String CharacterMap::character_map_name()
+{
+ return m_character_map_name;
+}
+}
diff --git a/Userland/Libraries/LibKeyboard/CharacterMap.h b/Userland/Libraries/LibKeyboard/CharacterMap.h
new file mode 100644
index 0000000000..413f7b4ded
--- /dev/null
+++ b/Userland/Libraries/LibKeyboard/CharacterMap.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <Kernel/API/KeyCode.h>
+#include <LibKeyboard/CharacterMapData.h>
+
+namespace Keyboard {
+
+class CharacterMap {
+
+public:
+ CharacterMap(const String& file_name);
+
+#ifndef KERNEL
+ int set_system_map();
+#endif
+
+ u32 get_char(KeyEvent);
+ void set_character_map_data(CharacterMapData character_map_data);
+ void set_character_map_name(const String& character_map_name);
+
+ const String character_map_name();
+
+private:
+ CharacterMapData m_character_map_data;
+ String m_character_map_name;
+};
+
+}
diff --git a/Userland/Libraries/LibKeyboard/CharacterMapData.h b/Userland/Libraries/LibKeyboard/CharacterMapData.h
new file mode 100644
index 0000000000..03e11e0983
--- /dev/null
+++ b/Userland/Libraries/LibKeyboard/CharacterMapData.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+
+#define CHAR_MAP_SIZE 0x80
+
+namespace Keyboard {
+
+struct CharacterMapData {
+ u32 map[CHAR_MAP_SIZE];
+ u32 shift_map[CHAR_MAP_SIZE];
+ u32 alt_map[CHAR_MAP_SIZE];
+ u32 altgr_map[CHAR_MAP_SIZE];
+ u32 shift_altgr_map[CHAR_MAP_SIZE];
+};
+
+// clang-format off
+static const CharacterMapData default_character_map =
+{
+ .map = {
+ 0, '\033', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 0x08,
+ '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
+ 0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0,
+ '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, '*', 0,
+ ' ', 0, 0,
+ //60 70 80
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.', 0, 0, '\\', 0, 0, 0,
+
+ },
+
+ .shift_map = {
+ 0, '\033', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', 0x08,
+ '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
+ 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', 0,
+ '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0, '*', 0,
+ ' ', 0, 0,
+ //60 70 80
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.', 0, 0, '|', 0, 0, 0,
+
+ },
+
+ .alt_map = {
+ 0, '\033', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 0x08,
+ '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
+ 0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0,
+ '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, '*', 0,
+ ' ', 0, 0,
+
+ //60 70 80
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.', 0, 0, '\\', 0, 0, 0,
+
+ },
+
+ .altgr_map = {
+ 0, '\033', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 0x08,
+ '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
+ 0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0,
+ '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, '*', 0,
+ ' ', 0, 0,
+ //60 70 80
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.', 0, 0, '\\', 0, 0, 0,
+ },
+
+ .shift_altgr_map = {
+ 0, '\033', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 0x08,
+ '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
+ 0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0,
+ '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, '*', 0,
+ ' ', 0, 0,
+ //60 70 80
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.', 0, 0, '\\', 0, 0, 0,
+ },
+};
+// clang-format on
+
+}
diff --git a/Userland/Libraries/LibKeyboard/CharacterMapFile.cpp b/Userland/Libraries/LibKeyboard/CharacterMapFile.cpp
new file mode 100644
index 0000000000..b6fdc593ed
--- /dev/null
+++ b/Userland/Libraries/LibKeyboard/CharacterMapFile.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CharacterMapFile.h"
+#include <AK/ByteBuffer.h>
+#include <AK/Utf8View.h>
+#include <LibCore/File.h>
+
+namespace Keyboard {
+
+Optional<CharacterMapData> CharacterMapFile::load_from_file(const String& file_name)
+{
+ auto path = file_name;
+ if (!path.ends_with(".json")) {
+ StringBuilder full_path;
+ full_path.append("/res/keymaps/");
+ full_path.append(file_name);
+ full_path.append(".json");
+ path = full_path.to_string();
+ }
+
+ auto file = Core::File::construct(path);
+ file->open(Core::IODevice::ReadOnly);
+ if (!file->is_open()) {
+ dbg() << "Failed to open " << file_name << ":" << file->error_string();
+ return {};
+ }
+
+ auto file_contents = file->read_all();
+ auto json_result = JsonValue::from_string(file_contents);
+ if (!json_result.has_value())
+ return {};
+ auto json = json_result.value().as_object();
+
+ Vector<u32> map = read_map(json, "map");
+ Vector<u32> shift_map = read_map(json, "shift_map");
+ Vector<u32> alt_map = read_map(json, "alt_map");
+ Vector<u32> altgr_map = read_map(json, "altgr_map");
+ Vector<u32> shift_altgr_map = read_map(json, "shift_altgr_map");
+
+ CharacterMapData character_map;
+ for (int i = 0; i < CHAR_MAP_SIZE; i++) {
+ character_map.map[i] = map.at(i);
+ character_map.shift_map[i] = shift_map.at(i);
+ character_map.alt_map[i] = alt_map.at(i);
+ if (altgr_map.is_empty()) {
+ // AltGr map was not found, using Alt map as fallback.
+ character_map.altgr_map[i] = alt_map.at(i);
+ } else {
+ character_map.altgr_map[i] = altgr_map.at(i);
+ }
+ if (shift_altgr_map.is_empty()) {
+ // Shift+AltGr map was not found, using Alt map as fallback.
+ character_map.shift_altgr_map[i] = alt_map.at(i);
+ } else {
+ character_map.shift_altgr_map[i] = shift_altgr_map.at(i);
+ }
+ }
+
+ return character_map;
+}
+
+Vector<u32> CharacterMapFile::read_map(const JsonObject& json, const String& name)
+{
+ if (!json.has(name))
+ return {};
+
+ Vector<u32> buffer;
+ buffer.resize(CHAR_MAP_SIZE);
+
+ auto map_arr = json.get(name).as_array();
+ for (int i = 0; i < map_arr.size(); i++) {
+ auto key_value = map_arr.at(i).as_string();
+ if (key_value.length() == 0) {
+ buffer[i] = 0;
+ } else if (key_value.length() == 1) {
+ buffer[i] = key_value.characters()[0];
+ } else {
+ Utf8View m_utf8_view(key_value.characters());
+ buffer[i] = *m_utf8_view.begin();
+ }
+ }
+
+ return buffer;
+}
+
+}
diff --git a/Userland/Libraries/LibKeyboard/CharacterMapFile.h b/Userland/Libraries/LibKeyboard/CharacterMapFile.h
new file mode 100644
index 0000000000..60aaacb637
--- /dev/null
+++ b/Userland/Libraries/LibKeyboard/CharacterMapFile.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/JsonObject.h>
+#include <LibKeyboard/CharacterMapData.h>
+
+namespace Keyboard {
+
+class CharacterMapFile {
+
+public:
+ static Optional<CharacterMapData> load_from_file(const String& file_name);
+
+private:
+ static Vector<u32> read_map(const JsonObject& json, const String& name);
+};
+
+}
diff --git a/Userland/Libraries/LibLine/CMakeLists.txt b/Userland/Libraries/LibLine/CMakeLists.txt
new file mode 100644
index 0000000000..41da2dee0b
--- /dev/null
+++ b/Userland/Libraries/LibLine/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(SOURCES
+ Editor.cpp
+ InternalFunctions.cpp
+ KeyCallbackMachine.cpp
+ SuggestionManager.cpp
+ XtermSuggestionDisplay.cpp
+)
+
+serenity_lib(LibLine line)
+target_link_libraries(LibLine LibC LibCore)
diff --git a/Userland/Libraries/LibLine/Editor.cpp b/Userland/Libraries/LibLine/Editor.cpp
new file mode 100644
index 0000000000..0ac1fe100c
--- /dev/null
+++ b/Userland/Libraries/LibLine/Editor.cpp
@@ -0,0 +1,1769 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Editor.h"
+#include <AK/GenericLexer.h>
+#include <AK/JsonObject.h>
+#include <AK/ScopeGuard.h>
+#include <AK/StringBuilder.h>
+#include <AK/Utf32View.h>
+#include <AK/Utf8View.h>
+#include <LibCore/ConfigFile.h>
+#include <LibCore/Event.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/File.h>
+#include <LibCore/Notifier.h>
+#include <ctype.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+// #define SUGGESTIONS_DEBUG
+
+namespace {
+constexpr u32 ctrl(char c) { return c & 0x3f; }
+}
+
+namespace Line {
+
+Configuration Configuration::from_config(const StringView& libname)
+{
+ Configuration configuration;
+ auto config_file = Core::ConfigFile::get_for_lib(libname);
+
+ // Read behaviour options.
+ auto refresh = config_file->read_entry("behaviour", "refresh", "lazy");
+ auto operation = config_file->read_entry("behaviour", "operation_mode");
+
+ if (refresh.equals_ignoring_case("lazy"))
+ configuration.set(Configuration::Lazy);
+ else if (refresh.equals_ignoring_case("eager"))
+ configuration.set(Configuration::Eager);
+
+ if (operation.equals_ignoring_case("full"))
+ configuration.set(Configuration::OperationMode::Full);
+ else if (operation.equals_ignoring_case("noescapesequences"))
+ configuration.set(Configuration::OperationMode::NoEscapeSequences);
+ else if (operation.equals_ignoring_case("noninteractive"))
+ configuration.set(Configuration::OperationMode::NonInteractive);
+ else
+ configuration.set(Configuration::OperationMode::Unset);
+
+ // Read keybinds.
+
+ for (auto& binding_key : config_file->keys("keybinds")) {
+ GenericLexer key_lexer(binding_key);
+ auto has_ctrl = false;
+ auto alt = false;
+ auto escape = false;
+ Vector<Key> keys;
+
+ while (!key_lexer.is_eof()) {
+ unsigned key;
+ if (escape) {
+ key = key_lexer.consume_escaped_character();
+ escape = false;
+ } else {
+ if (key_lexer.next_is("alt+")) {
+ alt = key_lexer.consume_specific("alt+");
+ continue;
+ }
+ if (key_lexer.next_is("^[")) {
+ alt = key_lexer.consume_specific("^[");
+ continue;
+ }
+ if (key_lexer.next_is("^")) {
+ has_ctrl = key_lexer.consume_specific("^");
+ continue;
+ }
+ if (key_lexer.next_is("ctrl+")) {
+ has_ctrl = key_lexer.consume_specific("ctrl+");
+ continue;
+ }
+ if (key_lexer.next_is("\\")) {
+ escape = true;
+ continue;
+ }
+ // FIXME: Support utf?
+ key = key_lexer.consume();
+ }
+ if (has_ctrl)
+ key = ctrl(key);
+
+ keys.append(Key { key, alt ? Key::Alt : Key::None });
+ alt = false;
+ has_ctrl = false;
+ }
+
+ GenericLexer value_lexer { config_file->read_entry("keybinds", binding_key) };
+ StringBuilder value_builder;
+ while (!value_lexer.is_eof())
+ value_builder.append(value_lexer.consume_escaped_character());
+ auto value = value_builder.string_view();
+ if (value.starts_with("internal:")) {
+ configuration.set(KeyBinding {
+ keys,
+ KeyBinding::Kind::InternalFunction,
+ value.substring_view(9, value.length() - 9) });
+ } else {
+ configuration.set(KeyBinding {
+ keys,
+ KeyBinding::Kind::Insertion,
+ value });
+ }
+ }
+
+ return configuration;
+}
+
+void Editor::set_default_keybinds()
+{
+ register_key_input_callback(ctrl('N'), EDITOR_INTERNAL_FUNCTION(search_forwards));
+ register_key_input_callback(ctrl('P'), EDITOR_INTERNAL_FUNCTION(search_backwards));
+ // Normally ^W. `stty werase \^n` can change it to ^N (or something else), but Serenity doesn't have `stty` yet.
+ register_key_input_callback(m_termios.c_cc[VWERASE], EDITOR_INTERNAL_FUNCTION(erase_word_backwards));
+ // Normally ^U. `stty kill \^n` can change it to ^N (or something else), but Serenity doesn't have `stty` yet.
+ register_key_input_callback(m_termios.c_cc[VKILL], EDITOR_INTERNAL_FUNCTION(kill_line));
+ register_key_input_callback(ctrl('A'), EDITOR_INTERNAL_FUNCTION(go_home));
+ register_key_input_callback(ctrl('B'), EDITOR_INTERNAL_FUNCTION(cursor_left_character));
+ register_key_input_callback(ctrl('D'), EDITOR_INTERNAL_FUNCTION(erase_character_forwards));
+ register_key_input_callback(ctrl('E'), EDITOR_INTERNAL_FUNCTION(go_end));
+ register_key_input_callback(ctrl('F'), EDITOR_INTERNAL_FUNCTION(cursor_right_character));
+ // ^H: ctrl('H') == '\b'
+ register_key_input_callback(ctrl('H'), EDITOR_INTERNAL_FUNCTION(erase_character_backwards));
+ register_key_input_callback(m_termios.c_cc[VERASE], EDITOR_INTERNAL_FUNCTION(erase_character_backwards));
+ register_key_input_callback(ctrl('K'), EDITOR_INTERNAL_FUNCTION(erase_to_end));
+ register_key_input_callback(ctrl('L'), EDITOR_INTERNAL_FUNCTION(clear_screen));
+ register_key_input_callback(ctrl('R'), EDITOR_INTERNAL_FUNCTION(enter_search));
+ register_key_input_callback(ctrl('T'), EDITOR_INTERNAL_FUNCTION(transpose_characters));
+ register_key_input_callback('\n', EDITOR_INTERNAL_FUNCTION(finish));
+
+ // ^[.: alt-.: insert last arg of previous command (similar to `!$`)
+ register_key_input_callback(Key { '.', Key::Alt }, EDITOR_INTERNAL_FUNCTION(insert_last_words));
+ register_key_input_callback(Key { 'b', Key::Alt }, EDITOR_INTERNAL_FUNCTION(cursor_left_word));
+ register_key_input_callback(Key { 'f', Key::Alt }, EDITOR_INTERNAL_FUNCTION(cursor_right_word));
+ // ^[^H: alt-backspace: backward delete word
+ register_key_input_callback(Key { '\b', Key::Alt }, EDITOR_INTERNAL_FUNCTION(erase_alnum_word_backwards));
+ register_key_input_callback(Key { 'd', Key::Alt }, EDITOR_INTERNAL_FUNCTION(erase_alnum_word_forwards));
+ register_key_input_callback(Key { 'c', Key::Alt }, EDITOR_INTERNAL_FUNCTION(capitalize_word));
+ register_key_input_callback(Key { 'l', Key::Alt }, EDITOR_INTERNAL_FUNCTION(lowercase_word));
+ register_key_input_callback(Key { 'u', Key::Alt }, EDITOR_INTERNAL_FUNCTION(uppercase_word));
+ register_key_input_callback(Key { 't', Key::Alt }, EDITOR_INTERNAL_FUNCTION(transpose_words));
+}
+
+Editor::Editor(Configuration configuration)
+ : m_configuration(move(configuration))
+{
+ m_always_refresh = m_configuration.refresh_behaviour == Configuration::RefreshBehaviour::Eager;
+ m_pending_chars = ByteBuffer::create_uninitialized(0);
+ get_terminal_size();
+ m_suggestion_display = make<XtermSuggestionDisplay>(m_num_lines, m_num_columns);
+}
+
+Editor::~Editor()
+{
+ if (m_initialized)
+ restore();
+}
+
+void Editor::get_terminal_size()
+{
+ struct winsize ws;
+ if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) < 0) {
+ m_num_columns = 80;
+ m_num_lines = 25;
+ } else {
+ m_num_columns = ws.ws_col;
+ m_num_lines = ws.ws_row;
+ }
+}
+
+void Editor::add_to_history(const String& line)
+{
+ if (line.is_empty())
+ return;
+ String histcontrol = getenv("HISTCONTROL");
+ auto ignoredups = histcontrol == "ignoredups" || histcontrol == "ignoreboth";
+ auto ignorespace = histcontrol == "ignorespace" || histcontrol == "ignoreboth";
+ if (ignoredups && !m_history.is_empty() && line == m_history.last().entry)
+ return;
+ if (ignorespace && line.starts_with(' '))
+ return;
+ if ((m_history.size() + 1) > m_history_capacity)
+ m_history.take_first();
+ struct timeval tv;
+ gettimeofday(&tv, nullptr);
+ m_history.append({ line, tv.tv_sec });
+}
+
+bool Editor::load_history(const String& path)
+{
+ auto history_file = Core::File::construct(path);
+ if (!history_file->open(Core::IODevice::ReadOnly))
+ return false;
+ auto data = history_file->read_all();
+ auto hist = StringView { data.data(), data.size() };
+ for (auto& str : hist.split_view("\n\n")) {
+ auto it = str.find_first_of("::").value_or(0);
+ auto time = str.substring_view(0, it).to_uint<time_t>().value_or(0);
+ auto string = str.substring_view(it == 0 ? it : it + 2);
+ m_history.append({ string, time });
+ }
+ return true;
+}
+
+template<typename It0, typename It1, typename OutputT, typename MapperT, typename LessThan>
+static void merge(It0&& begin0, const It0& end0, It1&& begin1, const It1& end1, OutputT& output, MapperT left_mapper, LessThan less_than)
+{
+ for (;;) {
+ if (begin0 == end0 && begin1 == end1)
+ return;
+
+ if (begin0 == end0) {
+ auto&& right = *begin1;
+ if (output.last().entry != right.entry)
+ output.append(right);
+ ++begin1;
+ continue;
+ }
+
+ auto&& left = left_mapper(*begin0);
+ if (left.entry.is_whitespace()) {
+ ++begin0;
+ continue;
+ }
+ if (begin1 == end1) {
+ if (output.last().entry != left.entry)
+ output.append(left);
+ ++begin0;
+ continue;
+ }
+
+ auto&& right = *begin1;
+ if (less_than(left, right)) {
+ if (output.last().entry != left.entry)
+ output.append(left);
+ ++begin0;
+ } else {
+ if (output.last().entry != right.entry)
+ output.append(right);
+ ++begin1;
+ if (right.entry == left.entry)
+ ++begin0;
+ }
+ }
+}
+
+bool Editor::save_history(const String& path)
+{
+ Vector<HistoryEntry> final_history { { "", 0 } };
+ {
+ auto file_or_error = Core::File::open(path, Core::IODevice::ReadWrite, 0600);
+ if (file_or_error.is_error())
+ return false;
+ auto file = file_or_error.release_value();
+ merge(
+ file->line_begin(), file->line_end(), m_history.begin(), m_history.end(), final_history,
+ [](StringView str) {
+ auto it = str.find_first_of("::").value_or(0);
+ auto time = str.substring_view(0, it).to_uint<time_t>().value_or(0);
+ auto string = str.substring_view(it == 0 ? it : it + 2);
+ return HistoryEntry { string, time };
+ },
+ [](const HistoryEntry& left, const HistoryEntry& right) { return left.timestamp < right.timestamp; });
+ }
+
+ auto file_or_error = Core::File::open(path, Core::IODevice::WriteOnly, 0600);
+ if (file_or_error.is_error())
+ return false;
+ auto file = file_or_error.release_value();
+ final_history.take_first();
+ for (const auto& entry : final_history)
+ file->write(String::formatted("{}::{}\n\n", entry.timestamp, entry.entry));
+
+ return true;
+}
+
+void Editor::clear_line()
+{
+ for (size_t i = 0; i < m_cursor; ++i)
+ fputc(0x8, stderr);
+ fputs("\033[K", stderr);
+ fflush(stderr);
+ m_buffer.clear();
+ m_cursor = 0;
+ m_inline_search_cursor = m_cursor;
+}
+
+void Editor::insert(const Utf32View& string)
+{
+ for (size_t i = 0; i < string.length(); ++i)
+ insert(string.code_points()[i]);
+}
+
+void Editor::insert(const String& string)
+{
+ for (auto ch : Utf8View { string })
+ insert(ch);
+}
+
+void Editor::insert(const StringView& string_view)
+{
+ for (auto ch : Utf8View { string_view })
+ insert(ch);
+}
+
+void Editor::insert(const u32 cp)
+{
+ StringBuilder builder;
+ builder.append(Utf32View(&cp, 1));
+ auto str = builder.build();
+ m_pending_chars.append(str.characters(), str.length());
+
+ readjust_anchored_styles(m_cursor, ModificationKind::Insertion);
+
+ if (m_cursor == m_buffer.size()) {
+ m_buffer.append(cp);
+ m_cursor = m_buffer.size();
+ m_inline_search_cursor = m_cursor;
+ return;
+ }
+
+ m_buffer.insert(m_cursor, cp);
+ ++m_chars_inserted_in_the_middle;
+ ++m_cursor;
+ m_inline_search_cursor = m_cursor;
+}
+
+void Editor::register_key_input_callback(const KeyBinding& binding)
+{
+ if (binding.kind == KeyBinding::Kind::InternalFunction) {
+ auto internal_function = find_internal_function(binding.binding);
+ if (!internal_function) {
+ dbg() << "LibLine: Unknown internal function '" << binding.binding << "'";
+ return;
+ }
+ return register_key_input_callback(binding.keys, move(internal_function));
+ }
+
+ return register_key_input_callback(binding.keys, [binding = String(binding.binding)](auto& editor) {
+ editor.insert(binding);
+ return false;
+ });
+}
+
+static size_t code_point_length_in_utf8(u32 code_point)
+{
+ if (code_point <= 0x7f)
+ return 1;
+ if (code_point <= 0x07ff)
+ return 2;
+ if (code_point <= 0xffff)
+ return 3;
+ if (code_point <= 0x10ffff)
+ return 4;
+ return 3;
+}
+
+// buffer [ 0 1 2 3 . . . A . . . B . . . M . . . N ]
+// ^ ^ ^ ^
+// | | | +- end of buffer
+// | | +- scan offset = M
+// | +- range end = M - B
+// +- range start = M - A
+// This method converts a byte range defined by [start_byte_offset, end_byte_offset] to a code_point range [M - A, M - B] as shown in the diagram above.
+// If `reverse' is true, A and B are before M, if not, A and B are after M.
+Editor::CodepointRange Editor::byte_offset_range_to_code_point_offset_range(size_t start_byte_offset, size_t end_byte_offset, size_t scan_code_point_offset, bool reverse) const
+{
+ size_t byte_offset = 0;
+ size_t code_point_offset = scan_code_point_offset + (reverse ? 1 : 0);
+ CodepointRange range;
+
+ for (;;) {
+ if (!reverse) {
+ if (code_point_offset >= m_buffer.size())
+ break;
+ } else {
+ if (code_point_offset == 0)
+ break;
+ }
+
+ if (byte_offset > end_byte_offset)
+ break;
+
+ if (byte_offset < start_byte_offset)
+ ++range.start;
+
+ if (byte_offset < end_byte_offset)
+ ++range.end;
+
+ byte_offset += code_point_length_in_utf8(m_buffer[reverse ? --code_point_offset : code_point_offset++]);
+ }
+
+ return range;
+}
+
+void Editor::stylize(const Span& span, const Style& style)
+{
+ if (style.is_empty())
+ return;
+
+ auto start = span.beginning();
+ auto end = span.end();
+
+ if (span.mode() == Span::ByteOriented) {
+ auto offsets = byte_offset_range_to_code_point_offset_range(start, end, 0);
+
+ start = offsets.start;
+ end = offsets.end;
+ }
+
+ auto& spans_starting = style.is_anchored() ? m_anchored_spans_starting : m_spans_starting;
+ auto& spans_ending = style.is_anchored() ? m_anchored_spans_ending : m_spans_ending;
+
+ auto starting_map = spans_starting.get(start).value_or({});
+
+ if (!starting_map.contains(end))
+ m_refresh_needed = true;
+
+ starting_map.set(end, style);
+
+ spans_starting.set(start, starting_map);
+
+ auto ending_map = spans_ending.get(end).value_or({});
+
+ if (!ending_map.contains(start))
+ m_refresh_needed = true;
+ ending_map.set(start, style);
+
+ spans_ending.set(end, ending_map);
+}
+
+void Editor::suggest(size_t invariant_offset, size_t static_offset, Span::Mode offset_mode) const
+{
+ auto internal_static_offset = static_offset;
+ auto internal_invariant_offset = invariant_offset;
+ if (offset_mode == Span::Mode::ByteOriented) {
+ // FIXME: We're assuming that invariant_offset points to the end of the available data
+ // this is not necessarily true, but is true in most cases.
+ auto offsets = byte_offset_range_to_code_point_offset_range(internal_static_offset, internal_invariant_offset + internal_static_offset, m_cursor - 1, true);
+
+ internal_static_offset = offsets.start;
+ internal_invariant_offset = offsets.end - offsets.start;
+ }
+ m_suggestion_manager.set_suggestion_variants(internal_static_offset, internal_invariant_offset, 0);
+}
+
+void Editor::initialize()
+{
+ if (m_initialized)
+ return;
+
+ struct termios termios;
+ tcgetattr(0, &termios);
+ m_default_termios = termios; // grab a copy to restore
+ if (m_was_resized)
+ get_terminal_size();
+
+ if (m_configuration.operation_mode == Configuration::Unset) {
+ auto istty = isatty(STDIN_FILENO) && isatty(STDERR_FILENO);
+ if (!istty) {
+ m_configuration.set(Configuration::NonInteractive);
+ } else {
+ auto* term = getenv("TERM");
+ if (StringView { term }.starts_with("xterm"))
+ m_configuration.set(Configuration::Full);
+ else
+ m_configuration.set(Configuration::NoEscapeSequences);
+ }
+ }
+
+ // Because we use our own line discipline which includes echoing,
+ // we disable ICANON and ECHO.
+ if (m_configuration.operation_mode == Configuration::Full) {
+ termios.c_lflag &= ~(ECHO | ICANON);
+ tcsetattr(0, TCSANOW, &termios);
+ }
+
+ m_termios = termios;
+
+ set_default_keybinds();
+ for (auto& keybind : m_configuration.keybindings)
+ register_key_input_callback(keybind);
+
+ if (m_configuration.m_signal_mode == Configuration::WithSignalHandlers) {
+ m_signal_handlers.append(Core::EventLoop::register_signal(SIGINT, [this](int) {
+ interrupted();
+ }));
+
+ m_signal_handlers.append(Core::EventLoop::register_signal(SIGWINCH, [this](int) {
+ resized();
+ }));
+ }
+
+ m_initialized = true;
+}
+
+void Editor::interrupted()
+{
+ if (m_is_searching)
+ return m_search_editor->interrupted();
+
+ if (!m_is_editing)
+ return;
+
+ m_was_interrupted = true;
+ handle_interrupt_event();
+ if (!m_finish)
+ return;
+
+ m_finish = false;
+ reposition_cursor(true);
+ if (m_suggestion_display->cleanup())
+ reposition_cursor(true);
+ fprintf(stderr, "\n");
+ fflush(stderr);
+ m_buffer.clear();
+ m_is_editing = false;
+ restore();
+ m_notifier->set_enabled(false);
+ deferred_invoke([this](auto&) {
+ remove_child(*m_notifier);
+ m_notifier = nullptr;
+ Core::EventLoop::current().quit(Retry);
+ });
+}
+
+void Editor::really_quit_event_loop()
+{
+ m_finish = false;
+ reposition_cursor(true);
+ fprintf(stderr, "\n");
+ fflush(stderr);
+ auto string = line();
+ m_buffer.clear();
+ m_is_editing = false;
+ restore();
+
+ m_returned_line = string;
+
+ m_notifier->set_enabled(false);
+ deferred_invoke([this](auto&) {
+ remove_child(*m_notifier);
+ m_notifier = nullptr;
+ Core::EventLoop::current().quit(Exit);
+ });
+}
+
+auto Editor::get_line(const String& prompt) -> Result<String, Editor::Error>
+{
+ initialize();
+ m_is_editing = true;
+
+ if (m_configuration.operation_mode == Configuration::NoEscapeSequences || m_configuration.operation_mode == Configuration::NonInteractive) {
+ // Do not use escape sequences, instead, use LibC's getline.
+ size_t size = 0;
+ char* line = nullptr;
+ // Show the prompt only on interactive mode (NoEscapeSequences in this case).
+ if (m_configuration.operation_mode != Configuration::NonInteractive)
+ fputs(prompt.characters(), stderr);
+ auto line_length = getline(&line, &size, stdin);
+ // getline() returns -1 and sets errno=0 on EOF.
+ if (line_length == -1) {
+ if (line)
+ free(line);
+ if (errno == 0)
+ return Error::Eof;
+
+ return Error::ReadFailure;
+ }
+ restore();
+ if (line) {
+ String result { line, (size_t)line_length, Chomp };
+ free(line);
+ return result;
+ }
+
+ return Error::ReadFailure;
+ }
+
+ set_prompt(prompt);
+ reset();
+ strip_styles(true);
+
+ auto prompt_lines = max(current_prompt_metrics().line_metrics.size(), 1ul) - 1;
+ for (size_t i = 0; i < prompt_lines; ++i)
+ putc('\n', stderr);
+
+ VT::move_relative(-prompt_lines, 0);
+
+ set_origin();
+
+ m_history_cursor = m_history.size();
+
+ refresh_display();
+
+ Core::EventLoop loop;
+
+ m_notifier = Core::Notifier::construct(STDIN_FILENO, Core::Notifier::Read);
+ add_child(*m_notifier);
+
+ m_notifier->on_ready_to_read = [&] { try_update_once(); };
+ if (!m_incomplete_data.is_empty())
+ deferred_invoke([&](auto&) { try_update_once(); });
+
+ if (loop.exec() == Retry)
+ return get_line(prompt);
+
+ return m_input_error.has_value() ? Result<String, Editor::Error> { m_input_error.value() } : Result<String, Editor::Error> { m_returned_line };
+}
+
+void Editor::save_to(JsonObject& object)
+{
+ Core::Object::save_to(object);
+ object.set("is_searching", m_is_searching);
+ object.set("is_editing", m_is_editing);
+ object.set("cursor_offset", m_cursor);
+ object.set("needs_refresh", m_refresh_needed);
+ object.set("unprocessed_characters", m_incomplete_data.size());
+ object.set("history_size", m_history.size());
+ object.set("current_prompt", m_new_prompt);
+ object.set("was_interrupted", m_was_interrupted);
+ JsonObject display_area;
+ display_area.set("top_left_row", m_origin_row);
+ display_area.set("top_left_column", m_origin_column);
+ display_area.set("line_count", num_lines());
+ object.set("used_display_area", move(display_area));
+}
+
+void Editor::try_update_once()
+{
+ if (m_was_interrupted) {
+ handle_interrupt_event();
+ }
+
+ handle_read_event();
+
+ if (m_always_refresh)
+ m_refresh_needed = true;
+
+ refresh_display();
+
+ if (m_finish)
+ really_quit_event_loop();
+}
+
+void Editor::handle_interrupt_event()
+{
+ m_was_interrupted = false;
+
+ m_callback_machine.interrupted(*this);
+ if (!m_callback_machine.should_process_last_pressed_key())
+ return;
+
+ fprintf(stderr, "^C");
+ fflush(stderr);
+
+ if (on_interrupt_handled)
+ on_interrupt_handled();
+
+ m_buffer.clear();
+ m_cursor = 0;
+
+ finish();
+}
+
+void Editor::handle_read_event()
+{
+ char keybuf[16];
+ ssize_t nread = 0;
+
+ if (!m_incomplete_data.size())
+ nread = read(0, keybuf, sizeof(keybuf));
+
+ if (nread < 0) {
+ if (errno == EINTR) {
+ if (!m_was_interrupted) {
+ if (m_was_resized)
+ return;
+
+ finish();
+ return;
+ }
+
+ handle_interrupt_event();
+ return;
+ }
+
+ ScopedValueRollback errno_restorer(errno);
+ perror("read failed");
+
+ m_input_error = Error::ReadFailure;
+ finish();
+ return;
+ }
+
+ m_incomplete_data.append(keybuf, nread);
+ nread = m_incomplete_data.size();
+
+ if (nread == 0) {
+ m_input_error = Error::Empty;
+ finish();
+ return;
+ }
+
+ auto reverse_tab = false;
+
+ // Discard starting bytes until they make sense as utf-8.
+ size_t valid_bytes = 0;
+ while (nread) {
+ Utf8View { StringView { m_incomplete_data.data(), (size_t)nread } }.validate(valid_bytes);
+ if (valid_bytes)
+ break;
+ m_incomplete_data.take_first();
+ --nread;
+ }
+
+ Utf8View input_view { StringView { m_incomplete_data.data(), valid_bytes } };
+ size_t consumed_code_points = 0;
+
+ Vector<u8, 4> csi_parameter_bytes;
+ Vector<unsigned, 4> csi_parameters;
+ Vector<u8> csi_intermediate_bytes;
+ u8 csi_final;
+ enum CSIMod {
+ Shift = 1,
+ Alt = 2,
+ Ctrl = 4,
+ };
+
+ for (auto code_point : input_view) {
+ if (m_finish)
+ break;
+
+ ++consumed_code_points;
+
+ if (code_point == 0)
+ continue;
+
+ switch (m_state) {
+ case InputState::GotEscape:
+ switch (code_point) {
+ case '[':
+ m_state = InputState::CSIExpectParameter;
+ continue;
+ default: {
+ m_callback_machine.key_pressed(*this, { code_point, Key::Alt });
+ m_state = InputState::Free;
+ cleanup_suggestions();
+ continue;
+ }
+ }
+ case InputState::CSIExpectParameter:
+ if (code_point >= 0x30 && code_point <= 0x3f) { // '0123456789:;<=>?'
+ csi_parameter_bytes.append(code_point);
+ continue;
+ }
+ m_state = InputState::CSIExpectIntermediate;
+ [[fallthrough]];
+ case InputState::CSIExpectIntermediate:
+ if (code_point >= 0x20 && code_point <= 0x2f) { // ' !"#$%&\'()*+,-./'
+ csi_intermediate_bytes.append(code_point);
+ continue;
+ }
+ m_state = InputState::CSIExpectFinal;
+ [[fallthrough]];
+ case InputState::CSIExpectFinal: {
+ m_state = InputState::Free;
+ if (!(code_point >= 0x40 && code_point <= 0x7f)) {
+ dbgln("LibLine: Invalid CSI: {:02x} ({:c})", code_point, code_point);
+ continue;
+ }
+ csi_final = code_point;
+
+ for (auto& parameter : String::copy(csi_parameter_bytes).split(';')) {
+ if (auto value = parameter.to_uint(); value.has_value())
+ csi_parameters.append(value.value());
+ else
+ csi_parameters.append(0);
+ }
+ unsigned param1 = 0, param2 = 0;
+ if (csi_parameters.size() >= 1)
+ param1 = csi_parameters[0];
+ if (csi_parameters.size() >= 2)
+ param2 = csi_parameters[1];
+ unsigned modifiers = param2 ? param2 - 1 : 0;
+
+ if (csi_final == 'Z') {
+ // 'reverse tab'
+ reverse_tab = true;
+ break;
+ }
+ cleanup_suggestions();
+
+ switch (csi_final) {
+ case 'A': // ^[[A: arrow up
+ search_backwards();
+ continue;
+ case 'B': // ^[[B: arrow down
+ search_forwards();
+ continue;
+ case 'D': // ^[[D: arrow left
+ if (modifiers == CSIMod::Alt || modifiers == CSIMod::Ctrl)
+ cursor_left_word();
+ else
+ cursor_left_character();
+ continue;
+ case 'C': // ^[[C: arrow right
+ if (modifiers == CSIMod::Alt || modifiers == CSIMod::Ctrl)
+ cursor_right_word();
+ else
+ cursor_right_character();
+ continue;
+ case 'H': // ^[[H: home
+ go_home();
+ continue;
+ case 'F': // ^[[F: end
+ go_end();
+ continue;
+ case '~':
+ if (param1 == 3) { // ^[[3~: delete
+ if (modifiers == CSIMod::Ctrl)
+ erase_alnum_word_forwards();
+ else
+ erase_character_forwards();
+ m_search_offset = 0;
+ continue;
+ }
+ // ^[[5~: page up
+ // ^[[6~: page down
+ dbgln("LibLine: Unhandled '~': {}", param1);
+ continue;
+ default:
+ dbgln("LibLine: Unhandled final: {:02x} ({:c})", code_point, code_point);
+ continue;
+ }
+ break;
+ }
+ case InputState::Verbatim:
+ m_state = InputState::Free;
+ // Verbatim mode will bypass all mechanisms and just insert the code point.
+ insert(code_point);
+ continue;
+ case InputState::Free:
+ if (code_point == 27) {
+ m_callback_machine.key_pressed(*this, code_point);
+ // Note that this should also deal with explicitly registered keys
+ // that would otherwise be interpreted as escapes.
+ if (m_callback_machine.should_process_last_pressed_key())
+ m_state = InputState::GotEscape;
+ continue;
+ }
+ if (code_point == 22) { // ^v
+ m_callback_machine.key_pressed(*this, code_point);
+ if (m_callback_machine.should_process_last_pressed_key())
+ m_state = InputState::Verbatim;
+ continue;
+ }
+ break;
+ }
+
+ // There are no sequences past this point, so short of 'tab', we will want to cleanup the suggestions.
+ ArmedScopeGuard suggestion_cleanup { [this] { cleanup_suggestions(); } };
+
+ // Normally ^D. `stty eof \^n` can change it to ^N (or something else), but Serenity doesn't have `stty` yet.
+ // Process this here since the keybinds might override its behaviour.
+ // This only applies when the buffer is empty. at any other time, the behaviour should be configurable.
+ if (code_point == m_termios.c_cc[VEOF] && m_buffer.size() == 0) {
+ finish_edit();
+ continue;
+ }
+
+ m_callback_machine.key_pressed(*this, code_point);
+ if (!m_callback_machine.should_process_last_pressed_key())
+ continue;
+
+ m_search_offset = 0; // reset search offset on any key
+
+ if (code_point == '\t' || reverse_tab) {
+ suggestion_cleanup.disarm();
+
+ if (!on_tab_complete)
+ continue;
+
+ // Reverse tab can count as regular tab here.
+ m_times_tab_pressed++;
+
+ int token_start = m_cursor;
+
+ // Ask for completions only on the first tab
+ // and scan for the largest common prefix to display,
+ // further tabs simply show the cached completions.
+ if (m_times_tab_pressed == 1) {
+ m_suggestion_manager.set_suggestions(on_tab_complete(*this));
+ m_prompt_lines_at_suggestion_initiation = num_lines();
+ if (m_suggestion_manager.count() == 0) {
+ // There are no suggestions, beep.
+ fputc('\a', stderr);
+ fflush(stderr);
+ }
+ }
+
+ // Adjust already incremented / decremented index when switching tab direction.
+ if (reverse_tab && m_tab_direction != TabDirection::Backward) {
+ m_suggestion_manager.previous();
+ m_suggestion_manager.previous();
+ m_tab_direction = TabDirection::Backward;
+ }
+ if (!reverse_tab && m_tab_direction != TabDirection::Forward) {
+ m_suggestion_manager.next();
+ m_suggestion_manager.next();
+ m_tab_direction = TabDirection::Forward;
+ }
+ reverse_tab = false;
+
+ SuggestionManager::CompletionMode completion_mode;
+ switch (m_times_tab_pressed) {
+ case 1:
+ completion_mode = SuggestionManager::CompletePrefix;
+ break;
+ case 2:
+ completion_mode = SuggestionManager::ShowSuggestions;
+ break;
+ default:
+ completion_mode = SuggestionManager::CycleSuggestions;
+ break;
+ }
+
+ auto completion_result = m_suggestion_manager.attempt_completion(completion_mode, token_start);
+
+ auto new_cursor = m_cursor + completion_result.new_cursor_offset;
+ for (size_t i = completion_result.offset_region_to_remove.start; i < completion_result.offset_region_to_remove.end; ++i)
+ remove_at_index(new_cursor);
+
+ m_cursor = new_cursor;
+ m_inline_search_cursor = new_cursor;
+ m_refresh_needed = true;
+
+ for (auto& view : completion_result.insert)
+ insert(view);
+
+ if (completion_result.style_to_apply.has_value()) {
+ // Apply the style of the last suggestion.
+ readjust_anchored_styles(m_suggestion_manager.current_suggestion().start_index, ModificationKind::ForcedOverlapRemoval);
+ stylize({ m_suggestion_manager.current_suggestion().start_index, m_cursor, Span::Mode::CodepointOriented }, completion_result.style_to_apply.value());
+ }
+
+ switch (completion_result.new_completion_mode) {
+ case SuggestionManager::DontComplete:
+ m_times_tab_pressed = 0;
+ break;
+ case SuggestionManager::CompletePrefix:
+ break;
+ default:
+ ++m_times_tab_pressed;
+ break;
+ }
+
+ if (m_times_tab_pressed > 1) {
+ if (m_suggestion_manager.count() > 0) {
+ if (m_suggestion_display->cleanup())
+ reposition_cursor();
+
+ m_suggestion_display->set_initial_prompt_lines(m_prompt_lines_at_suggestion_initiation);
+
+ m_suggestion_display->display(m_suggestion_manager);
+
+ m_origin_row = m_suggestion_display->origin_row();
+ }
+ }
+
+ if (m_times_tab_pressed > 2) {
+ if (m_tab_direction == TabDirection::Forward)
+ m_suggestion_manager.next();
+ else
+ m_suggestion_manager.previous();
+ }
+
+ if (m_suggestion_manager.count() < 2) {
+ // We have none, or just one suggestion,
+ // we should just commit that and continue
+ // after it, as if it were auto-completed.
+ suggest(0, 0, Span::CodepointOriented);
+ m_times_tab_pressed = 0;
+ m_suggestion_manager.reset();
+ m_suggestion_display->finish();
+ }
+ continue;
+ }
+
+ insert(code_point);
+ }
+
+ if (consumed_code_points == m_incomplete_data.size()) {
+ m_incomplete_data.clear();
+ } else {
+ for (size_t i = 0; i < consumed_code_points; ++i)
+ m_incomplete_data.take_first();
+ }
+
+ if (!m_incomplete_data.is_empty() && !m_finish)
+ deferred_invoke([&](auto&) { try_update_once(); });
+}
+
+void Editor::cleanup_suggestions()
+{
+ if (m_times_tab_pressed) {
+ // Apply the style of the last suggestion.
+ readjust_anchored_styles(m_suggestion_manager.current_suggestion().start_index, ModificationKind::ForcedOverlapRemoval);
+ stylize({ m_suggestion_manager.current_suggestion().start_index, m_cursor, Span::Mode::CodepointOriented }, m_suggestion_manager.current_suggestion().style);
+ // We probably have some suggestions drawn,
+ // let's clean them up.
+ if (m_suggestion_display->cleanup()) {
+ reposition_cursor();
+ m_refresh_needed = true;
+ }
+ m_suggestion_manager.reset();
+ suggest(0, 0, Span::CodepointOriented);
+ m_suggestion_display->finish();
+ }
+ m_times_tab_pressed = 0; // Safe to say if we get here, the user didn't press TAB
+}
+
+bool Editor::search(const StringView& phrase, bool allow_empty, bool from_beginning)
+{
+
+ int last_matching_offset = -1;
+ bool found = false;
+
+ // Do not search for empty strings.
+ if (allow_empty || phrase.length() > 0) {
+ size_t search_offset = m_search_offset;
+ for (size_t i = m_history_cursor; i > 0; --i) {
+ auto& entry = m_history[i - 1];
+ auto contains = from_beginning ? entry.entry.starts_with(phrase) : entry.entry.contains(phrase);
+ if (contains) {
+ last_matching_offset = i - 1;
+ if (search_offset == 0) {
+ found = true;
+ break;
+ }
+ --search_offset;
+ }
+ }
+
+ if (!found) {
+ fputc('\a', stderr);
+ fflush(stderr);
+ }
+ }
+
+ if (found) {
+ m_buffer.clear();
+ m_cursor = 0;
+ insert(m_history[last_matching_offset].entry);
+ // Always needed, as we have cleared the buffer above.
+ m_refresh_needed = true;
+ }
+
+ return found;
+}
+
+void Editor::recalculate_origin()
+{
+ // Changing the columns can affect our origin if
+ // the new size is smaller than our prompt, which would
+ // cause said prompt to take up more space, so we should
+ // compensate for that.
+ if (m_cached_prompt_metrics.max_line_length >= m_num_columns) {
+ auto added_lines = (m_cached_prompt_metrics.max_line_length + 1) / m_num_columns - 1;
+ m_origin_row += added_lines;
+ }
+
+ // We also need to recalculate our cursor position,
+ // but that will be calculated and applied at the next
+ // refresh cycle.
+}
+void Editor::cleanup()
+{
+ auto current_buffer_metrics = actual_rendered_string_metrics(buffer_view());
+ auto new_lines = current_prompt_metrics().lines_with_addition(current_buffer_metrics, m_num_columns);
+ auto shown_lines = num_lines();
+ if (new_lines < shown_lines)
+ m_extra_forward_lines = max(shown_lines - new_lines, m_extra_forward_lines);
+
+ reposition_cursor(true);
+ auto current_line = num_lines() - 1;
+ VT::clear_lines(current_line, m_extra_forward_lines);
+ m_extra_forward_lines = 0;
+ reposition_cursor();
+};
+
+void Editor::refresh_display()
+{
+ auto has_cleaned_up = false;
+ // Someone changed the window size, figure it out
+ // and react to it, we might need to redraw.
+ if (m_was_resized) {
+ if (m_previous_num_columns != m_num_columns) {
+ // We need to cleanup and redo everything.
+ m_cached_prompt_valid = false;
+ m_refresh_needed = true;
+ swap(m_previous_num_columns, m_num_columns);
+ recalculate_origin();
+ cleanup();
+ swap(m_previous_num_columns, m_num_columns);
+ has_cleaned_up = true;
+ }
+ m_was_resized = false;
+ }
+ // We might be at the last line, and have more than one line;
+ // Refreshing the display will cause the terminal to scroll,
+ // so note that fact and bring origin up, making sure to
+ // reserve the space for however many lines we move it up.
+ auto current_num_lines = num_lines();
+ if (m_origin_row + current_num_lines > m_num_lines) {
+ if (current_num_lines > m_num_lines) {
+ for (size_t i = 0; i < m_num_lines; ++i)
+ putc('\n', stderr);
+ m_origin_row = 0;
+ } else {
+ auto old_origin_row = m_origin_row;
+ m_origin_row = m_num_lines - current_num_lines + 1;
+ for (size_t i = 0; i < old_origin_row - m_origin_row; ++i)
+ putc('\n', stderr);
+ }
+ fflush(stderr);
+ }
+ // Do not call hook on pure cursor movement.
+ if (m_cached_prompt_valid && !m_refresh_needed && m_pending_chars.size() == 0) {
+ // Probably just moving around.
+ reposition_cursor();
+ m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view());
+ return;
+ }
+
+ if (on_display_refresh)
+ on_display_refresh(*this);
+
+ if (m_cached_prompt_valid) {
+ if (!m_refresh_needed && m_cursor == m_buffer.size()) {
+ // Just write the characters out and continue,
+ // no need to refresh the entire line.
+ char null = 0;
+ m_pending_chars.append(&null, 1);
+ fputs((char*)m_pending_chars.data(), stderr);
+ m_pending_chars.clear();
+ m_drawn_cursor = m_cursor;
+ m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view());
+ fflush(stderr);
+ return;
+ }
+ }
+
+ // Ouch, reflow entire line.
+ if (!has_cleaned_up) {
+ cleanup();
+ }
+ VT::move_absolute(m_origin_row, m_origin_column);
+
+ fputs(m_new_prompt.characters(), stderr);
+
+ VT::clear_to_end_of_line();
+ HashMap<u32, Style> empty_styles {};
+ StringBuilder builder;
+ for (size_t i = 0; i < m_buffer.size(); ++i) {
+ auto ends = m_spans_ending.get(i).value_or(empty_styles);
+ auto starts = m_spans_starting.get(i).value_or(empty_styles);
+
+ auto anchored_ends = m_anchored_spans_ending.get(i).value_or(empty_styles);
+ auto anchored_starts = m_anchored_spans_starting.get(i).value_or(empty_styles);
+
+ auto c = m_buffer[i];
+ bool should_print_caret = iscntrl(c) && c != '\n';
+
+ if (ends.size() || anchored_ends.size()) {
+ Style style;
+
+ for (auto& applicable_style : ends)
+ style.unify_with(applicable_style.value);
+
+ for (auto& applicable_style : anchored_ends)
+ style.unify_with(applicable_style.value);
+
+ // Disable any style that should be turned off.
+ VT::apply_style(style, false);
+
+ // Reapply styles for overlapping spans that include this one.
+ style = find_applicable_style(i);
+ VT::apply_style(style, true);
+ }
+ if (starts.size() || anchored_starts.size()) {
+ Style style;
+
+ for (auto& applicable_style : starts)
+ style.unify_with(applicable_style.value);
+
+ for (auto& applicable_style : anchored_starts)
+ style.unify_with(applicable_style.value);
+
+ // Set new styles.
+ VT::apply_style(style, true);
+ }
+
+ builder.clear();
+ if (should_print_caret)
+ builder.appendff("^{:c}", c + 64);
+ else
+ builder.append(Utf32View { &c, 1 });
+
+ if (should_print_caret)
+ fputs("\033[7m", stderr);
+
+ fputs(builder.to_string().characters(), stderr);
+
+ if (should_print_caret)
+ fputs("\033[27m", stderr);
+ }
+
+ VT::apply_style(Style::reset_style()); // don't bleed to EOL
+
+ m_pending_chars.clear();
+ m_refresh_needed = false;
+ m_cached_buffer_metrics = actual_rendered_string_metrics(buffer_view());
+ m_chars_inserted_in_the_middle = 0;
+ if (!m_cached_prompt_valid) {
+ m_cached_prompt_valid = true;
+ }
+
+ reposition_cursor();
+ fflush(stderr);
+}
+
+void Editor::strip_styles(bool strip_anchored)
+{
+ m_spans_starting.clear();
+ m_spans_ending.clear();
+
+ if (strip_anchored) {
+ m_anchored_spans_starting.clear();
+ m_anchored_spans_ending.clear();
+ }
+
+ m_refresh_needed = true;
+}
+
+void Editor::reposition_cursor(bool to_end)
+{
+ auto cursor = m_cursor;
+ auto saved_cursor = m_cursor;
+ if (to_end)
+ cursor = m_buffer.size();
+
+ m_cursor = cursor;
+ m_drawn_cursor = cursor;
+
+ auto line = cursor_line() - 1;
+ auto column = offset_in_line();
+
+ ASSERT(column + m_origin_column <= m_num_columns);
+ VT::move_absolute(line + m_origin_row, column + m_origin_column);
+
+ if (line + m_origin_row > m_num_lines) {
+ for (size_t i = m_num_lines; i < line + m_origin_row; ++i)
+ fputc('\n', stderr);
+ m_origin_row -= line + m_origin_row - m_num_lines;
+ VT::move_relative(0, column + m_origin_column);
+ }
+
+ m_cursor = saved_cursor;
+}
+
+void VT::move_absolute(u32 row, u32 col)
+{
+ fprintf(stderr, "\033[%d;%dH", row, col);
+ fflush(stderr);
+}
+
+void VT::move_relative(int row, int col)
+{
+ char x_op = 'A', y_op = 'D';
+
+ if (row > 0)
+ x_op = 'B';
+ else
+ row = -row;
+ if (col > 0)
+ y_op = 'C';
+ else
+ col = -col;
+
+ if (row > 0)
+ fprintf(stderr, "\033[%d%c", row, x_op);
+ if (col > 0)
+ fprintf(stderr, "\033[%d%c", col, y_op);
+}
+
+Style Editor::find_applicable_style(size_t offset) const
+{
+ // Walk through our styles and merge all that fit in the offset.
+ auto style = Style::reset_style();
+ auto unify = [&](auto& entry) {
+ if (entry.key >= offset)
+ return;
+ for (auto& style_value : entry.value) {
+ if (style_value.key <= offset)
+ return;
+ style.unify_with(style_value.value, true);
+ }
+ };
+
+ for (auto& entry : m_spans_starting) {
+ unify(entry);
+ }
+
+ for (auto& entry : m_anchored_spans_starting) {
+ unify(entry);
+ }
+
+ return style;
+}
+
+String Style::Background::to_vt_escape() const
+{
+ if (is_default())
+ return "";
+
+ if (m_is_rgb) {
+ return String::format("\033[48;2;%d;%d;%dm", m_rgb_color[0], m_rgb_color[1], m_rgb_color[2]);
+ } else {
+ return String::format("\033[%dm", (u8)m_xterm_color + 40);
+ }
+}
+
+String Style::Foreground::to_vt_escape() const
+{
+ if (is_default())
+ return "";
+
+ if (m_is_rgb) {
+ return String::format("\033[38;2;%d;%d;%dm", m_rgb_color[0], m_rgb_color[1], m_rgb_color[2]);
+ } else {
+ return String::format("\033[%dm", (u8)m_xterm_color + 30);
+ }
+}
+
+String Style::Hyperlink::to_vt_escape(bool starting) const
+{
+ if (is_empty())
+ return "";
+
+ return String::format("\033]8;;%s\033\\", starting ? m_link.characters() : "");
+}
+
+void Style::unify_with(const Style& other, bool prefer_other)
+{
+ // Unify colors.
+ if (prefer_other || m_background.is_default())
+ m_background = other.background();
+
+ if (prefer_other || m_foreground.is_default())
+ m_foreground = other.foreground();
+
+ // Unify graphic renditions.
+ if (other.bold())
+ set(Bold);
+
+ if (other.italic())
+ set(Italic);
+
+ if (other.underline())
+ set(Underline);
+
+ // Unify links.
+ if (prefer_other || m_hyperlink.is_empty())
+ m_hyperlink = other.hyperlink();
+}
+
+String Style::to_string() const
+{
+ StringBuilder builder;
+ builder.append("Style { ");
+
+ if (!m_foreground.is_default()) {
+ builder.append("Foreground(");
+ if (m_foreground.m_is_rgb) {
+ builder.join(", ", m_foreground.m_rgb_color);
+ } else {
+ builder.appendf("(XtermColor) %d", (int)m_foreground.m_xterm_color);
+ }
+ builder.append("), ");
+ }
+
+ if (!m_background.is_default()) {
+ builder.append("Background(");
+ if (m_background.m_is_rgb) {
+ builder.join(' ', m_background.m_rgb_color);
+ } else {
+ builder.appendf("(XtermColor) %d", (int)m_background.m_xterm_color);
+ }
+ builder.append("), ");
+ }
+
+ if (bold())
+ builder.append("Bold, ");
+
+ if (underline())
+ builder.append("Underline, ");
+
+ if (italic())
+ builder.append("Italic, ");
+
+ if (!m_hyperlink.is_empty())
+ builder.appendf("Hyperlink(\"%s\"), ", m_hyperlink.m_link.characters());
+
+ builder.append("}");
+
+ return builder.build();
+}
+
+void VT::apply_style(const Style& style, bool is_starting)
+{
+ if (is_starting) {
+ fprintf(stderr,
+ "\033[%d;%d;%dm%s%s%s",
+ style.bold() ? 1 : 22,
+ style.underline() ? 4 : 24,
+ style.italic() ? 3 : 23,
+ style.background().to_vt_escape().characters(),
+ style.foreground().to_vt_escape().characters(),
+ style.hyperlink().to_vt_escape(true).characters());
+ } else {
+ fprintf(stderr, "%s", style.hyperlink().to_vt_escape(false).characters());
+ }
+}
+
+void VT::clear_lines(size_t count_above, size_t count_below)
+{
+ // Go down count_below lines.
+ if (count_below > 0)
+ fprintf(stderr, "\033[%dB", (int)count_below);
+ // Then clear lines going upwards.
+ for (size_t i = count_below + count_above; i > 0; --i)
+ fputs(i == 1 ? "\033[2K" : "\033[2K\033[A", stderr);
+}
+
+void VT::save_cursor()
+{
+ fputs("\033[s", stderr);
+ fflush(stderr);
+}
+
+void VT::restore_cursor()
+{
+ fputs("\033[u", stderr);
+ fflush(stderr);
+}
+
+void VT::clear_to_end_of_line()
+{
+ fputs("\033[K", stderr);
+ fflush(stderr);
+}
+
+StringMetrics Editor::actual_rendered_string_metrics(const StringView& string)
+{
+ StringMetrics metrics;
+ StringMetrics::LineMetrics current_line;
+ VTState state { Free };
+ Utf8View view { string };
+ auto it = view.begin();
+
+ for (; it != view.end(); ++it) {
+ auto c = *it;
+ auto it_copy = it;
+ ++it_copy;
+ auto next_c = it_copy == view.end() ? 0 : *it_copy;
+ state = actual_rendered_string_length_step(metrics, view.iterator_offset(it), current_line, c, next_c, state);
+ }
+
+ metrics.line_metrics.append(current_line);
+
+ for (auto& line : metrics.line_metrics)
+ metrics.max_line_length = max(line.total_length(), metrics.max_line_length);
+
+ return metrics;
+}
+
+StringMetrics Editor::actual_rendered_string_metrics(const Utf32View& view)
+{
+ StringMetrics metrics;
+ StringMetrics::LineMetrics current_line;
+ VTState state { Free };
+
+ for (size_t i = 0; i < view.length(); ++i) {
+ auto c = view.code_points()[i];
+ auto next_c = i + 1 < view.length() ? view.code_points()[i + 1] : 0;
+ state = actual_rendered_string_length_step(metrics, i, current_line, c, next_c, state);
+ }
+
+ metrics.line_metrics.append(current_line);
+
+ for (auto& line : metrics.line_metrics)
+ metrics.max_line_length = max(line.total_length(), metrics.max_line_length);
+
+ return metrics;
+}
+
+Editor::VTState Editor::actual_rendered_string_length_step(StringMetrics& metrics, size_t index, StringMetrics::LineMetrics& current_line, u32 c, u32 next_c, VTState state)
+{
+ switch (state) {
+ case Free:
+ if (c == '\x1b') { // escape
+ return Escape;
+ }
+ if (c == '\r') { // carriage return
+ current_line.masked_chars = {};
+ current_line.length = 0;
+ if (!metrics.line_metrics.is_empty())
+ metrics.line_metrics.last() = { {}, 0 };
+ return state;
+ }
+ if (c == '\n') { // return
+ metrics.line_metrics.append(current_line);
+ current_line.masked_chars = {};
+ current_line.length = 0;
+ return state;
+ }
+ if (iscntrl(c) && c != '\n')
+ current_line.masked_chars.append({ index, 1, 2 });
+ // FIXME: This will not support anything sophisticated
+ ++current_line.length;
+ ++metrics.total_length;
+ return state;
+ case Escape:
+ if (c == ']') {
+ if (next_c == '0')
+ state = Title;
+ return state;
+ }
+ if (c == '[') {
+ return Bracket;
+ }
+ // FIXME: This does not support non-VT (aside from set-title) escapes
+ return state;
+ case Bracket:
+ if (isdigit(c)) {
+ return BracketArgsSemi;
+ }
+ return state;
+ case BracketArgsSemi:
+ if (c == ';') {
+ return Bracket;
+ }
+ if (!isdigit(c))
+ state = Free;
+ return state;
+ case Title:
+ if (c == 7)
+ state = Free;
+ return state;
+ }
+ return state;
+}
+
+Vector<size_t, 2> Editor::vt_dsr()
+{
+ char buf[16];
+ u32 length { 0 };
+
+ // Read whatever junk there is before talking to the terminal
+ // and insert them later when we're reading user input.
+ bool more_junk_to_read { false };
+ timeval timeout { 0, 0 };
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(0, &readfds);
+
+ do {
+ more_junk_to_read = false;
+ [[maybe_unused]] auto rc = select(1, &readfds, nullptr, nullptr, &timeout);
+ if (FD_ISSET(0, &readfds)) {
+ auto nread = read(0, buf, 16);
+ if (nread < 0) {
+ m_input_error = Error::ReadFailure;
+ finish();
+ break;
+ }
+
+ if (nread == 0)
+ break;
+
+ m_incomplete_data.append(buf, nread);
+ more_junk_to_read = true;
+ }
+ } while (more_junk_to_read);
+
+ if (m_input_error.has_value())
+ return { 1, 1 };
+
+ fputs("\033[6n", stderr);
+ fflush(stderr);
+
+ do {
+ auto nread = read(0, buf + length, 16 - length);
+ if (nread < 0) {
+ if (errno == 0 || errno == EINTR) {
+ // ????
+ continue;
+ }
+ dbg() << "Error while reading DSR: " << strerror(errno);
+ m_input_error = Error::ReadFailure;
+ finish();
+ return { 1, 1 };
+ }
+ if (nread == 0) {
+ m_input_error = Error::Empty;
+ finish();
+ dbgln("Terminal DSR issue; received no response");
+ return { 1, 1 };
+ }
+ length += nread;
+ } while (buf[length - 1] != 'R' && length < 16);
+ size_t row { 1 }, col { 1 };
+
+ if (buf[0] == '\033' && buf[1] == '[') {
+ auto parts = StringView(buf + 2, length - 3).split_view(';');
+ auto row_opt = parts[0].to_int();
+ if (!row_opt.has_value()) {
+ dbgln("Terminal DSR issue; received garbage row");
+ } else {
+ row = row_opt.value();
+ }
+ auto col_opt = parts[1].to_int();
+ if (!col_opt.has_value()) {
+ dbgln("Terminal DSR issue; received garbage col");
+ } else {
+ col = col_opt.value();
+ }
+ }
+ return { row, col };
+}
+
+String Editor::line(size_t up_to_index) const
+{
+ StringBuilder builder;
+ builder.append(Utf32View { m_buffer.data(), min(m_buffer.size(), up_to_index) });
+ return builder.build();
+}
+
+void Editor::remove_at_index(size_t index)
+{
+ // See if we have any anchored styles, and reposition them if needed.
+ readjust_anchored_styles(index, ModificationKind::Removal);
+ auto cp = m_buffer[index];
+ m_buffer.remove(index);
+ if (cp == '\n')
+ ++m_extra_forward_lines;
+}
+
+void Editor::readjust_anchored_styles(size_t hint_index, ModificationKind modification)
+{
+ struct Anchor {
+ Span old_span;
+ Span new_span;
+ Style style;
+ };
+ Vector<Anchor> anchors_to_relocate;
+ auto index_shift = modification == ModificationKind::Insertion ? 1 : -1;
+ auto forced_removal = modification == ModificationKind::ForcedOverlapRemoval;
+
+ for (auto& start_entry : m_anchored_spans_starting) {
+ for (auto& end_entry : start_entry.value) {
+ if (forced_removal) {
+ if (start_entry.key <= hint_index && end_entry.key > hint_index) {
+ // Remove any overlapping regions.
+ continue;
+ }
+ }
+ if (start_entry.key >= hint_index) {
+ if (start_entry.key == hint_index && end_entry.key == hint_index + 1 && modification == ModificationKind::Removal) {
+ // Remove the anchor, as all its text was wiped.
+ continue;
+ }
+ // Shift everything.
+ anchors_to_relocate.append({ { start_entry.key, end_entry.key, Span::Mode::CodepointOriented }, { start_entry.key + index_shift, end_entry.key + index_shift, Span::Mode::CodepointOriented }, end_entry.value });
+ continue;
+ }
+ if (end_entry.key > hint_index) {
+ // Shift just the end.
+ anchors_to_relocate.append({ { start_entry.key, end_entry.key, Span::Mode::CodepointOriented }, { start_entry.key, end_entry.key + index_shift, Span::Mode::CodepointOriented }, end_entry.value });
+ continue;
+ }
+ anchors_to_relocate.append({ { start_entry.key, end_entry.key, Span::Mode::CodepointOriented }, { start_entry.key, end_entry.key, Span::Mode::CodepointOriented }, end_entry.value });
+ }
+ }
+
+ m_anchored_spans_ending.clear();
+ m_anchored_spans_starting.clear();
+ // Pass over the relocations and update the stale entries.
+ for (auto& relocation : anchors_to_relocate) {
+ stylize(relocation.new_span, relocation.style);
+ }
+}
+
+size_t StringMetrics::lines_with_addition(const StringMetrics& offset, size_t column_width) const
+{
+ size_t lines = 0;
+
+ for (size_t i = 0; i < line_metrics.size() - 1; ++i)
+ lines += (line_metrics[i].total_length() + column_width) / column_width;
+
+ auto last = line_metrics.last().total_length();
+ last += offset.line_metrics.first().total_length();
+ lines += (last + column_width) / column_width;
+
+ for (size_t i = 1; i < offset.line_metrics.size(); ++i)
+ lines += (offset.line_metrics[i].total_length() + column_width) / column_width;
+
+ return lines;
+}
+
+size_t StringMetrics::offset_with_addition(const StringMetrics& offset, size_t column_width) const
+{
+ if (offset.line_metrics.size() > 1)
+ return offset.line_metrics.last().total_length() % column_width;
+
+ auto last = line_metrics.last().total_length();
+ last += offset.line_metrics.first().total_length();
+ return last % column_width;
+}
+
+}
diff --git a/Userland/Libraries/LibLine/Editor.h b/Userland/Libraries/LibLine/Editor.h
new file mode 100644
index 0000000000..407b3fd9fa
--- /dev/null
+++ b/Userland/Libraries/LibLine/Editor.h
@@ -0,0 +1,489 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/BinarySearch.h>
+#include <AK/ByteBuffer.h>
+#include <AK/Function.h>
+#include <AK/HashMap.h>
+#include <AK/NonnullOwnPtr.h>
+#include <AK/QuickSort.h>
+#include <AK/Result.h>
+#include <AK/String.h>
+#include <AK/Traits.h>
+#include <AK/Utf32View.h>
+#include <AK/Utf8View.h>
+#include <AK/Vector.h>
+#include <LibCore/DirIterator.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/Notifier.h>
+#include <LibCore/Object.h>
+#include <LibLine/KeyCallbackMachine.h>
+#include <LibLine/Span.h>
+#include <LibLine/StringMetrics.h>
+#include <LibLine/Style.h>
+#include <LibLine/SuggestionDisplay.h>
+#include <LibLine/SuggestionManager.h>
+#include <LibLine/VT.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <termios.h>
+
+namespace Line {
+
+struct KeyBinding {
+ Vector<Key> keys;
+ enum class Kind {
+ InternalFunction,
+ Insertion,
+ } kind { Kind::InternalFunction };
+ String binding;
+};
+
+struct Configuration {
+ enum RefreshBehaviour {
+ Lazy,
+ Eager,
+ };
+ enum OperationMode {
+ Unset,
+ Full,
+ NoEscapeSequences,
+ NonInteractive,
+ };
+ enum SignalHandler {
+ WithSignalHandlers,
+ NoSignalHandlers,
+ };
+
+ Configuration()
+ {
+ }
+
+ template<typename Arg, typename... Rest>
+ Configuration(Arg arg, Rest... rest)
+ : Configuration(rest...)
+ {
+ set(arg);
+ }
+
+ void set(RefreshBehaviour refresh) { refresh_behaviour = refresh; }
+ void set(OperationMode mode) { operation_mode = mode; }
+ void set(SignalHandler mode) { m_signal_mode = mode; }
+ void set(const KeyBinding& binding) { keybindings.append(binding); }
+
+ static Configuration from_config(const StringView& libname = "line");
+
+ RefreshBehaviour refresh_behaviour { RefreshBehaviour::Lazy };
+ SignalHandler m_signal_mode { SignalHandler::WithSignalHandlers };
+ OperationMode operation_mode { OperationMode::Unset };
+ Vector<KeyBinding> keybindings;
+};
+
+#define ENUMERATE_EDITOR_INTERNAL_FUNCTIONS(M) \
+ M(clear_screen) \
+ M(cursor_left_character) \
+ M(cursor_left_word) \
+ M(cursor_right_character) \
+ M(cursor_right_word) \
+ M(enter_search) \
+ M(erase_character_backwards) \
+ M(erase_character_forwards) \
+ M(erase_to_beginning) \
+ M(erase_to_end) \
+ M(erase_word_backwards) \
+ M(finish_edit) \
+ M(go_end) \
+ M(go_home) \
+ M(kill_line) \
+ M(search_backwards) \
+ M(search_forwards) \
+ M(transpose_characters) \
+ M(transpose_words) \
+ M(insert_last_words) \
+ M(erase_alnum_word_backwards) \
+ M(erase_alnum_word_forwards) \
+ M(capitalize_word) \
+ M(lowercase_word) \
+ M(uppercase_word)
+
+#define EDITOR_INTERNAL_FUNCTION(name) \
+ [](auto& editor) { editor.name(); return false; }
+
+class Editor : public Core::Object {
+ C_OBJECT(Editor);
+
+public:
+ enum class Error {
+ ReadFailure,
+ Empty,
+ Eof,
+ };
+
+ ~Editor();
+
+ Result<String, Error> get_line(const String& prompt);
+
+ void initialize();
+
+ void add_to_history(const String& line);
+ bool load_history(const String& path);
+ bool save_history(const String& path);
+ const auto& history() const { return m_history; }
+
+ void register_key_input_callback(const KeyBinding&);
+ void register_key_input_callback(Vector<Key> keys, Function<bool(Editor&)> callback) { m_callback_machine.register_key_input_callback(move(keys), move(callback)); }
+ void register_key_input_callback(Key key, Function<bool(Editor&)> callback) { register_key_input_callback(Vector<Key> { key }, move(callback)); }
+
+ static StringMetrics actual_rendered_string_metrics(const StringView&);
+ static StringMetrics actual_rendered_string_metrics(const Utf32View&);
+
+ Function<Vector<CompletionSuggestion>(const Editor&)> on_tab_complete;
+ Function<void()> on_interrupt_handled;
+ Function<void(Editor&)> on_display_refresh;
+
+ static Function<bool(Editor&)> find_internal_function(const StringView& name);
+ enum class CaseChangeOp {
+ Lowercase,
+ Uppercase,
+ Capital,
+ };
+ void case_change_word(CaseChangeOp);
+#define __ENUMERATE_EDITOR_INTERNAL_FUNCTION(name) \
+ void name();
+
+ ENUMERATE_EDITOR_INTERNAL_FUNCTIONS(__ENUMERATE_EDITOR_INTERNAL_FUNCTION)
+
+#undef __ENUMERATE_EDITOR_INTERNAL_FUNCTION
+
+ void interrupted();
+ void resized()
+ {
+ m_was_resized = true;
+ m_previous_num_columns = m_num_columns;
+ get_terminal_size();
+ m_suggestion_display->set_vt_size(m_num_lines, m_num_columns);
+ if (m_is_searching)
+ m_search_editor->resized();
+ }
+
+ size_t cursor() const { return m_cursor; }
+ void set_cursor(size_t cursor)
+ {
+ if (cursor > m_buffer.size())
+ cursor = m_buffer.size();
+ m_cursor = cursor;
+ }
+ const Vector<u32, 1024>& buffer() const { return m_buffer; }
+ u32 buffer_at(size_t pos) const { return m_buffer.at(pos); }
+ String line() const { return line(m_buffer.size()); }
+ String line(size_t up_to_index) const;
+
+ // Only makes sense inside a character_input callback or on_* callback.
+ void set_prompt(const String& prompt)
+ {
+ if (m_cached_prompt_valid)
+ m_old_prompt_metrics = m_cached_prompt_metrics;
+ m_cached_prompt_valid = false;
+ m_cached_prompt_metrics = actual_rendered_string_metrics(prompt);
+ m_new_prompt = prompt;
+ }
+
+ void clear_line();
+ void insert(const String&);
+ void insert(const StringView&);
+ void insert(const Utf32View&);
+ void insert(const u32);
+ void stylize(const Span&, const Style&);
+ void strip_styles(bool strip_anchored = false);
+
+ // Invariant Offset is an offset into the suggested data, hinting the editor what parts of the suggestion will not change
+ // Static Offset is an offset into the token, signifying where the suggestions start
+ // e.g.
+ // foobar<suggestion initiated>, on_tab_complete returns "barx", "bary", "barz"
+ // ^ ^
+ // +-|- static offset: the suggestions start here
+ // +- invariant offset: the suggestions do not change up to here
+ //
+ void suggest(size_t invariant_offset = 0, size_t static_offset = 0, Span::Mode offset_mode = Span::ByteOriented) const;
+
+ const struct termios& termios() const { return m_termios; }
+ const struct termios& default_termios() const { return m_default_termios; }
+ struct winsize terminal_size() const
+ {
+ winsize ws { (u16)m_num_lines, (u16)m_num_columns, 0, 0 };
+ return ws;
+ }
+
+ void finish()
+ {
+ m_finish = true;
+ }
+
+ bool is_editing() const { return m_is_editing; }
+
+ const Utf32View buffer_view() const { return { m_buffer.data(), m_buffer.size() }; }
+
+private:
+ explicit Editor(Configuration configuration = Configuration::from_config());
+
+ void set_default_keybinds();
+
+ enum VTState {
+ Free = 1,
+ Escape = 3,
+ Bracket = 5,
+ BracketArgsSemi = 7,
+ Title = 9,
+ };
+
+ static VTState actual_rendered_string_length_step(StringMetrics&, size_t, StringMetrics::LineMetrics& current_line, u32, u32, VTState);
+
+ enum LoopExitCode {
+ Exit = 0,
+ Retry
+ };
+
+ // FIXME: Port to Core::Property
+ void save_to(JsonObject&);
+
+ void try_update_once();
+ void handle_interrupt_event();
+ void handle_read_event();
+
+ Vector<size_t, 2> vt_dsr();
+ void remove_at_index(size_t);
+
+ enum class ModificationKind {
+ Insertion,
+ Removal,
+ ForcedOverlapRemoval,
+ };
+ void readjust_anchored_styles(size_t hint_index, ModificationKind);
+
+ Style find_applicable_style(size_t offset) const;
+
+ bool search(const StringView&, bool allow_empty = false, bool from_beginning = true);
+ inline void end_search()
+ {
+ m_is_searching = false;
+ m_refresh_needed = true;
+ m_search_offset = 0;
+ if (m_reset_buffer_on_search_end) {
+ m_buffer.clear();
+ for (auto ch : m_pre_search_buffer)
+ m_buffer.append(ch);
+ m_cursor = m_pre_search_cursor;
+ }
+ m_reset_buffer_on_search_end = true;
+ m_search_editor = nullptr;
+ }
+
+ void reset()
+ {
+ m_cached_buffer_metrics.reset();
+ m_cached_prompt_valid = false;
+ m_cursor = 0;
+ m_drawn_cursor = 0;
+ m_inline_search_cursor = 0;
+ m_search_offset = 0;
+ m_search_offset_state = SearchOffsetState::Unbiased;
+ m_old_prompt_metrics = m_cached_prompt_metrics;
+ set_origin(0, 0);
+ m_prompt_lines_at_suggestion_initiation = 0;
+ m_refresh_needed = true;
+ m_input_error.clear();
+ m_returned_line = String::empty();
+ }
+
+ void refresh_display();
+ void cleanup();
+ void cleanup_suggestions();
+ void really_quit_event_loop();
+
+ void restore()
+ {
+ ASSERT(m_initialized);
+ tcsetattr(0, TCSANOW, &m_default_termios);
+ m_initialized = false;
+ for (auto id : m_signal_handlers)
+ Core::EventLoop::unregister_signal(id);
+ }
+
+ const StringMetrics& current_prompt_metrics() const
+ {
+ return m_cached_prompt_valid ? m_cached_prompt_metrics : m_old_prompt_metrics;
+ }
+
+ size_t num_lines() const
+ {
+ return current_prompt_metrics().lines_with_addition(m_cached_buffer_metrics, m_num_columns);
+ }
+
+ size_t cursor_line() const
+ {
+ auto cursor = m_drawn_cursor;
+ if (cursor > m_cursor)
+ cursor = m_cursor;
+ return current_prompt_metrics().lines_with_addition(
+ actual_rendered_string_metrics(buffer_view().substring_view(0, cursor)),
+ m_num_columns);
+ }
+
+ size_t offset_in_line() const
+ {
+ auto cursor = m_drawn_cursor;
+ if (cursor > m_cursor)
+ cursor = m_cursor;
+ auto buffer_metrics = actual_rendered_string_metrics(buffer_view().substring_view(0, cursor));
+ return current_prompt_metrics().offset_with_addition(buffer_metrics, m_num_columns);
+ }
+
+ void set_origin()
+ {
+ auto position = vt_dsr();
+ set_origin(position[0], position[1]);
+ }
+
+ void set_origin(int row, int col)
+ {
+ m_origin_row = row;
+ m_origin_column = col;
+ m_suggestion_display->set_origin(row, col, {});
+ }
+
+ void recalculate_origin();
+ void reposition_cursor(bool to_end = false);
+
+ struct CodepointRange {
+ size_t start { 0 };
+ size_t end { 0 };
+ };
+ CodepointRange byte_offset_range_to_code_point_offset_range(size_t byte_start, size_t byte_end, size_t code_point_scan_offset, bool reverse = false) const;
+
+ void get_terminal_size();
+
+ bool m_finish { false };
+
+ RefPtr<Editor> m_search_editor;
+ bool m_is_searching { false };
+ bool m_reset_buffer_on_search_end { true };
+ size_t m_search_offset { 0 };
+ enum class SearchOffsetState {
+ Unbiased,
+ Backwards,
+ Forwards,
+ } m_search_offset_state { SearchOffsetState::Unbiased };
+ size_t m_pre_search_cursor { 0 };
+ Vector<u32, 1024> m_pre_search_buffer;
+
+ Vector<u32, 1024> m_buffer;
+ ByteBuffer m_pending_chars;
+ Vector<char, 512> m_incomplete_data;
+ Optional<Error> m_input_error;
+ String m_returned_line;
+
+ size_t m_cursor { 0 };
+ size_t m_drawn_cursor { 0 };
+ size_t m_inline_search_cursor { 0 };
+ size_t m_chars_inserted_in_the_middle { 0 };
+ size_t m_times_tab_pressed { 0 };
+ size_t m_num_columns { 0 };
+ size_t m_num_lines { 1 };
+ size_t m_previous_num_columns { 0 };
+ size_t m_extra_forward_lines { 0 };
+ StringMetrics m_cached_prompt_metrics;
+ StringMetrics m_old_prompt_metrics;
+ StringMetrics m_cached_buffer_metrics;
+ size_t m_prompt_lines_at_suggestion_initiation { 0 };
+ bool m_cached_prompt_valid { false };
+
+ // Exact position before our prompt in the terminal.
+ size_t m_origin_row { 0 };
+ size_t m_origin_column { 0 };
+
+ OwnPtr<SuggestionDisplay> m_suggestion_display;
+
+ String m_new_prompt;
+
+ SuggestionManager m_suggestion_manager;
+
+ bool m_always_refresh { false };
+
+ enum class TabDirection {
+ Forward,
+ Backward,
+ };
+ TabDirection m_tab_direction { TabDirection::Forward };
+
+ KeyCallbackMachine m_callback_machine;
+
+ struct termios m_termios {
+ };
+ struct termios m_default_termios {
+ };
+ bool m_was_interrupted { false };
+ bool m_was_resized { false };
+
+ // FIXME: This should be something more take_first()-friendly.
+ struct HistoryEntry {
+ String entry;
+ time_t timestamp;
+ };
+ Vector<HistoryEntry> m_history;
+ size_t m_history_cursor { 0 };
+ size_t m_history_capacity { 1024 };
+
+ enum class InputState {
+ Free,
+ Verbatim,
+ GotEscape,
+ CSIExpectParameter,
+ CSIExpectIntermediate,
+ CSIExpectFinal,
+ };
+ InputState m_state { InputState::Free };
+
+ HashMap<u32, HashMap<u32, Style>> m_spans_starting;
+ HashMap<u32, HashMap<u32, Style>> m_spans_ending;
+
+ HashMap<u32, HashMap<u32, Style>> m_anchored_spans_starting;
+ HashMap<u32, HashMap<u32, Style>> m_anchored_spans_ending;
+
+ RefPtr<Core::Notifier> m_notifier;
+
+ bool m_initialized { false };
+ bool m_refresh_needed { false };
+ Vector<int, 2> m_signal_handlers;
+
+ bool m_is_editing { false };
+
+ Configuration m_configuration;
+};
+
+}
diff --git a/Userland/Libraries/LibLine/InternalFunctions.cpp b/Userland/Libraries/LibLine/InternalFunctions.cpp
new file mode 100644
index 0000000000..87a7d03b55
--- /dev/null
+++ b/Userland/Libraries/LibLine/InternalFunctions.cpp
@@ -0,0 +1,496 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibLine/Editor.h>
+#include <ctype.h>
+#include <stdio.h>
+
+namespace {
+constexpr u32 ctrl(char c) { return c & 0x3f; }
+}
+
+namespace Line {
+
+Function<bool(Editor&)> Editor::find_internal_function(const StringView& name)
+{
+#define __ENUMERATE(internal_name) \
+ if (name == #internal_name) \
+ return EDITOR_INTERNAL_FUNCTION(internal_name);
+
+ ENUMERATE_EDITOR_INTERNAL_FUNCTIONS(__ENUMERATE)
+
+ return {};
+}
+
+void Editor::search_forwards()
+{
+ ScopedValueRollback inline_search_cursor_rollback { m_inline_search_cursor };
+ StringBuilder builder;
+ builder.append(Utf32View { m_buffer.data(), m_inline_search_cursor });
+ String search_phrase = builder.to_string();
+ if (m_search_offset_state == SearchOffsetState::Backwards)
+ --m_search_offset;
+ if (m_search_offset > 0) {
+ ScopedValueRollback search_offset_rollback { m_search_offset };
+ --m_search_offset;
+ if (search(search_phrase, true)) {
+ m_search_offset_state = SearchOffsetState::Forwards;
+ search_offset_rollback.set_override_rollback_value(m_search_offset);
+ } else {
+ m_search_offset_state = SearchOffsetState::Unbiased;
+ }
+ } else {
+ m_search_offset_state = SearchOffsetState::Unbiased;
+ m_cursor = 0;
+ m_buffer.clear();
+ insert(search_phrase);
+ m_refresh_needed = true;
+ }
+}
+
+void Editor::search_backwards()
+{
+ ScopedValueRollback inline_search_cursor_rollback { m_inline_search_cursor };
+ StringBuilder builder;
+ builder.append(Utf32View { m_buffer.data(), m_inline_search_cursor });
+ String search_phrase = builder.to_string();
+ if (m_search_offset_state == SearchOffsetState::Forwards)
+ ++m_search_offset;
+ if (search(search_phrase, true)) {
+ m_search_offset_state = SearchOffsetState::Backwards;
+ ++m_search_offset;
+ } else {
+ m_search_offset_state = SearchOffsetState::Unbiased;
+ --m_search_offset;
+ }
+}
+
+void Editor::cursor_left_word()
+{
+ if (m_cursor > 0) {
+ auto skipped_at_least_one_character = false;
+ for (;;) {
+ if (m_cursor == 0)
+ break;
+ if (skipped_at_least_one_character && !isalnum(m_buffer[m_cursor - 1])) // stop *after* a non-alnum, but only if it changes the position
+ break;
+ skipped_at_least_one_character = true;
+ --m_cursor;
+ }
+ }
+ m_inline_search_cursor = m_cursor;
+}
+
+void Editor::cursor_left_character()
+{
+ if (m_cursor > 0)
+ --m_cursor;
+ m_inline_search_cursor = m_cursor;
+}
+
+void Editor::cursor_right_word()
+{
+ if (m_cursor < m_buffer.size()) {
+ // Temporarily put a space at the end of our buffer,
+ // doing this greatly simplifies the logic below.
+ m_buffer.append(' ');
+ for (;;) {
+ if (m_cursor >= m_buffer.size())
+ break;
+ if (!isalnum(m_buffer[++m_cursor]))
+ break;
+ }
+ m_buffer.take_last();
+ }
+ m_inline_search_cursor = m_cursor;
+ m_search_offset = 0;
+}
+
+void Editor::cursor_right_character()
+{
+ if (m_cursor < m_buffer.size()) {
+ ++m_cursor;
+ }
+ m_inline_search_cursor = m_cursor;
+ m_search_offset = 0;
+}
+
+void Editor::erase_character_backwards()
+{
+ if (m_is_searching) {
+ return;
+ }
+ if (m_cursor == 0) {
+ fputc('\a', stderr);
+ fflush(stderr);
+ return;
+ }
+ remove_at_index(m_cursor - 1);
+ --m_cursor;
+ m_inline_search_cursor = m_cursor;
+ // We will have to redraw :(
+ m_refresh_needed = true;
+}
+
+void Editor::erase_character_forwards()
+{
+ if (m_cursor == m_buffer.size()) {
+ fputc('\a', stderr);
+ fflush(stderr);
+ return;
+ }
+ remove_at_index(m_cursor);
+ m_refresh_needed = true;
+}
+
+void Editor::finish_edit()
+{
+ fprintf(stderr, "<EOF>\n");
+ if (!m_always_refresh) {
+ m_input_error = Error::Eof;
+ finish();
+ really_quit_event_loop();
+ }
+}
+
+void Editor::kill_line()
+{
+ for (size_t i = 0; i < m_cursor; ++i)
+ remove_at_index(0);
+ m_cursor = 0;
+ m_refresh_needed = true;
+}
+
+void Editor::erase_word_backwards()
+{
+ // A word here is space-separated. `foo=bar baz` is two words.
+ bool has_seen_nonspace = false;
+ while (m_cursor > 0) {
+ if (isspace(m_buffer[m_cursor - 1])) {
+ if (has_seen_nonspace)
+ break;
+ } else {
+ has_seen_nonspace = true;
+ }
+ erase_character_backwards();
+ }
+}
+
+void Editor::erase_to_end()
+{
+ while (m_cursor < m_buffer.size())
+ erase_character_forwards();
+}
+
+void Editor::erase_to_beginning()
+{
+}
+
+void Editor::transpose_characters()
+{
+ if (m_cursor > 0 && m_buffer.size() >= 2) {
+ if (m_cursor < m_buffer.size())
+ ++m_cursor;
+ swap(m_buffer[m_cursor - 1], m_buffer[m_cursor - 2]);
+ // FIXME: Update anchored styles too.
+ m_refresh_needed = true;
+ }
+}
+
+void Editor::enter_search()
+{
+ if (m_is_searching) {
+ // How did we get here?
+ ASSERT_NOT_REACHED();
+ } else {
+ m_is_searching = true;
+ m_search_offset = 0;
+ m_pre_search_buffer.clear();
+ for (auto code_point : m_buffer)
+ m_pre_search_buffer.append(code_point);
+ m_pre_search_cursor = m_cursor;
+
+ // Disable our own notifier so as to avoid interfering with the search editor.
+ m_notifier->set_enabled(false);
+
+ m_search_editor = Editor::construct(Configuration { Configuration::Eager, Configuration::NoSignalHandlers }); // Has anyone seen 'Inception'?
+ m_search_editor->initialize();
+ add_child(*m_search_editor);
+
+ m_search_editor->on_display_refresh = [this](Editor& search_editor) {
+ StringBuilder builder;
+ builder.append(Utf32View { search_editor.buffer().data(), search_editor.buffer().size() });
+ if (!search(builder.build(), false, false)) {
+ m_buffer.clear();
+ m_cursor = 0;
+ }
+ refresh_display();
+ };
+
+ // Whenever the search editor gets a ^R, cycle between history entries.
+ m_search_editor->register_key_input_callback(ctrl('R'), [this](Editor& search_editor) {
+ ++m_search_offset;
+ search_editor.m_refresh_needed = true;
+ return false; // Do not process this key event
+ });
+
+ // Whenever the search editor gets a backspace, cycle back between history entries
+ // unless we're at the zeroth entry, in which case, allow the deletion.
+ m_search_editor->register_key_input_callback(m_termios.c_cc[VERASE], [this](Editor& search_editor) {
+ if (m_search_offset > 0) {
+ --m_search_offset;
+ search_editor.m_refresh_needed = true;
+ return false; // Do not process this key event
+ }
+
+ search_editor.erase_character_backwards();
+ return false;
+ });
+
+ // ^L - This is a source of issues, as the search editor refreshes first,
+ // and we end up with the wrong order of prompts, so we will first refresh
+ // ourselves, then refresh the search editor, and then tell him not to process
+ // this event.
+ m_search_editor->register_key_input_callback(ctrl('L'), [this](auto& search_editor) {
+ fprintf(stderr, "\033[3J\033[H\033[2J"); // Clear screen.
+
+ // refresh our own prompt
+ set_origin(1, 1);
+ m_refresh_needed = true;
+ refresh_display();
+
+ // move the search prompt below ours
+ // and tell it to redraw itself
+ search_editor.set_origin(2, 1);
+ search_editor.m_refresh_needed = true;
+
+ return false;
+ });
+
+ // quit without clearing the current buffer
+ m_search_editor->register_key_input_callback('\t', [this](Editor& search_editor) {
+ search_editor.finish();
+ m_reset_buffer_on_search_end = false;
+ return false;
+ });
+
+ fprintf(stderr, "\n");
+ fflush(stderr);
+
+ auto search_prompt = "\x1b[32msearch:\x1b[0m ";
+
+ // While the search editor is active, we do not want editing events.
+ m_is_editing = false;
+
+ auto search_string_result = m_search_editor->get_line(search_prompt);
+
+ // Grab where the search origin last was, anything up to this point will be cleared.
+ auto search_end_row = m_search_editor->m_origin_row;
+
+ remove_child(*m_search_editor);
+ m_search_editor = nullptr;
+ m_is_searching = false;
+ m_is_editing = true;
+ m_search_offset = 0;
+
+ // Re-enable the notifier after discarding the search editor.
+ m_notifier->set_enabled(true);
+
+ if (search_string_result.is_error()) {
+ // Somethine broke, fail
+ m_input_error = search_string_result.error();
+ finish();
+ return;
+ }
+
+ auto& search_string = search_string_result.value();
+
+ // Manually cleanup the search line.
+ reposition_cursor();
+ auto search_metrics = actual_rendered_string_metrics(search_string);
+ auto metrics = actual_rendered_string_metrics(search_prompt);
+ VT::clear_lines(0, metrics.lines_with_addition(search_metrics, m_num_columns) + search_end_row - m_origin_row - 1);
+
+ reposition_cursor();
+
+ if (!m_reset_buffer_on_search_end || search_metrics.total_length == 0) {
+ // If the entry was empty, or we purposely quit without a newline,
+ // do not return anything; instead, just end the search.
+ end_search();
+ return;
+ }
+
+ // Return the string,
+ finish();
+ }
+}
+
+void Editor::transpose_words()
+{
+ // A word here is contiguous alnums. `foo=bar baz` is three words.
+
+ // 'abcd,.:efg...' should become 'efg...,.:abcd' if caret is after
+ // 'efg...'. If it's in 'efg', it should become 'efg,.:abcd...'
+ // with the caret after it, which then becomes 'abcd...,.:efg'
+ // when alt-t is pressed a second time.
+
+ // Move to end of word under (or after) caret.
+ size_t cursor = m_cursor;
+ while (cursor < m_buffer.size() && !isalnum(m_buffer[cursor]))
+ ++cursor;
+ while (cursor < m_buffer.size() && isalnum(m_buffer[cursor]))
+ ++cursor;
+
+ // Move left over second word and the space to its right.
+ size_t end = cursor;
+ size_t start = cursor;
+ while (start > 0 && !isalnum(m_buffer[start - 1]))
+ --start;
+ while (start > 0 && isalnum(m_buffer[start - 1]))
+ --start;
+ size_t start_second_word = start;
+
+ // Move left over space between the two words.
+ while (start > 0 && !isalnum(m_buffer[start - 1]))
+ --start;
+ size_t start_gap = start;
+
+ // Move left over first word.
+ while (start > 0 && isalnum(m_buffer[start - 1]))
+ --start;
+
+ if (start != start_gap) {
+ // To swap the two words, swap each word (and the gap) individually, and then swap the whole range.
+ auto swap_range = [this](auto from, auto to) {
+ for (size_t i = 0; i < (to - from) / 2; ++i)
+ swap(m_buffer[from + i], m_buffer[to - 1 - i]);
+ };
+ swap_range(start, start_gap);
+ swap_range(start_gap, start_second_word);
+ swap_range(start_second_word, end);
+ swap_range(start, end);
+ m_cursor = cursor;
+ // FIXME: Update anchored styles too.
+ m_refresh_needed = true;
+ }
+}
+
+void Editor::go_home()
+{
+ m_cursor = 0;
+ m_inline_search_cursor = m_cursor;
+ m_search_offset = 0;
+}
+
+void Editor::go_end()
+{
+ m_cursor = m_buffer.size();
+ m_inline_search_cursor = m_cursor;
+ m_search_offset = 0;
+}
+
+void Editor::clear_screen()
+{
+ fprintf(stderr, "\033[3J\033[H\033[2J"); // Clear screen.
+ VT::move_absolute(1, 1);
+ set_origin(1, 1);
+ m_refresh_needed = true;
+}
+
+void Editor::insert_last_words()
+{
+ if (!m_history.is_empty()) {
+ // FIXME: This isn't quite right: if the last arg was `"foo bar"` or `foo\ bar` (but not `foo\\ bar`), we should insert that whole arg as last token.
+ if (auto last_words = m_history.last().entry.split_view(' '); !last_words.is_empty())
+ insert(last_words.last());
+ }
+}
+
+void Editor::erase_alnum_word_backwards()
+{
+ // A word here is contiguous alnums. `foo=bar baz` is three words.
+ bool has_seen_alnum = false;
+ while (m_cursor > 0) {
+ if (!isalnum(m_buffer[m_cursor - 1])) {
+ if (has_seen_alnum)
+ break;
+ } else {
+ has_seen_alnum = true;
+ }
+ erase_character_backwards();
+ }
+}
+
+void Editor::erase_alnum_word_forwards()
+{
+ // A word here is contiguous alnums. `foo=bar baz` is three words.
+ bool has_seen_alnum = false;
+ while (m_cursor < m_buffer.size()) {
+ if (!isalnum(m_buffer[m_cursor])) {
+ if (has_seen_alnum)
+ break;
+ } else {
+ has_seen_alnum = true;
+ }
+ erase_character_forwards();
+ }
+}
+
+void Editor::case_change_word(Editor::CaseChangeOp change_op)
+{
+ // A word here is contiguous alnums. `foo=bar baz` is three words.
+ while (m_cursor < m_buffer.size() && !isalnum(m_buffer[m_cursor]))
+ ++m_cursor;
+ size_t start = m_cursor;
+ while (m_cursor < m_buffer.size() && isalnum(m_buffer[m_cursor])) {
+ if (change_op == CaseChangeOp::Uppercase || (change_op == CaseChangeOp::Capital && m_cursor == start)) {
+ m_buffer[m_cursor] = toupper(m_buffer[m_cursor]);
+ } else {
+ ASSERT(change_op == CaseChangeOp::Lowercase || (change_op == CaseChangeOp::Capital && m_cursor > start));
+ m_buffer[m_cursor] = tolower(m_buffer[m_cursor]);
+ }
+ ++m_cursor;
+ m_refresh_needed = true;
+ }
+}
+
+void Editor::capitalize_word()
+{
+ case_change_word(CaseChangeOp::Capital);
+}
+
+void Editor::lowercase_word()
+{
+ case_change_word(CaseChangeOp::Lowercase);
+}
+
+void Editor::uppercase_word()
+{
+ case_change_word(CaseChangeOp::Uppercase);
+}
+
+}
diff --git a/Userland/Libraries/LibLine/KeyCallbackMachine.cpp b/Userland/Libraries/LibLine/KeyCallbackMachine.cpp
new file mode 100644
index 0000000000..8f4272810a
--- /dev/null
+++ b/Userland/Libraries/LibLine/KeyCallbackMachine.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibLine/Editor.h>
+
+namespace {
+constexpr u32 ctrl(char c) { return c & 0x3f; }
+}
+
+namespace Line {
+
+void KeyCallbackMachine::register_key_input_callback(Vector<Key> keys, Function<bool(Editor&)> callback)
+{
+ m_key_callbacks.set(keys, make<KeyCallback>(move(callback)));
+}
+
+void KeyCallbackMachine::key_pressed(Editor& editor, Key key)
+{
+#ifdef CALLBACK_MACHINE_DEBUG
+ dbgln("Key<{}, {}> pressed, seq_length={}, {} things in the matching vector", key.key, key.modifiers, m_sequence_length, m_current_matching_keys.size());
+#endif
+ if (m_sequence_length == 0) {
+ ASSERT(m_current_matching_keys.is_empty());
+
+ for (auto& it : m_key_callbacks) {
+ if (it.key.first() == key)
+ m_current_matching_keys.append(it.key);
+ }
+
+ if (m_current_matching_keys.is_empty()) {
+ m_should_process_this_key = true;
+ return;
+ }
+ }
+
+ ++m_sequence_length;
+ Vector<Vector<Key>> old_macthing_keys;
+ swap(m_current_matching_keys, old_macthing_keys);
+
+ for (auto& okey : old_macthing_keys) {
+ if (okey.size() < m_sequence_length)
+ continue;
+
+ if (okey[m_sequence_length - 1] == key)
+ m_current_matching_keys.append(okey);
+ }
+
+ if (m_current_matching_keys.is_empty()) {
+ // Insert any keys that were captured
+ if (!old_macthing_keys.is_empty()) {
+ auto& keys = old_macthing_keys.first();
+ for (size_t i = 0; i < m_sequence_length - 1; ++i)
+ editor.insert(keys[i].key);
+ }
+ m_sequence_length = 0;
+ m_should_process_this_key = true;
+ return;
+ }
+
+#ifdef CALLBACK_MACHINE_DEBUG
+ dbgln("seq_length={}, matching vector:", m_sequence_length);
+ for (auto& key : m_current_matching_keys) {
+ for (auto& k : key)
+ dbgln(" {}, {}", k.key, k.modifiers);
+ dbgln("");
+ }
+#endif
+
+ m_should_process_this_key = false;
+ for (auto& key : m_current_matching_keys) {
+ if (key.size() == m_sequence_length) {
+ m_should_process_this_key = m_key_callbacks.get(key).value()->callback(editor);
+ m_sequence_length = 0;
+ m_current_matching_keys.clear();
+ return;
+ }
+ }
+}
+
+void KeyCallbackMachine::interrupted(Editor& editor)
+{
+ m_sequence_length = 0;
+ m_current_matching_keys.clear();
+ if (auto callback = m_key_callbacks.get({ ctrl('C') }); callback.has_value())
+ m_should_process_this_key = callback.value()->callback(editor);
+ else
+ m_should_process_this_key = true;
+}
+
+}
diff --git a/Userland/Libraries/LibLine/KeyCallbackMachine.h b/Userland/Libraries/LibLine/KeyCallbackMachine.h
new file mode 100644
index 0000000000..b0689904e8
--- /dev/null
+++ b/Userland/Libraries/LibLine/KeyCallbackMachine.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/HashMap.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+
+namespace Line {
+
+class Editor;
+
+struct Key {
+ enum Modifier : int {
+ None = 0,
+ Alt = 1,
+ };
+
+ int modifiers { None };
+ unsigned key { 0 };
+
+ Key(unsigned c)
+ : modifiers(None)
+ , key(c) {};
+
+ Key(unsigned c, int modifiers)
+ : modifiers(modifiers)
+ , key(c)
+ {
+ }
+
+ bool operator==(const Key& other) const
+ {
+ return other.key == key && other.modifiers == modifiers;
+ }
+
+ bool operator!=(const Key& other) const
+ {
+ return !(*this == other);
+ }
+};
+
+struct KeyCallback {
+ KeyCallback(Function<bool(Editor&)> cb)
+ : callback(move(cb))
+ {
+ }
+ Function<bool(Editor&)> callback;
+};
+
+class KeyCallbackMachine {
+public:
+ void register_key_input_callback(Vector<Key>, Function<bool(Editor&)> callback);
+ void key_pressed(Editor&, Key);
+ void interrupted(Editor&);
+ bool should_process_last_pressed_key() const { return m_should_process_this_key; }
+
+private:
+ HashMap<Vector<Key>, NonnullOwnPtr<KeyCallback>> m_key_callbacks;
+ Vector<Vector<Key>> m_current_matching_keys;
+ size_t m_sequence_length { 0 };
+ bool m_should_process_this_key { true };
+};
+
+}
+
+namespace AK {
+
+template<>
+struct Traits<Line::Key> : public GenericTraits<Line::Key> {
+ static constexpr bool is_trivial() { return true; }
+ static unsigned hash(Line::Key k) { return pair_int_hash(k.key, k.modifiers); }
+};
+
+template<>
+struct Traits<Vector<Line::Key>> : public GenericTraits<Vector<Line::Key>> {
+ static constexpr bool is_trivial() { return false; }
+ static unsigned hash(const Vector<Line::Key>& ks)
+ {
+ unsigned h = 0;
+ for (auto& k : ks)
+ h ^= Traits<Line::Key>::hash(k);
+ return h;
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibLine/Span.h b/Userland/Libraries/LibLine/Span.h
new file mode 100644
index 0000000000..0d1eb108f8
--- /dev/null
+++ b/Userland/Libraries/LibLine/Span.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace Line {
+
+class Span {
+public:
+ enum Mode {
+ ByteOriented,
+ CodepointOriented,
+ };
+
+ Span(size_t start, size_t end, Mode mode = ByteOriented)
+ : m_beginning(start)
+ , m_end(end)
+ , m_mode(mode)
+ {
+ }
+
+ size_t beginning() const { return m_beginning; }
+ size_t end() const { return m_end; }
+ Mode mode() const { return m_mode; }
+
+private:
+ size_t m_beginning { 0 };
+ size_t m_end { 0 };
+ Mode m_mode { CodepointOriented };
+};
+
+}
diff --git a/Userland/Libraries/LibLine/StringMetrics.h b/Userland/Libraries/LibLine/StringMetrics.h
new file mode 100644
index 0000000000..5a23953976
--- /dev/null
+++ b/Userland/Libraries/LibLine/StringMetrics.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2020-2021, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+#include <AK/Vector.h>
+
+namespace Line {
+
+struct StringMetrics {
+ struct MaskedChar {
+ size_t position { 0 };
+ size_t original_length { 0 };
+ size_t masked_length { 0 };
+ };
+ struct LineMetrics {
+ Vector<MaskedChar> masked_chars;
+ size_t length { 0 };
+
+ size_t total_length(ssize_t offset = -1) const
+ {
+ size_t length = this->length;
+ for (auto& mask : masked_chars) {
+ if (offset < 0 || mask.position <= (size_t)offset) {
+ length -= mask.original_length;
+ length += mask.masked_length;
+ }
+ }
+ return length;
+ }
+ };
+
+ Vector<LineMetrics> line_metrics;
+ size_t total_length { 0 };
+ size_t max_line_length { 0 };
+
+ size_t lines_with_addition(const StringMetrics& offset, size_t column_width) const;
+ size_t offset_with_addition(const StringMetrics& offset, size_t column_width) const;
+ void reset()
+ {
+ line_metrics.clear();
+ total_length = 0;
+ max_line_length = 0;
+ line_metrics.append({ {}, 0 });
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibLine/Style.h b/Userland/Libraries/LibLine/Style.h
new file mode 100644
index 0000000000..00066af1df
--- /dev/null
+++ b/Userland/Libraries/LibLine/Style.h
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <AK/Vector.h>
+#include <stdlib.h>
+
+namespace Line {
+
+class Style {
+public:
+ enum class XtermColor : int {
+ Default = 9,
+ Black = 0,
+ Red,
+ Green,
+ Yellow,
+ Blue,
+ Magenta,
+ Cyan,
+ White,
+ Unchanged,
+ };
+
+ struct AnchoredTag {
+ };
+ struct UnderlineTag {
+ };
+ struct BoldTag {
+ };
+ struct ItalicTag {
+ };
+ struct Color {
+ explicit Color(XtermColor color)
+ : m_xterm_color(color)
+ , m_is_rgb(false)
+ {
+ }
+ Color(u8 r, u8 g, u8 b)
+ : m_rgb_color({ r, g, b })
+ , m_is_rgb(true)
+ {
+ }
+
+ bool is_default() const
+ {
+ return !m_is_rgb && m_xterm_color == XtermColor::Unchanged;
+ }
+
+ XtermColor m_xterm_color { XtermColor::Unchanged };
+ Vector<int, 3> m_rgb_color;
+ bool m_is_rgb { false };
+ };
+
+ struct Background : public Color {
+ explicit Background(XtermColor color)
+ : Color(color)
+ {
+ }
+ Background(u8 r, u8 g, u8 b)
+ : Color(r, g, b)
+ {
+ }
+ String to_vt_escape() const;
+ };
+
+ struct Foreground : public Color {
+ explicit Foreground(XtermColor color)
+ : Color(color)
+ {
+ }
+ Foreground(u8 r, u8 g, u8 b)
+ : Color(r, g, b)
+ {
+ }
+
+ String to_vt_escape() const;
+ };
+
+ struct Hyperlink {
+ explicit Hyperlink(const StringView& link)
+ : m_link(link)
+ {
+ m_has_link = true;
+ }
+
+ Hyperlink() { }
+
+ String to_vt_escape(bool starting) const;
+
+ bool is_empty() const { return !m_has_link; }
+
+ String m_link;
+ bool m_has_link { false };
+ };
+
+ static constexpr UnderlineTag Underline {};
+ static constexpr BoldTag Bold {};
+ static constexpr ItalicTag Italic {};
+ static constexpr AnchoredTag Anchored {};
+
+ // Prepare for the horror of templates.
+ template<typename T, typename... Rest>
+ Style(const T& style_arg, Rest... rest)
+ : Style(rest...)
+ {
+ set(style_arg);
+ m_is_empty = false;
+ }
+ Style() { }
+
+ static Style reset_style()
+ {
+ return { Foreground(XtermColor::Default), Background(XtermColor::Default), Hyperlink("") };
+ }
+
+ Style unified_with(const Style& other, bool prefer_other = true) const
+ {
+ Style style = *this;
+ style.unify_with(other, prefer_other);
+ return style;
+ }
+
+ void unify_with(const Style&, bool prefer_other = false);
+
+ bool underline() const { return m_underline; }
+ bool bold() const { return m_bold; }
+ bool italic() const { return m_italic; }
+ Background background() const { return m_background; }
+ Foreground foreground() const { return m_foreground; }
+ Hyperlink hyperlink() const { return m_hyperlink; }
+
+ void set(const ItalicTag&) { m_italic = true; }
+ void set(const BoldTag&) { m_bold = true; }
+ void set(const UnderlineTag&) { m_underline = true; }
+ void set(const Background& bg) { m_background = bg; }
+ void set(const Foreground& fg) { m_foreground = fg; }
+ void set(const Hyperlink& link) { m_hyperlink = link; }
+ void set(const AnchoredTag&) { m_is_anchored = true; }
+
+ bool is_anchored() const { return m_is_anchored; }
+ bool is_empty() const { return m_is_empty; }
+
+ String to_string() const;
+
+private:
+ bool m_underline { false };
+ bool m_bold { false };
+ bool m_italic { false };
+ Background m_background { XtermColor::Unchanged };
+ Foreground m_foreground { XtermColor::Unchanged };
+ Hyperlink m_hyperlink;
+
+ bool m_is_anchored { false };
+
+ bool m_is_empty { true };
+};
+}
diff --git a/Userland/Libraries/LibLine/SuggestionDisplay.h b/Userland/Libraries/LibLine/SuggestionDisplay.h
new file mode 100644
index 0000000000..434b0ee3a2
--- /dev/null
+++ b/Userland/Libraries/LibLine/SuggestionDisplay.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/String.h>
+#include <LibLine/StringMetrics.h>
+#include <LibLine/SuggestionManager.h>
+#include <stdlib.h>
+
+namespace Line {
+
+class Editor;
+
+class SuggestionDisplay {
+public:
+ virtual ~SuggestionDisplay() { }
+ virtual void display(const SuggestionManager&) = 0;
+ virtual bool cleanup() = 0;
+ virtual void finish() = 0;
+ virtual void set_initial_prompt_lines(size_t) = 0;
+
+ virtual void set_vt_size(size_t lines, size_t columns) = 0;
+
+ size_t origin_row() const { return m_origin_row; }
+ size_t origin_col() const { return m_origin_column; }
+
+ void set_origin(int row, int col, Badge<Editor>)
+ {
+ m_origin_row = row;
+ m_origin_column = col;
+ }
+
+protected:
+ int m_origin_row { 0 };
+ int m_origin_column { 0 };
+};
+
+class XtermSuggestionDisplay : public SuggestionDisplay {
+public:
+ XtermSuggestionDisplay(size_t lines, size_t columns)
+ : m_num_lines(lines)
+ , m_num_columns(columns)
+ {
+ }
+ virtual ~XtermSuggestionDisplay() override { }
+ virtual void display(const SuggestionManager&) override;
+ virtual bool cleanup() override;
+ virtual void finish() override
+ {
+ m_pages.clear();
+ }
+
+ virtual void set_initial_prompt_lines(size_t lines) override
+ {
+ m_prompt_lines_at_suggestion_initiation = lines;
+ }
+
+ virtual void set_vt_size(size_t lines, size_t columns) override
+ {
+ m_num_lines = lines;
+ m_num_columns = columns;
+ m_pages.clear();
+ }
+
+private:
+ size_t fit_to_page_boundary(size_t selection_index);
+ size_t m_lines_used_for_last_suggestions { 0 };
+ size_t m_num_lines { 0 };
+ size_t m_num_columns { 0 };
+ size_t m_prompt_lines_at_suggestion_initiation { 0 };
+
+ struct PageRange {
+ size_t start;
+ size_t end;
+ };
+ Vector<PageRange> m_pages;
+};
+
+}
diff --git a/Userland/Libraries/LibLine/SuggestionManager.cpp b/Userland/Libraries/LibLine/SuggestionManager.cpp
new file mode 100644
index 0000000000..f7c0667e49
--- /dev/null
+++ b/Userland/Libraries/LibLine/SuggestionManager.cpp
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibLine/SuggestionManager.h>
+
+namespace Line {
+
+CompletionSuggestion::CompletionSuggestion(const StringView& completion, const StringView& trailing_trivia, Style style)
+ : style(style)
+ , text_string(completion)
+ , is_valid(true)
+{
+ Utf8View text_u8 { completion };
+ Utf8View trivia_u8 { trailing_trivia };
+
+ for (auto cp : text_u8)
+ text.append(cp);
+
+ for (auto cp : trivia_u8)
+ this->trailing_trivia.append(cp);
+
+ text_view = Utf32View { text.data(), text.size() };
+ trivia_view = Utf32View { this->trailing_trivia.data(), this->trailing_trivia.size() };
+}
+
+void SuggestionManager::set_suggestions(Vector<CompletionSuggestion>&& suggestions)
+{
+ m_suggestions = move(suggestions);
+
+ // make sure we were not given invalid suggestions
+ for (auto& suggestion : m_suggestions)
+ ASSERT(suggestion.is_valid);
+
+ size_t common_suggestion_prefix { 0 };
+ if (m_suggestions.size() == 1) {
+ m_largest_common_suggestion_prefix_length = m_suggestions[0].text_view.length();
+ } else if (m_suggestions.size()) {
+ u32 last_valid_suggestion_code_point;
+
+ for (;; ++common_suggestion_prefix) {
+ if (m_suggestions[0].text_view.length() <= common_suggestion_prefix)
+ goto no_more_commons;
+
+ last_valid_suggestion_code_point = m_suggestions[0].text_view.code_points()[common_suggestion_prefix];
+
+ for (auto& suggestion : m_suggestions) {
+ if (suggestion.text_view.length() <= common_suggestion_prefix || suggestion.text_view.code_points()[common_suggestion_prefix] != last_valid_suggestion_code_point) {
+ goto no_more_commons;
+ }
+ }
+ }
+ no_more_commons:;
+ m_largest_common_suggestion_prefix_length = common_suggestion_prefix;
+ } else {
+ m_largest_common_suggestion_prefix_length = 0;
+ }
+}
+
+void SuggestionManager::next()
+{
+ if (m_suggestions.size())
+ m_next_suggestion_index = (m_next_suggestion_index + 1) % m_suggestions.size();
+ else
+ m_next_suggestion_index = 0;
+}
+
+void SuggestionManager::previous()
+{
+ if (m_next_suggestion_index == 0)
+ m_next_suggestion_index = m_suggestions.size();
+ m_next_suggestion_index--;
+}
+
+const CompletionSuggestion& SuggestionManager::suggest()
+{
+ m_last_shown_suggestion = m_suggestions[m_next_suggestion_index];
+ m_selected_suggestion_index = m_next_suggestion_index;
+ return m_last_shown_suggestion;
+}
+
+void SuggestionManager::set_current_suggestion_initiation_index(size_t index)
+{
+
+ if (m_last_shown_suggestion_display_length)
+ m_last_shown_suggestion.start_index = index - m_next_suggestion_static_offset - m_last_shown_suggestion_display_length;
+ else
+ m_last_shown_suggestion.start_index = index - m_next_suggestion_static_offset - m_next_suggestion_invariant_offset;
+
+ m_last_shown_suggestion_display_length = m_last_shown_suggestion.text_view.length();
+ m_last_shown_suggestion_was_complete = true;
+}
+
+SuggestionManager::CompletionAttemptResult SuggestionManager::attempt_completion(CompletionMode mode, size_t initiation_start_index)
+{
+ CompletionAttemptResult result { mode };
+
+ if (m_next_suggestion_index < m_suggestions.size()) {
+ auto can_complete = m_next_suggestion_invariant_offset <= m_largest_common_suggestion_prefix_length;
+ if (!m_last_shown_suggestion.text.is_null()) {
+ ssize_t actual_offset;
+ size_t shown_length = m_last_shown_suggestion_display_length;
+ switch (mode) {
+ case CompletePrefix:
+ actual_offset = 0;
+ break;
+ case ShowSuggestions:
+ actual_offset = 0 - m_largest_common_suggestion_prefix_length + m_next_suggestion_invariant_offset;
+ if (can_complete)
+ shown_length = m_largest_common_suggestion_prefix_length + m_last_shown_suggestion.trivia_view.length();
+ break;
+ default:
+ if (m_last_shown_suggestion_display_length == 0)
+ actual_offset = 0;
+ else
+ actual_offset = 0 - m_last_shown_suggestion_display_length + m_next_suggestion_invariant_offset;
+ break;
+ }
+
+ result.offset_region_to_remove = { m_next_suggestion_invariant_offset, shown_length };
+ result.new_cursor_offset = actual_offset;
+ }
+
+ auto& suggestion = suggest();
+ set_current_suggestion_initiation_index(initiation_start_index);
+
+ if (mode == CompletePrefix) {
+ // Only auto-complete *if possible*.
+ if (can_complete) {
+ result.insert.append(suggestion.text_view.substring_view(m_next_suggestion_invariant_offset, m_largest_common_suggestion_prefix_length - m_next_suggestion_invariant_offset));
+ m_last_shown_suggestion_display_length = m_largest_common_suggestion_prefix_length;
+ // Do not increment the suggestion index, as the first tab should only be a *peek*.
+ if (m_suggestions.size() == 1) {
+ // If there's one suggestion, commit and forget.
+ result.new_completion_mode = DontComplete;
+ // Add in the trivia of the last selected suggestion.
+ result.insert.append(suggestion.trivia_view);
+ m_last_shown_suggestion_display_length = 0;
+ result.style_to_apply = suggestion.style;
+ m_last_shown_suggestion_was_complete = true;
+ return result;
+ }
+ } else {
+ m_last_shown_suggestion_display_length = 0;
+ }
+ result.new_completion_mode = CompletionMode::ShowSuggestions;
+ m_last_shown_suggestion_was_complete = false;
+ m_last_shown_suggestion = String::empty();
+ } else {
+ result.insert.append(suggestion.text_view.substring_view(m_next_suggestion_invariant_offset, suggestion.text_view.length() - m_next_suggestion_invariant_offset));
+ // Add in the trivia of the last selected suggestion.
+ result.insert.append(suggestion.trivia_view);
+ m_last_shown_suggestion_display_length += suggestion.trivia_view.length();
+ }
+ } else {
+ m_next_suggestion_index = 0;
+ }
+ return result;
+}
+
+size_t SuggestionManager::for_each_suggestion(Function<IterationDecision(const CompletionSuggestion&, size_t)> callback) const
+{
+ size_t start_index { 0 };
+ for (auto& suggestion : m_suggestions) {
+ if (start_index++ < m_last_displayed_suggestion_index)
+ continue;
+ if (callback(suggestion, start_index - 1) == IterationDecision::Break)
+ break;
+ }
+ return start_index;
+}
+
+}
diff --git a/Userland/Libraries/LibLine/SuggestionManager.h b/Userland/Libraries/LibLine/SuggestionManager.h
new file mode 100644
index 0000000000..0b804fef08
--- /dev/null
+++ b/Userland/Libraries/LibLine/SuggestionManager.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/String.h>
+#include <AK/Utf32View.h>
+#include <AK/Utf8View.h>
+#include <LibLine/Style.h>
+#include <stdlib.h>
+
+namespace Line {
+
+// FIXME: These objects are pretty heavy since they store two copies of text
+// somehow get rid of one.
+struct CompletionSuggestion {
+private:
+ struct ForSearchTag {
+ };
+
+public:
+ static constexpr ForSearchTag ForSearch {};
+
+ // Intentionally not explicit. (To allow suggesting bare strings)
+ CompletionSuggestion(const String& completion)
+ : CompletionSuggestion(completion, "", {})
+ {
+ }
+
+ CompletionSuggestion(const String& completion, ForSearchTag)
+ : text_string(completion)
+ {
+ }
+
+ CompletionSuggestion(const StringView& completion, const StringView& trailing_trivia)
+ : CompletionSuggestion(completion, trailing_trivia, {})
+ {
+ }
+
+ CompletionSuggestion(const StringView& completion, const StringView& trailing_trivia, Style style);
+
+ bool operator==(const CompletionSuggestion& suggestion) const
+ {
+ return suggestion.text_string == text_string;
+ }
+
+ Vector<u32> text;
+ Vector<u32> trailing_trivia;
+ Style style;
+ size_t start_index { 0 };
+ size_t input_offset { 0 };
+
+ Utf32View text_view;
+ Utf32View trivia_view;
+ String text_string;
+ bool is_valid { false };
+};
+
+class SuggestionManager {
+ friend class Editor;
+
+public:
+ void set_suggestions(Vector<CompletionSuggestion>&& suggestions);
+ void set_current_suggestion_initiation_index(size_t start_index);
+
+ size_t count() const { return m_suggestions.size(); }
+ size_t display_length() const { return m_last_shown_suggestion_display_length; }
+ size_t start_index() const { return m_last_displayed_suggestion_index; }
+ size_t next_index() const { return m_next_suggestion_index; }
+ void set_start_index(size_t index) const { m_last_displayed_suggestion_index = index; }
+
+ size_t for_each_suggestion(Function<IterationDecision(const CompletionSuggestion&, size_t)>) const;
+
+ enum CompletionMode {
+ DontComplete,
+ CompletePrefix,
+ ShowSuggestions,
+ CycleSuggestions,
+ };
+
+ class CompletionAttemptResult {
+ public:
+ CompletionMode new_completion_mode;
+
+ ssize_t new_cursor_offset { 0 };
+
+ struct {
+ size_t start;
+ size_t end;
+ } offset_region_to_remove { 0, 0 }; // The region to remove as defined by [start, end) translated by (old_cursor + new_cursor_offset)
+
+ Vector<Utf32View> insert {};
+
+ Optional<Style> style_to_apply {};
+ };
+
+ CompletionAttemptResult attempt_completion(CompletionMode, size_t initiation_start_index);
+
+ void next();
+ void previous();
+ void set_suggestion_variants(size_t static_offset, size_t invariant_offset, size_t suggestion_index) const
+ {
+ m_next_suggestion_index = suggestion_index;
+ m_next_suggestion_static_offset = static_offset;
+ m_next_suggestion_invariant_offset = invariant_offset;
+ }
+
+ const CompletionSuggestion& suggest();
+ const CompletionSuggestion& current_suggestion() const { return m_last_shown_suggestion; }
+ bool is_current_suggestion_complete() const { return m_last_shown_suggestion_was_complete; }
+
+ void reset()
+ {
+ m_last_shown_suggestion = String::empty();
+ m_last_shown_suggestion_display_length = 0;
+ m_suggestions.clear();
+ m_last_displayed_suggestion_index = 0;
+ }
+
+private:
+ SuggestionManager()
+ {
+ }
+
+ Vector<CompletionSuggestion> m_suggestions;
+ CompletionSuggestion m_last_shown_suggestion { String::empty() };
+ size_t m_last_shown_suggestion_display_length { 0 };
+ bool m_last_shown_suggestion_was_complete { false };
+ mutable size_t m_next_suggestion_index { 0 };
+ mutable size_t m_next_suggestion_invariant_offset { 0 };
+ mutable size_t m_next_suggestion_static_offset { 0 };
+ size_t m_largest_common_suggestion_prefix_length { 0 };
+ mutable size_t m_last_displayed_suggestion_index { 0 };
+ size_t m_selected_suggestion_index { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibLine/VT.h b/Userland/Libraries/LibLine/VT.h
new file mode 100644
index 0000000000..cb93393c22
--- /dev/null
+++ b/Userland/Libraries/LibLine/VT.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+#include <LibLine/Style.h>
+
+namespace Line {
+namespace VT {
+
+void save_cursor();
+void restore_cursor();
+void clear_to_end_of_line();
+void clear_lines(size_t count_above, size_t count_below = 0);
+void move_relative(int x, int y);
+void move_absolute(u32 x, u32 y);
+void apply_style(const Style&, bool is_starting = true);
+
+}
+}
diff --git a/Userland/Libraries/LibLine/XtermSuggestionDisplay.cpp b/Userland/Libraries/LibLine/XtermSuggestionDisplay.cpp
new file mode 100644
index 0000000000..7e341854ee
--- /dev/null
+++ b/Userland/Libraries/LibLine/XtermSuggestionDisplay.cpp
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2020-2021, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/BinarySearch.h>
+#include <AK/Function.h>
+#include <AK/StringBuilder.h>
+#include <LibLine/SuggestionDisplay.h>
+#include <LibLine/VT.h>
+#include <stdio.h>
+
+namespace Line {
+
+void XtermSuggestionDisplay::display(const SuggestionManager& manager)
+{
+ size_t longest_suggestion_length = 0;
+ size_t longest_suggestion_byte_length = 0;
+
+ manager.for_each_suggestion([&](auto& suggestion, auto) {
+ longest_suggestion_length = max(longest_suggestion_length, suggestion.text_view.length());
+ longest_suggestion_byte_length = max(longest_suggestion_byte_length, suggestion.text_string.length());
+ return IterationDecision::Continue;
+ });
+
+ size_t num_printed = 0;
+ size_t lines_used = 1;
+
+ VT::save_cursor();
+ VT::clear_lines(0, m_lines_used_for_last_suggestions);
+ VT::restore_cursor();
+
+ auto spans_entire_line { false };
+ Vector<StringMetrics::LineMetrics> lines;
+ for (size_t i = 0; i < m_prompt_lines_at_suggestion_initiation - 1; ++i)
+ lines.append({ {}, 0 });
+ lines.append({ {}, longest_suggestion_length });
+ auto max_line_count = StringMetrics { move(lines) }.lines_with_addition({ { { {}, 0 } } }, m_num_columns);
+ if (longest_suggestion_length >= m_num_columns - 2) {
+ spans_entire_line = true;
+ // We should make enough space for the biggest entry in
+ // the suggestion list to fit in the prompt line.
+ auto start = max_line_count - m_prompt_lines_at_suggestion_initiation;
+ for (size_t i = start; i < max_line_count; ++i) {
+ fputc('\n', stderr);
+ }
+ lines_used += max_line_count;
+ longest_suggestion_length = 0;
+ }
+
+ VT::move_absolute(max_line_count + m_origin_row, 1);
+
+ if (m_pages.is_empty()) {
+ size_t num_printed = 0;
+ size_t lines_used = 1;
+ // Cache the pages.
+ manager.set_start_index(0);
+ size_t page_start = 0;
+ manager.for_each_suggestion([&](auto& suggestion, auto index) {
+ size_t next_column = num_printed + suggestion.text_view.length() + longest_suggestion_length + 2;
+ if (next_column > m_num_columns) {
+ auto lines = (suggestion.text_view.length() + m_num_columns - 1) / m_num_columns;
+ lines_used += lines;
+ num_printed = 0;
+ }
+
+ if (lines_used + m_prompt_lines_at_suggestion_initiation >= m_num_lines) {
+ m_pages.append({ page_start, index });
+ page_start = index;
+ lines_used = 1;
+ num_printed = 0;
+ }
+
+ if (spans_entire_line)
+ num_printed += m_num_columns;
+ else
+ num_printed += longest_suggestion_length + 2;
+
+ return IterationDecision::Continue;
+ });
+ // Append the last page.
+ m_pages.append({ page_start, manager.count() });
+ }
+
+ auto page_index = fit_to_page_boundary(manager.next_index());
+
+ manager.set_start_index(m_pages[page_index].start);
+
+ manager.for_each_suggestion([&](auto& suggestion, auto index) {
+ size_t next_column = num_printed + suggestion.text_view.length() + longest_suggestion_length + 2;
+
+ if (next_column > m_num_columns) {
+ auto lines = (suggestion.text_view.length() + m_num_columns - 1) / m_num_columns;
+ lines_used += lines;
+ fputc('\n', stderr);
+ num_printed = 0;
+ }
+
+ // Show just enough suggestions to fill up the screen
+ // without moving the prompt out of view.
+ if (lines_used + m_prompt_lines_at_suggestion_initiation >= m_num_lines)
+ return IterationDecision::Break;
+
+ // Only apply color to the selection if something is *actually* added to the buffer.
+ if (manager.is_current_suggestion_complete() && index == manager.next_index()) {
+ VT::apply_style({ Style::Foreground(Style::XtermColor::Blue) });
+ fflush(stderr);
+ }
+
+ if (spans_entire_line) {
+ num_printed += m_num_columns;
+ fprintf(stderr, "%s", suggestion.text_string.characters());
+ } else {
+ fprintf(stderr, "%-*s", static_cast<int>(longest_suggestion_byte_length) + 2, suggestion.text_string.characters());
+ num_printed += longest_suggestion_length + 2;
+ }
+
+ if (manager.is_current_suggestion_complete() && index == manager.next_index()) {
+ VT::apply_style(Style::reset_style());
+ fflush(stderr);
+ }
+ return IterationDecision::Continue;
+ });
+
+ m_lines_used_for_last_suggestions = lines_used;
+
+ // If we filled the screen, move back the origin.
+ if (m_origin_row + lines_used >= m_num_lines) {
+ m_origin_row = m_num_lines - lines_used;
+ }
+
+ if (m_pages.size() > 1) {
+ auto left_arrow = page_index > 0 ? '<' : ' ';
+ auto right_arrow = page_index < m_pages.size() - 1 ? '>' : ' ';
+ auto string = String::format("%c page %zu of %zu %c", left_arrow, page_index + 1, m_pages.size(), right_arrow);
+
+ if (string.length() > m_num_columns - 1) {
+ // This would overflow into the next line, so just don't print an indicator.
+ fflush(stderr);
+ return;
+ }
+
+ VT::move_absolute(m_origin_row + lines_used, m_num_columns - string.length() - 1);
+ VT::apply_style({ Style::Background(Style::XtermColor::Green) });
+ fputs(string.characters(), stderr);
+ VT::apply_style(Style::reset_style());
+ }
+
+ fflush(stderr);
+}
+
+bool XtermSuggestionDisplay::cleanup()
+{
+ if (m_lines_used_for_last_suggestions) {
+ VT::clear_lines(0, m_lines_used_for_last_suggestions);
+ m_lines_used_for_last_suggestions = 0;
+ return true;
+ }
+
+ return false;
+}
+
+size_t XtermSuggestionDisplay::fit_to_page_boundary(size_t selection_index)
+{
+ ASSERT(m_pages.size() > 0);
+ size_t index = 0;
+
+ auto* match = binary_search(
+ m_pages.span(),
+ PageRange { selection_index, selection_index },
+ &index,
+ [](auto& a, auto& b) -> int {
+ if (a.start >= b.start && a.start < b.end)
+ return 0;
+ return a.start - b.start;
+ });
+
+ if (!match)
+ return m_pages.size() - 1;
+
+ return index;
+}
+
+}
diff --git a/Userland/Libraries/LibM/CMakeLists.txt b/Userland/Libraries/LibM/CMakeLists.txt
new file mode 100644
index 0000000000..bdb9baf78a
--- /dev/null
+++ b/Userland/Libraries/LibM/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(SOURCES
+ math.cpp
+)
+
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -nostdlib")
+serenity_libc(LibM m)
+#target_link_libraries(LibM)
+#set_target_properties(LibM PROPERTIES OUTPUT_NAME m)
+#target_link_directories(LibM PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
diff --git a/Userland/Libraries/LibM/TestMath.cpp b/Userland/Libraries/LibM/TestMath.cpp
new file mode 100644
index 0000000000..1a5ce4e87a
--- /dev/null
+++ b/Userland/Libraries/LibM/TestMath.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/TestSuite.h>
+
+#include <math.h>
+
+#define EXPECT_CLOSE(a, b) \
+ { \
+ EXPECT(fabs(a - b) < 0.000001); \
+ }
+
+TEST_CASE(trig)
+{
+ EXPECT_CLOSE(sin(1234), 0.653316);
+ EXPECT_CLOSE(cos(1234), -0.830914);
+ EXPECT_CLOSE(tan(1234), -0.786262);
+ EXPECT_CLOSE(sqrt(1234), 35.128336)
+ EXPECT_CLOSE(sin(-1), -0.867955);
+ EXPECT_CLOSE(cos(-1), 0.594715);
+ EXPECT_CLOSE(tan(-1), -1.459446);
+ EXPECT(isnan(sqrt(-1)));
+ EXPECT(isnan(asin(1.1)));
+ EXPECT(isnan(asin(-1.1)));
+ EXPECT_CLOSE(asin(0), 0.0);
+ EXPECT_CLOSE(asin(0.01), 0.01);
+ EXPECT_CLOSE(asin(0.1), 0.100167);
+ EXPECT_CLOSE(asin(0.3), 0.304693);
+ EXPECT_CLOSE(asin(0.499), 0.522444);
+ EXPECT_CLOSE(asin(0.5), 0.523599);
+ EXPECT_CLOSE(asin(0.501), 0.524754);
+ EXPECT_CLOSE(asin(0.9), 1.119770);
+ EXPECT_CLOSE(asin(0.99), 1.429246);
+ EXPECT_CLOSE(asin(1.0), 1.570750);
+ EXPECT_CLOSE(atan(0), 0.0)
+ EXPECT_CLOSE(atan(0.5), 0.463648)
+ EXPECT_CLOSE(atan(-0.5), -0.463648)
+ EXPECT_CLOSE(atan(5.5), 1.390943)
+ EXPECT_CLOSE(atan(-5.5), -1.390943)
+ EXPECT_CLOSE(atan(555.5), 1.568996)
+}
+
+TEST_CASE(other)
+{
+ EXPECT_EQ(trunc(9999999999999.5), 9999999999999.0);
+ EXPECT_EQ(trunc(-9999999999999.5), -9999999999999.0);
+}
+
+TEST_CASE(exponents)
+{
+ struct values {
+ double x;
+ double exp;
+ double sinh;
+ double cosh;
+ double tanh;
+ };
+
+ values values[8] {
+ { 1.500000, 4.481626, 2.129246, 2.352379, 0.905148 },
+ { 20.990000, 1304956710.432035, 652478355.216017, 652478355.216017, 1.000000 },
+ { 20.010000, 490041186.687082, 245020593.343541, 245020593.343541, 1.000000 },
+ { 0.000000, 1.000000, 0.000000, 1.000000, 0.000000 },
+ { 0.010000, 1.010050, 0.010000, 1.000050, 0.010000 },
+ { -0.010000, 0.990050, -0.010000, 1.000050, -0.010000 },
+ { -1.000000, 0.367879, -1.175201, 1.543081, -0.761594 },
+ { -17.000000, 0.000000, -12077476.376788, 12077476.376788, -1.000000 },
+ };
+ for (auto& v : values) {
+ EXPECT_CLOSE(exp(v.x), v.exp);
+ EXPECT_CLOSE(sinh(v.x), v.sinh);
+ EXPECT_CLOSE(cosh(v.x), v.cosh);
+ EXPECT_CLOSE(tanh(v.x), v.tanh);
+ }
+ EXPECT_EQ(exp(1000), std::numeric_limits<double>::infinity());
+}
+
+TEST_CASE(logarithms)
+{
+ EXPECT(isnan(log(-1)));
+ EXPECT(log(0) < -1000000);
+ EXPECT_CLOSE(log(0.5), -0.693233)
+ EXPECT_CLOSE(log(1.1), 0.095310)
+ EXPECT_CLOSE(log(5), 1.609480)
+ EXPECT_CLOSE(log(5.5), 1.704842)
+ EXPECT_CLOSE(log(500), 6.214104)
+ EXPECT_CLOSE(log2(5), 2.321989)
+ EXPECT_CLOSE(log10(5), 0.698988)
+}
+
+TEST_MAIN(Math)
diff --git a/Userland/Libraries/LibM/math.cpp b/Userland/Libraries/LibM/math.cpp
new file mode 100644
index 0000000000..2c5c86bdcf
--- /dev/null
+++ b/Userland/Libraries/LibM/math.cpp
@@ -0,0 +1,566 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibC/assert.h>
+#include <math.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+template<size_t>
+constexpr double e_to_power();
+template<>
+constexpr double e_to_power<0>() { return 1; }
+template<size_t exponent>
+constexpr double e_to_power() { return M_E * e_to_power<exponent - 1>(); }
+
+template<size_t>
+constexpr size_t factorial();
+template<>
+constexpr size_t factorial<0>() { return 1; }
+template<size_t value>
+constexpr size_t factorial() { return value * factorial<value - 1>(); }
+
+template<size_t>
+constexpr size_t product_even();
+template<>
+constexpr size_t product_even<2>() { return 2; }
+template<size_t value>
+constexpr size_t product_even() { return value * product_even<value - 2>(); }
+
+template<size_t>
+constexpr size_t product_odd();
+template<>
+constexpr size_t product_odd<1>() { return 1; }
+template<size_t value>
+constexpr size_t product_odd() { return value * product_odd<value - 2>(); }
+
+extern "C" {
+
+double trunc(double x) NOEXCEPT
+{
+ return (int64_t)x;
+}
+
+double cos(double angle) NOEXCEPT
+{
+ return sin(angle + M_PI_2);
+}
+
+float cosf(float angle) NOEXCEPT
+{
+ return sinf(angle + M_PI_2);
+}
+
+// This can also be done with a taylor expansion, but for
+// now this works pretty well (and doesn't mess anything up
+// in quake in particular, which is very Floating-Point precision
+// heavy)
+double sin(double angle) NOEXCEPT
+{
+ double ret = 0.0;
+ __asm__(
+ "fsin"
+ : "=t"(ret)
+ : "0"(angle));
+
+ return ret;
+}
+
+float sinf(float angle) NOEXCEPT
+{
+ float ret = 0.0f;
+ __asm__(
+ "fsin"
+ : "=t"(ret)
+ : "0"(angle));
+ return ret;
+}
+
+double pow(double x, double y) NOEXCEPT
+{
+ // FIXME: Please fix me. I am naive.
+ if (isnan(y))
+ return y;
+ if (y == 0)
+ return 1;
+ if (x == 0)
+ return 0;
+ if (y == 1)
+ return x;
+ int y_as_int = (int)y;
+ if (y == (double)y_as_int) {
+ double result = x;
+ for (int i = 0; i < fabs(y) - 1; ++i)
+ result *= x;
+ if (y < 0)
+ result = 1.0 / result;
+ return result;
+ }
+ return exp2(y * log2(x));
+}
+
+float powf(float x, float y) NOEXCEPT
+{
+ return (float)pow(x, y);
+}
+
+double ldexp(double x, int exp) NOEXCEPT
+{
+ return x * exp2(exp);
+}
+
+float ldexpf(float x, int exp) NOEXCEPT
+{
+ return x * exp2f(exp);
+}
+
+double tanh(double x) NOEXCEPT
+{
+ if (x > 0) {
+ double exponentiated = exp(2 * x);
+ return (exponentiated - 1) / (exponentiated + 1);
+ }
+ double plusX = exp(x);
+ double minusX = 1 / plusX;
+ return (plusX - minusX) / (plusX + minusX);
+}
+
+static double ampsin(double angle) NOEXCEPT
+{
+ double looped_angle = fmod(M_PI + angle, M_TAU) - M_PI;
+ double looped_angle_squared = looped_angle * looped_angle;
+
+ double quadratic_term;
+ if (looped_angle > 0) {
+ quadratic_term = -looped_angle_squared;
+ } else {
+ quadratic_term = looped_angle_squared;
+ }
+
+ double linear_term = M_PI * looped_angle;
+
+ return quadratic_term + linear_term;
+}
+
+double tan(double angle) NOEXCEPT
+{
+ return ampsin(angle) / ampsin(M_PI_2 + angle);
+}
+
+double sqrt(double x) NOEXCEPT
+{
+ double res;
+ __asm__("fsqrt"
+ : "=t"(res)
+ : "0"(x));
+ return res;
+}
+
+float sqrtf(float x) NOEXCEPT
+{
+ float res;
+ __asm__("fsqrt"
+ : "=t"(res)
+ : "0"(x));
+ return res;
+}
+
+double sinh(double x) NOEXCEPT
+{
+ double exponentiated = exp(x);
+ if (x > 0)
+ return (exponentiated * exponentiated - 1) / 2 / exponentiated;
+ return (exponentiated - 1 / exponentiated) / 2;
+}
+
+double log10(double x) NOEXCEPT
+{
+ double ret = 0.0;
+ __asm__(
+ "fldlg2\n"
+ "fld %%st(1)\n"
+ "fyl2x\n"
+ "fstp %%st(1)"
+ : "=t"(ret)
+ : "0"(x));
+ return ret;
+}
+
+double log(double x) NOEXCEPT
+{
+ double ret = 0.0;
+ __asm__(
+ "fldln2\n"
+ "fld %%st(1)\n"
+ "fyl2x\n"
+ "fstp %%st(1)"
+ : "=t"(ret)
+ : "0"(x));
+ return ret;
+}
+
+float logf(float x) NOEXCEPT
+{
+ return (float)log(x);
+}
+
+double fmod(double index, double period) NOEXCEPT
+{
+ return index - trunc(index / period) * period;
+}
+
+float fmodf(float index, float period) NOEXCEPT
+{
+ return index - trunc(index / period) * period;
+}
+
+double exp(double exponent) NOEXCEPT
+{
+ double res = 0;
+ __asm__("fldl2e\n"
+ "fmulp\n"
+ "fld1\n"
+ "fld %%st(1)\n"
+ "fprem\n"
+ "f2xm1\n"
+ "faddp\n"
+ "fscale\n"
+ "fstp %%st(1)"
+ : "=t"(res)
+ : "0"(exponent));
+ return res;
+}
+
+float expf(float exponent) NOEXCEPT
+{
+ return (float)exp(exponent);
+}
+
+double exp2(double exponent) NOEXCEPT
+{
+ double res = 0;
+ __asm__("fld1\n"
+ "fld %%st(1)\n"
+ "fprem\n"
+ "f2xm1\n"
+ "faddp\n"
+ "fscale\n"
+ "fstp %%st(1)"
+ : "=t"(res)
+ : "0"(exponent));
+ return res;
+}
+
+float exp2f(float exponent) NOEXCEPT
+{
+ return (float)exp2(exponent);
+}
+
+double cosh(double x) NOEXCEPT
+{
+ double exponentiated = exp(-x);
+ if (x < 0)
+ return (1 + exponentiated * exponentiated) / 2 / exponentiated;
+ return (1 / exponentiated + exponentiated) / 2;
+}
+
+double atan2(double y, double x) NOEXCEPT
+{
+ if (x > 0)
+ return atan(y / x);
+ if (x == 0) {
+ if (y > 0)
+ return M_PI_2;
+ if (y < 0)
+ return -M_PI_2;
+ return 0;
+ }
+ if (y >= 0)
+ return atan(y / x) + M_PI;
+ return atan(y / x) - M_PI;
+}
+
+float atan2f(float y, float x) NOEXCEPT
+{
+ return (float)atan2(y, x);
+}
+
+double atan(double x) NOEXCEPT
+{
+ if (x < 0)
+ return -atan(-x);
+ if (x > 1)
+ return M_PI_2 - atan(1 / x);
+ double squared = x * x;
+ return x / (1 + 1 * 1 * squared / (3 + 2 * 2 * squared / (5 + 3 * 3 * squared / (7 + 4 * 4 * squared / (9 + 5 * 5 * squared / (11 + 6 * 6 * squared / (13 + 7 * 7 * squared)))))));
+}
+
+double asin(double x) NOEXCEPT
+{
+ if (x > 1 || x < -1)
+ return NAN;
+ if (x > 0.5 || x < -0.5)
+ return 2 * atan(x / (1 + sqrt(1 - x * x)));
+ double squared = x * x;
+ double value = x;
+ double i = x * squared;
+ value += i * product_odd<1>() / product_even<2>() / 3;
+ i *= squared;
+ value += i * product_odd<3>() / product_even<4>() / 5;
+ i *= squared;
+ value += i * product_odd<5>() / product_even<6>() / 7;
+ i *= squared;
+ value += i * product_odd<7>() / product_even<8>() / 9;
+ i *= squared;
+ value += i * product_odd<9>() / product_even<10>() / 11;
+ i *= squared;
+ value += i * product_odd<11>() / product_even<12>() / 13;
+ return value;
+}
+
+float asinf(float x) NOEXCEPT
+{
+ return (float)asin(x);
+}
+
+double acos(double x) NOEXCEPT
+{
+ return M_PI_2 - asin(x);
+}
+
+float acosf(float x) NOEXCEPT
+{
+ return M_PI_2 - asinf(x);
+}
+
+double fabs(double value) NOEXCEPT
+{
+ return value < 0 ? -value : value;
+}
+
+double log2(double x) NOEXCEPT
+{
+ double ret = 0.0;
+ __asm__(
+ "fld1\n"
+ "fld %%st(1)\n"
+ "fyl2x\n"
+ "fstp %%st(1)"
+ : "=t"(ret)
+ : "0"(x));
+ return ret;
+}
+
+float log2f(float x) NOEXCEPT
+{
+ return log2(x);
+}
+
+long double log2l(long double x) NOEXCEPT
+{
+ return log2(x);
+}
+
+double frexp(double, int*) NOEXCEPT
+{
+ ASSERT_NOT_REACHED();
+ return 0;
+}
+
+float frexpf(float, int*) NOEXCEPT
+{
+ ASSERT_NOT_REACHED();
+ return 0;
+}
+
+long double frexpl(long double, int*) NOEXCEPT
+{
+ ASSERT_NOT_REACHED();
+ return 0;
+}
+
+double round(double value) NOEXCEPT
+{
+ // FIXME: Please fix me. I am naive.
+ if (value >= 0.0)
+ return (double)(int)(value + 0.5);
+ return (double)(int)(value - 0.5);
+}
+
+float roundf(float value) NOEXCEPT
+{
+ // FIXME: Please fix me. I am naive.
+ if (value >= 0.0f)
+ return (float)(int)(value + 0.5f);
+ return (float)(int)(value - 0.5f);
+}
+
+float floorf(float value) NOEXCEPT
+{
+ if (value >= 0)
+ return (int)value;
+ int intvalue = (int)value;
+ return ((float)intvalue == value) ? intvalue : intvalue - 1;
+}
+
+double floor(double value) NOEXCEPT
+{
+ if (value >= 0)
+ return (int)value;
+ int intvalue = (int)value;
+ return ((double)intvalue == value) ? intvalue : intvalue - 1;
+}
+
+double rint(double value) NOEXCEPT
+{
+ return (int)roundf(value);
+}
+
+float ceilf(float value) NOEXCEPT
+{
+ // FIXME: Please fix me. I am naive.
+ int as_int = (int)value;
+ if (value == (float)as_int)
+ return as_int;
+ if (value < 0) {
+ if (as_int == 0)
+ return -0;
+ return as_int;
+ }
+ return as_int + 1;
+}
+
+double ceil(double value) NOEXCEPT
+{
+ // FIXME: Please fix me. I am naive.
+ int as_int = (int)value;
+ if (value == (double)as_int)
+ return as_int;
+ if (value < 0) {
+ if (as_int == 0)
+ return -0;
+ return as_int;
+ }
+ return as_int + 1;
+}
+
+double modf(double x, double* intpart) NOEXCEPT
+{
+ *intpart = (double)((int)(x));
+ return x - (int)x;
+}
+
+double gamma(double x) NOEXCEPT
+{
+ // Stirling approximation
+ return sqrt(2.0 * M_PI / x) * pow(x / M_E, x);
+}
+
+double expm1(double x) NOEXCEPT
+{
+ return exp(x) - 1;
+}
+
+double cbrt(double x) NOEXCEPT
+{
+ if (isinf(x) || x == 0)
+ return x;
+ if (x < 0)
+ return -cbrt(-x);
+
+ double r = x;
+ double ex = 0;
+
+ while (r < 0.125) {
+ r *= 8;
+ ex--;
+ }
+ while (r > 1.0) {
+ r *= 0.125;
+ ex++;
+ }
+
+ r = (-0.46946116 * r + 1.072302) * r + 0.3812513;
+
+ while (ex < 0) {
+ r *= 0.5;
+ ex++;
+ }
+ while (ex > 0) {
+ r *= 2;
+ ex--;
+ }
+
+ r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
+ r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
+ r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
+ r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
+
+ return r;
+}
+
+double log1p(double x) NOEXCEPT
+{
+ return log(1 + x);
+}
+
+double acosh(double x) NOEXCEPT
+{
+ return log(x + sqrt(x * x - 1));
+}
+
+double asinh(double x) NOEXCEPT
+{
+ return log(x + sqrt(x * x + 1));
+}
+
+double atanh(double x) NOEXCEPT
+{
+ return log((1 + x) / (1 - x)) / 2.0;
+}
+
+double hypot(double x, double y) NOEXCEPT
+{
+ return sqrt(x * x + y * y);
+}
+
+double erf(double x) NOEXCEPT
+{
+ // algorithm taken from Abramowitz and Stegun (no. 26.2.17)
+ double t = 1 / (1 + 0.47047 * fabs(x));
+ double poly = t * (0.3480242 + t * (-0.958798 + t * 0.7478556));
+ double answer = 1 - poly * exp(-x * x);
+ if (x < 0)
+ return -answer;
+
+ return answer;
+}
+
+double erfc(double x) NOEXCEPT
+{
+ return 1 - erf(x);
+}
+}
diff --git a/Userland/Libraries/LibM/math.h b/Userland/Libraries/LibM/math.h
new file mode 100644
index 0000000000..8d9b727ddc
--- /dev/null
+++ b/Userland/Libraries/LibM/math.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+#if __cplusplus >= 201103L
+# define NOEXCEPT noexcept
+#else
+# define NOEXCEPT
+#endif
+
+__BEGIN_DECLS
+
+#define HUGE_VAL 1e10000
+#define INFINITY __builtin_huge_val()
+#define NAN __builtin_nan("")
+#define M_E 2.718281828459045
+#define M_PI 3.141592653589793
+#define M_PI_2 1.570796326794896
+#define M_TAU 6.283185307179586
+#define M_DEG2RAD 0.017453292519943
+#define M_RAD2DEG 57.29577951308232
+#define M_LN2 0.69314718055995
+#define M_LN10 2.30258509299405
+#define M_SQRT2 1.4142135623730951
+#define M_SQRT1_2 0.7071067811865475
+
+#define FP_NAN 0
+#define FP_INFINITE 1
+#define FP_ZERO 2
+#define FP_SUBNORMAL 3
+#define FP_NORMAL 4
+#define fpclassify(x) __builtin_fpclassify(FP_NAN, FP_INFINITE, FP_ZERO, FP_SUBNORMAL, FP_ZERO, x)
+
+#define signbit(x) __builtin_signbit(x)
+#define isnan(x) __builtin_isnan(x)
+#define isinf(x) __builtin_isinf_sign(x)
+#define isfinite(x) __builtin_isfinite(x)
+#define isnormal(x) __builtin_isnormal(x)
+
+#define DOUBLE_MAX ((double)0b0111111111101111111111111111111111111111111111111111111111111111)
+#define DOUBLE_MIN ((double)0b0000000000010000000000000000000000000000000000000000000000000000)
+
+double acos(double) NOEXCEPT;
+float acosf(float) NOEXCEPT;
+double asin(double) NOEXCEPT;
+float asinf(float) NOEXCEPT;
+double atan(double) NOEXCEPT;
+float atanf(float) NOEXCEPT;
+double atan2(double, double) NOEXCEPT;
+float atan2f(float, float) NOEXCEPT;
+double cos(double) NOEXCEPT;
+float cosf(float) NOEXCEPT;
+double cosh(double) NOEXCEPT;
+float coshf(float) NOEXCEPT;
+double sin(double) NOEXCEPT;
+float sinf(float) NOEXCEPT;
+double sinh(double) NOEXCEPT;
+float sinhf(float) NOEXCEPT;
+double tan(double) NOEXCEPT;
+float tanf(float) NOEXCEPT;
+double tanh(double) NOEXCEPT;
+float tanhf(float) NOEXCEPT;
+double ceil(double) NOEXCEPT;
+float ceilf(float) NOEXCEPT;
+double floor(double) NOEXCEPT;
+float floorf(float) NOEXCEPT;
+double round(double) NOEXCEPT;
+float roundf(float) NOEXCEPT;
+double fabs(double) NOEXCEPT;
+float fabsf(float) NOEXCEPT;
+double fmod(double, double) NOEXCEPT;
+float fmodf(float, float) NOEXCEPT;
+double exp(double) NOEXCEPT;
+float expf(float) NOEXCEPT;
+double exp2(double) NOEXCEPT;
+float exp2f(float) NOEXCEPT;
+double frexp(double, int* exp) NOEXCEPT;
+float frexpf(float, int* exp) NOEXCEPT;
+double log(double) NOEXCEPT;
+float logf(float) NOEXCEPT;
+double log10(double) NOEXCEPT;
+float log10f(float) NOEXCEPT;
+double sqrt(double) NOEXCEPT;
+float sqrtf(float) NOEXCEPT;
+double modf(double, double*) NOEXCEPT;
+float modff(float, float*) NOEXCEPT;
+double ldexp(double, int exp) NOEXCEPT;
+float ldexpf(float, int exp) NOEXCEPT;
+
+double pow(double x, double y) NOEXCEPT;
+float powf(float x, float y) NOEXCEPT;
+
+double log2(double) NOEXCEPT;
+float log2f(float) NOEXCEPT;
+long double log2l(long double) NOEXCEPT;
+double frexp(double, int*) NOEXCEPT;
+float frexpf(float, int*) NOEXCEPT;
+long double frexpl(long double, int*) NOEXCEPT;
+
+double gamma(double) NOEXCEPT;
+double expm1(double) NOEXCEPT;
+double cbrt(double) NOEXCEPT;
+double log1p(double) NOEXCEPT;
+double acosh(double) NOEXCEPT;
+double asinh(double) NOEXCEPT;
+double atanh(double) NOEXCEPT;
+double hypot(double, double) NOEXCEPT;
+double erf(double) NOEXCEPT;
+double erfc(double) NOEXCEPT;
+
+__END_DECLS
diff --git a/Userland/Libraries/LibMarkdown/Block.h b/Userland/Libraries/LibMarkdown/Block.h
new file mode 100644
index 0000000000..239c473165
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/Block.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/StringView.h>
+#include <AK/Vector.h>
+
+namespace Markdown {
+
+class Block {
+public:
+ virtual ~Block() { }
+
+ virtual String render_to_html() const = 0;
+ virtual String render_for_terminal(size_t view_width = 0) const = 0;
+};
+
+}
diff --git a/Userland/Libraries/LibMarkdown/CMakeLists.txt b/Userland/Libraries/LibMarkdown/CMakeLists.txt
new file mode 100644
index 0000000000..034e9c6da8
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(SOURCES
+ CodeBlock.cpp
+ Document.cpp
+ Heading.cpp
+ HorizontalRule.cpp
+ List.cpp
+ Paragraph.cpp
+ Table.cpp
+ Text.cpp
+)
+
+serenity_lib(LibMarkdown markdown)
+target_link_libraries(LibMarkdown LibJS)
diff --git a/Userland/Libraries/LibMarkdown/CodeBlock.cpp b/Userland/Libraries/LibMarkdown/CodeBlock.cpp
new file mode 100644
index 0000000000..a869dea26e
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/CodeBlock.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibJS/MarkupGenerator.h>
+#include <LibMarkdown/CodeBlock.h>
+
+namespace Markdown {
+
+Text::Style CodeBlock::style() const
+{
+ if (m_style_spec.spans().is_empty())
+ return {};
+ return m_style_spec.spans()[0].style;
+}
+
+String CodeBlock::style_language() const
+{
+ if (m_style_spec.spans().is_empty())
+ return {};
+ return m_style_spec.spans()[0].text;
+}
+
+String CodeBlock::render_to_html() const
+{
+ StringBuilder builder;
+
+ String style_language = this->style_language();
+ Text::Style style = this->style();
+
+ if (style.strong)
+ builder.append("<b>");
+ if (style.emph)
+ builder.append("<i>");
+
+ if (style_language.is_empty())
+ builder.append("<code>");
+ else
+ builder.appendff("<code class=\"{}\">", style_language);
+
+ if (style_language == "js")
+ builder.append(JS::MarkupGenerator::html_from_source(m_code));
+ else
+ builder.append(escape_html_entities(m_code));
+
+ builder.append("</code>");
+
+ if (style.emph)
+ builder.append("</i>");
+ if (style.strong)
+ builder.append("</b>");
+
+ builder.append('\n');
+
+ return builder.build();
+}
+
+String CodeBlock::render_for_terminal(size_t) const
+{
+ StringBuilder builder;
+
+ Text::Style style = this->style();
+ bool needs_styling = style.strong || style.emph;
+ if (needs_styling) {
+ builder.append("\033[");
+ bool first = true;
+ if (style.strong) {
+ builder.append('1');
+ first = false;
+ }
+ if (style.emph) {
+ if (!first)
+ builder.append(';');
+ builder.append('4');
+ }
+ builder.append('m');
+ }
+
+ builder.append(m_code);
+
+ if (needs_styling)
+ builder.append("\033[0m");
+
+ builder.append("\n\n");
+
+ return builder.build();
+}
+
+OwnPtr<CodeBlock> CodeBlock::parse(Vector<StringView>::ConstIterator& lines)
+{
+ if (lines.is_end())
+ return {};
+
+ constexpr auto tick_tick_tick = "```";
+
+ StringView line = *lines;
+ if (!line.starts_with(tick_tick_tick))
+ return {};
+
+ // Our Markdown extension: we allow
+ // specifying a style and a language
+ // for a code block, like so:
+ //
+ // ```**sh**
+ // $ echo hello friends!
+ // ````
+ //
+ // The code block will be made bold,
+ // and if possible syntax-highlighted
+ // as appropriate for a shell script.
+ StringView style_spec = line.substring_view(3, line.length() - 3);
+ auto spec = Text::parse(style_spec);
+ if (!spec.has_value())
+ return {};
+
+ ++lines;
+
+ bool first = true;
+ StringBuilder builder;
+
+ while (true) {
+ if (lines.is_end())
+ break;
+ line = *lines;
+ ++lines;
+ if (line == tick_tick_tick)
+ break;
+ if (!first)
+ builder.append('\n');
+ builder.append(line);
+ first = false;
+ }
+
+ return make<CodeBlock>(move(spec.value()), builder.build());
+}
+
+}
diff --git a/Userland/Libraries/LibMarkdown/CodeBlock.h b/Userland/Libraries/LibMarkdown/CodeBlock.h
new file mode 100644
index 0000000000..fb12fb4b65
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/CodeBlock.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/OwnPtr.h>
+#include <LibMarkdown/Block.h>
+#include <LibMarkdown/Text.h>
+
+namespace Markdown {
+
+class CodeBlock final : public Block {
+public:
+ CodeBlock(Text&& style_spec, const String& code)
+ : m_code(move(code))
+ , m_style_spec(move(style_spec))
+ {
+ }
+ virtual ~CodeBlock() override { }
+
+ virtual String render_to_html() const override;
+ virtual String render_for_terminal(size_t view_width = 0) const override;
+ static OwnPtr<CodeBlock> parse(Vector<StringView>::ConstIterator& lines);
+
+private:
+ String style_language() const;
+ Text::Style style() const;
+
+ String m_code;
+ Text m_style_spec;
+};
+
+}
diff --git a/Userland/Libraries/LibMarkdown/Document.cpp b/Userland/Libraries/LibMarkdown/Document.cpp
new file mode 100644
index 0000000000..3b08dd2315
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/Document.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibMarkdown/CodeBlock.h>
+#include <LibMarkdown/Document.h>
+#include <LibMarkdown/Heading.h>
+#include <LibMarkdown/HorizontalRule.h>
+#include <LibMarkdown/List.h>
+#include <LibMarkdown/Paragraph.h>
+#include <LibMarkdown/Table.h>
+
+namespace Markdown {
+
+String Document::render_to_html() const
+{
+ StringBuilder builder;
+
+ builder.append("<!DOCTYPE html>\n");
+ builder.append("<html>\n");
+ builder.append("<head>\n");
+ builder.append("<style>\n");
+ builder.append("code { white-space: pre; }\n");
+ builder.append("</style>\n");
+ builder.append("</head>\n");
+ builder.append("<body>\n");
+
+ for (auto& block : m_blocks) {
+ auto s = block.render_to_html();
+ builder.append(s);
+ }
+
+ builder.append("</body>\n");
+ builder.append("</html>\n");
+ return builder.build();
+}
+
+String Document::render_for_terminal(size_t view_width) const
+{
+ StringBuilder builder;
+
+ for (auto& block : m_blocks) {
+ auto s = block.render_for_terminal(view_width);
+ builder.append(s);
+ }
+
+ return builder.build();
+}
+
+template<typename BlockType>
+static bool helper(Vector<StringView>::ConstIterator& lines, NonnullOwnPtrVector<Block>& blocks)
+{
+ OwnPtr<BlockType> block = BlockType::parse(lines);
+ if (!block)
+ return false;
+ blocks.append(block.release_nonnull());
+ return true;
+}
+
+OwnPtr<Document> Document::parse(const StringView& str)
+{
+ const Vector<StringView> lines_vec = str.lines();
+ auto lines = lines_vec.begin();
+ auto document = make<Document>();
+ auto& blocks = document->m_blocks;
+ NonnullOwnPtrVector<Paragraph::Line> paragraph_lines;
+
+ auto flush_paragraph = [&] {
+ if (paragraph_lines.is_empty())
+ return;
+ auto paragraph = make<Paragraph>(move(paragraph_lines));
+ document->m_blocks.append(move(paragraph));
+ paragraph_lines.clear();
+ };
+ while (true) {
+ if (lines.is_end())
+ break;
+
+ if ((*lines).is_empty()) {
+ ++lines;
+ flush_paragraph();
+ continue;
+ }
+
+ bool any = helper<Table>(lines, blocks) || helper<List>(lines, blocks) || helper<CodeBlock>(lines, blocks)
+ || helper<Heading>(lines, blocks) || helper<HorizontalRule>(lines, blocks);
+
+ if (any) {
+ if (!paragraph_lines.is_empty()) {
+ auto last_block = document->m_blocks.take_last();
+ flush_paragraph();
+ document->m_blocks.append(move(last_block));
+ }
+ continue;
+ }
+
+ auto line = Paragraph::Line::parse(lines);
+ if (!line)
+ return {};
+
+ paragraph_lines.append(line.release_nonnull());
+ }
+
+ if (!paragraph_lines.is_empty())
+ flush_paragraph();
+
+ return document;
+}
+
+}
diff --git a/Userland/Libraries/LibMarkdown/Document.h b/Userland/Libraries/LibMarkdown/Document.h
new file mode 100644
index 0000000000..752cd12f4c
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/Document.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullOwnPtrVector.h>
+#include <AK/String.h>
+#include <LibMarkdown/Block.h>
+
+namespace Markdown {
+
+class Document final {
+public:
+ String render_to_html() const;
+ String render_for_terminal(size_t view_width = 0) const;
+
+ static OwnPtr<Document> parse(const StringView&);
+
+private:
+ NonnullOwnPtrVector<Block> m_blocks;
+};
+
+}
diff --git a/Userland/Libraries/LibMarkdown/Heading.cpp b/Userland/Libraries/LibMarkdown/Heading.cpp
new file mode 100644
index 0000000000..d3d05f7a38
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/Heading.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibMarkdown/Heading.h>
+
+namespace Markdown {
+
+String Heading::render_to_html() const
+{
+ StringBuilder builder;
+ builder.appendf("<h%zu>", m_level);
+ builder.append(m_text.render_to_html());
+ builder.appendf("</h%zu>\n", m_level);
+ return builder.build();
+}
+
+String Heading::render_for_terminal(size_t) const
+{
+ StringBuilder builder;
+
+ switch (m_level) {
+ case 1:
+ case 2:
+ builder.append("\n\033[1m");
+ builder.append(m_text.render_for_terminal().to_uppercase());
+ builder.append("\033[0m\n");
+ break;
+ default:
+ builder.append("\n\033[1m");
+ builder.append(m_text.render_for_terminal());
+ builder.append("\033[0m\n");
+ break;
+ }
+
+ return builder.build();
+}
+
+OwnPtr<Heading> Heading::parse(Vector<StringView>::ConstIterator& lines)
+{
+ if (lines.is_end())
+ return {};
+
+ const StringView& line = *lines;
+ size_t level;
+
+ for (level = 0; level < line.length(); level++) {
+ if (line[level] != '#')
+ break;
+ }
+
+ if (!level || level >= line.length() || line[level] != ' ')
+ return {};
+
+ StringView title_view = line.substring_view(level + 1, line.length() - level - 1);
+ auto text = Text::parse(title_view);
+ if (!text.has_value())
+ return {};
+
+ auto heading = make<Heading>(move(text.value()), level);
+
+ ++lines;
+ return heading;
+}
+
+}
diff --git a/Userland/Libraries/LibMarkdown/Heading.h b/Userland/Libraries/LibMarkdown/Heading.h
new file mode 100644
index 0000000000..5628f68679
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/Heading.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/OwnPtr.h>
+#include <AK/StringView.h>
+#include <AK/Vector.h>
+#include <LibMarkdown/Block.h>
+#include <LibMarkdown/Text.h>
+
+namespace Markdown {
+
+class Heading final : public Block {
+public:
+ Heading(Text&& text, size_t level)
+ : m_text(move(text))
+ , m_level(level)
+ {
+ ASSERT(m_level > 0);
+ }
+ virtual ~Heading() override { }
+
+ virtual String render_to_html() const override;
+ virtual String render_for_terminal(size_t view_width = 0) const override;
+ static OwnPtr<Heading> parse(Vector<StringView>::ConstIterator& lines);
+
+private:
+ Text m_text;
+ size_t m_level { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibMarkdown/HorizontalRule.cpp b/Userland/Libraries/LibMarkdown/HorizontalRule.cpp
new file mode 100644
index 0000000000..3aa59349b2
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/HorizontalRule.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <LibMarkdown/HorizontalRule.h>
+
+namespace Markdown {
+
+String HorizontalRule::render_to_html() const
+{
+ return "<hr>\n";
+}
+
+String HorizontalRule::render_for_terminal(size_t view_width) const
+{
+ StringBuilder builder(view_width + 1);
+ for (size_t i = 0; i < view_width; ++i)
+ builder.append('-');
+ builder.append("\n\n");
+ return builder.to_string();
+}
+
+OwnPtr<HorizontalRule> HorizontalRule::parse(Vector<StringView>::ConstIterator& lines)
+{
+ if (lines.is_end())
+ return {};
+
+ const StringView& line = *lines;
+
+ if (line.length() < 3)
+ return {};
+ if (!line.starts_with('-') && !line.starts_with('_') && !line.starts_with('*'))
+ return {};
+
+ auto first_character = line.characters_without_null_termination()[0];
+ for (auto ch : line) {
+ if (ch != first_character)
+ return {};
+ }
+
+ ++lines;
+ return make<HorizontalRule>();
+}
+
+}
diff --git a/Userland/Libraries/LibMarkdown/HorizontalRule.h b/Userland/Libraries/LibMarkdown/HorizontalRule.h
new file mode 100644
index 0000000000..42f669f9fb
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/HorizontalRule.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/OwnPtr.h>
+#include <AK/StringView.h>
+#include <AK/Vector.h>
+#include <LibMarkdown/Block.h>
+
+namespace Markdown {
+
+class HorizontalRule final : public Block {
+public:
+ HorizontalRule()
+ {
+ }
+ virtual ~HorizontalRule() override { }
+
+ virtual String render_to_html() const override;
+ virtual String render_for_terminal(size_t view_width = 0) const override;
+ static OwnPtr<HorizontalRule> parse(Vector<StringView>::ConstIterator& lines);
+};
+
+}
diff --git a/Userland/Libraries/LibMarkdown/List.cpp b/Userland/Libraries/LibMarkdown/List.cpp
new file mode 100644
index 0000000000..d9484cb098
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/List.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibMarkdown/List.h>
+
+namespace Markdown {
+
+String List::render_to_html() const
+{
+ StringBuilder builder;
+
+ const char* tag = m_is_ordered ? "ol" : "ul";
+ builder.appendf("<%s>", tag);
+
+ for (auto& item : m_items) {
+ builder.append("<li>");
+ builder.append(item.render_to_html());
+ builder.append("</li>\n");
+ }
+
+ builder.appendf("</%s>\n", tag);
+
+ return builder.build();
+}
+
+String List::render_for_terminal(size_t) const
+{
+ StringBuilder builder;
+
+ int i = 0;
+ for (auto& item : m_items) {
+ builder.append(" ");
+ if (m_is_ordered)
+ builder.appendf("%d. ", ++i);
+ else
+ builder.append("* ");
+ builder.append(item.render_for_terminal());
+ builder.append("\n");
+ }
+ builder.append("\n");
+
+ return builder.build();
+}
+
+OwnPtr<List> List::parse(Vector<StringView>::ConstIterator& lines)
+{
+ Vector<Text> items;
+ bool is_ordered = false;
+
+ bool first = true;
+ size_t offset = 0;
+ StringBuilder item_builder;
+ auto flush_item_if_needed = [&] {
+ if (first)
+ return true;
+
+ auto text = Text::parse(item_builder.string_view());
+ if (!text.has_value())
+ return false;
+
+ items.append(move(text.value()));
+
+ item_builder.clear();
+ return true;
+ };
+
+ while (true) {
+ if (lines.is_end())
+ break;
+ const StringView& line = *lines;
+ if (line.is_empty())
+ break;
+
+ bool appears_unordered = false;
+ if (line.length() > 2) {
+ if (line[1] == ' ' && (line[0] == '*' || line[0] == '-')) {
+ appears_unordered = true;
+ offset = 2;
+ }
+ }
+
+ bool appears_ordered = false;
+ for (size_t i = 0; i < 10 && i < line.length(); i++) {
+ char ch = line[i];
+ if ('0' <= ch && ch <= '9')
+ continue;
+ if (ch == '.' || ch == ')')
+ if (i + 1 < line.length() && line[i + 1] == ' ') {
+ appears_ordered = true;
+ offset = i + 1;
+ }
+ break;
+ }
+
+ ASSERT(!(appears_unordered && appears_ordered));
+
+ if (appears_unordered || appears_ordered) {
+ if (first)
+ is_ordered = appears_ordered;
+ else if (is_ordered != appears_ordered)
+ return {};
+
+ if (!flush_item_if_needed())
+ return {};
+
+ while (offset + 1 < line.length() && line[offset + 1] == ' ')
+ offset++;
+
+ } else {
+ if (first)
+ return {};
+ for (size_t i = 0; i < offset; i++) {
+ if (line[i] != ' ')
+ return {};
+ }
+ }
+
+ first = false;
+ if (!item_builder.is_empty())
+ item_builder.append(' ');
+ ASSERT(offset <= line.length());
+ item_builder.append(line.substring_view(offset, line.length() - offset));
+ ++lines;
+ offset = 0;
+ }
+
+ if (!flush_item_if_needed() || first)
+ return {};
+ return make<List>(move(items), is_ordered);
+}
+
+}
diff --git a/Userland/Libraries/LibMarkdown/List.h b/Userland/Libraries/LibMarkdown/List.h
new file mode 100644
index 0000000000..b853ccb801
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/List.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/OwnPtr.h>
+#include <AK/Vector.h>
+#include <LibMarkdown/Block.h>
+#include <LibMarkdown/Text.h>
+
+namespace Markdown {
+
+class List final : public Block {
+public:
+ List(Vector<Text>&& text, bool is_ordered)
+ : m_items(move(text))
+ , m_is_ordered(is_ordered)
+ {
+ }
+ virtual ~List() override { }
+
+ virtual String render_to_html() const override;
+ virtual String render_for_terminal(size_t view_width = 0) const override;
+
+ static OwnPtr<List> parse(Vector<StringView>::ConstIterator& lines);
+
+private:
+ // TODO: List items should be considered blocks of their own kind.
+ Vector<Text> m_items;
+ bool m_is_ordered { false };
+};
+
+}
diff --git a/Userland/Libraries/LibMarkdown/Paragraph.cpp b/Userland/Libraries/LibMarkdown/Paragraph.cpp
new file mode 100644
index 0000000000..6a02289ae2
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/Paragraph.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibMarkdown/Paragraph.h>
+
+namespace Markdown {
+
+String Paragraph::render_to_html() const
+{
+ StringBuilder builder;
+ builder.appendf("<p>");
+ bool first = true;
+ for (auto& line : m_lines) {
+ if (!first)
+ builder.append(' ');
+ first = false;
+ builder.append(line.text().render_to_html());
+ }
+ builder.appendf("</p>\n");
+ return builder.build();
+}
+
+String Paragraph::render_for_terminal(size_t) const
+{
+ StringBuilder builder;
+ bool first = true;
+ for (auto& line : m_lines) {
+ if (!first)
+ builder.append(' ');
+ first = false;
+ builder.append(line.text().render_for_terminal());
+ }
+ builder.appendf("\n\n");
+ return builder.build();
+}
+
+OwnPtr<Paragraph::Line> Paragraph::Line::parse(Vector<StringView>::ConstIterator& lines)
+{
+ if (lines.is_end())
+ return {};
+
+ auto text = Text::parse(*lines++);
+ if (!text.has_value())
+ return {};
+
+ return make<Paragraph::Line>(text.release_value());
+}
+}
diff --git a/Userland/Libraries/LibMarkdown/Paragraph.h b/Userland/Libraries/LibMarkdown/Paragraph.h
new file mode 100644
index 0000000000..98e9dc9b9e
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/Paragraph.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullOwnPtrVector.h>
+#include <AK/OwnPtr.h>
+#include <LibMarkdown/Block.h>
+#include <LibMarkdown/Text.h>
+
+namespace Markdown {
+
+class Paragraph final : public Block {
+public:
+ class Line {
+ public:
+ explicit Line(Text&& text)
+ : m_text(move(text))
+ {
+ }
+
+ static OwnPtr<Line> parse(Vector<StringView>::ConstIterator& lines);
+ const Text& text() const { return m_text; }
+
+ private:
+ Text m_text;
+ };
+
+ Paragraph(NonnullOwnPtrVector<Line>&& lines)
+ : m_lines(move(lines))
+ {
+ }
+
+ virtual ~Paragraph() override { }
+
+ virtual String render_to_html() const override;
+ virtual String render_for_terminal(size_t view_width = 0) const override;
+
+ void add_line(NonnullOwnPtr<Line>&& line);
+
+private:
+ NonnullOwnPtrVector<Line> m_lines;
+};
+
+}
diff --git a/Userland/Libraries/LibMarkdown/Table.cpp b/Userland/Libraries/LibMarkdown/Table.cpp
new file mode 100644
index 0000000000..b33fb9c532
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/Table.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibMarkdown/Table.h>
+
+namespace Markdown {
+
+String Table::render_for_terminal(size_t view_width) const
+{
+ auto unit_width_length = view_width == 0 ? 4 : ((float)(view_width - m_columns.size()) / (float)m_total_width);
+ StringBuilder builder;
+
+ auto write_aligned = [&](const auto& text, auto width, auto alignment) {
+ size_t original_length = 0;
+ for (auto& span : text.spans())
+ original_length += span.text.length();
+ auto string = text.render_for_terminal();
+ if (alignment == Alignment::Center) {
+ auto padding_length = (width - original_length) / 2;
+ builder.appendf("%*s%s%*s", (int)padding_length, "", string.characters(), (int)padding_length, "");
+ if ((width - original_length) % 2)
+ builder.append(' ');
+ } else {
+ builder.appendf(alignment == Alignment::Left ? "%-*s" : "%*s", (int)(width + (string.length() - original_length)), string.characters());
+ }
+ };
+
+ bool first = true;
+ for (auto& col : m_columns) {
+ if (!first)
+ builder.append('|');
+ first = false;
+ size_t width = col.relative_width * unit_width_length;
+ write_aligned(col.header, width, col.alignment);
+ }
+
+ builder.append("\n");
+ for (size_t i = 0; i < view_width; ++i)
+ builder.append('-');
+ builder.append("\n");
+
+ for (size_t i = 0; i < m_row_count; ++i) {
+ bool first = true;
+ for (auto& col : m_columns) {
+ ASSERT(i < col.rows.size());
+ auto& cell = col.rows[i];
+
+ if (!first)
+ builder.append('|');
+ first = false;
+
+ size_t width = col.relative_width * unit_width_length;
+ write_aligned(cell, width, col.alignment);
+ }
+ builder.append("\n");
+ }
+
+ return builder.to_string();
+}
+
+String Table::render_to_html() const
+{
+ StringBuilder builder;
+
+ builder.append("<table>");
+ builder.append("<thead>");
+ builder.append("<tr>");
+ for (auto& column : m_columns) {
+ builder.append("<th>");
+ builder.append(column.header.render_to_html());
+ builder.append("</th>");
+ }
+ builder.append("</tr>");
+ builder.append("</thead>");
+ builder.append("<tbody>");
+ for (size_t i = 0; i < m_row_count; ++i) {
+ builder.append("<tr>");
+ for (auto& column : m_columns) {
+ ASSERT(i < column.rows.size());
+ builder.append("<td>");
+ builder.append(column.rows[i].render_to_html());
+ builder.append("</td>");
+ }
+ builder.append("</tr>");
+ }
+ builder.append("</tbody>");
+ builder.append("</table>");
+
+ return builder.to_string();
+}
+
+OwnPtr<Table> Table::parse(Vector<StringView>::ConstIterator& lines)
+{
+ auto peek_it = lines;
+ auto first_line = *peek_it;
+ if (!first_line.starts_with('|'))
+ return {};
+
+ ++peek_it;
+
+ if (peek_it.is_end())
+ return {};
+
+ auto header_segments = first_line.split_view('|', true);
+ auto header_delimiters = peek_it->split_view('|', true);
+
+ if (!header_segments.is_empty())
+ header_segments.take_first();
+ if (!header_segments.is_empty() && header_segments.last().is_empty())
+ header_segments.take_last();
+
+ if (!header_delimiters.is_empty())
+ header_delimiters.take_first();
+ if (!header_delimiters.is_empty() && header_delimiters.last().is_empty())
+ header_delimiters.take_last();
+
+ ++peek_it;
+
+ if (header_delimiters.size() != header_segments.size())
+ return {};
+
+ if (header_delimiters.is_empty())
+ return {};
+
+ size_t total_width = 0;
+
+ auto table = make<Table>();
+ table->m_columns.resize(header_delimiters.size());
+
+ for (size_t i = 0; i < header_segments.size(); ++i) {
+ auto text_option = Text::parse(header_segments[i]);
+ if (!text_option.has_value())
+ return {}; // An invalid 'text' in the header should just fail the table parse.
+
+ auto text = text_option.release_value();
+ auto& column = table->m_columns[i];
+
+ column.header = move(text);
+
+ auto delimiter = header_delimiters[i].trim_whitespace();
+
+ auto align_left = delimiter.starts_with(':');
+ auto align_right = delimiter != ":" && delimiter.ends_with(':');
+
+ if (align_left)
+ delimiter = delimiter.substring_view(1, delimiter.length() - 1);
+ if (align_right)
+ delimiter = delimiter.substring_view(0, delimiter.length() - 1);
+
+ if (align_left && align_right)
+ column.alignment = Alignment::Center;
+ else if (align_right)
+ column.alignment = Alignment::Right;
+ else
+ column.alignment = Alignment::Left;
+
+ size_t relative_width = delimiter.length();
+ for (auto ch : delimiter) {
+ if (ch != '-') {
+#ifdef DEBUG_MARKDOWN
+ dbg() << "Invalid character _" << ch << "_ in table heading delimiter (ignored)";
+#endif
+ --relative_width;
+ }
+ }
+
+ column.relative_width = relative_width;
+ total_width += relative_width;
+ }
+
+ table->m_total_width = total_width;
+
+ for (off_t i = 0; i < peek_it - lines; ++i)
+ ++lines;
+
+ size_t row_count = 0;
+ ++lines;
+ while (!lines.is_end()) {
+ auto& line = *lines;
+ if (!line.starts_with('|'))
+ break;
+
+ ++lines;
+
+ auto segments = line.split_view('|', true);
+ segments.take_first();
+ if (!segments.is_empty() && segments.last().is_empty())
+ segments.take_last();
+ ++row_count;
+
+ for (size_t i = 0; i < header_segments.size(); ++i) {
+ if (i >= segments.size()) {
+ // Ran out of segments, but still have headers.
+ // Just make an empty cell.
+ table->m_columns[i].rows.append(Text { "" });
+ } else {
+ auto text_option = Text::parse(segments[i]);
+ // We treat an invalid 'text' as a literal.
+ if (text_option.has_value()) {
+ auto text = text_option.release_value();
+ table->m_columns[i].rows.append(move(text));
+ } else {
+ table->m_columns[i].rows.append(Text { segments[i] });
+ }
+ }
+ }
+ }
+
+ table->m_row_count = row_count;
+
+ return move(table);
+}
+
+}
diff --git a/Userland/Libraries/LibMarkdown/Table.h b/Userland/Libraries/LibMarkdown/Table.h
new file mode 100644
index 0000000000..086fad81f1
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/Table.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullOwnPtrVector.h>
+#include <AK/OwnPtr.h>
+#include <LibMarkdown/Block.h>
+#include <LibMarkdown/Text.h>
+
+namespace Markdown {
+
+class Table final : public Block {
+public:
+ enum class Alignment {
+ Center,
+ Left,
+ Right,
+ };
+
+ struct Column {
+ Text header;
+ Vector<Text> rows;
+ Alignment alignment { Alignment::Left };
+ size_t relative_width { 0 };
+ };
+
+ Table() { }
+ virtual ~Table() override { }
+
+ virtual String render_to_html() const override;
+ virtual String render_for_terminal(size_t view_width = 0) const override;
+ static OwnPtr<Table> parse(Vector<StringView>::ConstIterator& lines);
+
+private:
+ Vector<Column> m_columns;
+ size_t m_total_width { 1 };
+ size_t m_row_count { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibMarkdown/Text.cpp b/Userland/Libraries/LibMarkdown/Text.cpp
new file mode 100644
index 0000000000..9361e05d60
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/Text.cpp
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ScopeGuard.h>
+#include <AK/StringBuilder.h>
+#include <LibMarkdown/Text.h>
+#include <string.h>
+
+//#define DEBUG_MARKDOWN
+
+namespace Markdown {
+
+static String unescape(const StringView& text)
+{
+ StringBuilder builder;
+ for (size_t i = 0; i < text.length(); ++i) {
+ if (text[i] == '\\' && i != text.length() - 1) {
+ builder.append(text[i + 1]);
+ i++;
+ continue;
+ }
+ builder.append(text[i]);
+ }
+ return builder.build();
+}
+
+Text::Text(String&& text)
+{
+ m_spans.append({ move(text), Style {} });
+}
+
+String Text::render_to_html() const
+{
+ StringBuilder builder;
+
+ Vector<String> open_tags;
+ Style current_style;
+
+ for (auto& span : m_spans) {
+ struct TagAndFlag {
+ String tag;
+ bool Style::*flag;
+ };
+ TagAndFlag tags_and_flags[] = {
+ { "i", &Style::emph },
+ { "b", &Style::strong },
+ { "code", &Style::code }
+ };
+ auto it = open_tags.find_if([&](const String& open_tag) {
+ if (open_tag == "a" && current_style.href != span.style.href)
+ return true;
+ if (open_tag == "img" && current_style.img != span.style.img)
+ return true;
+ for (auto& tag_and_flag : tags_and_flags) {
+ if (open_tag == tag_and_flag.tag && !(span.style.*tag_and_flag.flag))
+ return true;
+ }
+ return false;
+ });
+
+ if (!it.is_end()) {
+ // We found an open tag that should
+ // not be open for the new span. Close
+ // it and all the open tags that follow
+ // it.
+ for (ssize_t j = open_tags.size() - 1; j >= static_cast<ssize_t>(it.index()); --j) {
+ auto& tag = open_tags[j];
+ if (tag == "img") {
+ builder.append("\" />");
+ current_style.img = {};
+ continue;
+ }
+ builder.appendf("</%s>", tag.characters());
+ if (tag == "a") {
+ current_style.href = {};
+ continue;
+ }
+ for (auto& tag_and_flag : tags_and_flags)
+ if (tag == tag_and_flag.tag)
+ current_style.*tag_and_flag.flag = false;
+ }
+ open_tags.shrink(it.index());
+ }
+ if (current_style.href.is_null() && !span.style.href.is_null()) {
+ open_tags.append("a");
+ builder.appendf("<a href=\"%s\">", span.style.href.characters());
+ }
+ if (current_style.img.is_null() && !span.style.img.is_null()) {
+ open_tags.append("img");
+ builder.appendf("<img src=\"%s\" alt=\"", span.style.img.characters());
+ }
+ for (auto& tag_and_flag : tags_and_flags) {
+ if (current_style.*tag_and_flag.flag != span.style.*tag_and_flag.flag) {
+ open_tags.append(tag_and_flag.tag);
+ builder.appendf("<%s>", tag_and_flag.tag.characters());
+ }
+ }
+
+ current_style = span.style;
+ builder.append(escape_html_entities(span.text));
+ }
+
+ for (ssize_t i = open_tags.size() - 1; i >= 0; --i) {
+ auto& tag = open_tags[i];
+ if (tag == "img") {
+ builder.append("\" />");
+ continue;
+ }
+ builder.appendf("</%s>", tag.characters());
+ }
+
+ return builder.build();
+}
+
+String Text::render_for_terminal() const
+{
+ StringBuilder builder;
+
+ for (auto& span : m_spans) {
+ bool needs_styling = span.style.strong || span.style.emph || span.style.code;
+ if (needs_styling) {
+ builder.append("\033[");
+ bool first = true;
+ if (span.style.strong || span.style.code) {
+ builder.append('1');
+ first = false;
+ }
+ if (span.style.emph) {
+ if (!first)
+ builder.append(';');
+ builder.append('4');
+ }
+ builder.append('m');
+ }
+
+ if (!span.style.href.is_null()) {
+ if (strstr(span.style.href.characters(), "://") != nullptr) {
+ builder.append("\033]8;;");
+ builder.append(span.style.href);
+ builder.append("\033\\");
+ }
+ }
+
+ builder.append(span.text.characters());
+
+ if (needs_styling)
+ builder.append("\033[0m");
+
+ if (!span.style.href.is_null()) {
+ // When rendering for the terminal, ignore any
+ // non-absolute links, because the user has no
+ // chance to follow them anyway.
+ if (strstr(span.style.href.characters(), "://") != nullptr) {
+ builder.appendf(" <%s>", span.style.href.characters());
+ builder.append("\033]8;;\033\\");
+ }
+ }
+ if (!span.style.img.is_null()) {
+ if (strstr(span.style.img.characters(), "://") != nullptr) {
+ builder.appendf(" <%s>", span.style.img.characters());
+ }
+ }
+ }
+
+ return builder.build();
+}
+
+Optional<Text> Text::parse(const StringView& str)
+{
+ Style current_style;
+ size_t current_span_start = 0;
+ int first_span_in_the_current_link = -1;
+ bool current_link_is_actually_img = false;
+ Vector<Span> spans;
+
+ auto append_span_if_needed = [&](size_t offset) {
+ ASSERT(current_span_start <= offset);
+ if (current_span_start != offset) {
+ Span span {
+ unescape(str.substring_view(current_span_start, offset - current_span_start)),
+ current_style
+ };
+ spans.append(move(span));
+ current_span_start = offset;
+ }
+ };
+
+ for (size_t offset = 0; offset < str.length(); offset++) {
+ char ch = str[offset];
+
+ bool is_escape = ch == '\\';
+ if (is_escape && offset != str.length() - 1) {
+ offset++;
+ continue;
+ }
+
+ bool is_special_character = false;
+ is_special_character |= ch == '`';
+ if (!current_style.code)
+ is_special_character |= ch == '*' || ch == '_' || ch == '[' || ch == ']' || (ch == '!' && offset + 1 < str.length() && str[offset + 1] == '[');
+ if (!is_special_character)
+ continue;
+
+ append_span_if_needed(offset);
+
+ switch (ch) {
+ case '`':
+ current_style.code = !current_style.code;
+ break;
+ case '*':
+ case '_':
+ if (offset + 1 < str.length() && str[offset + 1] == ch) {
+ offset++;
+ current_style.strong = !current_style.strong;
+ } else {
+ current_style.emph = !current_style.emph;
+ }
+ break;
+ case '!':
+ current_link_is_actually_img = true;
+ break;
+ case '[':
+#ifdef DEBUG_MARKDOWN
+ if (first_span_in_the_current_link != -1)
+ dbgln("Dropping the outer link");
+#endif
+ first_span_in_the_current_link = spans.size();
+ break;
+ case ']': {
+ if (first_span_in_the_current_link == -1) {
+#ifdef DEBUG_MARKDOWN
+ dbgln("Unmatched ]");
+#endif
+ continue;
+ }
+ ScopeGuard guard = [&] {
+ first_span_in_the_current_link = -1;
+ current_link_is_actually_img = false;
+ };
+ if (offset + 2 >= str.length() || str[offset + 1] != '(')
+ continue;
+ offset += 2;
+ size_t start_of_href = offset;
+
+ do
+ offset++;
+ while (offset < str.length() && str[offset] != ')');
+ if (offset == str.length())
+ offset--;
+
+ const StringView href = str.substring_view(start_of_href, offset - start_of_href);
+ for (size_t i = first_span_in_the_current_link; i < spans.size(); i++) {
+ if (current_link_is_actually_img)
+ spans[i].style.img = href;
+ else
+ spans[i].style.href = href;
+ }
+ break;
+ }
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ // We've processed the character as a special, so the next offset will
+ // start after it. Note that explicit continue statements skip over this
+ // line, effectively treating the character as not special.
+ current_span_start = offset + 1;
+ }
+
+ append_span_if_needed(str.length());
+
+ return Text(move(spans));
+}
+
+}
diff --git a/Userland/Libraries/LibMarkdown/Text.h b/Userland/Libraries/LibMarkdown/Text.h
new file mode 100644
index 0000000000..ee9c89a33f
--- /dev/null
+++ b/Userland/Libraries/LibMarkdown/Text.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Noncopyable.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+
+namespace Markdown {
+
+class Text final {
+ AK_MAKE_NONCOPYABLE(Text);
+
+public:
+ struct Style {
+ bool emph { false };
+ bool strong { false };
+ bool code { false };
+ String href;
+ String img;
+ };
+
+ struct Span {
+ String text;
+ Style style;
+ };
+
+ explicit Text(String&& text);
+ Text(Text&& text) = default;
+ Text() = default;
+
+ Text& operator=(Text&&) = default;
+
+ const Vector<Span>& spans() const { return m_spans; }
+
+ String render_to_html() const;
+ String render_for_terminal() const;
+
+ static Optional<Text> parse(const StringView&);
+
+private:
+ Text(Vector<Span>&& spans)
+ : m_spans(move(spans))
+ {
+ }
+
+ Vector<Span> m_spans;
+};
+
+}
diff --git a/Userland/Libraries/LibPCIDB/CMakeLists.txt b/Userland/Libraries/LibPCIDB/CMakeLists.txt
new file mode 100644
index 0000000000..07dd0a2f08
--- /dev/null
+++ b/Userland/Libraries/LibPCIDB/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(SOURCES
+ Database.cpp
+)
+
+serenity_lib(LibPCIDB pcidb)
+target_link_libraries(LibPCIDB LibC)
diff --git a/Userland/Libraries/LibPCIDB/Database.cpp b/Userland/Libraries/LibPCIDB/Database.cpp
new file mode 100644
index 0000000000..597868ae1a
--- /dev/null
+++ b/Userland/Libraries/LibPCIDB/Database.cpp
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/OwnPtr.h>
+#include <AK/RefPtr.h>
+#include <AK/StringView.h>
+
+#include "Database.h"
+
+namespace PCIDB {
+
+RefPtr<Database> Database::open(const StringView& file_name)
+{
+ auto file_or_error = MappedFile::map(file_name);
+ if (file_or_error.is_error())
+ return nullptr;
+ auto res = adopt(*new Database(file_or_error.release_value()));
+ if (res->init() != 0)
+ return nullptr;
+ return res;
+}
+
+const StringView Database::get_vendor(u16 vendor_id) const
+{
+ const auto& vendor = m_vendors.get(vendor_id);
+ if (!vendor.has_value())
+ return "";
+ return vendor.value()->name;
+}
+
+const StringView Database::get_device(u16 vendor_id, u16 device_id) const
+{
+ const auto& vendor = m_vendors.get(vendor_id);
+ if (!vendor.has_value())
+ return "";
+ const auto& device = vendor.value()->devices.get(device_id);
+ if (!device.has_value())
+ return "";
+ return device.value()->name;
+}
+
+const StringView Database::get_subsystem(u16 vendor_id, u16 device_id, u16 subvendor_id, u16 subdevice_id) const
+{
+ const auto& vendor = m_vendors.get(vendor_id);
+ if (!vendor.has_value())
+ return "";
+ const auto& device = vendor.value()->devices.get(device_id);
+ if (!device.has_value())
+ return "";
+ const auto& subsystem = device.value()->subsystems.get((subvendor_id << 16) + subdevice_id);
+ if (!subsystem.has_value())
+ return "";
+ return subsystem.value()->name;
+}
+
+const StringView Database::get_class(u8 class_id) const
+{
+ const auto& xclass = m_classes.get(class_id);
+ if (!xclass.has_value())
+ return "";
+ return xclass.value()->name;
+}
+
+const StringView Database::get_subclass(u8 class_id, u8 subclass_id) const
+{
+ const auto& xclass = m_classes.get(class_id);
+ if (!xclass.has_value())
+ return "";
+ const auto& subclass = xclass.value()->subclasses.get(subclass_id);
+ if (!subclass.has_value())
+ return "";
+ return subclass.value()->name;
+}
+
+const StringView Database::get_programming_interface(u8 class_id, u8 subclass_id, u8 programming_interface_id) const
+{
+ const auto& xclass = m_classes.get(class_id);
+ if (!xclass.has_value())
+ return "";
+ const auto& subclass = xclass.value()->subclasses.get(subclass_id);
+ if (!subclass.has_value())
+ return "";
+ const auto& programming_interface = subclass.value()->programming_interfaces.get(programming_interface_id);
+ if (!programming_interface.has_value())
+ return "";
+ return programming_interface.value()->name;
+}
+
+static u8 parse_hex_digit(char digit)
+{
+ if (digit >= '0' && digit <= '9')
+ return digit - '0';
+ ASSERT(digit >= 'a' && digit <= 'f');
+ return 10 + (digit - 'a');
+}
+
+template<typename T>
+static T parse_hex(StringView str, size_t count)
+{
+ ASSERT(str.length() >= count);
+
+ T res = 0;
+ for (size_t i = 0; i < count; i++)
+ res = (res << 4) + parse_hex_digit(str[i]);
+
+ return res;
+}
+
+int Database::init()
+{
+ if (m_ready)
+ return 0;
+
+ m_view = StringView { m_file->bytes() };
+
+ ParseMode mode = ParseMode::UnknownMode;
+
+ OwnPtr<Vendor> current_vendor {};
+ OwnPtr<Device> current_device {};
+ OwnPtr<Class> current_class {};
+ OwnPtr<Subclass> current_subclass {};
+
+ auto commit_device = [&]() {
+ if (current_device && current_vendor) {
+ auto id = current_device->id;
+ current_vendor->devices.set(id, current_device.release_nonnull());
+ }
+ };
+
+ auto commit_vendor = [&]() {
+ commit_device();
+ if (current_vendor) {
+ auto id = current_vendor->id;
+ m_vendors.set(id, current_vendor.release_nonnull());
+ }
+ };
+
+ auto commit_subclass = [&]() {
+ if (current_subclass && current_class) {
+ auto id = current_subclass->id;
+ current_class->subclasses.set(id, current_subclass.release_nonnull());
+ }
+ };
+
+ auto commit_class = [&]() {
+ commit_subclass();
+ if (current_class) {
+ auto id = current_class->id;
+ m_classes.set(id, current_class.release_nonnull());
+ }
+ };
+
+ auto commit_all = [&]() {
+ commit_vendor();
+ commit_class();
+ };
+
+ auto lines = m_view.split_view('\n');
+
+ for (auto& line : lines) {
+ if (line.length() < 2 || line[0] == '#')
+ continue;
+
+ if (line[0] == 'C') {
+ mode = ParseMode::ClassMode;
+ commit_all();
+ } else if ((line[0] >= '0' && line[0] <= '9') || (line[0] >= 'a' && line[0] <= 'f')) {
+ mode = ParseMode::VendorMode;
+ commit_all();
+ } else if (line[0] != '\t') {
+ mode = ParseMode::UnknownMode;
+ continue;
+ }
+
+ switch (mode) {
+ case ParseMode::VendorMode:
+ if (line[0] != '\t') {
+ commit_vendor();
+ current_vendor = make<Vendor>();
+ current_vendor->id = parse_hex<u16>(line, 4);
+ current_vendor->name = line.substring_view(6, line.length() - 6);
+ } else if (line[0] == '\t' && line[1] != '\t') {
+ commit_device();
+ current_device = make<Device>();
+ current_device->id = parse_hex<u16>(line.substring_view(1, line.length() - 1), 4);
+ current_device->name = line.substring_view(7, line.length() - 7);
+ } else if (line[0] == '\t' && line[1] == '\t') {
+ auto subsystem = make<Subsystem>();
+ subsystem->vendor_id = parse_hex<u16>(line.substring_view(2, 4), 4);
+ subsystem->device_id = parse_hex<u16>(line.substring_view(7, 4), 4);
+ subsystem->name = line.substring_view(13, line.length() - 13);
+ current_device->subsystems.set((subsystem->vendor_id << 8) + subsystem->device_id, move(subsystem));
+ }
+ break;
+ case ParseMode::ClassMode:
+ if (line[0] != '\t') {
+ commit_class();
+ current_class = make<Class>();
+ current_class->id = parse_hex<u8>(line.substring_view(2, 2), 2);
+ current_class->name = line.substring_view(6, line.length() - 6);
+ } else if (line[0] == '\t' && line[1] != '\t') {
+ commit_subclass();
+ current_subclass = make<Subclass>();
+ current_subclass->id = parse_hex<u8>(line.substring_view(1, 2), 2);
+ current_subclass->name = line.substring_view(5, line.length() - 5);
+ } else if (line[0] == '\t' && line[1] == '\t') {
+ auto programming_interface = make<ProgrammingInterface>();
+ programming_interface->id = parse_hex<u8>(line.substring_view(2, 2), 2);
+ programming_interface->name = line.substring_view(6, line.length() - 6);
+ current_subclass->programming_interfaces.set(programming_interface->id, move(programming_interface));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ commit_all();
+
+ m_ready = true;
+
+ return 0;
+}
+
+}
diff --git a/Userland/Libraries/LibPCIDB/Database.h b/Userland/Libraries/LibPCIDB/Database.h
new file mode 100644
index 0000000000..dbbe8c9482
--- /dev/null
+++ b/Userland/Libraries/LibPCIDB/Database.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/MappedFile.h>
+#include <AK/NonnullOwnPtr.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <AK/StringView.h>
+
+namespace PCIDB {
+
+struct Subsystem {
+ u16 vendor_id;
+ u16 device_id;
+ StringView name;
+};
+
+struct Device {
+ u16 id;
+ StringView name;
+ HashMap<int, NonnullOwnPtr<Subsystem>> subsystems;
+};
+
+struct Vendor {
+ u16 id;
+ StringView name;
+ HashMap<int, NonnullOwnPtr<Device>> devices;
+};
+
+struct ProgrammingInterface {
+ u8 id { 0 };
+ StringView name {};
+};
+
+struct Subclass {
+ u8 id { 0 };
+ StringView name {};
+ HashMap<int, NonnullOwnPtr<ProgrammingInterface>> programming_interfaces;
+};
+
+struct Class {
+ u8 id { 0 };
+ StringView name {};
+ HashMap<int, NonnullOwnPtr<Subclass>> subclasses;
+};
+
+class Database : public RefCounted<Database> {
+public:
+ static RefPtr<Database> open(const StringView& file_name);
+ static RefPtr<Database> open() { return open("/res/pci.ids"); };
+
+ const StringView get_vendor(u16 vendor_id) const;
+ const StringView get_device(u16 vendor_id, u16 device_id) const;
+ const StringView get_subsystem(u16 vendor_id, u16 device_id, u16 subvendor_id, u16 subdevice_id) const;
+ const StringView get_class(u8 class_id) const;
+ const StringView get_subclass(u8 class_id, u8 subclass_id) const;
+ const StringView get_programming_interface(u8 class_id, u8 subclass_id, u8 programming_interface_id) const;
+
+private:
+ explicit Database(NonnullRefPtr<MappedFile> file)
+ : m_file(move(file))
+ {
+ }
+
+ int init();
+
+ enum ParseMode {
+ UnknownMode,
+ VendorMode,
+ ClassMode,
+ };
+
+ NonnullRefPtr<MappedFile> m_file;
+ StringView m_view {};
+ HashMap<int, NonnullOwnPtr<Vendor>> m_vendors;
+ HashMap<int, NonnullOwnPtr<Class>> m_classes;
+ bool m_ready { false };
+};
+
+}
diff --git a/Userland/Libraries/LibProtocol/CMakeLists.txt b/Userland/Libraries/LibProtocol/CMakeLists.txt
new file mode 100644
index 0000000000..7c2e59d45d
--- /dev/null
+++ b/Userland/Libraries/LibProtocol/CMakeLists.txt
@@ -0,0 +1,12 @@
+set(SOURCES
+ Client.cpp
+ Download.cpp
+)
+
+set(GENERATED_SOURCES
+ ../../Services/ProtocolServer/ProtocolClientEndpoint.h
+ ../../Services/ProtocolServer/ProtocolServerEndpoint.h
+)
+
+serenity_lib(LibProtocol protocol)
+target_link_libraries(LibProtocol LibIPC)
diff --git a/Userland/Libraries/LibProtocol/Client.cpp b/Userland/Libraries/LibProtocol/Client.cpp
new file mode 100644
index 0000000000..6d227fe1b1
--- /dev/null
+++ b/Userland/Libraries/LibProtocol/Client.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/FileStream.h>
+#include <AK/SharedBuffer.h>
+#include <LibProtocol/Client.h>
+#include <LibProtocol/Download.h>
+
+namespace Protocol {
+
+Client::Client()
+ : IPC::ServerConnection<ProtocolClientEndpoint, ProtocolServerEndpoint>(*this, "/tmp/portal/protocol")
+{
+ handshake();
+}
+
+void Client::handshake()
+{
+ auto response = send_sync<Messages::ProtocolServer::Greet>();
+ set_my_client_id(response->client_id());
+}
+
+bool Client::is_supported_protocol(const String& protocol)
+{
+ return send_sync<Messages::ProtocolServer::IsSupportedProtocol>(protocol)->supported();
+}
+
+template<typename RequestHashMapTraits>
+RefPtr<Download> Client::start_download(const String& method, const String& url, const HashMap<String, String, RequestHashMapTraits>& request_headers, ReadonlyBytes request_body)
+{
+ IPC::Dictionary header_dictionary;
+ for (auto& it : request_headers)
+ header_dictionary.add(it.key, it.value);
+
+ auto response = send_sync<Messages::ProtocolServer::StartDownload>(method, url, header_dictionary, ByteBuffer::copy(request_body));
+ auto download_id = response->download_id();
+ if (download_id < 0 || !response->response_fd().has_value())
+ return nullptr;
+ auto response_fd = response->response_fd().value().fd();
+ auto download = Download::create_from_id({}, *this, download_id);
+ download->set_download_fd({}, response_fd);
+ m_downloads.set(download_id, download);
+ return download;
+}
+
+bool Client::stop_download(Badge<Download>, Download& download)
+{
+ if (!m_downloads.contains(download.id()))
+ return false;
+ return send_sync<Messages::ProtocolServer::StopDownload>(download.id())->success();
+}
+
+bool Client::set_certificate(Badge<Download>, Download& download, String certificate, String key)
+{
+ if (!m_downloads.contains(download.id()))
+ return false;
+ return send_sync<Messages::ProtocolServer::SetCertificate>(download.id(), move(certificate), move(key))->success();
+}
+
+void Client::handle(const Messages::ProtocolClient::DownloadFinished& message)
+{
+ RefPtr<Download> download;
+ if ((download = m_downloads.get(message.download_id()).value_or(nullptr))) {
+ download->did_finish({}, message.success(), message.total_size());
+ }
+ m_downloads.remove(message.download_id());
+}
+
+void Client::handle(const Messages::ProtocolClient::DownloadProgress& message)
+{
+ if (auto download = const_cast<Download*>(m_downloads.get(message.download_id()).value_or(nullptr))) {
+ download->did_progress({}, message.total_size(), message.downloaded_size());
+ }
+}
+
+void Client::handle(const Messages::ProtocolClient::HeadersBecameAvailable& message)
+{
+ if (auto download = const_cast<Download*>(m_downloads.get(message.download_id()).value_or(nullptr))) {
+ HashMap<String, String, CaseInsensitiveStringTraits> headers;
+ message.response_headers().for_each_entry([&](auto& name, auto& value) { headers.set(name, value); });
+ download->did_receive_headers({}, headers, message.status_code());
+ }
+}
+
+OwnPtr<Messages::ProtocolClient::CertificateRequestedResponse> Client::handle(const Messages::ProtocolClient::CertificateRequested& message)
+{
+ if (auto download = const_cast<Download*>(m_downloads.get(message.download_id()).value_or(nullptr))) {
+ download->did_request_certificates({});
+ }
+
+ return make<Messages::ProtocolClient::CertificateRequestedResponse>();
+}
+
+}
+
+template RefPtr<Protocol::Download> Protocol::Client::start_download(const String& method, const String& url, const HashMap<String, String>& request_headers, ReadonlyBytes request_body);
+template RefPtr<Protocol::Download> Protocol::Client::start_download(const String& method, const String& url, const HashMap<String, String, CaseInsensitiveStringTraits>& request_headers, ReadonlyBytes request_body);
diff --git a/Userland/Libraries/LibProtocol/Client.h b/Userland/Libraries/LibProtocol/Client.h
new file mode 100644
index 0000000000..0546edaf2d
--- /dev/null
+++ b/Userland/Libraries/LibProtocol/Client.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <LibIPC/ServerConnection.h>
+#include <ProtocolServer/ProtocolClientEndpoint.h>
+#include <ProtocolServer/ProtocolServerEndpoint.h>
+
+namespace Protocol {
+
+class Download;
+
+class Client
+ : public IPC::ServerConnection<ProtocolClientEndpoint, ProtocolServerEndpoint>
+ , public ProtocolClientEndpoint {
+ C_OBJECT(Client);
+
+public:
+ virtual void handshake() override;
+
+ bool is_supported_protocol(const String&);
+ template<typename RequestHashMapTraits = Traits<String>>
+ RefPtr<Download> start_download(const String& method, const String& url, const HashMap<String, String, RequestHashMapTraits>& request_headers = {}, ReadonlyBytes request_body = {});
+
+ bool stop_download(Badge<Download>, Download&);
+ bool set_certificate(Badge<Download>, Download&, String, String);
+
+private:
+ Client();
+
+ virtual void handle(const Messages::ProtocolClient::DownloadProgress&) override;
+ virtual void handle(const Messages::ProtocolClient::DownloadFinished&) override;
+ virtual OwnPtr<Messages::ProtocolClient::CertificateRequestedResponse> handle(const Messages::ProtocolClient::CertificateRequested&) override;
+ virtual void handle(const Messages::ProtocolClient::HeadersBecameAvailable&) override;
+
+ HashMap<i32, RefPtr<Download>> m_downloads;
+};
+
+}
diff --git a/Userland/Libraries/LibProtocol/Download.cpp b/Userland/Libraries/LibProtocol/Download.cpp
new file mode 100644
index 0000000000..07f5e76fff
--- /dev/null
+++ b/Userland/Libraries/LibProtocol/Download.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/SharedBuffer.h>
+#include <LibProtocol/Client.h>
+#include <LibProtocol/Download.h>
+
+namespace Protocol {
+
+Download::Download(Client& client, i32 download_id)
+ : m_client(client)
+ , m_download_id(download_id)
+{
+}
+
+bool Download::stop()
+{
+ return m_client->stop_download({}, *this);
+}
+
+void Download::stream_into(OutputStream& stream)
+{
+ ASSERT(!m_internal_stream_data);
+
+ auto notifier = Core::Notifier::construct(fd(), Core::Notifier::Read);
+
+ m_internal_stream_data = make<InternalStreamData>(fd());
+ m_internal_stream_data->read_notifier = notifier;
+
+ auto user_on_finish = move(on_finish);
+ on_finish = [this](auto success, auto total_size) {
+ m_internal_stream_data->success = success;
+ m_internal_stream_data->total_size = total_size;
+ m_internal_stream_data->download_done = true;
+ };
+
+ notifier->on_ready_to_read = [this, &stream, user_on_finish = move(user_on_finish)] {
+ constexpr size_t buffer_size = 1 * KiB;
+ static char buf[buffer_size];
+ auto nread = m_internal_stream_data->read_stream.read({ buf, buffer_size });
+ if (!stream.write_or_error({ buf, nread })) {
+ // FIXME: What do we do here?
+ TODO();
+ }
+
+ if (m_internal_stream_data->read_stream.eof() && m_internal_stream_data->download_done) {
+ m_internal_stream_data->read_notifier->close();
+ user_on_finish(m_internal_stream_data->success, m_internal_stream_data->total_size);
+ } else {
+ m_internal_stream_data->read_stream.handle_any_error();
+ }
+ };
+}
+
+void Download::set_should_buffer_all_input(bool value)
+{
+ if (m_should_buffer_all_input == value)
+ return;
+
+ if (m_internal_buffered_data && !value) {
+ m_internal_buffered_data = nullptr;
+ m_should_buffer_all_input = false;
+ return;
+ }
+
+ ASSERT(!m_internal_stream_data);
+ ASSERT(!m_internal_buffered_data);
+ ASSERT(on_buffered_download_finish); // Not having this set makes no sense.
+ m_internal_buffered_data = make<InternalBufferedData>(fd());
+ m_should_buffer_all_input = true;
+
+ on_headers_received = [this](auto& headers, auto response_code) {
+ m_internal_buffered_data->response_headers = headers;
+ m_internal_buffered_data->response_code = move(response_code);
+ };
+
+ on_finish = [this](auto success, u32 total_size) {
+ auto output_buffer = m_internal_buffered_data->payload_stream.copy_into_contiguous_buffer();
+ on_buffered_download_finish(
+ success,
+ total_size,
+ m_internal_buffered_data->response_headers,
+ m_internal_buffered_data->response_code,
+ output_buffer);
+ };
+
+ stream_into(m_internal_buffered_data->payload_stream);
+}
+
+void Download::did_finish(Badge<Client>, bool success, u32 total_size)
+{
+ if (!on_finish)
+ return;
+
+ on_finish(success, total_size);
+}
+
+void Download::did_progress(Badge<Client>, Optional<u32> total_size, u32 downloaded_size)
+{
+ if (on_progress)
+ on_progress(total_size, downloaded_size);
+}
+
+void Download::did_receive_headers(Badge<Client>, const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers, Optional<u32> response_code)
+{
+ if (on_headers_received)
+ on_headers_received(response_headers, response_code);
+}
+
+void Download::did_request_certificates(Badge<Client>)
+{
+ if (on_certificate_requested) {
+ auto result = on_certificate_requested();
+ if (!m_client->set_certificate({}, *this, result.certificate, result.key)) {
+ dbgln("Download: set_certificate failed");
+ }
+ }
+}
+}
diff --git a/Userland/Libraries/LibProtocol/Download.h b/Userland/Libraries/LibProtocol/Download.h
new file mode 100644
index 0000000000..42d6b21b04
--- /dev/null
+++ b/Userland/Libraries/LibProtocol/Download.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Badge.h>
+#include <AK/ByteBuffer.h>
+#include <AK/FileStream.h>
+#include <AK/Function.h>
+#include <AK/MemoryStream.h>
+#include <AK/RefCounted.h>
+#include <AK/String.h>
+#include <AK/WeakPtr.h>
+#include <LibCore/Notifier.h>
+#include <LibIPC/Forward.h>
+
+namespace Protocol {
+
+class Client;
+
+class Download : public RefCounted<Download> {
+public:
+ struct CertificateAndKey {
+ String certificate;
+ String key;
+ };
+
+ static NonnullRefPtr<Download> create_from_id(Badge<Client>, Client& client, i32 download_id)
+ {
+ return adopt(*new Download(client, download_id));
+ }
+
+ int id() const { return m_download_id; }
+ int fd() const { return m_fd; }
+ bool stop();
+
+ void stream_into(OutputStream&);
+
+ bool should_buffer_all_input() const { return m_should_buffer_all_input; }
+ /// Note: Will override `on_finish', and `on_headers_received', and expects `on_buffered_download_finish' to be set!
+ void set_should_buffer_all_input(bool);
+
+ /// Note: Must be set before `set_should_buffer_all_input(true)`.
+ Function<void(bool success, u32 total_size, const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers, Optional<u32> response_code, ReadonlyBytes payload)> on_buffered_download_finish;
+ Function<void(bool success, u32 total_size)> on_finish;
+ Function<void(Optional<u32> total_size, u32 downloaded_size)> on_progress;
+ Function<void(const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers, Optional<u32> response_code)> on_headers_received;
+ Function<CertificateAndKey()> on_certificate_requested;
+
+ void did_finish(Badge<Client>, bool success, u32 total_size);
+ void did_progress(Badge<Client>, Optional<u32> total_size, u32 downloaded_size);
+ void did_receive_headers(Badge<Client>, const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers, Optional<u32> response_code);
+ void did_request_certificates(Badge<Client>);
+
+ RefPtr<Core::Notifier>& write_notifier(Badge<Client>) { return m_write_notifier; }
+ void set_download_fd(Badge<Client>, int fd) { m_fd = fd; }
+
+private:
+ explicit Download(Client&, i32 download_id);
+ WeakPtr<Client> m_client;
+ int m_download_id { -1 };
+ RefPtr<Core::Notifier> m_write_notifier;
+ int m_fd { -1 };
+ bool m_should_buffer_all_input { false };
+
+ struct InternalBufferedData {
+ InternalBufferedData(int fd)
+ : read_stream(fd)
+ {
+ }
+
+ InputFileStream read_stream;
+ DuplexMemoryStream payload_stream;
+ HashMap<String, String, CaseInsensitiveStringTraits> response_headers;
+ Optional<u32> response_code;
+ };
+
+ struct InternalStreamData {
+ InternalStreamData(int fd)
+ : read_stream(fd)
+ {
+ }
+
+ InputFileStream read_stream;
+ RefPtr<Core::Notifier> read_notifier;
+ bool success;
+ u32 total_size { 0 };
+ bool download_done { false };
+ };
+
+ OwnPtr<InternalBufferedData> m_internal_buffered_data;
+ OwnPtr<InternalStreamData> m_internal_stream_data;
+};
+
+}
diff --git a/Userland/Libraries/LibPthread/CMakeLists.txt b/Userland/Libraries/LibPthread/CMakeLists.txt
new file mode 100644
index 0000000000..c8644e0868
--- /dev/null
+++ b/Userland/Libraries/LibPthread/CMakeLists.txt
@@ -0,0 +1,8 @@
+set(SOURCES
+ pthread.cpp
+ pthread_once.cpp
+)
+
+serenity_libc(LibPthread pthread)
+target_link_libraries(LibPthread LibC)
+target_include_directories(LibPthread PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/Userland/Libraries/LibPthread/pthread.cpp b/Userland/Libraries/LibPthread/pthread.cpp
new file mode 100644
index 0000000000..9fe543a3de
--- /dev/null
+++ b/Userland/Libraries/LibPthread/pthread.cpp
@@ -0,0 +1,659 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/Atomic.h>
+#include <AK/StdLibExtras.h>
+#include <Kernel/API/Syscall.h>
+#include <limits.h>
+#include <pthread.h>
+#include <serenity.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+
+//#define PTHREAD_DEBUG
+
+namespace {
+using PthreadAttrImpl = Syscall::SC_create_thread_params;
+
+struct KeyDestroyer {
+ ~KeyDestroyer() { destroy_for_current_thread(); }
+ static void destroy_for_current_thread();
+};
+
+} // end anonymous namespace
+
+constexpr size_t required_stack_alignment = 4 * MiB;
+constexpr size_t highest_reasonable_guard_size = 32 * PAGE_SIZE;
+constexpr size_t highest_reasonable_stack_size = 8 * MiB; // That's the default in Ubuntu?
+
+// Create an RAII object with a global destructor to destroy pthread keys for the main thread.
+// Impact of this: Any global object that wants to do something with pthread_getspecific
+// in its destructor from the main thread might be in for a nasty surprise.
+static KeyDestroyer s_key_destroyer;
+
+#define __RETURN_PTHREAD_ERROR(rc) \
+ return ((rc) < 0 ? -(rc) : 0)
+
+extern "C" {
+
+static void* pthread_create_helper(void* (*routine)(void*), void* argument)
+{
+ void* ret_val = routine(argument);
+ pthread_exit(ret_val);
+ return nullptr;
+}
+
+static int create_thread(pthread_t* thread, void* (*entry)(void*), void* argument, PthreadAttrImpl* thread_params)
+{
+ void** stack = (void**)((uintptr_t)thread_params->m_stack_location + thread_params->m_stack_size);
+
+ auto push_on_stack = [&](void* data) {
+ stack--;
+ *stack = data;
+ thread_params->m_stack_size -= sizeof(void*);
+ };
+
+ // We set up the stack for pthread_create_helper.
+ // Note that we need to align the stack to 16B, accounting for
+ // the fact that we also push 8 bytes.
+ while (((uintptr_t)stack - 8) % 16 != 0)
+ push_on_stack(nullptr);
+
+ push_on_stack(argument);
+ push_on_stack((void*)entry);
+ ASSERT((uintptr_t)stack % 16 == 0);
+
+ // Push a fake return address
+ push_on_stack(nullptr);
+
+ int rc = syscall(SC_create_thread, pthread_create_helper, thread_params);
+ if (rc >= 0)
+ *thread = rc;
+ __RETURN_PTHREAD_ERROR(rc);
+}
+
+[[noreturn]] static void exit_thread(void* code)
+{
+ KeyDestroyer::destroy_for_current_thread();
+ syscall(SC_exit_thread, code);
+ ASSERT_NOT_REACHED();
+}
+
+int pthread_self()
+{
+ return gettid();
+}
+
+int pthread_create(pthread_t* thread, pthread_attr_t* attributes, void* (*start_routine)(void*), void* argument_to_start_routine)
+{
+ if (!thread)
+ return -EINVAL;
+
+ PthreadAttrImpl default_attributes {};
+ PthreadAttrImpl** arg_attributes = reinterpret_cast<PthreadAttrImpl**>(attributes);
+
+ PthreadAttrImpl* used_attributes = arg_attributes ? *arg_attributes : &default_attributes;
+
+ if (!used_attributes->m_stack_location) {
+ // adjust stack size, user might have called setstacksize, which has no restrictions on size/alignment
+ if (0 != (used_attributes->m_stack_size % required_stack_alignment))
+ used_attributes->m_stack_size += required_stack_alignment - (used_attributes->m_stack_size % required_stack_alignment);
+
+ used_attributes->m_stack_location = mmap_with_name(nullptr, used_attributes->m_stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, 0, 0, "Thread stack");
+ if (!used_attributes->m_stack_location)
+ return -1;
+ }
+
+#ifdef PTHREAD_DEBUG
+ dbgprintf("pthread_create: Creating thread with attributes at %p, detach state %s, priority %d, guard page size %d, stack size %d, stack location %p\n",
+ used_attributes,
+ (PTHREAD_CREATE_JOINABLE == used_attributes->m_detach_state) ? "joinable" : "detached",
+ used_attributes->m_schedule_priority,
+ used_attributes->m_guard_page_size,
+ used_attributes->m_stack_size,
+ used_attributes->m_stack_location);
+#endif
+
+ return create_thread(thread, start_routine, argument_to_start_routine, used_attributes);
+}
+
+void pthread_exit(void* value_ptr)
+{
+ exit_thread(value_ptr);
+}
+
+int pthread_join(pthread_t thread, void** exit_value_ptr)
+{
+ int rc = syscall(SC_join_thread, thread, exit_value_ptr);
+ __RETURN_PTHREAD_ERROR(rc);
+}
+
+int pthread_detach(pthread_t thread)
+{
+ int rc = syscall(SC_detach_thread, thread);
+ __RETURN_PTHREAD_ERROR(rc);
+}
+
+int pthread_sigmask(int how, const sigset_t* set, sigset_t* old_set)
+{
+ if (sigprocmask(how, set, old_set))
+ return errno;
+ return 0;
+}
+
+int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attributes)
+{
+ mutex->lock = 0;
+ mutex->owner = 0;
+ mutex->level = 0;
+ mutex->type = attributes ? attributes->type : PTHREAD_MUTEX_NORMAL;
+ return 0;
+}
+
+int pthread_mutex_destroy(pthread_mutex_t*)
+{
+ return 0;
+}
+
+int pthread_mutex_lock(pthread_mutex_t* mutex)
+{
+ auto& atomic = reinterpret_cast<Atomic<u32>&>(mutex->lock);
+ pthread_t this_thread = pthread_self();
+ for (;;) {
+ u32 expected = false;
+ if (!atomic.compare_exchange_strong(expected, true, AK::memory_order_acq_rel)) {
+ if (mutex->type == PTHREAD_MUTEX_RECURSIVE && mutex->owner == this_thread) {
+ mutex->level++;
+ return 0;
+ }
+ sched_yield();
+ continue;
+ }
+ mutex->owner = this_thread;
+ mutex->level = 0;
+ return 0;
+ }
+}
+
+int pthread_mutex_trylock(pthread_mutex_t* mutex)
+{
+ auto& atomic = reinterpret_cast<Atomic<u32>&>(mutex->lock);
+ u32 expected = false;
+ if (!atomic.compare_exchange_strong(expected, true, AK::memory_order_acq_rel)) {
+ if (mutex->type == PTHREAD_MUTEX_RECURSIVE && mutex->owner == pthread_self()) {
+ mutex->level++;
+ return 0;
+ }
+ return EBUSY;
+ }
+ mutex->owner = pthread_self();
+ mutex->level = 0;
+ return 0;
+}
+
+int pthread_mutex_unlock(pthread_mutex_t* mutex)
+{
+ if (mutex->type == PTHREAD_MUTEX_RECURSIVE && mutex->level > 0) {
+ mutex->level--;
+ return 0;
+ }
+ mutex->owner = 0;
+ mutex->lock = 0;
+ return 0;
+}
+
+int pthread_mutexattr_init(pthread_mutexattr_t* attr)
+{
+ attr->type = PTHREAD_MUTEX_NORMAL;
+ return 0;
+}
+
+int pthread_mutexattr_destroy(pthread_mutexattr_t*)
+{
+ return 0;
+}
+
+int pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type)
+{
+ if (!attr)
+ return EINVAL;
+ if (type != PTHREAD_MUTEX_NORMAL && type != PTHREAD_MUTEX_RECURSIVE)
+ return EINVAL;
+ attr->type = type;
+ return 0;
+}
+
+int pthread_attr_init(pthread_attr_t* attributes)
+{
+ auto* impl = new PthreadAttrImpl {};
+ *attributes = impl;
+
+#ifdef PTHREAD_DEBUG
+ dbgprintf("pthread_attr_init: New thread attributes at %p, detach state %s, priority %d, guard page size %d, stack size %d, stack location %p\n",
+ impl,
+ (PTHREAD_CREATE_JOINABLE == impl->m_detach_state) ? "joinable" : "detached",
+ impl->m_schedule_priority,
+ impl->m_guard_page_size,
+ impl->m_stack_size,
+ impl->m_stack_location);
+#endif
+
+ return 0;
+}
+
+int pthread_attr_destroy(pthread_attr_t* attributes)
+{
+ auto* attributes_impl = *(reinterpret_cast<PthreadAttrImpl**>(attributes));
+ delete attributes_impl;
+ return 0;
+}
+
+int pthread_attr_getdetachstate(const pthread_attr_t* attributes, int* p_detach_state)
+{
+ auto* attributes_impl = *(reinterpret_cast<const PthreadAttrImpl* const*>(attributes));
+
+ if (!attributes_impl || !p_detach_state)
+ return EINVAL;
+
+ *p_detach_state = attributes_impl->m_detach_state;
+ return 0;
+}
+
+int pthread_attr_setdetachstate(pthread_attr_t* attributes, int detach_state)
+{
+ auto* attributes_impl = *(reinterpret_cast<PthreadAttrImpl**>(attributes));
+
+ if (!attributes_impl)
+ return EINVAL;
+
+ if (detach_state != PTHREAD_CREATE_JOINABLE && detach_state != PTHREAD_CREATE_DETACHED)
+ return EINVAL;
+
+ attributes_impl->m_detach_state = detach_state;
+
+#ifdef PTHREAD_DEBUG
+ dbgprintf("pthread_attr_setdetachstate: Thread attributes at %p, detach state %s, priority %d, guard page size %d, stack size %d, stack location %p\n",
+ attributes_impl,
+ (PTHREAD_CREATE_JOINABLE == attributes_impl->m_detach_state) ? "joinable" : "detached",
+ attributes_impl->m_schedule_priority,
+ attributes_impl->m_guard_page_size,
+ attributes_impl->m_stack_size,
+ attributes_impl->m_stack_location);
+#endif
+
+ return 0;
+}
+
+int pthread_attr_getguardsize(const pthread_attr_t* attributes, size_t* p_guard_size)
+{
+ auto* attributes_impl = *(reinterpret_cast<const PthreadAttrImpl* const*>(attributes));
+
+ if (!attributes_impl || !p_guard_size)
+ return EINVAL;
+
+ *p_guard_size = attributes_impl->m_reported_guard_page_size;
+ return 0;
+}
+
+int pthread_attr_setguardsize(pthread_attr_t* attributes, size_t guard_size)
+{
+ auto* attributes_impl = *(reinterpret_cast<PthreadAttrImpl**>(attributes));
+
+ if (!attributes_impl)
+ return EINVAL;
+
+ size_t actual_guard_size = guard_size;
+ // round up
+ if (0 != (guard_size % PAGE_SIZE))
+ actual_guard_size += PAGE_SIZE - (guard_size % PAGE_SIZE);
+
+ // what is the user even doing?
+ if (actual_guard_size > highest_reasonable_guard_size) {
+ return EINVAL;
+ }
+
+ attributes_impl->m_guard_page_size = actual_guard_size;
+ attributes_impl->m_reported_guard_page_size = guard_size; // POSIX, why?
+
+#ifdef PTHREAD_DEBUG
+ dbgprintf("pthread_attr_setguardsize: Thread attributes at %p, detach state %s, priority %d, guard page size %d, stack size %d, stack location %p\n",
+ attributes_impl,
+ (PTHREAD_CREATE_JOINABLE == attributes_impl->m_detach_state) ? "joinable" : "detached",
+ attributes_impl->m_schedule_priority,
+ attributes_impl->m_guard_page_size,
+ attributes_impl->m_stack_size,
+ attributes_impl->m_stack_location);
+#endif
+
+ return 0;
+}
+
+int pthread_attr_getschedparam(const pthread_attr_t* attributes, struct sched_param* p_sched_param)
+{
+ auto* attributes_impl = *(reinterpret_cast<const PthreadAttrImpl* const*>(attributes));
+
+ if (!attributes_impl || !p_sched_param)
+ return EINVAL;
+
+ p_sched_param->sched_priority = attributes_impl->m_schedule_priority;
+ return 0;
+}
+
+int pthread_attr_setschedparam(pthread_attr_t* attributes, const struct sched_param* p_sched_param)
+{
+ auto* attributes_impl = *(reinterpret_cast<PthreadAttrImpl**>(attributes));
+ if (!attributes_impl || !p_sched_param)
+ return EINVAL;
+
+ if (p_sched_param->sched_priority < THREAD_PRIORITY_MIN || p_sched_param->sched_priority > THREAD_PRIORITY_MAX)
+ return ENOTSUP;
+
+ attributes_impl->m_schedule_priority = p_sched_param->sched_priority;
+
+#ifdef PTHREAD_DEBUG
+ dbgprintf("pthread_attr_setschedparam: Thread attributes at %p, detach state %s, priority %d, guard page size %d, stack size %d, stack location %p\n",
+ attributes_impl,
+ (PTHREAD_CREATE_JOINABLE == attributes_impl->m_detach_state) ? "joinable" : "detached",
+ attributes_impl->m_schedule_priority,
+ attributes_impl->m_guard_page_size,
+ attributes_impl->m_stack_size,
+ attributes_impl->m_stack_location);
+#endif
+
+ return 0;
+}
+
+int pthread_attr_getstack(const pthread_attr_t* attributes, void** p_stack_ptr, size_t* p_stack_size)
+{
+ auto* attributes_impl = *(reinterpret_cast<const PthreadAttrImpl* const*>(attributes));
+
+ if (!attributes_impl || !p_stack_ptr || !p_stack_size)
+ return EINVAL;
+
+ *p_stack_ptr = attributes_impl->m_stack_location;
+ *p_stack_size = attributes_impl->m_stack_size;
+
+ return 0;
+}
+
+int pthread_attr_setstack(pthread_attr_t* attributes, void* p_stack, size_t stack_size)
+{
+ auto* attributes_impl = *(reinterpret_cast<PthreadAttrImpl**>(attributes));
+
+ if (!attributes_impl || !p_stack)
+ return EINVAL;
+
+ // Check for required alignment on size
+ if (0 != (stack_size % required_stack_alignment))
+ return EINVAL;
+
+ // FIXME: Check for required alignment on pointer?
+
+ // FIXME: "[EACCES] The stack page(s) described by stackaddr and stacksize are not both readable and writable by the thread."
+ // Have to check that the whole range is mapped to this process/thread? Can we defer this to create_thread?
+
+ attributes_impl->m_stack_size = stack_size;
+ attributes_impl->m_stack_location = p_stack;
+
+#ifdef PTHREAD_DEBUG
+ dbgprintf("pthread_attr_setstack: Thread attributes at %p, detach state %s, priority %d, guard page size %d, stack size %d, stack location %p\n",
+ attributes_impl,
+ (PTHREAD_CREATE_JOINABLE == attributes_impl->m_detach_state) ? "joinable" : "detached",
+ attributes_impl->m_schedule_priority,
+ attributes_impl->m_guard_page_size,
+ attributes_impl->m_stack_size,
+ attributes_impl->m_stack_location);
+#endif
+
+ return 0;
+}
+
+int pthread_attr_getstacksize(const pthread_attr_t* attributes, size_t* p_stack_size)
+{
+ auto* attributes_impl = *(reinterpret_cast<const PthreadAttrImpl* const*>(attributes));
+
+ if (!attributes_impl || !p_stack_size)
+ return EINVAL;
+
+ *p_stack_size = attributes_impl->m_stack_size;
+ return 0;
+}
+
+int pthread_attr_setstacksize(pthread_attr_t* attributes, size_t stack_size)
+{
+ auto* attributes_impl = *(reinterpret_cast<PthreadAttrImpl**>(attributes));
+
+ if (!attributes_impl)
+ return EINVAL;
+
+ if ((stack_size < PTHREAD_STACK_MIN) || stack_size > highest_reasonable_stack_size)
+ return EINVAL;
+
+ attributes_impl->m_stack_size = stack_size;
+
+#ifdef PTHREAD_DEBUG
+ dbgprintf("pthread_attr_setstacksize: Thread attributes at %p, detach state %s, priority %d, guard page size %d, stack size %d, stack location %p\n",
+ attributes_impl,
+ (PTHREAD_CREATE_JOINABLE == attributes_impl->m_detach_state) ? "joinable" : "detached",
+ attributes_impl->m_schedule_priority,
+ attributes_impl->m_guard_page_size,
+ attributes_impl->m_stack_size,
+ attributes_impl->m_stack_location);
+#endif
+
+ return 0;
+}
+
+int pthread_getschedparam([[maybe_unused]] pthread_t thread, [[maybe_unused]] int* policy, [[maybe_unused]] struct sched_param* param)
+{
+ return 0;
+}
+
+int pthread_setschedparam([[maybe_unused]] pthread_t thread, [[maybe_unused]] int policy, [[maybe_unused]] const struct sched_param* param)
+{
+ return 0;
+}
+
+int pthread_cond_init(pthread_cond_t* cond, const pthread_condattr_t* attr)
+{
+ cond->value = 0;
+ cond->previous = 0;
+ cond->clockid = attr ? attr->clockid : CLOCK_MONOTONIC_COARSE;
+ return 0;
+}
+
+int pthread_cond_destroy(pthread_cond_t*)
+{
+ return 0;
+}
+
+static int cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex, const struct timespec* abstime)
+{
+ i32 value = cond->value;
+ cond->previous = value;
+ pthread_mutex_unlock(mutex);
+ int rc = futex(&cond->value, FUTEX_WAIT, value, abstime);
+ pthread_mutex_lock(mutex);
+ return rc;
+}
+
+int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex)
+{
+ int rc = cond_wait(cond, mutex, nullptr);
+ ASSERT(rc == 0);
+ return 0;
+}
+
+int pthread_condattr_init(pthread_condattr_t* attr)
+{
+ attr->clockid = CLOCK_MONOTONIC_COARSE;
+ return 0;
+}
+
+int pthread_condattr_destroy(pthread_condattr_t*)
+{
+ return 0;
+}
+
+int pthread_condattr_setclock(pthread_condattr_t* attr, clockid_t clock)
+{
+ attr->clockid = clock;
+ return 0;
+}
+
+int pthread_cond_timedwait(pthread_cond_t* cond, pthread_mutex_t* mutex, const struct timespec* abstime)
+{
+ return cond_wait(cond, mutex, abstime);
+}
+
+int pthread_cond_signal(pthread_cond_t* cond)
+{
+ u32 value = cond->previous + 1;
+ cond->value = value;
+ int rc = futex(&cond->value, FUTEX_WAKE, 1, nullptr);
+ ASSERT(rc == 0);
+ return 0;
+}
+
+int pthread_cond_broadcast(pthread_cond_t* cond)
+{
+ u32 value = cond->previous + 1;
+ cond->value = value;
+ int rc = futex(&cond->value, FUTEX_WAKE, INT32_MAX, nullptr);
+ ASSERT(rc == 0);
+ return 0;
+}
+
+static constexpr int max_keys = PTHREAD_KEYS_MAX;
+
+typedef void (*KeyDestructor)(void*);
+
+struct KeyTable {
+ KeyDestructor destructors[max_keys] { nullptr };
+ int next { 0 };
+ pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+};
+
+struct SpecificTable {
+ void* values[max_keys] { nullptr };
+};
+
+static KeyTable s_keys;
+
+__thread SpecificTable t_specifics;
+
+int pthread_key_create(pthread_key_t* key, KeyDestructor destructor)
+{
+ int ret = 0;
+ pthread_mutex_lock(&s_keys.mutex);
+ if (s_keys.next >= max_keys) {
+ ret = EAGAIN;
+ } else {
+ *key = s_keys.next++;
+ s_keys.destructors[*key] = destructor;
+ ret = 0;
+ }
+ pthread_mutex_unlock(&s_keys.mutex);
+ return ret;
+}
+
+int pthread_key_delete(pthread_key_t key)
+{
+ if (key < 0 || key >= max_keys)
+ return EINVAL;
+ pthread_mutex_lock(&s_keys.mutex);
+ s_keys.destructors[key] = nullptr;
+ pthread_mutex_unlock(&s_keys.mutex);
+ return 0;
+}
+
+void* pthread_getspecific(pthread_key_t key)
+{
+ if (key < 0)
+ return nullptr;
+ if (key >= max_keys)
+ return nullptr;
+ return t_specifics.values[key];
+}
+
+int pthread_setspecific(pthread_key_t key, const void* value)
+{
+ if (key < 0)
+ return EINVAL;
+ if (key >= max_keys)
+ return EINVAL;
+
+ t_specifics.values[key] = const_cast<void*>(value);
+ return 0;
+}
+
+void KeyDestroyer::destroy_for_current_thread()
+{
+ // This function will either be called during exit_thread, for a pthread, or
+ // during global program shutdown for the main thread.
+
+ pthread_mutex_lock(&s_keys.mutex);
+ size_t num_used_keys = s_keys.next;
+
+ // Dr. POSIX accounts for weird key destructors setting their own key again.
+ // Or even, setting other unrelated keys? Odd, but whatever the Doc says goes.
+
+ for (size_t destruct_iteration = 0; destruct_iteration < PTHREAD_DESTRUCTOR_ITERATIONS; ++destruct_iteration) {
+ bool any_nonnull_destructors = false;
+ for (size_t key_index = 0; key_index < num_used_keys; ++key_index) {
+ void* value = exchange(t_specifics.values[key_index], nullptr);
+
+ if (value && s_keys.destructors[key_index]) {
+ any_nonnull_destructors = true;
+ (*s_keys.destructors[key_index])(value);
+ }
+ }
+ if (!any_nonnull_destructors)
+ break;
+ }
+ pthread_mutex_unlock(&s_keys.mutex);
+}
+
+int pthread_setname_np(pthread_t thread, const char* name)
+{
+ if (!name)
+ return EFAULT;
+ int rc = syscall(SC_set_thread_name, thread, name, strlen(name));
+ __RETURN_PTHREAD_ERROR(rc);
+}
+
+int pthread_getname_np(pthread_t thread, char* buffer, size_t buffer_size)
+{
+ int rc = syscall(SC_get_thread_name, thread, buffer, buffer_size);
+ __RETURN_PTHREAD_ERROR(rc);
+}
+
+} // extern "C"
diff --git a/Userland/Libraries/LibPthread/pthread.h b/Userland/Libraries/LibPthread/pthread.h
new file mode 100644
index 0000000000..617aac8a90
--- /dev/null
+++ b/Userland/Libraries/LibPthread/pthread.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sched.h>
+#include <stdint.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <time.h>
+
+__BEGIN_DECLS
+
+int pthread_create(pthread_t*, pthread_attr_t*, void* (*)(void*), void*);
+void pthread_exit(void*) __attribute__((noreturn));
+int pthread_kill(pthread_t, int);
+void pthread_cleanup_push(void (*)(void*), void*);
+void pthread_cleanup_pop(int);
+int pthread_join(pthread_t, void**);
+int pthread_mutex_lock(pthread_mutex_t*);
+int pthread_mutex_trylock(pthread_mutex_t* mutex);
+int pthread_mutex_unlock(pthread_mutex_t*);
+int pthread_mutex_init(pthread_mutex_t*, const pthread_mutexattr_t*);
+int pthread_mutex_destroy(pthread_mutex_t*);
+
+int pthread_attr_init(pthread_attr_t*);
+int pthread_attr_destroy(pthread_attr_t*);
+
+#define PTHREAD_CREATE_JOINABLE 0
+#define PTHREAD_CREATE_DETACHED 1
+
+int pthread_attr_getdetachstate(const pthread_attr_t*, int*);
+int pthread_attr_setdetachstate(pthread_attr_t*, int);
+
+int pthread_attr_getguardsize(const pthread_attr_t*, size_t*);
+int pthread_attr_setguardsize(pthread_attr_t*, size_t);
+
+int pthread_attr_getschedparam(const pthread_attr_t*, struct sched_param*);
+int pthread_attr_setschedparam(pthread_attr_t*, const struct sched_param*);
+
+int pthread_attr_getstack(const pthread_attr_t*, void**, size_t*);
+int pthread_attr_setstack(pthread_attr_t* attr, void*, size_t);
+
+int pthread_attr_getstacksize(const pthread_attr_t*, size_t*);
+int pthread_attr_setstacksize(pthread_attr_t*, size_t);
+
+int pthread_once(pthread_once_t*, void (*)(void));
+#define PTHREAD_ONCE_INIT 0
+void* pthread_getspecific(pthread_key_t key);
+int pthread_setspecific(pthread_key_t key, const void* value);
+
+int pthread_getschedparam(pthread_t thread, int* policy, struct sched_param* param);
+int pthread_setschedparam(pthread_t thread, int policy, const struct sched_param* param);
+
+#define PTHREAD_MUTEX_NORMAL 0
+#define PTHREAD_MUTEX_RECURSIVE 1
+#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
+#define PTHREAD_MUTEX_INITIALIZER \
+ { \
+ 0, 0, 0, PTHREAD_MUTEX_DEFAULT \
+ }
+#define PTHREAD_COND_INITIALIZER \
+ { \
+ 0, 0, CLOCK_MONOTONIC_COARSE \
+ }
+
+#define PTHREAD_KEYS_MAX 64
+#define PTHREAD_DESTRUCTOR_ITERATIONS 4
+
+int pthread_key_create(pthread_key_t* key, void (*destructor)(void*));
+int pthread_key_delete(pthread_key_t key);
+int pthread_cond_broadcast(pthread_cond_t*);
+int pthread_cond_init(pthread_cond_t*, const pthread_condattr_t*);
+int pthread_cond_signal(pthread_cond_t*);
+int pthread_cond_wait(pthread_cond_t*, pthread_mutex_t*);
+int pthread_condattr_init(pthread_condattr_t*);
+int pthread_condattr_setclock(pthread_condattr_t*, clockid_t);
+int pthread_condattr_destroy(pthread_condattr_t*);
+int pthread_cancel(pthread_t);
+int pthread_cond_destroy(pthread_cond_t*);
+int pthread_cond_timedwait(pthread_cond_t*, pthread_mutex_t*, const struct timespec*);
+
+void pthread_testcancel(void);
+
+int pthread_spin_destroy(pthread_spinlock_t*);
+int pthread_spin_init(pthread_spinlock_t*, int);
+int pthread_spin_lock(pthread_spinlock_t*);
+int pthread_spin_trylock(pthread_spinlock_t*);
+int pthread_spin_unlock(pthread_spinlock_t*);
+pthread_t pthread_self(void);
+int pthread_detach(pthread_t);
+int pthread_equal(pthread_t, pthread_t);
+int pthread_mutexattr_init(pthread_mutexattr_t*);
+int pthread_mutexattr_settype(pthread_mutexattr_t*, int);
+int pthread_mutexattr_destroy(pthread_mutexattr_t*);
+
+int pthread_setname_np(pthread_t, const char*);
+int pthread_getname_np(pthread_t, char*, size_t);
+
+__END_DECLS
diff --git a/Userland/Libraries/LibPthread/pthread_once.cpp b/Userland/Libraries/LibPthread/pthread_once.cpp
new file mode 100644
index 0000000000..141ad4acb7
--- /dev/null
+++ b/Userland/Libraries/LibPthread/pthread_once.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/Atomic.h>
+#include <AK/Types.h>
+#include <pthread.h>
+#include <serenity.h>
+
+enum State : i32 {
+ INITIAL = PTHREAD_ONCE_INIT,
+ DONE,
+ PERFORMING_NO_WAITERS,
+ PERFORMING_WITH_WAITERS,
+};
+
+int pthread_once(pthread_once_t* self, void (*callback)(void))
+{
+ auto& state = reinterpret_cast<Atomic<State>&>(*self);
+
+ // See what the current state is, and at the same time grab the lock if we
+ // got here first. We need acquire ordering here because if we see
+ // State::DONE, everything we do after that should "happen after" everything
+ // the other thread has done before writing the State::DONE.
+ State state2 = State::INITIAL;
+ bool have_exchanged = state.compare_exchange_strong(
+ state2, State::PERFORMING_NO_WAITERS, AK::memory_order_acquire);
+
+ if (have_exchanged) {
+ // We observed State::INITIAL and we've changed it to
+ // State::PERFORMING_NO_WAITERS, so it's us who should perform the
+ // operation.
+ callback();
+ // Now, record that we're done.
+ state2 = state.exchange(State::DONE, AK::memory_order_release);
+ switch (state2) {
+ case State::INITIAL:
+ case State::DONE:
+ ASSERT_NOT_REACHED();
+ case State::PERFORMING_NO_WAITERS:
+ // The fast path: there's no contention, so we don't have to wake
+ // anyone.
+ break;
+ case State::PERFORMING_WITH_WAITERS:
+ futex(self, FUTEX_WAKE, INT_MAX, nullptr);
+ break;
+ }
+
+ return 0;
+ }
+
+ // We did not get there first. Let's see if we have to wait.
+ // state2 contains the observed state.
+ while (true) {
+ switch (state2) {
+ case State::INITIAL:
+ ASSERT_NOT_REACHED();
+ case State::DONE:
+ // Awesome, nothing to do then.
+ return 0;
+ case State::PERFORMING_NO_WAITERS:
+ // We're going to wait for it, but we have to record that we're
+ // waiting and the other thread should wake us up. We need acquire
+ // ordering here for the same reason as above.
+ have_exchanged = state.compare_exchange_strong(
+ state2, State::PERFORMING_WITH_WAITERS, AK::memory_order_acquire);
+ if (!have_exchanged) {
+ // Something has changed already, reevaluate without waiting.
+ continue;
+ }
+ state2 = State::PERFORMING_WITH_WAITERS;
+ [[fallthrough]];
+ case State::PERFORMING_WITH_WAITERS:
+ // Let's wait for it.
+ futex(self, FUTEX_WAIT, state2, nullptr);
+ // We have been woken up, but that might have been due to a signal
+ // or something, so we have to reevaluate. We need acquire ordering
+ // here for the same reason as above. Hopefully we'll just see
+ // State::DONE this time, but who knows.
+ state2 = state.load(AK::memory_order_acquire);
+ continue;
+ }
+ }
+}
diff --git a/Userland/Libraries/LibRegex/C/Regex.cpp b/Userland/Libraries/LibRegex/C/Regex.cpp
new file mode 100644
index 0000000000..ad39b18af5
--- /dev/null
+++ b/Userland/Libraries/LibRegex/C/Regex.cpp
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <LibRegex/Regex.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef __serenity__
+# include <regex.h>
+#else
+# include <LibC/regex.h>
+#endif
+
+struct internal_regex_t {
+ u8 cflags;
+ u8 eflags;
+ OwnPtr<Regex<PosixExtended>> re;
+ size_t re_pat_errpos;
+ ReError re_pat_err;
+ String re_pat;
+ size_t re_nsub;
+};
+
+static internal_regex_t* impl_from(regex_t* re)
+{
+ if (!re)
+ return nullptr;
+
+ return reinterpret_cast<internal_regex_t*>(re->__data);
+}
+
+static const internal_regex_t* impl_from(const regex_t* re)
+{
+ return impl_from(const_cast<regex_t*>(re));
+}
+
+extern "C" {
+
+int regcomp(regex_t* reg, const char* pattern, int cflags)
+{
+ if (!reg)
+ return REG_ESPACE;
+
+ // Note that subsequent uses of regcomp() without regfree() _will_ leak memory
+ // This could've been prevented if libc provided a reginit() or similar, but it does not.
+ reg->__data = new internal_regex_t { 0, 0, {}, 0, ReError::REG_NOERR, {}, 0 };
+
+ auto preg = impl_from(reg);
+
+ if (!(cflags & REG_EXTENDED))
+ return REG_ENOSYS;
+
+ preg->cflags = cflags;
+
+ String pattern_str(pattern);
+ preg->re = make<Regex<PosixExtended>>(pattern_str, PosixOptions {} | (PosixFlags)cflags | PosixFlags::SkipTrimEmptyMatches);
+
+ auto parser_result = preg->re->parser_result;
+ if (parser_result.error != regex::Error::NoError) {
+ preg->re_pat_errpos = parser_result.error_token.position();
+ preg->re_pat_err = (ReError)parser_result.error;
+ preg->re_pat = pattern;
+
+ dbg() << "Have Error: " << (ReError)parser_result.error;
+
+ return (ReError)parser_result.error;
+ }
+
+ preg->re_nsub = parser_result.capture_groups_count;
+
+ return REG_NOERR;
+}
+
+int regexec(const regex_t* reg, const char* string, size_t nmatch, regmatch_t pmatch[], int eflags)
+{
+ auto preg = impl_from(reg);
+
+ if (!preg->re || preg->re_pat_err) {
+ if (preg->re_pat_err)
+ return preg->re_pat_err;
+ return REG_BADPAT;
+ }
+
+ RegexResult result;
+ if (eflags & REG_SEARCH)
+ result = preg->re->search(string, PosixOptions {} | (PosixFlags)eflags);
+ else
+ result = preg->re->match(string, PosixOptions {} | (PosixFlags)eflags);
+
+ if (result.success) {
+ auto size = result.matches.size();
+ if (size && nmatch && pmatch) {
+ pmatch[0].rm_cnt = size;
+
+ size_t match_index { 0 };
+ for (size_t i = 0; i < size; ++i) {
+ pmatch[match_index].rm_so = result.matches.at(i).global_offset;
+ pmatch[match_index].rm_eo = pmatch[match_index].rm_so + result.matches.at(i).view.length();
+ if (match_index > 0)
+ pmatch[match_index].rm_cnt = result.capture_group_matches.size();
+
+ ++match_index;
+ if (match_index >= nmatch)
+ return REG_NOERR;
+
+ if (i < result.capture_group_matches.size()) {
+ auto capture_groups_size = result.capture_group_matches.at(i).size();
+ for (size_t j = 0; j < preg->re->parser_result.capture_groups_count; ++j) {
+ if (j >= capture_groups_size || !result.capture_group_matches.at(i).at(j).view.length()) {
+ pmatch[match_index].rm_so = -1;
+ pmatch[match_index].rm_eo = -1;
+ pmatch[match_index].rm_cnt = 0;
+ } else {
+ pmatch[match_index].rm_so = result.capture_group_matches.at(i).at(j).global_offset;
+ pmatch[match_index].rm_eo = pmatch[match_index].rm_so + result.capture_group_matches.at(i).at(j).view.length();
+ pmatch[match_index].rm_cnt = 1;
+ }
+
+ ++match_index;
+ if (match_index >= nmatch)
+ return REG_NOERR;
+ }
+ }
+ }
+
+ if (match_index < nmatch) {
+ for (size_t i = match_index; i < nmatch; ++i) {
+ pmatch[i].rm_so = -1;
+ pmatch[i].rm_eo = -1;
+ pmatch[i].rm_cnt = 0;
+ }
+ }
+ }
+ return REG_NOERR;
+ } else {
+ if (nmatch && pmatch) {
+ pmatch[0].rm_so = -1;
+ pmatch[0].rm_eo = -1;
+ pmatch[0].rm_cnt = 0;
+ }
+ }
+
+ return REG_NOMATCH;
+}
+
+inline static String get_error(ReError errcode)
+{
+ String error;
+ switch ((ReError)errcode) {
+ case REG_NOERR:
+ error = "No error";
+ break;
+ case REG_NOMATCH:
+ error = "regexec() failed to match.";
+ break;
+ case REG_BADPAT:
+ error = "Invalid regular expression.";
+ break;
+ case REG_ECOLLATE:
+ error = "Invalid collating element referenced.";
+ break;
+ case REG_ECTYPE:
+ error = "Invalid character class type referenced.";
+ break;
+ case REG_EESCAPE:
+ error = "Trailing \\ in pattern.";
+ break;
+ case REG_ESUBREG:
+ error = "Number in \\digit invalid or in error.";
+ break;
+ case REG_EBRACK:
+ error = "[ ] imbalance.";
+ break;
+ case REG_EPAREN:
+ error = "\\( \\) or ( ) imbalance.";
+ break;
+ case REG_EBRACE:
+ error = "\\{ \\} imbalance.";
+ break;
+ case REG_BADBR:
+ error = "Content of \\{ \\} invalid: not a number, number too large, more than two numbers, first larger than second.";
+ break;
+ case REG_ERANGE:
+ error = "Invalid endpoint in range expression.";
+ break;
+ case REG_ESPACE:
+ error = "Out of memory.";
+ break;
+ case REG_BADRPT:
+ error = "?, * or + not preceded by valid regular expression.";
+ break;
+ case REG_ENOSYS:
+ error = "The implementation does not support the function.";
+ break;
+ case REG_EMPTY_EXPR:
+ error = "Empty expression provided";
+ break;
+ }
+
+ return error;
+}
+
+size_t regerror(int errcode, const regex_t* reg, char* errbuf, size_t errbuf_size)
+{
+ String error;
+ auto preg = impl_from(reg);
+
+ if (!preg)
+ error = get_error((ReError)errcode);
+ else
+ error = preg->re->error_string(get_error(preg->re_pat_err));
+
+ if (!errbuf_size)
+ return error.length();
+
+ if (!error.copy_characters_to_buffer(errbuf, errbuf_size))
+ return 0;
+
+ return error.length();
+}
+
+void regfree(regex_t* reg)
+{
+ auto preg = impl_from(reg);
+ if (preg) {
+ delete preg;
+ reg->__data = nullptr;
+ }
+}
+}
diff --git a/Userland/Libraries/LibRegex/CMakeLists.txt b/Userland/Libraries/LibRegex/CMakeLists.txt
new file mode 100644
index 0000000000..44cc6928f9
--- /dev/null
+++ b/Userland/Libraries/LibRegex/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(SOURCES
+ C/Regex.cpp
+ RegexByteCode.cpp
+ RegexLexer.cpp
+ RegexMatcher.cpp
+ RegexParser.cpp
+)
+
+serenity_lib(LibRegex regex)
+target_link_libraries(LibRegex LibC LibCore)
diff --git a/Userland/Libraries/LibRegex/Forward.h b/Userland/Libraries/LibRegex/Forward.h
new file mode 100644
index 0000000000..7d0397a843
--- /dev/null
+++ b/Userland/Libraries/LibRegex/Forward.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+
+namespace regex {
+enum class Error : u8;
+class Lexer;
+class PosixExtendedParser;
+class ECMA262Parser;
+
+class ByteCode;
+class OpCode;
+class OpCode_Exit;
+class OpCode_Jump;
+class OpCode_ForkJump;
+class OpCode_ForkStay;
+class OpCode_CheckBegin;
+class OpCode_CheckEnd;
+class OpCode_SaveLeftCaptureGroup;
+class OpCode_SaveRightCaptureGroup;
+class OpCode_SaveLeftNamedCaptureGroup;
+class OpCode_SaveNamedLeftCaptureGroup;
+class OpCode_SaveRightNamedCaptureGroup;
+class OpCode_Compare;
+class RegexStringView;
+}
+
+using regex::ECMA262Parser;
+using regex::Error;
+using regex::Lexer;
+using regex::PosixExtendedParser;
+using regex::RegexStringView;
diff --git a/Userland/Libraries/LibRegex/Regex.h b/Userland/Libraries/LibRegex/Regex.h
new file mode 100644
index 0000000000..d3fdcec7f4
--- /dev/null
+++ b/Userland/Libraries/LibRegex/Regex.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibRegex/Forward.h>
+#include <LibRegex/RegexDebug.h>
+#include <LibRegex/RegexMatcher.h>
diff --git a/Userland/Libraries/LibRegex/RegexByteCode.cpp b/Userland/Libraries/LibRegex/RegexByteCode.cpp
new file mode 100644
index 0000000000..63f526ee9a
--- /dev/null
+++ b/Userland/Libraries/LibRegex/RegexByteCode.cpp
@@ -0,0 +1,749 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "RegexByteCode.h"
+#include "AK/StringBuilder.h"
+#include "RegexDebug.h"
+
+#include <ctype.h>
+
+namespace regex {
+
+const char* OpCode::name(OpCodeId opcode_id)
+{
+ switch (opcode_id) {
+#define __ENUMERATE_OPCODE(x) \
+ case OpCodeId::x: \
+ return #x;
+ ENUMERATE_OPCODES
+#undef __ENUMERATE_OPCODE
+ default:
+ ASSERT_NOT_REACHED();
+ return "<Unknown>";
+ }
+}
+
+const char* OpCode::name() const
+{
+ return name(opcode_id());
+}
+
+const char* execution_result_name(ExecutionResult result)
+{
+ switch (result) {
+#define __ENUMERATE_EXECUTION_RESULT(x) \
+ case ExecutionResult::x: \
+ return #x;
+ ENUMERATE_EXECUTION_RESULTS
+#undef __ENUMERATE_EXECUTION_RESULT
+ default:
+ ASSERT_NOT_REACHED();
+ return "<Unknown>";
+ }
+}
+
+const char* boundary_check_type_name(BoundaryCheckType ty)
+{
+ switch (ty) {
+#define __ENUMERATE_BOUNDARY_CHECK_TYPE(x) \
+ case BoundaryCheckType::x: \
+ return #x;
+ ENUMERATE_BOUNDARY_CHECK_TYPES
+#undef __ENUMERATE_BOUNDARY_CHECK_TYPE
+ default:
+ ASSERT_NOT_REACHED();
+ return "<Unknown>";
+ }
+}
+
+const char* character_compare_type_name(CharacterCompareType ch_compare_type)
+{
+ switch (ch_compare_type) {
+#define __ENUMERATE_CHARACTER_COMPARE_TYPE(x) \
+ case CharacterCompareType::x: \
+ return #x;
+ ENUMERATE_CHARACTER_COMPARE_TYPES
+#undef __ENUMERATE_CHARACTER_COMPARE_TYPE
+ default:
+ ASSERT_NOT_REACHED();
+ return "<Unknown>";
+ }
+}
+
+static const char* character_class_name(CharClass ch_class)
+{
+ switch (ch_class) {
+#define __ENUMERATE_CHARACTER_CLASS(x) \
+ case CharClass::x: \
+ return #x;
+ ENUMERATE_CHARACTER_CLASSES
+#undef __ENUMERATE_CHARACTER_CLASS
+ default:
+ ASSERT_NOT_REACHED();
+ return "<Unknown>";
+ }
+}
+
+HashMap<u32, OwnPtr<OpCode>> ByteCode::s_opcodes {};
+
+ALWAYS_INLINE OpCode* ByteCode::get_opcode_by_id(OpCodeId id) const
+{
+ if (!s_opcodes.size()) {
+ for (u32 i = (u32)OpCodeId::First; i <= (u32)OpCodeId::Last; ++i) {
+ switch ((OpCodeId)i) {
+ case OpCodeId::Exit:
+ s_opcodes.set(i, make<OpCode_Exit>(*const_cast<ByteCode*>(this)));
+ break;
+ case OpCodeId::Jump:
+ s_opcodes.set(i, make<OpCode_Jump>(*const_cast<ByteCode*>(this)));
+ break;
+ case OpCodeId::Compare:
+ s_opcodes.set(i, make<OpCode_Compare>(*const_cast<ByteCode*>(this)));
+ break;
+ case OpCodeId::CheckEnd:
+ s_opcodes.set(i, make<OpCode_CheckEnd>(*const_cast<ByteCode*>(this)));
+ break;
+ case OpCodeId::CheckBoundary:
+ s_opcodes.set(i, make<OpCode_CheckBoundary>(*const_cast<ByteCode*>(this)));
+ break;
+ case OpCodeId::ForkJump:
+ s_opcodes.set(i, make<OpCode_ForkJump>(*const_cast<ByteCode*>(this)));
+ break;
+ case OpCodeId::ForkStay:
+ s_opcodes.set(i, make<OpCode_ForkStay>(*const_cast<ByteCode*>(this)));
+ break;
+ case OpCodeId::FailForks:
+ s_opcodes.set(i, make<OpCode_FailForks>(*const_cast<ByteCode*>(this)));
+ break;
+ case OpCodeId::Save:
+ s_opcodes.set(i, make<OpCode_Save>(*const_cast<ByteCode*>(this)));
+ break;
+ case OpCodeId::Restore:
+ s_opcodes.set(i, make<OpCode_Restore>(*const_cast<ByteCode*>(this)));
+ break;
+ case OpCodeId::GoBack:
+ s_opcodes.set(i, make<OpCode_GoBack>(*const_cast<ByteCode*>(this)));
+ break;
+ case OpCodeId::CheckBegin:
+ s_opcodes.set(i, make<OpCode_CheckBegin>(*const_cast<ByteCode*>(this)));
+ break;
+ case OpCodeId::SaveLeftCaptureGroup:
+ s_opcodes.set(i, make<OpCode_SaveLeftCaptureGroup>(*const_cast<ByteCode*>(this)));
+ break;
+ case OpCodeId::SaveRightCaptureGroup:
+ s_opcodes.set(i, make<OpCode_SaveRightCaptureGroup>(*const_cast<ByteCode*>(this)));
+ break;
+ case OpCodeId::SaveLeftNamedCaptureGroup:
+ s_opcodes.set(i, make<OpCode_SaveLeftNamedCaptureGroup>(*const_cast<ByteCode*>(this)));
+ break;
+ case OpCodeId::SaveRightNamedCaptureGroup:
+ s_opcodes.set(i, make<OpCode_SaveRightNamedCaptureGroup>(*const_cast<ByteCode*>(this)));
+ break;
+ }
+ }
+ }
+
+ if (id > OpCodeId::Last)
+ return nullptr;
+
+ return const_cast<OpCode*>(s_opcodes.get((u32)id).value())->set_bytecode(*const_cast<ByteCode*>(this));
+}
+
+OpCode* ByteCode::get_opcode(MatchState& state) const
+{
+ OpCode* op_code;
+
+ if (state.instruction_position >= size()) {
+ op_code = get_opcode_by_id(OpCodeId::Exit);
+ } else
+ op_code = get_opcode_by_id((OpCodeId)at(state.instruction_position));
+
+ if (op_code)
+ op_code->set_state(state);
+
+ return op_code;
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_Exit::execute(const MatchInput& input, MatchState& state, MatchOutput&) const
+{
+ if (state.string_position > input.view.length() || state.instruction_position >= m_bytecode->size())
+ return ExecutionResult::Succeeded;
+
+ return ExecutionResult::Failed;
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_Save::execute(const MatchInput& input, MatchState& state, MatchOutput&) const
+{
+ input.saved_positions.append(state.string_position);
+ return ExecutionResult::Continue;
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_Restore::execute(const MatchInput& input, MatchState& state, MatchOutput&) const
+{
+ if (input.saved_positions.is_empty())
+ return ExecutionResult::Failed;
+
+ state.string_position = input.saved_positions.take_last();
+ return ExecutionResult::Continue;
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_GoBack::execute(const MatchInput&, MatchState& state, MatchOutput&) const
+{
+ if (count() > state.string_position)
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+
+ state.string_position -= count();
+ return ExecutionResult::Continue;
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_FailForks::execute(const MatchInput& input, MatchState&, MatchOutput&) const
+{
+ ASSERT(count() > 0);
+
+ input.fail_counter += count() - 1;
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_Jump::execute(const MatchInput&, MatchState& state, MatchOutput&) const
+{
+
+ state.instruction_position += offset();
+ return ExecutionResult::Continue;
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_ForkJump::execute(const MatchInput&, MatchState& state, MatchOutput&) const
+{
+ state.fork_at_position = state.instruction_position + size() + offset();
+ return ExecutionResult::Fork_PrioHigh;
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_ForkStay::execute(const MatchInput&, MatchState& state, MatchOutput&) const
+{
+ state.fork_at_position = state.instruction_position + size() + offset();
+ return ExecutionResult::Fork_PrioLow;
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_CheckBegin::execute(const MatchInput& input, MatchState& state, MatchOutput&) const
+{
+ if (0 == state.string_position && (input.regex_options & AllFlags::MatchNotBeginOfLine))
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+
+ if ((0 == state.string_position && !(input.regex_options & AllFlags::MatchNotBeginOfLine))
+ || (0 != state.string_position && (input.regex_options & AllFlags::MatchNotBeginOfLine))
+ || (0 == state.string_position && (input.regex_options & AllFlags::Global)))
+ return ExecutionResult::Continue;
+
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_CheckBoundary::execute(const MatchInput& input, MatchState& state, MatchOutput&) const
+{
+ auto isword = [](auto ch) { return isalnum(ch) || ch == '_'; };
+ auto is_word_boundary = [&] {
+ if (state.string_position == input.view.length()) {
+ if (state.string_position > 0 && isword(input.view[state.string_position - 1]))
+ return true;
+ return false;
+ }
+
+ if (state.string_position == 0) {
+ if (isword(input.view[0]))
+ return true;
+
+ return false;
+ }
+
+ return !!(isword(input.view[state.string_position]) ^ isword(input.view[state.string_position - 1]));
+ };
+ switch (type()) {
+ case BoundaryCheckType::Word: {
+ if (is_word_boundary())
+ return ExecutionResult::Continue;
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+ }
+ case BoundaryCheckType::NonWord: {
+ if (!is_word_boundary())
+ return ExecutionResult::Continue;
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+ }
+ }
+ ASSERT_NOT_REACHED();
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_CheckEnd::execute(const MatchInput& input, MatchState& state, MatchOutput&) const
+{
+ if (state.string_position == input.view.length() && (input.regex_options & AllFlags::MatchNotEndOfLine))
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+
+ if ((state.string_position == input.view.length() && !(input.regex_options & AllFlags::MatchNotEndOfLine))
+ || (state.string_position != input.view.length() && (input.regex_options & AllFlags::MatchNotEndOfLine || input.regex_options & AllFlags::MatchNotBeginOfLine)))
+ return ExecutionResult::Continue;
+
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_SaveLeftCaptureGroup::execute(const MatchInput& input, MatchState& state, MatchOutput& output) const
+{
+ if (input.match_index >= output.capture_group_matches.size()) {
+ output.capture_group_matches.ensure_capacity(input.match_index);
+ auto capacity = output.capture_group_matches.capacity();
+ for (size_t i = output.capture_group_matches.size(); i <= capacity; ++i)
+ output.capture_group_matches.empend();
+ }
+
+ if (id() >= output.capture_group_matches.at(input.match_index).size()) {
+ output.capture_group_matches.at(input.match_index).ensure_capacity(id());
+ auto capacity = output.capture_group_matches.at(input.match_index).capacity();
+ for (size_t i = output.capture_group_matches.at(input.match_index).size(); i <= capacity; ++i)
+ output.capture_group_matches.at(input.match_index).empend();
+ }
+
+ output.capture_group_matches.at(input.match_index).at(id()).left_column = state.string_position;
+ return ExecutionResult::Continue;
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_SaveRightCaptureGroup::execute(const MatchInput& input, MatchState& state, MatchOutput& output) const
+{
+ auto& match = output.capture_group_matches.at(input.match_index).at(id());
+ auto start_position = match.left_column;
+ auto length = state.string_position - start_position;
+
+ if (start_position < match.column)
+ return ExecutionResult::Continue;
+
+ ASSERT(start_position + length <= input.view.length());
+
+ auto view = input.view.substring_view(start_position, length);
+
+ if (input.regex_options & AllFlags::StringCopyMatches) {
+ match = { view.to_string(), input.line, start_position, input.global_offset + start_position }; // create a copy of the original string
+ } else {
+ match = { view, input.line, start_position, input.global_offset + start_position }; // take view to original string
+ }
+
+ return ExecutionResult::Continue;
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_SaveLeftNamedCaptureGroup::execute(const MatchInput& input, MatchState& state, MatchOutput& output) const
+{
+ if (input.match_index >= output.named_capture_group_matches.size()) {
+ output.named_capture_group_matches.ensure_capacity(input.match_index);
+ auto capacity = output.named_capture_group_matches.capacity();
+ for (size_t i = output.named_capture_group_matches.size(); i <= capacity; ++i)
+ output.named_capture_group_matches.empend();
+ }
+ output.named_capture_group_matches.at(input.match_index).ensure(name()).column = state.string_position;
+ return ExecutionResult::Continue;
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_SaveRightNamedCaptureGroup::execute(const MatchInput& input, MatchState& state, MatchOutput& output) const
+{
+ StringView capture_group_name = name();
+
+ if (output.named_capture_group_matches.at(input.match_index).contains(capture_group_name)) {
+ auto start_position = output.named_capture_group_matches.at(input.match_index).ensure(capture_group_name).column;
+ auto length = state.string_position - start_position;
+
+ auto& map = output.named_capture_group_matches.at(input.match_index);
+
+#ifdef REGEX_DEBUG
+ ASSERT(start_position + length <= input.view.length());
+ dbg() << "Save named capture group with name=" << capture_group_name << " and content: " << input.view.substring_view(start_position, length).to_string();
+#endif
+
+ ASSERT(start_position + length <= input.view.length());
+ auto view = input.view.substring_view(start_position, length);
+ if (input.regex_options & AllFlags::StringCopyMatches) {
+ map.set(capture_group_name, { view.to_string(), input.line, start_position, input.global_offset + start_position }); // create a copy of the original string
+ } else {
+ map.set(capture_group_name, { view, input.line, start_position, input.global_offset + start_position }); // take view to original string
+ }
+ } else {
+ fprintf(stderr, "Didn't find corresponding capture group match for name=%s, match_index=%lu\n", capture_group_name.to_string().characters(), input.match_index);
+ }
+
+ return ExecutionResult::Continue;
+}
+
+ALWAYS_INLINE ExecutionResult OpCode_Compare::execute(const MatchInput& input, MatchState& state, MatchOutput& output) const
+{
+ bool inverse { false };
+ bool temporary_inverse { false };
+ bool reset_temp_inverse { false };
+
+ auto current_inversion_state = [&]() -> bool { return temporary_inverse ^ inverse; };
+
+ size_t string_position = state.string_position;
+ bool inverse_matched { false };
+
+ size_t offset { state.instruction_position + 3 };
+ for (size_t i = 0; i < arguments_count(); ++i) {
+ if (state.string_position > string_position)
+ break;
+
+ if (reset_temp_inverse) {
+ reset_temp_inverse = false;
+ temporary_inverse = false;
+ } else {
+ reset_temp_inverse = true;
+ }
+
+ auto compare_type = (CharacterCompareType)m_bytecode->at(offset++);
+
+ if (compare_type == CharacterCompareType::Inverse)
+ inverse = true;
+
+ else if (compare_type == CharacterCompareType::TemporaryInverse) {
+ // If "TemporaryInverse" is given, negate the current inversion state only for the next opcode.
+ // it follows that this cannot be the last compare element.
+ ASSERT(i != arguments_count() - 1);
+
+ temporary_inverse = true;
+ reset_temp_inverse = false;
+
+ } else if (compare_type == CharacterCompareType::Char) {
+ u32 ch = m_bytecode->at(offset++);
+
+ // We want to compare a string that is longer or equal in length to the available string
+ if (input.view.length() - state.string_position < 1)
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+
+ compare_char(input, state, ch, current_inversion_state(), inverse_matched);
+
+ } else if (compare_type == CharacterCompareType::AnyChar) {
+ // We want to compare a string that is definitely longer than the available string
+ if (input.view.length() - state.string_position < 1)
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+
+ ASSERT(!current_inversion_state());
+ ++state.string_position;
+
+ } else if (compare_type == CharacterCompareType::String) {
+ ASSERT(!current_inversion_state());
+
+ const auto& length = m_bytecode->at(offset++);
+ StringBuilder str_builder;
+ for (size_t i = 0; i < length; ++i)
+ str_builder.append(m_bytecode->at(offset++));
+
+ // We want to compare a string that is definitely longer than the available string
+ if (input.view.length() - state.string_position < length)
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+
+ if (!compare_string(input, state, str_builder.string_view().characters_without_null_termination(), length))
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+
+ } else if (compare_type == CharacterCompareType::CharClass) {
+
+ if (input.view.length() - state.string_position < 1)
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+
+ auto character_class = (CharClass)m_bytecode->at(offset++);
+ auto ch = input.view[state.string_position];
+
+ compare_character_class(input, state, character_class, ch, current_inversion_state(), inverse_matched);
+
+ } else if (compare_type == CharacterCompareType::CharRange) {
+ auto value = (CharRange)m_bytecode->at(offset++);
+
+ auto from = value.from;
+ auto to = value.to;
+ auto ch = input.view[state.string_position];
+
+ compare_character_range(input, state, from, to, ch, current_inversion_state(), inverse_matched);
+
+ } else if (compare_type == CharacterCompareType::Reference) {
+ auto reference_number = (size_t)m_bytecode->at(offset++);
+ auto& groups = output.capture_group_matches.at(input.match_index);
+ if (groups.size() <= reference_number)
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+
+ auto str = groups.at(reference_number).view;
+
+ // We want to compare a string that is definitely longer than the available string
+ if (input.view.length() - state.string_position < str.length())
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+
+ if (!compare_string(input, state, str.characters_without_null_termination(), str.length()))
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+
+ } else if (compare_type == CharacterCompareType::NamedReference) {
+ auto ptr = (const char*)m_bytecode->at(offset++);
+ auto length = (size_t)m_bytecode->at(offset++);
+ StringView name { ptr, length };
+
+ auto group = output.named_capture_group_matches.at(input.match_index).get(name);
+ if (!group.has_value())
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+
+ auto str = group.value().view;
+
+ // We want to compare a string that is definitely longer than the available string
+ if (input.view.length() - state.string_position < str.length())
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+
+ if (!compare_string(input, state, str.characters_without_null_termination(), str.length()))
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+
+ } else {
+ fprintf(stderr, "Undefined comparison: %i\n", (int)compare_type);
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ }
+
+ if (current_inversion_state() && !inverse_matched)
+ ++state.string_position;
+
+ if (string_position == state.string_position || state.string_position > input.view.length())
+ return ExecutionResult::Failed_ExecuteLowPrioForks;
+
+ return ExecutionResult::Continue;
+}
+
+ALWAYS_INLINE void OpCode_Compare::compare_char(const MatchInput& input, MatchState& state, u32 ch1, bool inverse, bool& inverse_matched)
+{
+ u32 ch2 = input.view[state.string_position];
+
+ if (input.regex_options & AllFlags::Insensitive) {
+ ch1 = tolower(ch1);
+ ch2 = tolower(ch2);
+ }
+
+ if (ch1 == ch2) {
+ if (inverse)
+ inverse_matched = true;
+ else
+ ++state.string_position;
+ }
+}
+
+ALWAYS_INLINE bool OpCode_Compare::compare_string(const MatchInput& input, MatchState& state, const char* str, size_t length)
+{
+ if (input.view.is_u8_view()) {
+ auto str_view1 = StringView(str, length);
+ auto str_view2 = StringView(&input.view.u8view()[state.string_position], length);
+
+ String str1, str2;
+ if (input.regex_options & AllFlags::Insensitive) {
+ str1 = str_view1.to_string().to_lowercase();
+ str2 = str_view2.to_string().to_lowercase();
+ str_view1 = str1.view();
+ str_view2 = str2.view();
+ }
+
+ if (str_view1 == str_view2) {
+ state.string_position += length;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+ALWAYS_INLINE void OpCode_Compare::compare_character_class(const MatchInput& input, MatchState& state, CharClass character_class, u32 ch, bool inverse, bool& inverse_matched)
+{
+ switch (character_class) {
+ case CharClass::Alnum:
+ if (isalnum(ch)) {
+ if (inverse)
+ inverse_matched = true;
+ else
+ ++state.string_position;
+ }
+ break;
+ case CharClass::Alpha:
+ if (isalpha(ch))
+ ++state.string_position;
+ break;
+ case CharClass::Blank:
+ if (ch == ' ' || ch == '\t') {
+ if (inverse)
+ inverse_matched = true;
+ else
+ ++state.string_position;
+ }
+ break;
+ case CharClass::Cntrl:
+ if (iscntrl(ch)) {
+ if (inverse)
+ inverse_matched = true;
+ else
+ ++state.string_position;
+ }
+ break;
+ case CharClass::Digit:
+ if (isdigit(ch)) {
+ if (inverse)
+ inverse_matched = true;
+ else
+ ++state.string_position;
+ }
+ break;
+ case CharClass::Graph:
+ if (isgraph(ch)) {
+ if (inverse)
+ inverse_matched = true;
+ else
+ ++state.string_position;
+ }
+ break;
+ case CharClass::Lower:
+ if (islower(ch) || ((input.regex_options & AllFlags::Insensitive) && isupper(ch))) {
+ if (inverse)
+ inverse_matched = true;
+ else
+ ++state.string_position;
+ }
+ break;
+ case CharClass::Print:
+ if (isprint(ch)) {
+ if (inverse)
+ inverse_matched = true;
+ else
+ ++state.string_position;
+ }
+ break;
+ case CharClass::Punct:
+ if (ispunct(ch)) {
+ if (inverse)
+ inverse_matched = true;
+ else
+ ++state.string_position;
+ }
+ break;
+ case CharClass::Space:
+ if (isspace(ch)) {
+ if (inverse)
+ inverse_matched = true;
+ else
+ ++state.string_position;
+ }
+ break;
+ case CharClass::Upper:
+ if (isupper(ch) || ((input.regex_options & AllFlags::Insensitive) && islower(ch))) {
+ if (inverse)
+ inverse_matched = true;
+ else
+ ++state.string_position;
+ }
+ break;
+ case CharClass::Word:
+ if (isalnum(ch) || ch == '_') {
+ if (inverse)
+ inverse_matched = true;
+ else
+ ++state.string_position;
+ }
+ break;
+ case CharClass::Xdigit:
+ if (isxdigit(ch)) {
+ if (inverse)
+ inverse_matched = true;
+ else
+ ++state.string_position;
+ }
+ break;
+ }
+}
+
+ALWAYS_INLINE void OpCode_Compare::compare_character_range(const MatchInput& input, MatchState& state, u32 from, u32 to, u32 ch, bool inverse, bool& inverse_matched)
+{
+ if (input.regex_options & AllFlags::Insensitive) {
+ from = tolower(from);
+ to = tolower(to);
+ ch = tolower(ch);
+ }
+
+ if (ch >= from && ch <= to) {
+ if (inverse)
+ inverse_matched = true;
+ else
+ ++state.string_position;
+ }
+}
+
+const String OpCode_Compare::arguments_string() const
+{
+ return String::format("argc=%lu, args=%lu ", arguments_count(), arguments_size());
+}
+
+const Vector<String> OpCode_Compare::variable_arguments_to_string(Optional<MatchInput> input) const
+{
+ Vector<String> result;
+
+ size_t offset { state().instruction_position + 3 };
+ RegexStringView view = ((input.has_value()) ? input.value().view : nullptr);
+
+ for (size_t i = 0; i < arguments_count(); ++i) {
+ auto compare_type = (CharacterCompareType)m_bytecode->at(offset++);
+ result.empend(String::format("type=%lu [%s]", (size_t)compare_type, character_compare_type_name(compare_type)));
+
+ auto compared_against_string_start_offset = state().string_position > 0 ? state().string_position - 1 : state().string_position;
+
+ if (compare_type == CharacterCompareType::Char) {
+ char ch = m_bytecode->at(offset++);
+ result.empend(String::format("value='%c'", ch));
+ if (!view.is_null() && view.length() > state().string_position)
+ result.empend(String::format(
+ "compare against: '%s'",
+ view.substring_view(compared_against_string_start_offset, state().string_position > view.length() ? 0 : 1).to_string().characters()));
+ } else if (compare_type == CharacterCompareType::NamedReference) {
+ auto ptr = (const char*)m_bytecode->at(offset++);
+ auto length = m_bytecode->at(offset++);
+ result.empend(String::format("name='%.*s'", (int)length, ptr));
+ } else if (compare_type == CharacterCompareType::Reference) {
+ auto ref = m_bytecode->at(offset++);
+ result.empend(String::formatted("number={}", ref));
+ } else if (compare_type == CharacterCompareType::String) {
+ auto& length = m_bytecode->at(offset++);
+ StringBuilder str_builder;
+ for (size_t i = 0; i < length; ++i)
+ str_builder.append(m_bytecode->at(offset++));
+ result.empend(String::format("value=\"%.*s\"", (int)length, str_builder.string_view().characters_without_null_termination()));
+ if (!view.is_null() && view.length() > state().string_position)
+ result.empend(String::format(
+ "compare against: \"%s\"",
+ input.value().view.substring_view(compared_against_string_start_offset, compared_against_string_start_offset + length > view.length() ? 0 : length).to_string().characters()));
+ } else if (compare_type == CharacterCompareType::CharClass) {
+ auto character_class = (CharClass)m_bytecode->at(offset++);
+ result.empend(String::format("ch_class=%lu [%s]", (size_t)character_class, character_class_name(character_class)));
+ if (!view.is_null() && view.length() > state().string_position)
+ result.empend(String::format(
+ "compare against: '%s'",
+ input.value().view.substring_view(compared_against_string_start_offset, state().string_position > view.length() ? 0 : 1).to_string().characters()));
+ } else if (compare_type == CharacterCompareType::CharRange) {
+ auto value = (CharRange)m_bytecode->at(offset++);
+ result.empend(String::format("ch_range='%c'-'%c'", value.from, value.to));
+ if (!view.is_null() && view.length() > state().string_position)
+ result.empend(String::format(
+ "compare against: '%s'",
+ input.value().view.substring_view(compared_against_string_start_offset, state().string_position > view.length() ? 0 : 1).to_string().characters()));
+ }
+ }
+ return result;
+}
+}
diff --git a/Userland/Libraries/LibRegex/RegexByteCode.h b/Userland/Libraries/LibRegex/RegexByteCode.h
new file mode 100644
index 0000000000..940ed14a1c
--- /dev/null
+++ b/Userland/Libraries/LibRegex/RegexByteCode.h
@@ -0,0 +1,837 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "RegexMatch.h"
+#include "RegexOptions.h"
+
+#include <AK/Format.h>
+#include <AK/Forward.h>
+#include <AK/HashMap.h>
+#include <AK/NonnullOwnPtr.h>
+#include <AK/OwnPtr.h>
+#include <AK/Traits.h>
+#include <AK/Types.h>
+#include <AK/Vector.h>
+
+namespace regex {
+
+using ByteCodeValueType = u64;
+
+#define ENUMERATE_OPCODES \
+ __ENUMERATE_OPCODE(Compare) \
+ __ENUMERATE_OPCODE(Jump) \
+ __ENUMERATE_OPCODE(ForkJump) \
+ __ENUMERATE_OPCODE(ForkStay) \
+ __ENUMERATE_OPCODE(FailForks) \
+ __ENUMERATE_OPCODE(SaveLeftCaptureGroup) \
+ __ENUMERATE_OPCODE(SaveRightCaptureGroup) \
+ __ENUMERATE_OPCODE(SaveLeftNamedCaptureGroup) \
+ __ENUMERATE_OPCODE(SaveRightNamedCaptureGroup) \
+ __ENUMERATE_OPCODE(CheckBegin) \
+ __ENUMERATE_OPCODE(CheckEnd) \
+ __ENUMERATE_OPCODE(CheckBoundary) \
+ __ENUMERATE_OPCODE(Save) \
+ __ENUMERATE_OPCODE(Restore) \
+ __ENUMERATE_OPCODE(GoBack) \
+ __ENUMERATE_OPCODE(Exit)
+
+// clang-format off
+enum class OpCodeId : ByteCodeValueType {
+#define __ENUMERATE_OPCODE(x) x,
+ ENUMERATE_OPCODES
+#undef __ENUMERATE_OPCODE
+
+ First = Compare,
+ Last = Exit,
+};
+// clang-format on
+
+#define ENUMERATE_CHARACTER_COMPARE_TYPES \
+ __ENUMERATE_CHARACTER_COMPARE_TYPE(Undefined) \
+ __ENUMERATE_CHARACTER_COMPARE_TYPE(Inverse) \
+ __ENUMERATE_CHARACTER_COMPARE_TYPE(TemporaryInverse) \
+ __ENUMERATE_CHARACTER_COMPARE_TYPE(AnyChar) \
+ __ENUMERATE_CHARACTER_COMPARE_TYPE(Char) \
+ __ENUMERATE_CHARACTER_COMPARE_TYPE(String) \
+ __ENUMERATE_CHARACTER_COMPARE_TYPE(CharClass) \
+ __ENUMERATE_CHARACTER_COMPARE_TYPE(CharRange) \
+ __ENUMERATE_CHARACTER_COMPARE_TYPE(Reference) \
+ __ENUMERATE_CHARACTER_COMPARE_TYPE(NamedReference) \
+ __ENUMERATE_CHARACTER_COMPARE_TYPE(RangeExpressionDummy)
+
+enum class CharacterCompareType : ByteCodeValueType {
+#define __ENUMERATE_CHARACTER_COMPARE_TYPE(x) x,
+ ENUMERATE_CHARACTER_COMPARE_TYPES
+#undef __ENUMERATE_CHARACTER_COMPARE_TYPE
+};
+
+#define ENUMERATE_CHARACTER_CLASSES \
+ __ENUMERATE_CHARACTER_CLASS(Alnum) \
+ __ENUMERATE_CHARACTER_CLASS(Cntrl) \
+ __ENUMERATE_CHARACTER_CLASS(Lower) \
+ __ENUMERATE_CHARACTER_CLASS(Space) \
+ __ENUMERATE_CHARACTER_CLASS(Alpha) \
+ __ENUMERATE_CHARACTER_CLASS(Digit) \
+ __ENUMERATE_CHARACTER_CLASS(Print) \
+ __ENUMERATE_CHARACTER_CLASS(Upper) \
+ __ENUMERATE_CHARACTER_CLASS(Blank) \
+ __ENUMERATE_CHARACTER_CLASS(Graph) \
+ __ENUMERATE_CHARACTER_CLASS(Punct) \
+ __ENUMERATE_CHARACTER_CLASS(Word) \
+ __ENUMERATE_CHARACTER_CLASS(Xdigit)
+
+enum class CharClass : ByteCodeValueType {
+#define __ENUMERATE_CHARACTER_CLASS(x) x,
+ ENUMERATE_CHARACTER_CLASSES
+#undef __ENUMERATE_CHARACTER_CLASS
+};
+
+#define ENUMERATE_BOUNDARY_CHECK_TYPES \
+ __ENUMERATE_BOUNDARY_CHECK_TYPE(Word) \
+ __ENUMERATE_BOUNDARY_CHECK_TYPE(NonWord)
+
+enum class BoundaryCheckType : ByteCodeValueType {
+#define __ENUMERATE_BOUNDARY_CHECK_TYPE(x) x,
+ ENUMERATE_BOUNDARY_CHECK_TYPES
+#undef __ENUMERATE_BOUNDARY_CHECK_TYPE
+};
+
+struct CharRange {
+ const u32 from;
+ const u32 to;
+
+ CharRange(u64 value)
+ : from(value >> 32)
+ , to(value & 0xffffffff)
+ {
+ }
+
+ CharRange(u32 from, u32 to)
+ : from(from)
+ , to(to)
+ {
+ }
+
+ operator ByteCodeValueType() const { return ((u64)from << 32) | to; }
+};
+
+struct CompareTypeAndValuePair {
+ CharacterCompareType type;
+ ByteCodeValueType value;
+};
+
+class OpCode;
+
+class ByteCode : public Vector<ByteCodeValueType> {
+public:
+ ByteCode() = default;
+ virtual ~ByteCode() = default;
+
+ void insert_bytecode_compare_values(Vector<CompareTypeAndValuePair>&& pairs)
+ {
+ ByteCode bytecode;
+
+ bytecode.empend(static_cast<ByteCodeValueType>(OpCodeId::Compare));
+ bytecode.empend(pairs.size()); // number of arguments
+
+ ByteCode arguments;
+ for (auto& value : pairs) {
+ ASSERT(value.type != CharacterCompareType::RangeExpressionDummy);
+ ASSERT(value.type != CharacterCompareType::Undefined);
+ ASSERT(value.type != CharacterCompareType::String);
+ ASSERT(value.type != CharacterCompareType::NamedReference);
+
+ arguments.append((ByteCodeValueType)value.type);
+ if (value.type != CharacterCompareType::Inverse && value.type != CharacterCompareType::AnyChar && value.type != CharacterCompareType::TemporaryInverse)
+ arguments.append(move(value.value));
+ }
+
+ bytecode.empend(arguments.size()); // size of arguments
+ bytecode.append(move(arguments));
+
+ append(move(bytecode));
+ }
+
+ void insert_bytecode_check_boundary(BoundaryCheckType type)
+ {
+ ByteCode bytecode;
+ bytecode.empend((ByteCodeValueType)OpCodeId::CheckBoundary);
+ bytecode.empend((ByteCodeValueType)type);
+
+ append(move(bytecode));
+ }
+
+ void insert_bytecode_compare_string(StringView view)
+ {
+ ByteCode bytecode;
+
+ bytecode.empend(static_cast<ByteCodeValueType>(OpCodeId::Compare));
+ bytecode.empend(static_cast<u64>(1)); // number of arguments
+
+ ByteCode arguments;
+
+ arguments.empend(static_cast<ByteCodeValueType>(CharacterCompareType::String));
+ arguments.insert_string(view);
+
+ bytecode.empend(arguments.size()); // size of arguments
+ bytecode.append(move(arguments));
+
+ append(move(bytecode));
+ }
+
+ void insert_bytecode_compare_named_reference(StringView name)
+ {
+ ByteCode bytecode;
+
+ bytecode.empend(static_cast<ByteCodeValueType>(OpCodeId::Compare));
+ bytecode.empend(static_cast<u64>(1)); // number of arguments
+
+ ByteCode arguments;
+
+ arguments.empend(static_cast<ByteCodeValueType>(CharacterCompareType::NamedReference));
+ arguments.empend(reinterpret_cast<ByteCodeValueType>(name.characters_without_null_termination()));
+ arguments.empend(name.length());
+
+ bytecode.empend(arguments.size()); // size of arguments
+ bytecode.append(move(arguments));
+
+ append(move(bytecode));
+ }
+
+ void insert_bytecode_group_capture_left(size_t capture_groups_count)
+ {
+ empend(static_cast<ByteCodeValueType>(OpCodeId::SaveLeftCaptureGroup));
+ empend(capture_groups_count);
+ }
+
+ void insert_bytecode_group_capture_left(const StringView& name)
+ {
+ empend(static_cast<ByteCodeValueType>(OpCodeId::SaveLeftNamedCaptureGroup));
+ empend(reinterpret_cast<ByteCodeValueType>(name.characters_without_null_termination()));
+ empend(name.length());
+ }
+
+ void insert_bytecode_group_capture_right(size_t capture_groups_count)
+ {
+ empend(static_cast<ByteCodeValueType>(OpCodeId::SaveRightCaptureGroup));
+ empend(capture_groups_count);
+ }
+
+ void insert_bytecode_group_capture_right(const StringView& name)
+ {
+ empend(static_cast<ByteCodeValueType>(OpCodeId::SaveRightNamedCaptureGroup));
+ empend(reinterpret_cast<ByteCodeValueType>(name.characters_without_null_termination()));
+ empend(name.length());
+ }
+
+ enum class LookAroundType {
+ LookAhead,
+ LookBehind,
+ NegatedLookAhead,
+ NegatedLookBehind,
+ };
+ void insert_bytecode_lookaround(ByteCode&& lookaround_body, LookAroundType type, size_t match_length = 0)
+ {
+ // FIXME: The save stack will grow infinitely with repeated failures
+ // as we do not discard that on failure (we don't necessarily know how many to pop with the current architecture).
+ switch (type) {
+ case LookAroundType::LookAhead: {
+ // SAVE
+ // REGEXP BODY
+ // RESTORE
+ empend((ByteCodeValueType)OpCodeId::Save);
+ append(move(lookaround_body));
+ empend((ByteCodeValueType)OpCodeId::Restore);
+ return;
+ }
+ case LookAroundType::NegatedLookAhead: {
+ // JUMP _A
+ // LABEL _L
+ // REGEXP BODY
+ // FAIL 2
+ // LABEL _A
+ // SAVE
+ // FORKJUMP _L
+ // RESTORE
+ auto body_length = lookaround_body.size();
+ empend((ByteCodeValueType)OpCodeId::Jump);
+ empend((ByteCodeValueType)body_length + 2); // JUMP to label _A
+ append(move(lookaround_body));
+ empend((ByteCodeValueType)OpCodeId::FailForks);
+ empend((ByteCodeValueType)2); // Fail two forks
+ empend((ByteCodeValueType)OpCodeId::Save);
+ empend((ByteCodeValueType)OpCodeId::ForkJump);
+ empend((ByteCodeValueType) - (body_length + 5)); // JUMP to lavel _L
+ empend((ByteCodeValueType)OpCodeId::Restore);
+ return;
+ }
+ case LookAroundType::LookBehind:
+ // SAVE
+ // GOBACK match_length(BODY)
+ // REGEXP BODY
+ // RESTORE
+ empend((ByteCodeValueType)OpCodeId::Save);
+ empend((ByteCodeValueType)OpCodeId::GoBack);
+ empend((ByteCodeValueType)match_length);
+ append(move(lookaround_body));
+ empend((ByteCodeValueType)OpCodeId::Restore);
+ return;
+ case LookAroundType::NegatedLookBehind: {
+ // JUMP _A
+ // LABEL _L
+ // GOBACK match_length(BODY)
+ // REGEXP BODY
+ // FAIL 2
+ // LABEL _A
+ // SAVE
+ // FORKJUMP _L
+ // RESTORE
+ auto body_length = lookaround_body.size();
+ empend((ByteCodeValueType)OpCodeId::Jump);
+ empend((ByteCodeValueType)body_length + 4); // JUMP to label _A
+ empend((ByteCodeValueType)OpCodeId::GoBack);
+ empend((ByteCodeValueType)match_length);
+ append(move(lookaround_body));
+ empend((ByteCodeValueType)OpCodeId::FailForks);
+ empend((ByteCodeValueType)2); // Fail two forks
+ empend((ByteCodeValueType)OpCodeId::Save);
+ empend((ByteCodeValueType)OpCodeId::ForkJump);
+ empend((ByteCodeValueType) - (body_length + 7)); // JUMP to lavel _L
+ empend((ByteCodeValueType)OpCodeId::Restore);
+ return;
+ }
+ }
+
+ ASSERT_NOT_REACHED();
+ }
+
+ void insert_bytecode_alternation(ByteCode&& left, ByteCode&& right)
+ {
+
+ // FORKJUMP _ALT
+ // REGEXP ALT1
+ // JUMP _END
+ // LABEL _ALT
+ // REGEXP ALT2
+ // LABEL _END
+
+ ByteCode byte_code;
+
+ empend(static_cast<ByteCodeValueType>(OpCodeId::ForkJump));
+ empend(left.size() + 2); // Jump to the _ALT label
+
+ for (auto& op : left)
+ append(move(op));
+
+ empend(static_cast<ByteCodeValueType>(OpCodeId::Jump));
+ empend(right.size()); // Jump to the _END label
+
+ // LABEL _ALT = bytecode.size() + 2
+
+ for (auto& op : right)
+ append(move(op));
+
+ // LABEL _END = alterantive_bytecode.size
+ }
+
+ void insert_bytecode_repetition_min_max(ByteCode& bytecode_to_repeat, size_t minimum, Optional<size_t> maximum)
+ {
+ ByteCode new_bytecode;
+ new_bytecode.insert_bytecode_repetition_n(bytecode_to_repeat, minimum);
+
+ if (maximum.has_value()) {
+ if (maximum.value() > minimum) {
+ auto diff = maximum.value() - minimum;
+ new_bytecode.empend(static_cast<ByteCodeValueType>(OpCodeId::ForkStay));
+ new_bytecode.empend(diff * (bytecode_to_repeat.size() + 2)); // Jump to the _END label
+
+ for (size_t i = 0; i < diff; ++i) {
+ new_bytecode.append(bytecode_to_repeat);
+ new_bytecode.empend(static_cast<ByteCodeValueType>(OpCodeId::ForkStay));
+ new_bytecode.empend((diff - i - 1) * (bytecode_to_repeat.size() + 2)); // Jump to the _END label
+ }
+ }
+ } else {
+ // no maximum value set, repeat finding if possible
+ new_bytecode.empend(static_cast<ByteCodeValueType>(OpCodeId::ForkJump));
+ new_bytecode.empend(-bytecode_to_repeat.size() - 2); // Jump to the last iteration
+ }
+
+ bytecode_to_repeat = move(new_bytecode);
+ }
+
+ void insert_bytecode_repetition_n(ByteCode& bytecode_to_repeat, size_t n)
+ {
+ for (size_t i = 0; i < n; ++i)
+ append(bytecode_to_repeat);
+ }
+
+ void insert_bytecode_repetition_min_one(ByteCode& bytecode_to_repeat, bool greedy)
+ {
+ // LABEL _START = -bytecode_to_repeat.size()
+ // REGEXP
+ // FORKSTAY _START (FORKJUMP -> Greedy)
+
+ if (greedy)
+ bytecode_to_repeat.empend(static_cast<ByteCodeValueType>(OpCodeId::ForkJump));
+ else
+ bytecode_to_repeat.empend(static_cast<ByteCodeValueType>(OpCodeId::ForkStay));
+
+ bytecode_to_repeat.empend(-(bytecode_to_repeat.size() + 1)); // Jump to the _START label
+ }
+
+ void insert_bytecode_repetition_any(ByteCode& bytecode_to_repeat, bool greedy)
+ {
+ // LABEL _START
+ // FORKJUMP _END (FORKSTAY -> Greedy)
+ // REGEXP
+ // JUMP _START
+ // LABEL _END
+
+ // LABEL _START = m_bytes.size();
+ ByteCode bytecode;
+
+ if (greedy)
+ bytecode.empend(static_cast<ByteCodeValueType>(OpCodeId::ForkStay));
+ else
+ bytecode.empend(static_cast<ByteCodeValueType>(OpCodeId::ForkJump));
+
+ bytecode.empend(bytecode_to_repeat.size() + 2); // Jump to the _END label
+
+ for (auto& op : bytecode_to_repeat)
+ bytecode.append(move(op));
+
+ bytecode.empend(static_cast<ByteCodeValueType>(OpCodeId::Jump));
+ bytecode.empend(-bytecode.size() - 1); // Jump to the _START label
+ // LABEL _END = bytecode.size()
+
+ bytecode_to_repeat = move(bytecode);
+ }
+
+ void insert_bytecode_repetition_zero_or_one(ByteCode& bytecode_to_repeat, bool greedy)
+ {
+ // FORKJUMP _END (FORKSTAY -> Greedy)
+ // REGEXP
+ // LABEL _END
+ ByteCode bytecode;
+
+ if (greedy)
+ bytecode.empend(static_cast<ByteCodeValueType>(OpCodeId::ForkStay));
+ else
+ bytecode.empend(static_cast<ByteCodeValueType>(OpCodeId::ForkJump));
+
+ bytecode.empend(bytecode_to_repeat.size()); // Jump to the _END label
+
+ for (auto& op : bytecode_to_repeat)
+ bytecode.append(move(op));
+ // LABEL _END = bytecode.size()
+
+ bytecode_to_repeat = move(bytecode);
+ }
+
+ OpCode* get_opcode(MatchState& state) const;
+
+private:
+ void insert_string(const StringView& view)
+ {
+ empend((ByteCodeValueType)view.length());
+ for (size_t i = 0; i < view.length(); ++i)
+ empend((ByteCodeValueType)view[i]);
+ }
+
+ ALWAYS_INLINE OpCode* get_opcode_by_id(OpCodeId id) const;
+ static HashMap<u32, OwnPtr<OpCode>> s_opcodes;
+};
+
+#define ENUMERATE_EXECUTION_RESULTS \
+ __ENUMERATE_EXECUTION_RESULT(Continue) \
+ __ENUMERATE_EXECUTION_RESULT(Fork_PrioHigh) \
+ __ENUMERATE_EXECUTION_RESULT(Fork_PrioLow) \
+ __ENUMERATE_EXECUTION_RESULT(Failed) \
+ __ENUMERATE_EXECUTION_RESULT(Failed_ExecuteLowPrioForks) \
+ __ENUMERATE_EXECUTION_RESULT(Succeeded)
+
+enum class ExecutionResult : u8 {
+#define __ENUMERATE_EXECUTION_RESULT(x) x,
+ ENUMERATE_EXECUTION_RESULTS
+#undef __ENUMERATE_EXECUTION_RESULT
+};
+
+const char* execution_result_name(ExecutionResult result);
+const char* opcode_id_name(OpCodeId opcode_id);
+const char* boundary_check_type_name(BoundaryCheckType);
+const char* character_compare_type_name(CharacterCompareType result);
+const char* execution_result_name(ExecutionResult result);
+
+class OpCode {
+public:
+ OpCode(ByteCode& bytecode)
+ : m_bytecode(&bytecode)
+ {
+ }
+
+ virtual ~OpCode() = default;
+
+ virtual OpCodeId opcode_id() const = 0;
+ virtual size_t size() const = 0;
+ virtual ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const = 0;
+
+ ALWAYS_INLINE ByteCodeValueType argument(size_t offset) const
+ {
+ ASSERT(state().instruction_position + offset <= m_bytecode->size());
+ return m_bytecode->at(state().instruction_position + 1 + offset);
+ }
+
+ ALWAYS_INLINE const char* name() const;
+ static const char* name(const OpCodeId);
+
+ ALWAYS_INLINE OpCode* set_state(MatchState& state)
+ {
+ m_state = &state;
+ return this;
+ }
+
+ ALWAYS_INLINE OpCode* set_bytecode(ByteCode& bytecode)
+ {
+ m_bytecode = &bytecode;
+ return this;
+ }
+
+ ALWAYS_INLINE void reset_state() { m_state.clear(); }
+
+ ALWAYS_INLINE const MatchState& state() const
+ {
+ ASSERT(m_state.has_value());
+ return *m_state.value();
+ }
+
+ const String to_string() const
+ {
+ return String::format("[0x%02X] %s", (int)opcode_id(), name(opcode_id()));
+ }
+
+ virtual const String arguments_string() const = 0;
+
+ ALWAYS_INLINE const ByteCode& bytecode() const { return *m_bytecode; }
+
+protected:
+ ByteCode* m_bytecode;
+ Optional<MatchState*> m_state;
+};
+
+class OpCode_Exit final : public OpCode {
+public:
+ OpCode_Exit(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::Exit; }
+ ALWAYS_INLINE size_t size() const override { return 1; }
+ const String arguments_string() const override { return ""; }
+};
+
+class OpCode_FailForks final : public OpCode {
+public:
+ OpCode_FailForks(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::FailForks; }
+ ALWAYS_INLINE size_t size() const override { return 2; }
+ ALWAYS_INLINE size_t count() const { return argument(0); }
+ const String arguments_string() const override { return String::formatted("count={}", count()); }
+};
+
+class OpCode_Save final : public OpCode {
+public:
+ OpCode_Save(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::Save; }
+ ALWAYS_INLINE size_t size() const override { return 1; }
+ const String arguments_string() const override { return ""; }
+};
+
+class OpCode_Restore final : public OpCode {
+public:
+ OpCode_Restore(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::Restore; }
+ ALWAYS_INLINE size_t size() const override { return 1; }
+ const String arguments_string() const override { return ""; }
+};
+
+class OpCode_GoBack final : public OpCode {
+public:
+ OpCode_GoBack(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::GoBack; }
+ ALWAYS_INLINE size_t size() const override { return 2; }
+ ALWAYS_INLINE size_t count() const { return argument(0); }
+ const String arguments_string() const override { return String::formatted("count={}", count()); }
+};
+
+class OpCode_Jump final : public OpCode {
+public:
+ OpCode_Jump(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::Jump; }
+ ALWAYS_INLINE size_t size() const override { return 2; }
+ ALWAYS_INLINE ssize_t offset() const { return argument(0); }
+ const String arguments_string() const override
+ {
+ return String::format("offset=%zd [&%zu]", offset(), state().instruction_position + size() + offset());
+ }
+};
+
+class OpCode_ForkJump final : public OpCode {
+public:
+ OpCode_ForkJump(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::ForkJump; }
+ ALWAYS_INLINE size_t size() const override { return 2; }
+ ALWAYS_INLINE ssize_t offset() const { return argument(0); }
+ const String arguments_string() const override
+ {
+ return String::format("offset=%zd [&%zu], sp: %zu", offset(), state().instruction_position + size() + offset(), state().string_position);
+ }
+};
+
+class OpCode_ForkStay final : public OpCode {
+public:
+ OpCode_ForkStay(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::ForkStay; }
+ ALWAYS_INLINE size_t size() const override { return 2; }
+ ALWAYS_INLINE ssize_t offset() const { return argument(0); }
+ const String arguments_string() const override
+ {
+ return String::format("offset=%zd [&%zu], sp: %zu", offset(), state().instruction_position + size() + offset(), state().string_position);
+ }
+};
+
+class OpCode_CheckBegin final : public OpCode {
+public:
+ OpCode_CheckBegin(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::CheckBegin; }
+ ALWAYS_INLINE size_t size() const override { return 1; }
+ const String arguments_string() const override { return ""; }
+};
+
+class OpCode_CheckEnd final : public OpCode {
+public:
+ OpCode_CheckEnd(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::CheckEnd; }
+ ALWAYS_INLINE size_t size() const override { return 1; }
+ const String arguments_string() const override { return ""; }
+};
+
+class OpCode_CheckBoundary final : public OpCode {
+public:
+ OpCode_CheckBoundary(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::CheckBoundary; }
+ ALWAYS_INLINE size_t size() const override { return 2; }
+ ALWAYS_INLINE size_t arguments_count() const { return 1; }
+ ALWAYS_INLINE BoundaryCheckType type() const { return static_cast<BoundaryCheckType>(argument(0)); }
+ const String arguments_string() const override { return String::format("kind=%lu (%s)", (long unsigned int)argument(0), boundary_check_type_name(type())); }
+};
+
+class OpCode_SaveLeftCaptureGroup final : public OpCode {
+public:
+ OpCode_SaveLeftCaptureGroup(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::SaveLeftCaptureGroup; }
+ ALWAYS_INLINE size_t size() const override { return 2; }
+ ALWAYS_INLINE size_t id() const { return argument(0); }
+ const String arguments_string() const override { return String::format("id=%lu", id()); }
+};
+
+class OpCode_SaveRightCaptureGroup final : public OpCode {
+public:
+ OpCode_SaveRightCaptureGroup(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::SaveRightCaptureGroup; }
+ ALWAYS_INLINE size_t size() const override { return 2; }
+ ALWAYS_INLINE size_t id() const { return argument(0); }
+ const String arguments_string() const override { return String::format("id=%lu", id()); }
+};
+
+class OpCode_SaveLeftNamedCaptureGroup final : public OpCode {
+public:
+ OpCode_SaveLeftNamedCaptureGroup(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::SaveLeftNamedCaptureGroup; }
+ ALWAYS_INLINE size_t size() const override { return 3; }
+ ALWAYS_INLINE StringView name() const { return { reinterpret_cast<char*>(argument(0)), length() }; }
+ ALWAYS_INLINE size_t length() const { return argument(1); }
+ const String arguments_string() const override
+ {
+ return String::format("name=%s, length=%lu", name().to_string().characters(), length());
+ }
+};
+
+class OpCode_SaveRightNamedCaptureGroup final : public OpCode {
+public:
+ OpCode_SaveRightNamedCaptureGroup(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::SaveRightNamedCaptureGroup; }
+ ALWAYS_INLINE size_t size() const override { return 3; }
+ ALWAYS_INLINE StringView name() const { return { reinterpret_cast<char*>(argument(0)), length() }; }
+ ALWAYS_INLINE size_t length() const { return argument(1); }
+ const String arguments_string() const override
+ {
+ return String::format("name=%s, length=%zu", name().to_string().characters(), length());
+ }
+};
+
+class OpCode_Compare final : public OpCode {
+public:
+ OpCode_Compare(ByteCode& bytecode)
+ : OpCode(bytecode)
+ {
+ }
+ ExecutionResult execute(const MatchInput& input, MatchState& state, MatchOutput& output) const override;
+ ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::Compare; }
+ ALWAYS_INLINE size_t size() const override { return arguments_size() + 3; }
+ ALWAYS_INLINE size_t arguments_count() const { return argument(0); }
+ ALWAYS_INLINE size_t arguments_size() const { return argument(1); }
+ const String arguments_string() const override;
+ const Vector<String> variable_arguments_to_string(Optional<MatchInput> input = {}) const;
+
+private:
+ ALWAYS_INLINE static void compare_char(const MatchInput& input, MatchState& state, u32 ch1, bool inverse, bool& inverse_matched);
+ ALWAYS_INLINE static bool compare_string(const MatchInput& input, MatchState& state, const char* str, size_t length);
+ ALWAYS_INLINE static void compare_character_class(const MatchInput& input, MatchState& state, CharClass character_class, u32 ch, bool inverse, bool& inverse_matched);
+ ALWAYS_INLINE static void compare_character_range(const MatchInput& input, MatchState& state, u32 from, u32 to, u32 ch, bool inverse, bool& inverse_matched);
+};
+
+template<typename T>
+bool is(const OpCode&);
+
+template<typename T>
+ALWAYS_INLINE bool is(const OpCode&)
+{
+ return false;
+}
+
+template<typename T>
+ALWAYS_INLINE bool is(const OpCode* opcode)
+{
+ return is<T>(*opcode);
+}
+
+template<>
+ALWAYS_INLINE bool is<OpCode_ForkStay>(const OpCode& opcode)
+{
+ return opcode.opcode_id() == OpCodeId::ForkStay;
+}
+
+template<>
+ALWAYS_INLINE bool is<OpCode_Exit>(const OpCode& opcode)
+{
+ return opcode.opcode_id() == OpCodeId::Exit;
+}
+
+template<>
+ALWAYS_INLINE bool is<OpCode_Compare>(const OpCode& opcode)
+{
+ return opcode.opcode_id() == OpCodeId::Compare;
+}
+
+template<typename T>
+ALWAYS_INLINE const T& to(const OpCode& opcode)
+{
+ ASSERT(is<T>(opcode));
+ return static_cast<const T&>(opcode);
+}
+
+template<typename T>
+ALWAYS_INLINE T* to(OpCode* opcode)
+{
+ ASSERT(is<T>(opcode));
+ return static_cast<T*>(opcode);
+}
+
+template<typename T>
+ALWAYS_INLINE const T* to(const OpCode* opcode)
+{
+ ASSERT(is<T>(opcode));
+ return static_cast<const T*>(opcode);
+}
+
+template<typename T>
+ALWAYS_INLINE T& to(OpCode& opcode)
+{
+ ASSERT(is<T>(opcode));
+ return static_cast<T&>(opcode);
+}
+
+}
diff --git a/Userland/Libraries/LibRegex/RegexDebug.h b/Userland/Libraries/LibRegex/RegexDebug.h
new file mode 100644
index 0000000000..c90c587d29
--- /dev/null
+++ b/Userland/Libraries/LibRegex/RegexDebug.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "AK/StringBuilder.h"
+#include "LibRegex/RegexMatcher.h"
+
+//#define REGEX_DEBUG
+
+#ifdef REGEX_DEBUG
+
+namespace regex {
+
+class RegexDebug {
+public:
+ RegexDebug(FILE* file = stdout)
+ : m_file(file)
+ {
+ }
+
+ virtual ~RegexDebug() = default;
+
+ template<typename T>
+ void print_raw_bytecode(Regex<T>& regex) const
+ {
+ auto& bytecode = regex.parser_result.bytecode;
+ size_t index { 0 };
+ for (auto& value : bytecode) {
+ fprintf(m_file, "OpCode i=%3lu [0x%02X]\n", index, (u32)value);
+ ++index;
+ }
+ }
+
+ template<typename T>
+ void print_bytecode(Regex<T>& regex) const
+ {
+ MatchState state;
+ auto& bytecode = regex.parser_result.bytecode;
+
+ for (;;) {
+ auto* opcode = bytecode.get_opcode(state);
+ if (!opcode) {
+ dbgln("Wrong opcode... failed!");
+ return;
+ }
+
+ print_opcode("PrintBytecode", *opcode, state);
+ fprintf(m_file, "%s", m_debug_stripline.characters());
+
+ if (is<OpCode_Exit>(*opcode))
+ break;
+
+ state.instruction_position += opcode->size();
+ }
+
+ fflush(m_file);
+ }
+
+ void print_opcode(const String& system, OpCode& opcode, MatchState& state, size_t recursion = 0, bool newline = true) const
+ {
+ fprintf(m_file, "%-15s | %-5lu | %-9lu | %-35s | %-30s | %-20s%s",
+ system.characters(),
+ state.instruction_position,
+ recursion,
+ opcode.to_string().characters(),
+ opcode.arguments_string().characters(),
+ String::format("ip: %3lu, sp: %3lu", state.instruction_position, state.string_position).characters(),
+ newline ? "\n" : "");
+
+ if (newline && is<OpCode_Compare>(opcode)) {
+ for (auto& line : to<OpCode_Compare>(opcode).variable_arguments_to_string()) {
+ fprintf(m_file, "%-15s | %-5s | %-9s | %-35s | %-30s | %-20s%s", "", "", "", "", line.characters(), "", "\n");
+ }
+ }
+ }
+
+ void print_result(const OpCode& opcode, const ByteCode& bytecode, const MatchInput& input, MatchState& state, ExecutionResult result) const
+ {
+ StringBuilder builder;
+ builder.append(execution_result_name(result));
+ builder.appendff(", fc: {}, ss: {}", input.fail_counter, input.saved_positions.size());
+ if (result == ExecutionResult::Succeeded) {
+ builder.appendf(", ip: %lu/%lu, sp: %lu/%lu", state.instruction_position, bytecode.size() - 1, state.string_position, input.view.length() - 1);
+ } else if (result == ExecutionResult::Fork_PrioHigh) {
+ builder.appendf(", next ip: %lu", state.fork_at_position + opcode.size());
+ } else if (result != ExecutionResult::Failed) {
+ builder.appendf(", next ip: %lu", state.instruction_position + opcode.size());
+ }
+
+ fprintf(m_file, " | %-20s\n", builder.to_string().characters());
+
+ if (is<OpCode_Compare>(opcode)) {
+ for (auto& line : to<OpCode_Compare>(opcode).variable_arguments_to_string(input)) {
+ fprintf(m_file, "%-15s | %-5s | %-9s | %-35s | %-30s | %-20s%s", "", "", "", "", line.characters(), "", "\n");
+ }
+ }
+
+ fprintf(m_file, "%s", m_debug_stripline.characters());
+ }
+
+ void print_header()
+ {
+ StringBuilder builder;
+ builder.appendf("%-15s | %-5s | %-9s | %-35s | %-30s | %-20s | %-20s\n", "System", "Index", "Recursion", "OpCode", "Arguments", "State", "Result");
+ auto length = builder.length();
+ for (size_t i = 0; i < length; ++i) {
+ builder.append('=');
+ }
+
+ fprintf(m_file, "%s\n", builder.to_string().characters());
+ fflush(m_file);
+
+ builder.clear();
+ for (size_t i = 0; i < length; ++i) {
+ builder.append('-');
+ }
+ builder.append('\n');
+ m_debug_stripline = builder.to_string();
+ }
+
+private:
+ String m_debug_stripline;
+ FILE* m_file;
+};
+
+}
+
+using regex::RegexDebug;
+
+#endif
diff --git a/Userland/Libraries/LibRegex/RegexError.h b/Userland/Libraries/LibRegex/RegexError.h
new file mode 100644
index 0000000000..90d6a71eca
--- /dev/null
+++ b/Userland/Libraries/LibRegex/RegexError.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/Types.h>
+#ifdef __serenity__
+# include <regex.h>
+#else
+# include <LibC/regex.h>
+#endif
+
+namespace regex {
+
+enum class Error : u8 {
+ NoError = __Regex_NoError,
+ InvalidPattern = __Regex_InvalidPattern, // Invalid regular expression.
+ InvalidCollationElement = __Regex_InvalidCollationElement, // Invalid collating element referenced.
+ InvalidCharacterClass = __Regex_InvalidCharacterClass, // Invalid character class type referenced.
+ InvalidTrailingEscape = __Regex_InvalidTrailingEscape, // Trailing \ in pattern.
+ InvalidNumber = __Regex_InvalidNumber, // Number in \digit invalid or in error.
+ MismatchingBracket = __Regex_MismatchingBracket, // [ ] imbalance.
+ MismatchingParen = __Regex_MismatchingParen, // ( ) imbalance.
+ MismatchingBrace = __Regex_MismatchingBrace, // { } imbalance.
+ InvalidBraceContent = __Regex_InvalidBraceContent, // Content of {} invalid: not a number, number too large, more than two numbers, first larger than second.
+ InvalidBracketContent = __Regex_InvalidBracketContent, // Content of [] invalid.
+ InvalidRange = __Regex_InvalidRange, // Invalid endpoint in range expression.
+ InvalidRepetitionMarker = __Regex_InvalidRepetitionMarker, // ?, * or + not preceded by valid regular expression.
+ ReachedMaxRecursion = __Regex_ReachedMaxRecursion, // MaximumRecursion has been reached.
+ EmptySubExpression = __Regex_EmptySubExpression, // Sub expression has empty content.
+ InvalidCaptureGroup = __Regex_InvalidCaptureGroup, // Content of capture group is invalid.
+ InvalidNameForCaptureGroup = __Regex_InvalidNameForCaptureGroup, // Name of capture group is invalid.
+};
+
+inline String get_error_string(Error error)
+{
+ switch (error) {
+ case Error::NoError:
+ return "No error";
+ case Error::InvalidPattern:
+ return "Invalid regular expression.";
+ case Error::InvalidCollationElement:
+ return "Invalid collating element referenced.";
+ case Error::InvalidCharacterClass:
+ return "Invalid character class type referenced.";
+ case Error::InvalidTrailingEscape:
+ return "Trailing \\ in pattern.";
+ case Error::InvalidNumber:
+ return "Number in \\digit invalid or in error.";
+ case Error::MismatchingBracket:
+ return "[ ] imbalance.";
+ case Error::MismatchingParen:
+ return "( ) imbalance.";
+ case Error::MismatchingBrace:
+ return "{ } imbalance.";
+ case Error::InvalidBraceContent:
+ return "Content of {} invalid: not a number, number too large, more than two numbers, first larger than second.";
+ case Error::InvalidBracketContent:
+ return "Content of [] invalid.";
+ case Error::InvalidRange:
+ return "Invalid endpoint in range expression.";
+ case Error::InvalidRepetitionMarker:
+ return "?, * or + not preceded by valid regular expression.";
+ case Error::ReachedMaxRecursion:
+ return "Maximum recursion has been reached.";
+ case Error::EmptySubExpression:
+ return "Sub expression has empty content.";
+ case Error::InvalidCaptureGroup:
+ return "Content of capture group is invalid.";
+ case Error::InvalidNameForCaptureGroup:
+ return "Name of capture group is invalid.";
+ }
+ return "Undefined error.";
+}
+}
+
+using regex::Error;
+using regex::get_error_string;
diff --git a/Userland/Libraries/LibRegex/RegexLexer.cpp b/Userland/Libraries/LibRegex/RegexLexer.cpp
new file mode 100644
index 0000000000..c7e154e307
--- /dev/null
+++ b/Userland/Libraries/LibRegex/RegexLexer.cpp
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "RegexLexer.h"
+#include <AK/Assertions.h>
+#include <AK/LogStream.h>
+#include <stdio.h>
+
+namespace regex {
+
+const char* Token::name(const TokenType type)
+{
+ switch (type) {
+#define __ENUMERATE_REGEX_TOKEN(x) \
+ case TokenType::x: \
+ return #x;
+ ENUMERATE_REGEX_TOKENS
+#undef __ENUMERATE_REGEX_TOKEN
+ default:
+ ASSERT_NOT_REACHED();
+ return "<Unknown>";
+ }
+}
+
+const char* Token::name() const
+{
+ return name(m_type);
+}
+
+Lexer::Lexer(const StringView source)
+ : m_source(source)
+{
+}
+
+ALWAYS_INLINE char Lexer::peek(size_t offset) const
+{
+ if ((m_position + offset) >= m_source.length())
+ return EOF;
+ return m_source[m_position + offset];
+}
+
+void Lexer::back(size_t offset)
+{
+ if (offset == m_position + 1)
+ offset = m_position; // 'position == 0' occurs twice.
+
+ ASSERT(offset <= m_position);
+ if (!offset)
+ return;
+ m_position -= offset;
+ m_previous_position = (m_position > 0) ? m_position - 1 : 0;
+ m_current_char = m_source[m_position];
+}
+
+ALWAYS_INLINE void Lexer::consume()
+{
+ m_previous_position = m_position;
+
+ if (m_position >= m_source.length()) {
+ m_position = m_source.length() + 1;
+ m_current_char = EOF;
+ return;
+ }
+
+ m_current_char = m_source[m_position++];
+}
+
+void Lexer::reset()
+{
+ m_position = 0;
+ m_current_token = { TokenType::Eof, 0, StringView(nullptr) };
+ m_current_char = 0;
+ m_previous_position = 0;
+}
+
+bool Lexer::try_skip(char c)
+{
+ if (peek() != c)
+ return false;
+
+ consume();
+ return true;
+}
+
+char Lexer::skip()
+{
+ auto c = peek();
+ consume();
+ return c;
+}
+
+Token Lexer::next()
+{
+ size_t token_start_position;
+
+ auto begin_token = [&] {
+ token_start_position = m_position;
+ };
+
+ auto commit_token = [&](auto type) -> Token& {
+ ASSERT(token_start_position + m_previous_position - token_start_position + 1 <= m_source.length());
+ auto substring = m_source.substring_view(token_start_position, m_previous_position - token_start_position + 1);
+ m_current_token = Token(type, token_start_position, substring);
+ return m_current_token;
+ };
+
+ auto emit_token = [&](auto type) -> Token& {
+ m_current_token = Token(type, m_position, m_source.substring_view(m_position, 1));
+ consume();
+ return m_current_token;
+ };
+
+ auto match_escape_sequence = [&]() -> size_t {
+ switch (peek(1)) {
+ case '^':
+ case '.':
+ case '[':
+ case ']':
+ case '$':
+ case '(':
+ case ')':
+ case '|':
+ case '*':
+ case '+':
+ case '?':
+ case '{':
+ case '\\':
+ return 2;
+ default:
+#ifdef REGEX_DEBUG
+ fprintf(stderr, "[LEXER] Found invalid escape sequence: \\%c (the parser will have to deal with this!)\n", peek(1));
+#endif
+ return 0;
+ }
+ };
+
+ while (m_position <= m_source.length()) {
+ auto ch = peek();
+ if (ch == '(')
+ return emit_token(TokenType::LeftParen);
+
+ if (ch == ')')
+ return emit_token(TokenType::RightParen);
+
+ if (ch == '{')
+ return emit_token(TokenType::LeftCurly);
+
+ if (ch == '}')
+ return emit_token(TokenType::RightCurly);
+
+ if (ch == '[')
+ return emit_token(TokenType::LeftBracket);
+
+ if (ch == ']')
+ return emit_token(TokenType::RightBracket);
+
+ if (ch == '.')
+ return emit_token(TokenType::Period);
+
+ if (ch == '*')
+ return emit_token(TokenType::Asterisk);
+
+ if (ch == '+')
+ return emit_token(TokenType::Plus);
+
+ if (ch == '$')
+ return emit_token(TokenType::Dollar);
+
+ if (ch == '^')
+ return emit_token(TokenType::Circumflex);
+
+ if (ch == '|')
+ return emit_token(TokenType::Pipe);
+
+ if (ch == '?')
+ return emit_token(TokenType::Questionmark);
+
+ if (ch == ',')
+ return emit_token(TokenType::Comma);
+
+ if (ch == '/')
+ return emit_token(TokenType::Slash);
+
+ if (ch == '=')
+ return emit_token(TokenType::EqualSign);
+
+ if (ch == ':')
+ return emit_token(TokenType::Colon);
+
+ if (ch == '-')
+ return emit_token(TokenType::HyphenMinus);
+
+ if (ch == '\\') {
+ size_t escape = match_escape_sequence();
+ if (escape > 0) {
+ begin_token();
+ for (size_t i = 0; i < escape; ++i)
+ consume();
+ return commit_token(TokenType::EscapeSequence);
+ }
+ }
+
+ if (ch == EOF)
+ break;
+
+ return emit_token(TokenType::Char);
+ }
+
+ return Token(TokenType::Eof, m_position, nullptr);
+}
+
+}
diff --git a/Userland/Libraries/LibRegex/RegexLexer.h b/Userland/Libraries/LibRegex/RegexLexer.h
new file mode 100644
index 0000000000..959fa50c33
--- /dev/null
+++ b/Userland/Libraries/LibRegex/RegexLexer.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/StringView.h>
+
+namespace regex {
+
+#define ENUMERATE_REGEX_TOKENS \
+ __ENUMERATE_REGEX_TOKEN(Eof) \
+ __ENUMERATE_REGEX_TOKEN(Char) \
+ __ENUMERATE_REGEX_TOKEN(Circumflex) \
+ __ENUMERATE_REGEX_TOKEN(Period) \
+ __ENUMERATE_REGEX_TOKEN(LeftParen) \
+ __ENUMERATE_REGEX_TOKEN(RightParen) \
+ __ENUMERATE_REGEX_TOKEN(LeftCurly) \
+ __ENUMERATE_REGEX_TOKEN(RightCurly) \
+ __ENUMERATE_REGEX_TOKEN(LeftBracket) \
+ __ENUMERATE_REGEX_TOKEN(RightBracket) \
+ __ENUMERATE_REGEX_TOKEN(Asterisk) \
+ __ENUMERATE_REGEX_TOKEN(EscapeSequence) \
+ __ENUMERATE_REGEX_TOKEN(Dollar) \
+ __ENUMERATE_REGEX_TOKEN(Pipe) \
+ __ENUMERATE_REGEX_TOKEN(Plus) \
+ __ENUMERATE_REGEX_TOKEN(Comma) \
+ __ENUMERATE_REGEX_TOKEN(Slash) \
+ __ENUMERATE_REGEX_TOKEN(EqualSign) \
+ __ENUMERATE_REGEX_TOKEN(HyphenMinus) \
+ __ENUMERATE_REGEX_TOKEN(Colon) \
+ __ENUMERATE_REGEX_TOKEN(Questionmark)
+
+enum class TokenType {
+#define __ENUMERATE_REGEX_TOKEN(x) x,
+ ENUMERATE_REGEX_TOKENS
+#undef __ENUMERATE_REGEX_TOKEN
+};
+
+class Token {
+public:
+ Token() = default;
+ Token(const TokenType type, const size_t start_position, const StringView value)
+ : m_type(type)
+ , m_position(start_position)
+ , m_value(value)
+ {
+ }
+
+ TokenType type() const { return m_type; }
+ const StringView& value() const { return m_value; }
+ size_t position() const { return m_position; }
+
+ const char* name() const;
+ static const char* name(const TokenType);
+
+private:
+ TokenType m_type { TokenType::Eof };
+ size_t m_position { 0 };
+ StringView m_value { nullptr };
+};
+
+class Lexer {
+public:
+ Lexer() = default;
+ explicit Lexer(const StringView source);
+ Token next();
+ void reset();
+ void back(size_t offset);
+ void set_source(const StringView source) { m_source = source; }
+ bool try_skip(char);
+ char skip();
+
+private:
+ ALWAYS_INLINE char peek(size_t offset = 0) const;
+ ALWAYS_INLINE void consume();
+
+ StringView m_source {};
+ size_t m_position { 0 };
+ size_t m_previous_position { 0 };
+ Token m_current_token { TokenType::Eof, 0, StringView(nullptr) };
+ int m_current_char { 0 };
+};
+
+}
+
+using regex::Lexer;
diff --git a/Userland/Libraries/LibRegex/RegexMatch.h b/Userland/Libraries/LibRegex/RegexMatch.h
new file mode 100644
index 0000000000..26f31d7eeb
--- /dev/null
+++ b/Userland/Libraries/LibRegex/RegexMatch.h
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "RegexOptions.h"
+
+#include "AK/FlyString.h"
+#include "AK/HashMap.h"
+#include "AK/String.h"
+#include "AK/StringBuilder.h"
+#include "AK/StringView.h"
+#include "AK/Utf32View.h"
+#include "AK/Vector.h"
+
+namespace regex {
+
+class RegexStringView {
+public:
+ RegexStringView(const char* chars)
+ : m_u8view(chars)
+ {
+ }
+
+ RegexStringView(const String& string)
+ : m_u8view(string)
+ {
+ }
+
+ RegexStringView(const StringView view)
+ : m_u8view(view)
+ {
+ }
+ RegexStringView(const Utf32View view)
+ : m_u32view(view)
+ {
+ }
+
+ bool is_u8_view() const { return m_u8view.has_value(); }
+ bool is_u32_view() const { return m_u32view.has_value(); }
+
+ const StringView& u8view() const
+ {
+ ASSERT(m_u8view.has_value());
+ return m_u8view.value();
+ };
+
+ const Utf32View& u32view() const
+ {
+ ASSERT(m_u32view.has_value());
+ return m_u32view.value();
+ };
+
+ bool is_empty() const
+ {
+ if (is_u8_view())
+ return m_u8view.value().is_empty();
+ else
+ return m_u32view.value().is_empty();
+ }
+
+ bool is_null() const
+ {
+ if (is_u8_view())
+ return m_u8view.value().is_null();
+ else
+ return m_u32view.value().code_points() == nullptr;
+ }
+
+ size_t length() const
+ {
+ if (is_u8_view())
+ return m_u8view.value().length();
+ else
+ return m_u32view.value().length();
+ }
+
+ Vector<RegexStringView> lines() const
+ {
+ if (is_u8_view()) {
+ auto views = u8view().lines(false);
+ Vector<RegexStringView> new_views;
+ for (auto& view : views)
+ new_views.append(move(view));
+ return new_views;
+ }
+
+ // FIXME: line splitting for Utf32View needed
+ Vector<RegexStringView> views;
+ views.append(m_u32view.value());
+ return views;
+ }
+
+ RegexStringView substring_view(size_t offset, size_t length) const
+ {
+ if (is_u8_view()) {
+ return u8view().substring_view(offset, length);
+ }
+ return u32view().substring_view(offset, length);
+ }
+
+ String to_string() const
+ {
+ if (is_u8_view()) {
+ return u8view().to_string();
+ }
+
+ StringBuilder builder;
+ builder.append(u32view());
+ return builder.to_string();
+ }
+
+ u32 operator[](size_t index) const
+ {
+ if (is_u8_view()) {
+ return u8view()[index];
+ }
+ return u32view().code_points()[index];
+ }
+
+ bool operator==(const char* cstring) const
+ {
+ if (is_u8_view())
+ return u8view() == cstring;
+
+ return to_string() == cstring;
+ }
+
+ bool operator!=(const char* cstring) const
+ {
+ return !(*this == cstring);
+ }
+
+ bool operator==(const String& string) const
+ {
+ if (is_u8_view())
+ return u8view() == string;
+
+ return to_string() == string;
+ }
+
+ bool operator==(const StringView& other) const
+ {
+ if (is_u8_view())
+ return u8view() == other;
+
+ return false;
+ }
+
+ bool operator!=(const StringView& other) const
+ {
+ return !(*this == other);
+ }
+
+ bool operator==(const Utf32View& other) const
+ {
+ if (is_u32_view()) {
+ StringBuilder builder;
+ builder.append(other);
+ return to_string() == builder.to_string();
+ }
+
+ return false;
+ }
+
+ bool operator!=(const Utf32View& other) const
+ {
+ return !(*this == other);
+ }
+
+ const char* characters_without_null_termination() const
+ {
+ if (is_u8_view())
+ return u8view().characters_without_null_termination();
+
+ return to_string().characters(); // FIXME: it contains the null termination, does that actually matter?
+ }
+
+ bool starts_with(const StringView& str) const
+ {
+ if (is_u32_view())
+ return false;
+ return u8view().starts_with(str);
+ }
+
+ bool starts_with(const Utf32View& str) const
+ {
+ if (is_u8_view())
+ return false;
+
+ StringBuilder builder;
+ builder.append(str);
+ return to_string().starts_with(builder.to_string());
+ }
+
+private:
+ Optional<StringView> m_u8view;
+ Optional<Utf32View> m_u32view;
+};
+
+class Match final {
+private:
+ Optional<FlyString> string;
+
+public:
+ Match() = default;
+ ~Match() = default;
+
+ Match(const RegexStringView view_, const size_t line_, const size_t column_, const size_t global_offset_)
+ : view(view_)
+ , line(line_)
+ , column(column_)
+ , global_offset(global_offset_)
+ , left_column(column_)
+ {
+ }
+
+ Match(const String string_, const size_t line_, const size_t column_, const size_t global_offset_)
+ : string(string_)
+ , view(string.value().view())
+ , line(line_)
+ , column(column_)
+ , global_offset(global_offset_)
+ , left_column(column_)
+ {
+ }
+
+ RegexStringView view { nullptr };
+ size_t line { 0 };
+ size_t column { 0 };
+ size_t global_offset { 0 };
+
+ // ugly, as not usable by user, but needed to prevent to create extra vectors that are
+ // able to store the column when the left paren has been found
+ size_t left_column { 0 };
+};
+
+struct MatchInput {
+ RegexStringView view { nullptr };
+ AllOptions regex_options {};
+ size_t start_offset { 0 }; // For Stateful matches, saved and restored from Regex::start_offset.
+
+ size_t match_index { 0 };
+ size_t line { 0 };
+ size_t column { 0 };
+
+ size_t global_offset { 0 }; // For multiline matching, knowing the offset from start could be important
+
+ mutable size_t fail_counter { 0 };
+ mutable Vector<size_t> saved_positions;
+};
+
+struct MatchState {
+ size_t string_position { 0 };
+ size_t instruction_position { 0 };
+ size_t fork_at_position { 0 };
+};
+
+struct MatchOutput {
+ size_t operations;
+ Vector<Match> matches;
+ Vector<Vector<Match>> capture_group_matches;
+ Vector<HashMap<String, Match>> named_capture_group_matches;
+};
+
+}
+
+using regex::RegexStringView;
diff --git a/Userland/Libraries/LibRegex/RegexMatcher.cpp b/Userland/Libraries/LibRegex/RegexMatcher.cpp
new file mode 100644
index 0000000000..fd2f7dab75
--- /dev/null
+++ b/Userland/Libraries/LibRegex/RegexMatcher.cpp
@@ -0,0 +1,396 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "RegexMatcher.h"
+#include "RegexDebug.h"
+#include "RegexParser.h"
+#include <AK/ScopedValueRollback.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+
+namespace regex {
+
+#ifdef REGEX_DEBUG
+static RegexDebug s_regex_dbg(stderr);
+#endif
+
+template<class Parser>
+Regex<Parser>::Regex(StringView pattern, typename ParserTraits<Parser>::OptionsType regex_options)
+{
+ pattern_value = pattern.to_string();
+ regex::Lexer lexer(pattern);
+
+ Parser parser(lexer, regex_options);
+ parser_result = parser.parse();
+
+ if (parser_result.error == regex::Error::NoError)
+ matcher = make<Matcher<Parser>>(*this, regex_options);
+}
+
+template<class Parser>
+typename ParserTraits<Parser>::OptionsType Regex<Parser>::options() const
+{
+ if (parser_result.error != Error::NoError)
+ return {};
+
+ return matcher->options();
+}
+
+template<class Parser>
+String Regex<Parser>::error_string(Optional<String> message) const
+{
+ StringBuilder eb;
+ eb.appendf("Error during parsing of regular expression:\n");
+ eb.appendf(" %s\n ", pattern_value.characters());
+ for (size_t i = 0; i < parser_result.error_token.position(); ++i)
+ eb.append(" ");
+
+ eb.appendf("^---- %s", message.value_or(get_error_string(parser_result.error)).characters());
+ return eb.build();
+}
+
+template<typename Parser>
+RegexResult Matcher<Parser>::match(const RegexStringView& view, Optional<typename ParserTraits<Parser>::OptionsType> regex_options) const
+{
+ AllOptions options = m_regex_options | regex_options.value_or({}).value();
+
+ if (options.has_flag_set(AllFlags::Multiline))
+ return match(view.lines(), regex_options); // FIXME: how do we know, which line ending a line has (1char or 2char)? This is needed to get the correct match offsets from start of string...
+
+ Vector<RegexStringView> views;
+ views.append(view);
+ return match(views, regex_options);
+}
+
+template<typename Parser>
+RegexResult Matcher<Parser>::match(const Vector<RegexStringView> views, Optional<typename ParserTraits<Parser>::OptionsType> regex_options) const
+{
+ // If the pattern *itself* isn't stateful, reset any changes to start_offset.
+ if (!((AllFlags)m_regex_options.value() & AllFlags::Internal_Stateful))
+ m_pattern.start_offset = 0;
+
+ size_t match_count { 0 };
+
+ MatchInput input;
+ MatchState state;
+ MatchOutput output;
+
+ input.regex_options = m_regex_options | regex_options.value_or({}).value();
+ input.start_offset = m_pattern.start_offset;
+ output.operations = 0;
+
+ if (input.regex_options.has_flag_set(AllFlags::Internal_Stateful))
+ ASSERT(views.size() == 1);
+
+ if (c_match_preallocation_count) {
+ output.matches.ensure_capacity(c_match_preallocation_count);
+ output.capture_group_matches.ensure_capacity(c_match_preallocation_count);
+ output.named_capture_group_matches.ensure_capacity(c_match_preallocation_count);
+
+ auto& capture_groups_count = m_pattern.parser_result.capture_groups_count;
+ auto& named_capture_groups_count = m_pattern.parser_result.named_capture_groups_count;
+
+ for (size_t j = 0; j < c_match_preallocation_count; ++j) {
+ output.matches.empend();
+ output.capture_group_matches.unchecked_append({});
+ output.capture_group_matches.at(j).ensure_capacity(capture_groups_count);
+ for (size_t k = 0; k < capture_groups_count; ++k)
+ output.capture_group_matches.at(j).unchecked_append({});
+
+ output.named_capture_group_matches.unchecked_append({});
+ output.named_capture_group_matches.at(j).ensure_capacity(named_capture_groups_count);
+ }
+ }
+
+ auto append_match = [](auto& input, auto& state, auto& output, auto& start_position) {
+ if (output.matches.size() == input.match_index)
+ output.matches.empend();
+
+ ASSERT(start_position + state.string_position - start_position <= input.view.length());
+ if (input.regex_options.has_flag_set(AllFlags::StringCopyMatches)) {
+ output.matches.at(input.match_index) = { input.view.substring_view(start_position, state.string_position - start_position).to_string(), input.line, start_position, input.global_offset + start_position };
+ } else { // let the view point to the original string ...
+ output.matches.at(input.match_index) = { input.view.substring_view(start_position, state.string_position - start_position), input.line, start_position, input.global_offset + start_position };
+ }
+ };
+
+#ifdef REGEX_DEBUG
+ s_regex_dbg.print_header();
+#endif
+
+ bool continue_search = input.regex_options.has_flag_set(AllFlags::Global) || input.regex_options.has_flag_set(AllFlags::Multiline);
+ if (input.regex_options.has_flag_set(AllFlags::Internal_Stateful))
+ continue_search = false;
+
+ for (auto& view : views) {
+ input.view = view;
+#ifdef REGEX_DEBUG
+ dbg() << "[match] Starting match with view (" << view.length() << "): _" << view.to_string() << "_";
+#endif
+
+ auto view_length = view.length();
+ size_t view_index = m_pattern.start_offset;
+ state.string_position = view_index;
+
+ if (view_index == view_length && m_pattern.parser_result.match_length_minimum == 0) {
+ // Run the code until it tries to consume something.
+ // This allows non-consuming code to run on empty strings, for instance
+ // e.g. "Exit"
+ MatchOutput temp_output { output };
+
+ input.column = match_count;
+ input.match_index = match_count;
+
+ state.string_position = view_index;
+ state.instruction_position = 0;
+
+ auto success = execute(input, state, temp_output, 0);
+ // This success is acceptable only if it doesn't read anything from the input (input length is 0).
+ if (state.string_position <= view_index) {
+ if (success.value()) {
+ output = move(temp_output);
+ if (!match_count) {
+ // Nothing was *actually* matched, so append an empty match.
+ append_match(input, state, output, view_index);
+ ++match_count;
+ }
+ }
+ }
+ }
+
+ for (; view_index < view_length; ++view_index) {
+ auto& match_length_minimum = m_pattern.parser_result.match_length_minimum;
+ // FIXME: More performant would be to know the remaining minimum string
+ // length needed to match from the current position onwards within
+ // the vm. Add new OpCode for MinMatchLengthFromSp with the value of
+ // the remaining string length from the current path. The value though
+ // has to be filled in reverse. That implies a second run over bytecode
+ // after generation has finished.
+ if (match_length_minimum && match_length_minimum > view_length - view_index)
+ break;
+
+ input.column = match_count;
+ input.match_index = match_count;
+
+ state.string_position = view_index;
+ state.instruction_position = 0;
+
+ auto success = execute(input, state, output, 0);
+ if (!success.has_value())
+ return { false, 0, {}, {}, {}, output.operations };
+
+ if (success.value()) {
+
+ if (input.regex_options.has_flag_set(AllFlags::MatchNotEndOfLine) && state.string_position == input.view.length()) {
+ if (!continue_search)
+ break;
+ continue;
+ }
+ if (input.regex_options.has_flag_set(AllFlags::MatchNotBeginOfLine) && view_index == 0) {
+ if (!continue_search)
+ break;
+ continue;
+ }
+
+#ifdef REGEX_DEBUG
+ dbg() << "state.string_position: " << state.string_position << " view_index: " << view_index;
+ dbg() << "[match] Found a match (length = " << state.string_position - view_index << "): " << input.view.substring_view(view_index, state.string_position - view_index).to_string();
+#endif
+ ++match_count;
+
+ if (continue_search) {
+ append_match(input, state, output, view_index);
+
+ bool has_zero_length = state.string_position == view_index;
+ view_index = state.string_position - (has_zero_length ? 0 : 1);
+ continue;
+
+ } else if (input.regex_options.has_flag_set(AllFlags::Internal_Stateful)) {
+ append_match(input, state, output, view_index);
+ break;
+
+ } else if (state.string_position < view_length) {
+ return { false, 0, {}, {}, {}, output.operations };
+ }
+
+ append_match(input, state, output, view_index);
+ break;
+ }
+
+ if (!continue_search && !input.regex_options.has_flag_set(AllFlags::Internal_Stateful))
+ break;
+ }
+
+ ++input.line;
+ input.global_offset += view.length() + 1; // +1 includes the line break character
+
+ if (input.regex_options.has_flag_set(AllFlags::Internal_Stateful))
+ m_pattern.start_offset = state.string_position;
+ }
+
+ MatchOutput output_copy;
+ if (match_count) {
+ auto capture_groups_count = min(output.capture_group_matches.size(), output.matches.size());
+ for (size_t i = 0; i < capture_groups_count; ++i) {
+ if (input.regex_options.has_flag_set(AllFlags::SkipTrimEmptyMatches)) {
+ output_copy.capture_group_matches.append(output.capture_group_matches.at(i));
+ } else {
+ Vector<Match> capture_group_matches;
+ for (size_t j = 0; j < output.capture_group_matches.at(i).size(); ++j) {
+ if (!output.capture_group_matches.at(i).at(j).view.is_null())
+ capture_group_matches.append(output.capture_group_matches.at(i).at(j));
+ }
+ output_copy.capture_group_matches.append(capture_group_matches);
+ }
+ }
+
+ auto named_capture_groups_count = min(output.named_capture_group_matches.size(), output.matches.size());
+ for (size_t i = 0; i < named_capture_groups_count; ++i) {
+ if (output.matches.at(i).view.length())
+ output_copy.named_capture_group_matches.append(output.named_capture_group_matches.at(i));
+ }
+
+ for (size_t i = 0; i < match_count; ++i)
+ output_copy.matches.append(output.matches.at(i));
+
+ } else {
+ output_copy.capture_group_matches.clear_with_capacity();
+ output_copy.named_capture_group_matches.clear_with_capacity();
+ }
+
+ return {
+ match_count ? true : false,
+ match_count,
+ move(output_copy.matches),
+ move(output_copy.capture_group_matches),
+ move(output_copy.named_capture_group_matches),
+ output.operations,
+ m_pattern.parser_result.capture_groups_count,
+ m_pattern.parser_result.named_capture_groups_count,
+ };
+}
+
+template<class Parser>
+Optional<bool> Matcher<Parser>::execute(const MatchInput& input, MatchState& state, MatchOutput& output, size_t recursion_level) const
+{
+ if (recursion_level > c_max_recursion)
+ return false;
+
+ Vector<MatchState> fork_low_prio_states;
+ MatchState fork_high_prio_state;
+ Optional<bool> success;
+
+ auto& bytecode = m_pattern.parser_result.bytecode;
+
+ for (;;) {
+ ++output.operations;
+ auto* opcode = bytecode.get_opcode(state);
+
+ if (!opcode) {
+ dbgln("Wrong opcode... failed!");
+ return {};
+ }
+
+#ifdef REGEX_DEBUG
+ s_regex_dbg.print_opcode("VM", *opcode, state, recursion_level, false);
+#endif
+
+ ExecutionResult result;
+ if (input.fail_counter > 0) {
+ --input.fail_counter;
+ result = ExecutionResult::Failed_ExecuteLowPrioForks;
+ } else {
+ result = opcode->execute(input, state, output);
+ }
+
+#ifdef REGEX_DEBUG
+ s_regex_dbg.print_result(*opcode, bytecode, input, state, result);
+#endif
+
+ state.instruction_position += opcode->size();
+
+ switch (result) {
+ case ExecutionResult::Fork_PrioLow:
+ fork_low_prio_states.prepend(state);
+ continue;
+ case ExecutionResult::Fork_PrioHigh:
+ fork_high_prio_state = state;
+ fork_high_prio_state.instruction_position = fork_high_prio_state.fork_at_position;
+ success = execute(input, fork_high_prio_state, output, ++recursion_level);
+ if (!success.has_value())
+ return {};
+
+ if (success.value()) {
+ state = fork_high_prio_state;
+ return true;
+ }
+
+ continue;
+ case ExecutionResult::Continue:
+ continue;
+ case ExecutionResult::Succeeded:
+ return true;
+ case ExecutionResult::Failed:
+ return false;
+ case ExecutionResult::Failed_ExecuteLowPrioForks:
+ return execute_low_prio_forks(input, state, output, fork_low_prio_states, recursion_level + 1);
+ }
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+template<class Parser>
+ALWAYS_INLINE Optional<bool> Matcher<Parser>::execute_low_prio_forks(const MatchInput& input, MatchState& original_state, MatchOutput& output, Vector<MatchState> states, size_t recursion_level) const
+{
+ for (auto& state : states) {
+
+ state.instruction_position = state.fork_at_position;
+#ifdef REGEX_DEBUG
+ fprintf(stderr, "Forkstay... ip = %lu, sp = %lu\n", state.instruction_position, state.string_position);
+#endif
+ auto success = execute(input, state, output, recursion_level);
+ if (!success.has_value())
+ return {};
+ if (success.value()) {
+#ifdef REGEX_DEBUG
+ fprintf(stderr, "Forkstay succeeded... ip = %lu, sp = %lu\n", state.instruction_position, state.string_position);
+#endif
+ original_state = state;
+ return true;
+ }
+ }
+
+ original_state.string_position = 0;
+ return false;
+}
+
+template class Matcher<PosixExtendedParser>;
+template class Regex<PosixExtendedParser>;
+
+template class Matcher<ECMA262Parser>;
+template class Regex<ECMA262Parser>;
+}
diff --git a/Userland/Libraries/LibRegex/RegexMatcher.h b/Userland/Libraries/LibRegex/RegexMatcher.h
new file mode 100644
index 0000000000..b62dab0be7
--- /dev/null
+++ b/Userland/Libraries/LibRegex/RegexMatcher.h
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "RegexByteCode.h"
+#include "RegexMatch.h"
+#include "RegexOptions.h"
+#include "RegexParser.h"
+
+#include <AK/Forward.h>
+#include <AK/HashMap.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <AK/Types.h>
+#include <AK/Utf32View.h>
+#include <AK/Vector.h>
+#include <ctype.h>
+
+#include <stdio.h>
+
+namespace regex {
+
+static const constexpr size_t c_max_recursion = 5000;
+static const constexpr size_t c_match_preallocation_count = 0;
+
+struct RegexResult final {
+ bool success { false };
+ size_t count { 0 };
+ Vector<Match> matches;
+ Vector<Vector<Match>> capture_group_matches;
+ Vector<HashMap<String, Match>> named_capture_group_matches;
+ size_t n_operations { 0 };
+ size_t n_capture_groups { 0 };
+ size_t n_named_capture_groups { 0 };
+};
+
+template<class Parser>
+class Regex;
+
+template<class Parser>
+class Matcher final {
+
+public:
+ Matcher(const Regex<Parser>& pattern, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {})
+ : m_pattern(pattern)
+ , m_regex_options(regex_options.value_or({}))
+ {
+ }
+ ~Matcher() = default;
+
+ RegexResult match(const RegexStringView&, Optional<typename ParserTraits<Parser>::OptionsType> = {}) const;
+ RegexResult match(const Vector<RegexStringView>, Optional<typename ParserTraits<Parser>::OptionsType> = {}) const;
+
+ typename ParserTraits<Parser>::OptionsType options() const
+ {
+ return m_regex_options;
+ }
+
+private:
+ Optional<bool> execute(const MatchInput& input, MatchState& state, MatchOutput& output, size_t recursion_level) const;
+ ALWAYS_INLINE Optional<bool> execute_low_prio_forks(const MatchInput& input, MatchState& original_state, MatchOutput& output, Vector<MatchState> states, size_t recursion_level) const;
+
+ const Regex<Parser>& m_pattern;
+ const typename ParserTraits<Parser>::OptionsType m_regex_options;
+};
+
+template<class Parser>
+class Regex final {
+public:
+ String pattern_value;
+ regex::Parser::Result parser_result;
+ OwnPtr<Matcher<Parser>> matcher { nullptr };
+ mutable size_t start_offset { 0 };
+
+ explicit Regex(StringView pattern, typename ParserTraits<Parser>::OptionsType regex_options = {});
+ ~Regex() = default;
+
+ typename ParserTraits<Parser>::OptionsType options() const;
+ void print_bytecode(FILE* f = stdout) const;
+ String error_string(Optional<String> message = {}) const;
+
+ RegexResult match(const RegexStringView view, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {}) const
+ {
+ if (!matcher || parser_result.error != Error::NoError)
+ return {};
+ return matcher->match(view, regex_options);
+ }
+
+ RegexResult match(const Vector<RegexStringView> views, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {}) const
+ {
+ if (!matcher || parser_result.error != Error::NoError)
+ return {};
+ return matcher->match(views, regex_options);
+ }
+
+ String replace(const RegexStringView view, const StringView& replacement_pattern, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {}) const
+ {
+ if (!matcher || parser_result.error != Error::NoError)
+ return {};
+
+ StringBuilder builder;
+ size_t start_offset = 0;
+ RegexResult result = matcher->match(view, regex_options);
+ if (!result.success)
+ return view.to_string();
+
+ for (size_t i = 0; i < result.matches.size(); ++i) {
+ auto& match = result.matches[i];
+ builder.append(view.substring_view(start_offset, match.global_offset - start_offset).to_string());
+ start_offset = match.global_offset + match.view.length();
+ GenericLexer lexer(replacement_pattern);
+ while (!lexer.is_eof()) {
+ if (lexer.consume_specific('\\')) {
+ if (lexer.consume_specific('\\')) {
+ builder.append('\\');
+ continue;
+ }
+ auto number = lexer.consume_while(isdigit);
+ if (auto index = number.to_uint(); index.has_value() && result.n_capture_groups >= index.value()) {
+ builder.append(result.capture_group_matches[i][index.value() - 1].view.to_string());
+ } else {
+ builder.appendff("\\{}", number);
+ }
+ } else {
+ builder.append(lexer.consume_while([](auto ch) { return ch != '\\'; }));
+ }
+ }
+ }
+
+ builder.append(view.substring_view(start_offset, view.length() - start_offset).to_string());
+
+ return builder.to_string();
+ }
+
+ // FIXME: replace(const Vector<RegexStringView>, ...)
+
+ RegexResult search(const RegexStringView view, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {}) const
+ {
+ if (!matcher || parser_result.error != Error::NoError)
+ return {};
+
+ AllOptions options = (AllOptions)regex_options.value_or({});
+ if ((options & AllFlags::MatchNotBeginOfLine) && (options & AllFlags::MatchNotEndOfLine)) {
+ options.reset_flag(AllFlags::MatchNotEndOfLine);
+ options.reset_flag(AllFlags::MatchNotBeginOfLine);
+ }
+ options |= AllFlags::Global;
+
+ return matcher->match(view, options);
+ }
+
+ RegexResult search(const Vector<RegexStringView> views, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {}) const
+ {
+ if (!matcher || parser_result.error != Error::NoError)
+ return {};
+
+ AllOptions options = (AllOptions)regex_options.value_or({});
+ if ((options & AllFlags::MatchNotBeginOfLine) && (options & AllFlags::MatchNotEndOfLine)) {
+ options.reset_flag(AllFlags::MatchNotEndOfLine);
+ options.reset_flag(AllFlags::MatchNotBeginOfLine);
+ }
+ options |= AllFlags::Global;
+
+ return matcher->match(views, options);
+ }
+
+ bool match(const RegexStringView view, RegexResult& m, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {}) const
+ {
+ m = match(view, regex_options);
+ return m.success;
+ }
+
+ bool match(const Vector<RegexStringView> views, RegexResult& m, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {}) const
+ {
+ m = match(views, regex_options);
+ return m.success;
+ }
+
+ bool search(const RegexStringView view, RegexResult& m, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {}) const
+ {
+ m = search(view, regex_options);
+ return m.success;
+ }
+
+ bool search(const Vector<RegexStringView> views, RegexResult& m, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {}) const
+ {
+ m = search(views, regex_options);
+ return m.success;
+ }
+
+ bool has_match(const RegexStringView view, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {}) const
+ {
+ if (!matcher || parser_result.error != Error::NoError)
+ return false;
+ RegexResult result = matcher->match(view, AllOptions { regex_options.value_or({}) } | AllFlags::SkipSubExprResults);
+ return result.success;
+ }
+
+ bool has_match(const Vector<RegexStringView> views, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {}) const
+ {
+ if (!matcher || parser_result.error != Error::NoError)
+ return false;
+ RegexResult result = matcher->match(views, AllOptions { regex_options.value_or({}) } | AllFlags::SkipSubExprResults);
+ return result.success;
+ }
+};
+
+// free standing functions for match, search and has_match
+template<class Parser>
+RegexResult match(const RegexStringView view, Regex<Parser>& pattern, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {})
+{
+ return pattern.match(view, regex_options);
+}
+
+template<class Parser>
+RegexResult match(const Vector<RegexStringView> view, Regex<Parser>& pattern, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {})
+{
+ return pattern.match(view, regex_options);
+}
+
+template<class Parser>
+bool match(const RegexStringView view, Regex<Parser>& pattern, RegexResult&, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {})
+{
+ return pattern.match(view, regex_options);
+}
+
+template<class Parser>
+bool match(const Vector<RegexStringView> view, Regex<Parser>& pattern, RegexResult&, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {})
+{
+ return pattern.match(view, regex_options);
+}
+
+template<class Parser>
+RegexResult search(const RegexStringView view, Regex<Parser>& pattern, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {})
+{
+ return pattern.search(view, regex_options);
+}
+
+template<class Parser>
+RegexResult search(const Vector<RegexStringView> views, Regex<Parser>& pattern, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {})
+{
+ return pattern.search(views, regex_options);
+}
+
+template<class Parser>
+bool search(const RegexStringView view, Regex<Parser>& pattern, RegexResult&, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {})
+{
+ return pattern.search(view, regex_options);
+}
+
+template<class Parser>
+bool search(const Vector<RegexStringView> views, Regex<Parser>& pattern, RegexResult&, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {})
+{
+ return pattern.search(views, regex_options);
+}
+
+template<class Parser>
+bool has_match(const RegexStringView view, Regex<Parser>& pattern, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {})
+{
+ return pattern.has_match(view, regex_options);
+}
+
+template<class Parser>
+bool has_match(const Vector<RegexStringView> views, Regex<Parser>& pattern, Optional<typename ParserTraits<Parser>::OptionsType> regex_options = {})
+{
+ return pattern.has_match(views, regex_options);
+}
+}
+
+using regex::has_match;
+using regex::match;
+using regex::Regex;
+using regex::RegexResult;
diff --git a/Userland/Libraries/LibRegex/RegexOptions.h b/Userland/Libraries/LibRegex/RegexOptions.h
new file mode 100644
index 0000000000..5e860215fd
--- /dev/null
+++ b/Userland/Libraries/LibRegex/RegexOptions.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+#include <stdio.h>
+#ifdef __serenity__
+# include <regex.h>
+#else
+# include <LibC/regex.h>
+#endif
+
+namespace regex {
+
+using FlagsUnderlyingType = u16;
+
+enum class AllFlags {
+ Global = __Regex_Global, // All matches (don't return after first match)
+ Insensitive = __Regex_Insensitive, // Case insensitive match (ignores case of [a-zA-Z])
+ Ungreedy = __Regex_Ungreedy, // The match becomes lazy by default. Now a ? following a quantifier makes it greedy
+ Unicode = __Regex_Unicode, // Enable all unicode features and interpret all unicode escape sequences as such
+ Extended = __Regex_Extended, // Ignore whitespaces. Spaces and text after a # in the pattern are ignored
+ Extra = __Regex_Extra, // Disallow meaningless escapes. A \ followed by a letter with no special meaning is faulted
+ MatchNotBeginOfLine = __Regex_MatchNotBeginOfLine, // Pattern is not forced to ^ -> search in whole string!
+ MatchNotEndOfLine = __Regex_MatchNotEndOfLine, // Don't Force the dollar sign, $, to always match end of the string, instead of end of the line. This option is ignored if the Multiline-flag is set
+ SkipSubExprResults = __Regex_SkipSubExprResults, // Do not return sub expressions in the result
+ StringCopyMatches = __Regex_StringCopyMatches, // Do explicitly copy results into new allocated string instead of StringView to original string.
+ SingleLine = __Regex_SingleLine, // Dot matches newline characters
+ Sticky = __Regex_Sticky, // Force the pattern to only match consecutive matches from where the previous match ended.
+ Multiline = __Regex_Multiline, // Handle newline characters. Match each line, one by one.
+ SkipTrimEmptyMatches = __Regex_SkipTrimEmptyMatches, // Do not remove empty capture group results.
+ Internal_Stateful = __Regex_Internal_Stateful, // Make global matches match one result at a time, and further match() calls on the same instance continue where the previous one left off.
+ Last = Internal_Stateful,
+};
+
+enum class PosixFlags : FlagsUnderlyingType {
+ Global = (FlagsUnderlyingType)AllFlags::Global,
+ Insensitive = (FlagsUnderlyingType)AllFlags::Insensitive,
+ Ungreedy = (FlagsUnderlyingType)AllFlags::Ungreedy,
+ Unicode = (FlagsUnderlyingType)AllFlags::Unicode,
+ Extended = (FlagsUnderlyingType)AllFlags::Extended,
+ Extra = (FlagsUnderlyingType)AllFlags::Extra,
+ MatchNotBeginOfLine = (FlagsUnderlyingType)AllFlags::MatchNotBeginOfLine,
+ MatchNotEndOfLine = (FlagsUnderlyingType)AllFlags::MatchNotEndOfLine,
+ SkipSubExprResults = (FlagsUnderlyingType)AllFlags::SkipSubExprResults,
+ SkipTrimEmptyMatches = (FlagsUnderlyingType)AllFlags::SkipTrimEmptyMatches,
+ Multiline = (FlagsUnderlyingType)AllFlags::Multiline,
+ StringCopyMatches = (FlagsUnderlyingType)AllFlags::StringCopyMatches,
+};
+
+enum class ECMAScriptFlags : FlagsUnderlyingType {
+ Global = (FlagsUnderlyingType)AllFlags::Global | (FlagsUnderlyingType)AllFlags::Internal_Stateful, // Note: ECMAScript "Global" creates a stateful regex.
+ Insensitive = (FlagsUnderlyingType)AllFlags::Insensitive,
+ Ungreedy = (FlagsUnderlyingType)AllFlags::Ungreedy,
+ Unicode = (FlagsUnderlyingType)AllFlags::Unicode,
+ Extended = (FlagsUnderlyingType)AllFlags::Extended,
+ Extra = (FlagsUnderlyingType)AllFlags::Extra,
+ SingleLine = (FlagsUnderlyingType)AllFlags::SingleLine,
+ Sticky = (FlagsUnderlyingType)AllFlags::Sticky,
+ Multiline = (FlagsUnderlyingType)AllFlags::Multiline,
+ StringCopyMatches = (FlagsUnderlyingType)AllFlags::StringCopyMatches,
+};
+
+template<class T>
+class RegexOptions {
+public:
+ using FlagsType = T;
+
+ RegexOptions() = default;
+
+ RegexOptions(T flags)
+ : m_flags(flags)
+ {
+ }
+
+ template<class U>
+ RegexOptions(RegexOptions<U> other)
+ : m_flags((T) static_cast<FlagsUnderlyingType>(other.value()))
+ {
+ }
+
+ operator bool() const { return !!*this; }
+ bool operator!() const { return (FlagsUnderlyingType)m_flags == 0; }
+
+ RegexOptions<T> operator|(T flag) const { return RegexOptions<T> { (T)((FlagsUnderlyingType)m_flags | (FlagsUnderlyingType)flag) }; }
+ RegexOptions<T> operator&(T flag) const { return RegexOptions<T> { (T)((FlagsUnderlyingType)m_flags & (FlagsUnderlyingType)flag) }; }
+
+ RegexOptions<T>& operator|=(T flag)
+ {
+ m_flags = (T)((FlagsUnderlyingType)m_flags | (FlagsUnderlyingType)flag);
+ return *this;
+ }
+
+ RegexOptions<T>& operator&=(T flag)
+ {
+ m_flags = (T)((FlagsUnderlyingType)m_flags & (FlagsUnderlyingType)flag);
+ return *this;
+ }
+
+ void reset_flags() { m_flags = (T)0; }
+ void reset_flag(T flag) { m_flags = (T)((FlagsUnderlyingType)m_flags & ~(FlagsUnderlyingType)flag); }
+ void set_flag(T flag) { *this |= flag; }
+ bool has_flag_set(T flag) const { return (FlagsUnderlyingType)flag == ((FlagsUnderlyingType)m_flags & (FlagsUnderlyingType)flag); }
+ T value() const { return m_flags; }
+
+private:
+ T m_flags { 0 };
+};
+
+template<class T>
+inline RegexOptions<T> operator|(T lhs, T rhs)
+{
+ return RegexOptions<T> { lhs } |= rhs;
+}
+
+template<class T>
+inline RegexOptions<T> operator&(T lhs, T rhs)
+{
+ return RegexOptions<T> { lhs } &= rhs;
+}
+
+template<class T>
+inline T operator~(T flag)
+{
+ return (T) ~((FlagsUnderlyingType)flag);
+}
+
+using AllOptions = RegexOptions<AllFlags>;
+using ECMAScriptOptions = RegexOptions<ECMAScriptFlags>;
+using PosixOptions = RegexOptions<PosixFlags>;
+
+}
+
+using regex::ECMAScriptFlags;
+using regex::ECMAScriptOptions;
+using regex::PosixFlags;
+using regex::PosixOptions;
diff --git a/Userland/Libraries/LibRegex/RegexParser.cpp b/Userland/Libraries/LibRegex/RegexParser.cpp
new file mode 100644
index 0000000000..33ade43661
--- /dev/null
+++ b/Userland/Libraries/LibRegex/RegexParser.cpp
@@ -0,0 +1,1493 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "RegexParser.h"
+#include "RegexDebug.h"
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/StringUtils.h>
+
+namespace regex {
+
+ALWAYS_INLINE bool Parser::set_error(Error error)
+{
+ if (m_parser_state.error == Error::NoError) {
+ m_parser_state.error = error;
+ m_parser_state.error_token = m_parser_state.current_token;
+ }
+ return false; // always return false, that eases the API usage (return set_error(...)) :^)
+}
+
+ALWAYS_INLINE bool Parser::done() const
+{
+ return match(TokenType::Eof);
+}
+
+ALWAYS_INLINE bool Parser::match(TokenType type) const
+{
+ return m_parser_state.current_token.type() == type;
+}
+
+ALWAYS_INLINE Token Parser::consume()
+{
+ auto old_token = m_parser_state.current_token;
+ m_parser_state.current_token = m_parser_state.lexer.next();
+ return old_token;
+}
+
+ALWAYS_INLINE Token Parser::consume(TokenType type, Error error)
+{
+ if (m_parser_state.current_token.type() != type) {
+ set_error(error);
+ dbg() << "[PARSER] Error: Unexpected token " << m_parser_state.current_token.name() << ". Expected: " << Token::name(type);
+ }
+ return consume();
+}
+
+ALWAYS_INLINE bool Parser::consume(const String& str)
+{
+ size_t potentially_go_back { 1 };
+ for (auto ch : str) {
+ if (match(TokenType::Char)) {
+ if (m_parser_state.current_token.value()[0] != ch) {
+ m_parser_state.lexer.back(potentially_go_back);
+ m_parser_state.current_token = m_parser_state.lexer.next();
+ return false;
+ }
+ } else {
+ m_parser_state.lexer.back(potentially_go_back);
+ m_parser_state.current_token = m_parser_state.lexer.next();
+ return false;
+ }
+ consume(TokenType::Char, Error::NoError);
+ ++potentially_go_back;
+ }
+ return true;
+}
+
+ALWAYS_INLINE bool Parser::try_skip(StringView str)
+{
+ if (str.starts_with(m_parser_state.current_token.value()))
+ str = str.substring_view(m_parser_state.current_token.value().length(), str.length() - m_parser_state.current_token.value().length());
+ else
+ return false;
+
+ size_t potentially_go_back { 0 };
+ for (auto ch : str) {
+ if (!m_parser_state.lexer.try_skip(ch)) {
+ m_parser_state.lexer.back(potentially_go_back);
+ return false;
+ }
+ ++potentially_go_back;
+ }
+
+ m_parser_state.current_token = m_parser_state.lexer.next();
+ return true;
+}
+
+ALWAYS_INLINE char Parser::skip()
+{
+ char ch;
+ if (m_parser_state.current_token.value().length() == 1) {
+ ch = m_parser_state.current_token.value()[0];
+ } else {
+ m_parser_state.lexer.back(m_parser_state.current_token.value().length());
+ ch = m_parser_state.lexer.skip();
+ }
+
+ m_parser_state.current_token = m_parser_state.lexer.next();
+ return ch;
+}
+
+ALWAYS_INLINE void Parser::reset()
+{
+ m_parser_state.bytecode.clear();
+ m_parser_state.lexer.reset();
+ m_parser_state.current_token = m_parser_state.lexer.next();
+ m_parser_state.error = Error::NoError;
+ m_parser_state.error_token = { TokenType::Eof, 0, StringView(nullptr) };
+}
+
+Parser::Result Parser::parse(Optional<AllOptions> regex_options)
+{
+ reset();
+ if (regex_options.has_value())
+ m_parser_state.regex_options = regex_options.value();
+ if (parse_internal(m_parser_state.bytecode, m_parser_state.match_length_minimum))
+ consume(TokenType::Eof, Error::InvalidPattern);
+ else
+ set_error(Error::InvalidPattern);
+
+#ifdef REGEX_DEBUG
+ fprintf(stderr, "[PARSER] Produced bytecode with %lu entries (opcodes + arguments)\n", m_parser_state.bytecode.size());
+#endif
+ return {
+ move(m_parser_state.bytecode),
+ move(m_parser_state.capture_groups_count),
+ move(m_parser_state.named_capture_groups_count),
+ move(m_parser_state.match_length_minimum),
+ move(m_parser_state.error),
+ move(m_parser_state.error_token)
+ };
+}
+
+ALWAYS_INLINE bool Parser::match_ordinary_characters()
+{
+ // NOTE: This method must not be called during bracket and repetition parsing!
+ // FIXME: Add assertion for that?
+ auto type = m_parser_state.current_token.type();
+ return (type == TokenType::Char
+ || type == TokenType::Comma
+ || type == TokenType::Slash
+ || type == TokenType::EqualSign
+ || type == TokenType::HyphenMinus
+ || type == TokenType::Colon);
+}
+
+// =============================
+// PosixExtended Parser
+// =============================
+
+bool PosixExtendedParser::parse_internal(ByteCode& stack, size_t& match_length_minimum)
+{
+ return parse_root(stack, match_length_minimum);
+}
+
+ALWAYS_INLINE bool PosixExtendedParser::match_repetition_symbol()
+{
+ auto type = m_parser_state.current_token.type();
+ return (type == TokenType::Asterisk
+ || type == TokenType::Plus
+ || type == TokenType::Questionmark
+ || type == TokenType::LeftCurly);
+}
+
+ALWAYS_INLINE bool PosixExtendedParser::parse_repetition_symbol(ByteCode& bytecode_to_repeat, size_t& match_length_minimum)
+{
+ if (match(TokenType::LeftCurly)) {
+ consume();
+
+ StringBuilder number_builder;
+
+ while (match(TokenType::Char)) {
+ number_builder.append(consume().value());
+ }
+
+ auto maybe_minimum = number_builder.build().to_uint();
+ if (!maybe_minimum.has_value())
+ return set_error(Error::InvalidBraceContent);
+
+ auto minimum = maybe_minimum.value();
+ match_length_minimum *= minimum;
+
+ if (match(TokenType::Comma)) {
+ consume();
+ } else {
+ ByteCode bytecode;
+ bytecode.insert_bytecode_repetition_n(bytecode_to_repeat, minimum);
+ bytecode_to_repeat = move(bytecode);
+
+ consume(TokenType::RightCurly, Error::MismatchingBrace);
+ return !has_error();
+ }
+
+ Optional<size_t> maybe_maximum {};
+ number_builder.clear();
+ while (match(TokenType::Char)) {
+ number_builder.append(consume().value());
+ }
+ if (!number_builder.is_empty()) {
+ auto value = number_builder.build().to_uint();
+ if (!value.has_value() || minimum > value.value())
+ return set_error(Error::InvalidBraceContent);
+
+ maybe_maximum = value.value();
+ }
+
+ bytecode_to_repeat.insert_bytecode_repetition_min_max(bytecode_to_repeat, minimum, maybe_maximum);
+
+ consume(TokenType::RightCurly, Error::MismatchingBrace);
+ return !has_error();
+
+ } else if (match(TokenType::Plus)) {
+ consume();
+
+ bool nongreedy = match(TokenType::Questionmark);
+ if (nongreedy)
+ consume();
+
+ // Note: don't touch match_length_minimum, it's already correct
+ bytecode_to_repeat.insert_bytecode_repetition_min_one(bytecode_to_repeat, !nongreedy);
+ return !has_error();
+
+ } else if (match(TokenType::Asterisk)) {
+ consume();
+ match_length_minimum = 0;
+
+ bool nongreedy = match(TokenType::Questionmark);
+ if (nongreedy)
+ consume();
+
+ bytecode_to_repeat.insert_bytecode_repetition_any(bytecode_to_repeat, !nongreedy);
+
+ return !has_error();
+
+ } else if (match(TokenType::Questionmark)) {
+ consume();
+ match_length_minimum = 0;
+
+ bool nongreedy = match(TokenType::Questionmark);
+ if (nongreedy)
+ consume();
+
+ bytecode_to_repeat.insert_bytecode_repetition_zero_or_one(bytecode_to_repeat, !nongreedy);
+ return !has_error();
+ }
+
+ return false;
+}
+
+ALWAYS_INLINE bool PosixExtendedParser::parse_bracket_expression(ByteCode& stack, size_t& match_length_minimum)
+{
+ Vector<CompareTypeAndValuePair> values;
+
+ for (;;) {
+
+ if (match(TokenType::HyphenMinus)) {
+ consume();
+
+ if (values.is_empty() || (values.size() == 1 && values.last().type == CharacterCompareType::Inverse)) {
+ // first in the bracket expression
+ values.append({ CharacterCompareType::Char, (ByteCodeValueType)'-' });
+ } else if (match(TokenType::RightBracket)) {
+ // Last in the bracket expression
+ values.append({ CharacterCompareType::Char, (ByteCodeValueType)'-' });
+ } else if (values.last().type == CharacterCompareType::Char) {
+ values.append({ CharacterCompareType::RangeExpressionDummy, 0 });
+
+ if (match(TokenType::HyphenMinus)) {
+ consume();
+ // Valid range, add ordinary character
+ values.append({ CharacterCompareType::Char, (ByteCodeValueType)'-' });
+ }
+ } else {
+ return set_error(Error::InvalidRange);
+ }
+
+ } else if (match(TokenType::Circumflex)) {
+ auto t = consume();
+
+ if (values.is_empty())
+ values.append({ CharacterCompareType::Inverse, 0 });
+ else
+ values.append({ CharacterCompareType::Char, (ByteCodeValueType)*t.value().characters_without_null_termination() });
+
+ } else if (match(TokenType::LeftBracket)) {
+ consume();
+
+ if (match(TokenType::Period)) {
+ consume();
+
+ // FIXME: Parse collating element, this is needed when we have locale support
+ // This could have impact on length parameter, I guess.
+ ASSERT_NOT_REACHED();
+
+ consume(TokenType::Period, Error::InvalidCollationElement);
+ consume(TokenType::RightBracket, Error::MismatchingBracket);
+
+ } else if (match(TokenType::EqualSign)) {
+ consume();
+ // FIXME: Parse collating element, this is needed when we have locale support
+ // This could have impact on length parameter, I guess.
+ ASSERT_NOT_REACHED();
+
+ consume(TokenType::EqualSign, Error::InvalidCollationElement);
+ consume(TokenType::RightBracket, Error::MismatchingBracket);
+
+ } else if (match(TokenType::Colon)) {
+ consume();
+
+ CharClass ch_class;
+ // parse character class
+ if (match(TokenType::Char)) {
+ if (consume("alnum"))
+ ch_class = CharClass::Alnum;
+ else if (consume("alpha"))
+ ch_class = CharClass::Alpha;
+ else if (consume("blank"))
+ ch_class = CharClass::Blank;
+ else if (consume("cntrl"))
+ ch_class = CharClass::Cntrl;
+ else if (consume("digit"))
+ ch_class = CharClass::Digit;
+ else if (consume("graph"))
+ ch_class = CharClass::Graph;
+ else if (consume("lower"))
+ ch_class = CharClass::Lower;
+ else if (consume("print"))
+ ch_class = CharClass::Print;
+ else if (consume("punct"))
+ ch_class = CharClass::Punct;
+ else if (consume("space"))
+ ch_class = CharClass::Space;
+ else if (consume("upper"))
+ ch_class = CharClass::Upper;
+ else if (consume("xdigit"))
+ ch_class = CharClass::Xdigit;
+ else
+ return set_error(Error::InvalidCharacterClass);
+
+ values.append({ CharacterCompareType::CharClass, (ByteCodeValueType)ch_class });
+
+ } else
+ return set_error(Error::InvalidCharacterClass);
+
+ // FIXME: we do not support locale specific character classes until locales are implemented
+
+ consume(TokenType::Colon, Error::InvalidCharacterClass);
+ consume(TokenType::RightBracket, Error::MismatchingBracket);
+ } else {
+ return set_error(Error::MismatchingBracket);
+ }
+
+ } else if (match(TokenType::RightBracket)) {
+
+ if (values.is_empty() || (values.size() == 1 && values.last().type == CharacterCompareType::Inverse)) {
+ // handle bracket as ordinary character
+ values.append({ CharacterCompareType::Char, (ByteCodeValueType)*consume().value().characters_without_null_termination() });
+ } else {
+ // closing bracket expression
+ break;
+ }
+ } else {
+ values.append({ CharacterCompareType::Char, (ByteCodeValueType)skip() });
+ }
+
+ // check if range expression has to be completed...
+ if (values.size() >= 3 && values.at(values.size() - 2).type == CharacterCompareType::RangeExpressionDummy) {
+ if (values.last().type != CharacterCompareType::Char)
+ return set_error(Error::InvalidRange);
+
+ auto value2 = values.take_last();
+ values.take_last(); // RangeExpressionDummy
+ auto value1 = values.take_last();
+
+ values.append({ CharacterCompareType::CharRange, static_cast<ByteCodeValueType>(CharRange { (u32)value1.value, (u32)value2.value }) });
+ }
+ }
+
+ if (values.size())
+ match_length_minimum = 1;
+
+ if (values.first().type == CharacterCompareType::Inverse)
+ match_length_minimum = 0;
+
+ stack.insert_bytecode_compare_values(move(values));
+
+ return !has_error();
+}
+
+ALWAYS_INLINE bool PosixExtendedParser::parse_sub_expression(ByteCode& stack, size_t& match_length_minimum)
+{
+ ByteCode bytecode;
+ size_t length = 0;
+ bool should_parse_repetition_symbol { false };
+
+ for (;;) {
+ if (match_ordinary_characters()) {
+ Token start_token = m_parser_state.current_token;
+ Token last_token = m_parser_state.current_token;
+ for (;;) {
+ if (!match_ordinary_characters())
+ break;
+ ++length;
+ last_token = consume();
+ }
+
+ if (length > 1) {
+ // last character is inserted into 'bytecode' for duplication symbol handling
+ auto new_length = length - ((match_repetition_symbol() && length > 1) ? 1 : 0);
+ stack.insert_bytecode_compare_string({ start_token.value().characters_without_null_termination(), new_length });
+ }
+
+ if ((match_repetition_symbol() && length > 1) || length == 1) // Create own compare opcode for last character before duplication symbol
+ bytecode.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)last_token.value().characters_without_null_termination()[0] } });
+
+ should_parse_repetition_symbol = true;
+ break;
+ }
+
+ if (match_repetition_symbol())
+ return set_error(Error::InvalidRepetitionMarker);
+
+ if (match(TokenType::Period)) {
+ length = 1;
+ consume();
+ bytecode.insert_bytecode_compare_values({ { CharacterCompareType::AnyChar, 0 } });
+ should_parse_repetition_symbol = true;
+ break;
+ }
+
+ if (match(TokenType::EscapeSequence)) {
+ length = 1;
+ Token t = consume();
+#ifdef REGEX_DEBUG
+ printf("[PARSER] EscapeSequence with substring %s\n", String(t.value()).characters());
+#endif
+
+ bytecode.insert_bytecode_compare_values({ { CharacterCompareType::Char, (u32)t.value().characters_without_null_termination()[1] } });
+ should_parse_repetition_symbol = true;
+ break;
+ }
+
+ if (match(TokenType::LeftBracket)) {
+ consume();
+
+ ByteCode sub_ops;
+ if (!parse_bracket_expression(sub_ops, length) || !sub_ops.size())
+ return set_error(Error::InvalidBracketContent);
+
+ bytecode.append(move(sub_ops));
+
+ consume(TokenType::RightBracket, Error::MismatchingBracket);
+ should_parse_repetition_symbol = true;
+ break;
+ }
+
+ if (match(TokenType::RightBracket)) {
+ return set_error(Error::MismatchingBracket);
+ }
+
+ if (match(TokenType::RightCurly)) {
+ return set_error(Error::MismatchingBrace);
+ }
+
+ if (match(TokenType::Circumflex)) {
+ consume();
+ bytecode.empend((ByteCodeValueType)OpCodeId::CheckBegin);
+ break;
+ }
+
+ if (match(TokenType::Dollar)) {
+ consume();
+ bytecode.empend((ByteCodeValueType)OpCodeId::CheckEnd);
+ break;
+ }
+
+ if (match(TokenType::RightParen))
+ return false;
+
+ if (match(TokenType::LeftParen)) {
+ consume();
+ Optional<StringView> capture_group_name;
+ bool prevent_capture_group = false;
+ if (match(TokenType::Questionmark)) {
+ consume();
+
+ if (match(TokenType::Colon)) {
+ consume();
+ prevent_capture_group = true;
+ } else if (consume("<")) { // named capturing group
+
+ Token start_token = m_parser_state.current_token;
+ Token last_token = m_parser_state.current_token;
+ size_t capture_group_name_length = 0;
+ for (;;) {
+ if (!match_ordinary_characters())
+ return set_error(Error::InvalidNameForCaptureGroup);
+ if (match(TokenType::Char) && m_parser_state.current_token.value()[0] == '>') {
+ consume();
+ break;
+ }
+ ++capture_group_name_length;
+ last_token = consume();
+ }
+ capture_group_name = StringView(start_token.value().characters_without_null_termination(), capture_group_name_length);
+
+ } else if (match(TokenType::EqualSign)) { // positive lookahead
+ consume();
+ ASSERT_NOT_REACHED();
+ } else if (consume("!")) { // negative lookahead
+ ASSERT_NOT_REACHED();
+ } else if (consume("<")) {
+ if (match(TokenType::EqualSign)) { // positive lookbehind
+ consume();
+ ASSERT_NOT_REACHED();
+ }
+ if (consume("!")) // negative lookbehind
+ ASSERT_NOT_REACHED();
+ } else {
+ return set_error(Error::InvalidRepetitionMarker);
+ }
+ }
+
+ if (!(m_parser_state.regex_options & AllFlags::SkipSubExprResults || prevent_capture_group)) {
+ if (capture_group_name.has_value())
+ bytecode.insert_bytecode_group_capture_left(capture_group_name.value());
+ else
+ bytecode.insert_bytecode_group_capture_left(m_parser_state.capture_groups_count);
+ }
+
+ ByteCode capture_group_bytecode;
+
+ if (!parse_root(capture_group_bytecode, length))
+ return set_error(Error::InvalidPattern);
+
+ bytecode.append(move(capture_group_bytecode));
+
+ consume(TokenType::RightParen, Error::MismatchingParen);
+
+ if (!(m_parser_state.regex_options & AllFlags::SkipSubExprResults || prevent_capture_group)) {
+ if (capture_group_name.has_value()) {
+ bytecode.insert_bytecode_group_capture_right(capture_group_name.value());
+ ++m_parser_state.named_capture_groups_count;
+ } else {
+ bytecode.insert_bytecode_group_capture_right(m_parser_state.capture_groups_count);
+ ++m_parser_state.capture_groups_count;
+ }
+ }
+ should_parse_repetition_symbol = true;
+ break;
+ }
+
+ return false;
+ }
+
+ if (match_repetition_symbol()) {
+ if (should_parse_repetition_symbol)
+ parse_repetition_symbol(bytecode, length);
+ else
+ return set_error(Error::InvalidRepetitionMarker);
+ }
+
+ stack.append(move(bytecode));
+ match_length_minimum += length;
+
+ return true;
+}
+
+bool PosixExtendedParser::parse_root(ByteCode& stack, size_t& match_length_minimum)
+{
+ ByteCode bytecode_left;
+ size_t match_length_minimum_left { 0 };
+
+ if (match_repetition_symbol())
+ return set_error(Error::InvalidRepetitionMarker);
+
+ for (;;) {
+ if (!parse_sub_expression(bytecode_left, match_length_minimum_left))
+ break;
+
+ if (match(TokenType::Pipe)) {
+ consume();
+
+ ByteCode bytecode_right;
+ size_t match_length_minimum_right { 0 };
+
+ if (!parse_root(bytecode_right, match_length_minimum_right) || bytecode_right.is_empty())
+ return set_error(Error::InvalidPattern);
+
+ ByteCode new_bytecode;
+ new_bytecode.insert_bytecode_alternation(move(bytecode_left), move(bytecode_right));
+ bytecode_left = move(new_bytecode);
+ match_length_minimum_left = min(match_length_minimum_right, match_length_minimum_left);
+ }
+ }
+
+ if (bytecode_left.is_empty())
+ set_error(Error::EmptySubExpression);
+
+ stack.append(move(bytecode_left));
+ match_length_minimum = match_length_minimum_left;
+ return !has_error();
+}
+
+// =============================
+// ECMA262 Parser
+// =============================
+
+bool ECMA262Parser::parse_internal(ByteCode& stack, size_t& match_length_minimum)
+{
+ if (m_parser_state.regex_options.has_flag_set(AllFlags::Unicode)) {
+ return parse_pattern(stack, match_length_minimum, true, true);
+ } else {
+ ByteCode new_stack;
+ size_t new_match_length = 0;
+ auto res = parse_pattern(new_stack, new_match_length, false, false);
+ if (m_parser_state.named_capture_groups_count > 0) {
+ reset();
+ return parse_pattern(stack, match_length_minimum, false, true);
+ }
+
+ if (!res)
+ return false;
+
+ stack.append(new_stack);
+ match_length_minimum = new_match_length;
+ return res;
+ }
+}
+
+bool ECMA262Parser::parse_pattern(ByteCode& stack, size_t& match_length_minimum, bool unicode, bool named)
+{
+ return parse_disjunction(stack, match_length_minimum, unicode, named);
+}
+
+bool ECMA262Parser::parse_disjunction(ByteCode& stack, size_t& match_length_minimum, bool unicode, bool named)
+{
+ ByteCode left_alternative_stack;
+ size_t left_alternative_min_length = 0;
+ auto alt_ok = parse_alternative(left_alternative_stack, left_alternative_min_length, unicode, named);
+ if (!alt_ok)
+ return false;
+
+ if (!match(TokenType::Pipe)) {
+ stack.append(left_alternative_stack);
+ match_length_minimum = left_alternative_min_length;
+ return alt_ok;
+ }
+
+ consume();
+ ByteCode right_alternative_stack;
+ size_t right_alternative_min_length = 0;
+ auto continuation_ok = parse_disjunction(right_alternative_stack, right_alternative_min_length, unicode, named);
+ if (!continuation_ok)
+ return false;
+
+ stack.insert_bytecode_alternation(move(left_alternative_stack), move(right_alternative_stack));
+ match_length_minimum = min(left_alternative_min_length, right_alternative_min_length);
+ return continuation_ok;
+}
+
+bool ECMA262Parser::parse_alternative(ByteCode& stack, size_t& match_length_minimum, bool unicode, bool named)
+{
+ for (;;) {
+ if (match(TokenType::Eof))
+ return true;
+
+ if (parse_term(stack, match_length_minimum, unicode, named))
+ continue;
+
+ return !has_error();
+ }
+}
+
+bool ECMA262Parser::parse_term(ByteCode& stack, size_t& match_length_minimum, bool unicode, bool named)
+{
+ if (parse_assertion(stack, match_length_minimum, unicode, named))
+ return true;
+
+ ByteCode atom_stack;
+ size_t minimum_atom_length = 0;
+ if (!parse_atom(atom_stack, minimum_atom_length, unicode, named))
+ return false;
+
+ if (!parse_quantifier(atom_stack, minimum_atom_length, unicode, named))
+ return false;
+
+ stack.append(move(atom_stack));
+ match_length_minimum += minimum_atom_length;
+ return true;
+}
+
+bool ECMA262Parser::parse_assertion(ByteCode& stack, [[maybe_unused]] size_t& match_length_minimum, bool unicode, bool named)
+{
+ if (match(TokenType::Circumflex)) {
+ consume();
+ stack.empend((ByteCodeValueType)OpCodeId::CheckBegin);
+ return true;
+ }
+
+ if (match(TokenType::Dollar)) {
+ consume();
+ stack.empend((ByteCodeValueType)OpCodeId::CheckEnd);
+ return true;
+ }
+
+ if (try_skip("\\b")) {
+ stack.insert_bytecode_check_boundary(BoundaryCheckType::Word);
+ return true;
+ }
+
+ if (try_skip("\\B")) {
+ stack.insert_bytecode_check_boundary(BoundaryCheckType::NonWord);
+ return true;
+ }
+
+ if (match(TokenType::LeftParen)) {
+ if (!try_skip("(?"))
+ return false;
+
+ if (done()) {
+ set_error(Error::InvalidCaptureGroup);
+ return false;
+ }
+
+ ByteCode assertion_stack;
+ size_t length_dummy = 0;
+
+ auto parse_inner_disjunction = [&] {
+ auto disjunction_ok = parse_disjunction(assertion_stack, length_dummy, unicode, named);
+ if (!disjunction_ok)
+ return false;
+ consume(TokenType::RightParen, Error::MismatchingParen);
+ return true;
+ };
+
+ if (try_skip("=")) {
+ if (!parse_inner_disjunction())
+ return false;
+ stack.insert_bytecode_lookaround(move(assertion_stack), ByteCode::LookAroundType::LookAhead);
+ return true;
+ }
+ if (try_skip("!")) {
+ if (!parse_inner_disjunction())
+ return false;
+ stack.insert_bytecode_lookaround(move(assertion_stack), ByteCode::LookAroundType::NegatedLookAhead);
+ return true;
+ }
+ if (try_skip("<=")) {
+ if (!parse_inner_disjunction())
+ return false;
+ // FIXME: Somehow ensure that this assertion regexp has a fixed length.
+ stack.insert_bytecode_lookaround(move(assertion_stack), ByteCode::LookAroundType::LookBehind, length_dummy);
+ return true;
+ }
+ if (try_skip("<!")) {
+ if (!parse_inner_disjunction())
+ return false;
+ stack.insert_bytecode_lookaround(move(assertion_stack), ByteCode::LookAroundType::NegatedLookBehind, length_dummy);
+ return true;
+ }
+
+ // If none of these matched, put the '(?' back.
+ m_parser_state.lexer.back(3);
+ m_parser_state.current_token = m_parser_state.lexer.next();
+ return false;
+ }
+
+ return false;
+}
+
+Optional<unsigned> ECMA262Parser::read_digits(ECMA262Parser::ReadDigitsInitialZeroState initial_zero, ECMA262Parser::ReadDigitFollowPolicy follow_policy, bool hex, int max_count)
+{
+ if (!match(TokenType::Char))
+ return {};
+
+ if (initial_zero != ReadDigitsInitialZeroState::Allow) {
+ auto has_initial_zero = m_parser_state.current_token.value() == "0";
+ if (initial_zero == ReadDigitsInitialZeroState::Disallow && has_initial_zero)
+ return {};
+
+ if (initial_zero == ReadDigitsInitialZeroState::Require && !has_initial_zero)
+ return {};
+ }
+
+ int count = 0;
+ size_t offset = 0;
+ auto start_token = m_parser_state.current_token;
+ while (match(TokenType::Char)) {
+ auto c = m_parser_state.current_token.value();
+ if (follow_policy == ReadDigitFollowPolicy::DisallowDigit) {
+ if (hex && AK::StringUtils::convert_to_uint_from_hex(c).has_value())
+ break;
+ if (!hex && c.to_uint().has_value())
+ break;
+ }
+
+ if (follow_policy == ReadDigitFollowPolicy::DisallowNonDigit) {
+ if (hex && !AK::StringUtils::convert_to_uint_from_hex(c).has_value())
+ break;
+ if (!hex && !c.to_uint().has_value())
+ break;
+ }
+
+ if (max_count > 0 && count >= max_count)
+ break;
+
+ offset += consume().value().length();
+ ++count;
+ }
+
+ StringView str { start_token.value().characters_without_null_termination(), offset };
+ if (hex)
+ return AK::StringUtils::convert_to_uint_from_hex(str);
+
+ return str.to_uint();
+}
+
+bool ECMA262Parser::parse_quantifier(ByteCode& stack, size_t& match_length_minimum, bool, bool)
+{
+ enum class Repetition {
+ OneOrMore,
+ ZeroOrMore,
+ Optional,
+ Explicit,
+ None,
+ } repetition_mark { Repetition::None };
+
+ bool ungreedy = false;
+ Optional<size_t> repeat_min, repeat_max;
+
+ if (match(TokenType::Asterisk)) {
+ consume();
+ repetition_mark = Repetition::ZeroOrMore;
+ } else if (match(TokenType::Plus)) {
+ consume();
+ repetition_mark = Repetition::OneOrMore;
+ } else if (match(TokenType::Questionmark)) {
+ consume();
+ repetition_mark = Repetition::Optional;
+ } else if (match(TokenType::LeftCurly)) {
+ consume();
+ repetition_mark = Repetition::Explicit;
+
+ auto low_bound = read_digits();
+
+ if (!low_bound.has_value()) {
+ set_error(Error::InvalidBraceContent);
+ return false;
+ }
+
+ repeat_min = low_bound.value();
+
+ if (match(TokenType::Comma)) {
+ consume();
+ auto high_bound = read_digits();
+ if (!high_bound.has_value()) {
+ set_error(Error::InvalidBraceContent);
+ return false;
+ }
+
+ repeat_max = high_bound.value();
+ }
+
+ if (!match(TokenType::RightCurly)) {
+ set_error(Error::MismatchingBrace);
+ return false;
+ }
+ consume();
+
+ if (repeat_max.has_value()) {
+ if (repeat_min.value() > repeat_max.value())
+ set_error(Error::InvalidBraceContent);
+ }
+ } else {
+ return true;
+ }
+
+ if (match(TokenType::Questionmark)) {
+ if (repetition_mark == Repetition::Explicit) {
+ set_error(Error::InvalidRepetitionMarker);
+ return false;
+ }
+ consume();
+ ungreedy = true;
+ }
+
+ ByteCode new_bytecode;
+ switch (repetition_mark) {
+ case Repetition::OneOrMore:
+ new_bytecode.insert_bytecode_repetition_min_one(stack, !ungreedy);
+ break;
+ case Repetition::ZeroOrMore:
+ new_bytecode.insert_bytecode_repetition_any(stack, !ungreedy);
+ match_length_minimum = 0;
+ break;
+ case Repetition::Optional:
+ new_bytecode.insert_bytecode_repetition_zero_or_one(stack, !ungreedy);
+ match_length_minimum = 0;
+ break;
+ case Repetition::Explicit:
+ new_bytecode.insert_bytecode_repetition_min_max(stack, repeat_min.value(), repeat_max);
+ match_length_minimum *= repeat_min.value();
+ break;
+ case Repetition::None:
+ ASSERT_NOT_REACHED();
+ }
+
+ return true;
+}
+
+bool ECMA262Parser::parse_atom(ByteCode& stack, size_t& match_length_minimum, bool unicode, bool named)
+{
+ if (match(TokenType::EscapeSequence)) {
+ // Also part of AtomEscape.
+ auto token = consume();
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)token.value()[0] } });
+ return true;
+ }
+ if (try_skip("\\")) {
+ // AtomEscape.
+ return parse_atom_escape(stack, match_length_minimum, unicode, named);
+ }
+
+ if (match(TokenType::LeftBracket)) {
+ // Character class.
+ return parse_character_class(stack, match_length_minimum, unicode, named);
+ }
+
+ if (match(TokenType::LeftParen)) {
+ // Non-capturing group, or a capture group.
+ return parse_capture_group(stack, match_length_minimum, unicode, named);
+ }
+
+ if (match(TokenType::Period)) {
+ consume();
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::AnyChar, 0 } });
+ return true;
+ }
+
+ if (match(TokenType::Circumflex) || match(TokenType::Dollar) || match(TokenType::RightBracket)
+ || match(TokenType::RightCurly) || match(TokenType::RightParen) || match(TokenType::Pipe)
+ || match(TokenType::Plus) || match(TokenType::Asterisk) || match(TokenType::Questionmark)) {
+
+ return false;
+ }
+
+ if (match_ordinary_characters()) {
+ auto token = consume().value();
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)token[0] } });
+ return true;
+ }
+
+ set_error(Error::InvalidPattern);
+ return false;
+}
+
+bool ECMA262Parser::parse_atom_escape(ByteCode& stack, size_t& match_length_minimum, bool unicode, bool named)
+{
+ if (auto escape = read_digits(ReadDigitsInitialZeroState::Disallow, ReadDigitFollowPolicy::DisallowNonDigit); escape.has_value()) {
+ auto maybe_length = m_parser_state.capture_group_minimum_lengths.get(escape.value());
+ if (!maybe_length.has_value()) {
+ set_error(Error::InvalidNumber);
+ return false;
+ }
+ match_length_minimum += maybe_length.value();
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Reference, (ByteCodeValueType)escape.value() } });
+ return true;
+ }
+
+ // CharacterEscape > ControlEscape
+ if (try_skip("f")) {
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)'\f' } });
+ return true;
+ }
+
+ if (try_skip("n")) {
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)'\n' } });
+ return true;
+ }
+
+ if (try_skip("r")) {
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)'\r' } });
+ return true;
+ }
+
+ if (try_skip("t")) {
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)'\t' } });
+ return true;
+ }
+
+ if (try_skip("v")) {
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)'\v' } });
+ return true;
+ }
+
+ // CharacterEscape > ControlLetter
+ if (try_skip("c")) {
+ for (auto c = 'A'; c <= 'z'; ++c) {
+ if (try_skip({ &c, 1 })) {
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)(c & 0x3f) } });
+ return true;
+ }
+ }
+
+ if (unicode) {
+ set_error(Error::InvalidPattern);
+ return false;
+ }
+
+ // Allow '\c' in non-unicode mode, just matches 'c'.
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)'c' } });
+ return true;
+ }
+
+ // '\0'
+ if (read_digits(ReadDigitsInitialZeroState::Require, ReadDigitFollowPolicy::DisallowDigit).has_value()) {
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)0 } });
+ return true;
+ }
+
+ // HexEscape
+ if (try_skip("x")) {
+ if (auto hex_escape = read_digits(ReadDigitsInitialZeroState::Allow, ReadDigitFollowPolicy::Any, true, 2); hex_escape.has_value()) {
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)hex_escape.value() } });
+ return true;
+ } else if (!unicode) {
+ // '\x' is allowed in non-unicode mode, just matches 'x'.
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)'x' } });
+ return true;
+ } else {
+ set_error(Error::InvalidPattern);
+ return false;
+ }
+ }
+
+ if (try_skip("u")) {
+ if (auto code_point = read_digits(ReadDigitsInitialZeroState::Allow, ReadDigitFollowPolicy::Any, true, 4); code_point.has_value()) {
+ // FIXME: The minimum length depends on the mode - should be utf8-length in u8 mode.
+ match_length_minimum += 1;
+ StringBuilder builder;
+ builder.append_code_point(code_point.value());
+ // FIXME: This isn't actually correct for ECMAScript.
+ auto u8_encoded = builder.string_view();
+ stack.insert_bytecode_compare_string(u8_encoded);
+ return true;
+ } else if (!unicode) {
+ // '\u' is allowed in non-unicode mode, just matches 'u'.
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)'u' } });
+ return true;
+ } else {
+ set_error(Error::InvalidPattern);
+ return false;
+ }
+ }
+
+ // IdentityEscape
+ for (auto ch : StringView { "^$\\.*+?()[]{}|" }) {
+ if (try_skip({ &ch, 1 })) {
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)ch } });
+ return true;
+ }
+ }
+
+ if (unicode) {
+ if (try_skip("/")) {
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)'/' } });
+ return true;
+ }
+ }
+
+ if (named && try_skip("k")) {
+ auto name = read_capture_group_specifier(true);
+ if (name.is_empty()) {
+ set_error(Error::InvalidNameForCaptureGroup);
+ return false;
+ }
+ auto maybe_length = m_parser_state.named_capture_group_minimum_lengths.get(name);
+ if (!maybe_length.has_value()) {
+ set_error(Error::InvalidNameForCaptureGroup);
+ return false;
+ }
+ match_length_minimum += maybe_length.value();
+
+ stack.insert_bytecode_compare_named_reference(name);
+ return true;
+ }
+
+ if (unicode) {
+ if (try_skip("p{")) {
+ // FIXME: Implement this path, Unicode property match.
+ TODO();
+ }
+ if (try_skip("P{")) {
+ // FIXME: Implement this path, Unicode property match.
+ TODO();
+ }
+ }
+
+ if (done())
+ return set_error(Error::InvalidTrailingEscape);
+
+ bool negate = false;
+ auto ch = parse_character_class_escape(negate);
+ if (!ch.has_value()) {
+ if (!unicode) {
+ // Allow all SourceCharacter's as escapes here.
+ auto token = consume();
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values({ { CharacterCompareType::Char, (ByteCodeValueType)token.value()[0] } });
+ return true;
+ }
+
+ set_error(Error::InvalidCharacterClass);
+ return false;
+ }
+
+ Vector<CompareTypeAndValuePair> compares;
+ if (negate)
+ compares.empend(CompareTypeAndValuePair { CharacterCompareType::Inverse, 0 });
+ compares.empend(CompareTypeAndValuePair { CharacterCompareType::CharClass, (ByteCodeValueType)ch.value() });
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values(move(compares));
+ return true;
+}
+
+Optional<CharClass> ECMA262Parser::parse_character_class_escape(bool& negate, bool expect_backslash)
+{
+ if (expect_backslash && !try_skip("\\"))
+ return {};
+
+ // CharacterClassEscape
+ CharClass ch_class;
+ if (try_skip("d")) {
+ ch_class = CharClass::Digit;
+ } else if (try_skip("D")) {
+ ch_class = CharClass::Digit;
+ negate = true;
+ } else if (try_skip("s")) {
+ ch_class = CharClass::Space;
+ } else if (try_skip("S")) {
+ ch_class = CharClass::Space;
+ negate = true;
+ } else if (try_skip("w")) {
+ ch_class = CharClass::Word;
+ } else if (try_skip("W")) {
+ ch_class = CharClass::Word;
+ negate = true;
+ } else {
+ return {};
+ }
+
+ return ch_class;
+}
+
+bool ECMA262Parser::parse_character_class(ByteCode& stack, size_t& match_length_minimum, bool unicode, bool)
+{
+ consume(TokenType::LeftBracket, Error::InvalidPattern);
+
+ Vector<CompareTypeAndValuePair> compares;
+
+ if (match(TokenType::Circumflex)) {
+ // Negated charclass
+ consume();
+ compares.empend(CompareTypeAndValuePair { CharacterCompareType::Inverse, 0 });
+ }
+
+ if (match(TokenType::RightBracket)) {
+ consume();
+ return true;
+ }
+
+ if (!parse_nonempty_class_ranges(compares, unicode))
+ return false;
+
+ match_length_minimum += 1;
+ stack.insert_bytecode_compare_values(move(compares));
+ return true;
+}
+
+struct CharClassRangeElement {
+ union {
+ CharClass character_class;
+ u32 code_point { 0 };
+ };
+
+ bool is_negated { false };
+ bool is_character_class { false };
+};
+
+bool ECMA262Parser::parse_nonempty_class_ranges(Vector<CompareTypeAndValuePair>& ranges, bool unicode)
+{
+ auto read_class_atom_no_dash = [&]() -> Optional<CharClassRangeElement> {
+ if (match(TokenType::EscapeSequence)) {
+ auto token = consume().value();
+ return { { .code_point = (u32)token[1], .is_character_class = false } };
+ }
+
+ if (try_skip("\\")) {
+ if (done()) {
+ set_error(Error::InvalidTrailingEscape);
+ return {};
+ }
+
+ if (try_skip("f"))
+ return { { .code_point = '\f', .is_character_class = false } };
+ if (try_skip("n"))
+ return { { .code_point = '\n', .is_character_class = false } };
+ if (try_skip("r"))
+ return { { .code_point = '\r', .is_character_class = false } };
+ if (try_skip("t"))
+ return { { .code_point = '\t', .is_character_class = false } };
+ if (try_skip("v"))
+ return { { .code_point = '\v', .is_character_class = false } };
+ if (try_skip("b"))
+ return { { .code_point = '\b', .is_character_class = false } };
+ if (try_skip("/"))
+ return { { .code_point = '/', .is_character_class = false } };
+
+ // CharacterEscape > ControlLetter
+ if (try_skip("c")) {
+ for (auto c = 'A'; c <= 'z'; ++c) {
+ if (try_skip({ &c, 1 }))
+ return { { .code_point = (u32)(c & 0x3f), .is_character_class = false } };
+ }
+ }
+
+ // '\0'
+ if (read_digits(ReadDigitsInitialZeroState::Require, ReadDigitFollowPolicy::DisallowDigit).has_value())
+ return { { .code_point = 0, .is_character_class = false } };
+
+ // HexEscape
+ if (try_skip("x")) {
+ if (auto hex_escape = read_digits(ReadDigitsInitialZeroState::Allow, ReadDigitFollowPolicy::Any, true, 2); hex_escape.has_value()) {
+ return { { .code_point = hex_escape.value(), .is_character_class = false } };
+ } else if (!unicode) {
+ // '\x' is allowed in non-unicode mode, just matches 'x'.
+ return { { .code_point = 'x', .is_character_class = false } };
+ } else {
+ set_error(Error::InvalidPattern);
+ return {};
+ }
+ }
+
+ if (try_skip("u")) {
+ if (auto code_point = read_digits(ReadDigitsInitialZeroState::Allow, ReadDigitFollowPolicy::Any, true, 4); code_point.has_value()) {
+ // FIXME: While codepoint ranges are supported, codepoint matches as "Char" are not!
+ return { { .code_point = code_point.value(), .is_character_class = false } };
+ } else if (!unicode) {
+ // '\u' is allowed in non-unicode mode, just matches 'u'.
+ return { { .code_point = 'u', .is_character_class = false } };
+ } else {
+ set_error(Error::InvalidPattern);
+ return {};
+ }
+ }
+
+ if (unicode) {
+ if (try_skip("-"))
+ return { { .code_point = '-', .is_character_class = false } };
+ }
+
+ if (try_skip("p{") || try_skip("P{")) {
+ // FIXME: Implement these; unicode properties.
+ TODO();
+ }
+
+ if (try_skip("d"))
+ return { { .character_class = CharClass::Digit, .is_character_class = true } };
+ if (try_skip("s"))
+ return { { .character_class = CharClass::Space, .is_character_class = true } };
+ if (try_skip("w"))
+ return { { .character_class = CharClass::Word, .is_character_class = true } };
+ if (try_skip("D"))
+ return { { .character_class = CharClass::Digit, .is_negated = true, .is_character_class = true } };
+ if (try_skip("S"))
+ return { { .character_class = CharClass::Space, .is_negated = true, .is_character_class = true } };
+ if (try_skip("W"))
+ return { { .character_class = CharClass::Word, .is_negated = true, .is_character_class = true } };
+
+ if (!unicode) {
+ // Any unrecognised escape is allowed in non-unicode mode.
+ return { { .code_point = (u32)skip(), .is_character_class = false } };
+ }
+ }
+
+ if (match(TokenType::RightBracket) || match(TokenType::HyphenMinus))
+ return {};
+
+ // Allow any (other) SourceCharacter.
+ return { { .code_point = (u32)skip(), .is_character_class = false } };
+ };
+ auto read_class_atom = [&]() -> Optional<CharClassRangeElement> {
+ if (match(TokenType::HyphenMinus)) {
+ consume();
+ return { { .code_point = '-', .is_character_class = false } };
+ }
+
+ return read_class_atom_no_dash();
+ };
+
+ while (!match(TokenType::RightBracket)) {
+ if (match(TokenType::Eof)) {
+ set_error(Error::MismatchingBracket);
+ return false;
+ }
+
+ auto first_atom = read_class_atom();
+ if (!first_atom.has_value())
+ return false;
+
+ if (match(TokenType::HyphenMinus)) {
+ consume();
+ if (match(TokenType::RightBracket)) {
+ // Allow '-' as the last element in a charclass, even after an atom.
+ m_parser_state.lexer.back(2); // -]
+ m_parser_state.current_token = m_parser_state.lexer.next();
+ goto read_as_single_atom;
+ }
+ auto second_atom = read_class_atom();
+ if (!second_atom.has_value())
+ return false;
+
+ if (first_atom.value().is_character_class || second_atom.value().is_character_class) {
+ set_error(Error::InvalidRange);
+ return false;
+ }
+
+ if (first_atom.value().code_point > second_atom.value().code_point) {
+ set_error(Error::InvalidRange);
+ return false;
+ }
+
+ ASSERT(!first_atom.value().is_negated);
+ ASSERT(!second_atom.value().is_negated);
+
+ ranges.empend(CompareTypeAndValuePair { CharacterCompareType::CharRange, CharRange { first_atom.value().code_point, second_atom.value().code_point } });
+ continue;
+ }
+
+ read_as_single_atom:;
+
+ auto atom = first_atom.value();
+
+ if (atom.is_character_class) {
+ if (atom.is_negated)
+ ranges.empend(CompareTypeAndValuePair { CharacterCompareType::TemporaryInverse, 0 });
+ ranges.empend(CompareTypeAndValuePair { CharacterCompareType::CharClass, (ByteCodeValueType)first_atom.value().character_class });
+ } else {
+ ASSERT(!atom.is_negated);
+ ranges.empend(CompareTypeAndValuePair { CharacterCompareType::Char, first_atom.value().code_point });
+ }
+ }
+
+ consume(TokenType::RightBracket, Error::MismatchingBracket);
+
+ return true;
+}
+
+StringView ECMA262Parser::read_capture_group_specifier(bool take_starting_angle_bracket)
+{
+ if (take_starting_angle_bracket && !consume("<"))
+ return {};
+
+ auto start_token = m_parser_state.current_token;
+ size_t offset = 0;
+ while (match(TokenType::Char)) {
+ auto c = m_parser_state.current_token.value();
+ if (c == ">")
+ break;
+ offset += consume().value().length();
+ }
+
+ StringView name { start_token.value().characters_without_null_termination(), offset };
+ if (!consume(">") || name.is_empty())
+ set_error(Error::InvalidNameForCaptureGroup);
+
+ return name;
+}
+
+bool ECMA262Parser::parse_capture_group(ByteCode& stack, size_t& match_length_minimum, bool unicode, bool named)
+{
+ consume(TokenType::LeftParen, Error::InvalidPattern);
+
+ if (match(TokenType::Questionmark)) {
+ // Non-capturing group or group with specifier.
+ consume();
+
+ if (match(TokenType::Colon)) {
+ consume();
+ ByteCode noncapture_group_bytecode;
+ size_t length = 0;
+ if (!parse_disjunction(noncapture_group_bytecode, length, unicode, named))
+ return set_error(Error::InvalidPattern);
+
+ consume(TokenType::RightParen, Error::MismatchingParen);
+
+ stack.append(move(noncapture_group_bytecode));
+ match_length_minimum += length;
+ return true;
+ }
+
+ if (consume("<")) {
+ ++m_parser_state.named_capture_groups_count;
+ auto name = read_capture_group_specifier();
+
+ if (name.is_empty()) {
+ set_error(Error::InvalidNameForCaptureGroup);
+ return false;
+ }
+
+ ByteCode capture_group_bytecode;
+ size_t length = 0;
+ if (!parse_disjunction(capture_group_bytecode, length, unicode, named))
+ return set_error(Error::InvalidPattern);
+
+ consume(TokenType::RightParen, Error::MismatchingParen);
+
+ stack.insert_bytecode_group_capture_left(name);
+ stack.append(move(capture_group_bytecode));
+ stack.insert_bytecode_group_capture_right(name);
+
+ match_length_minimum += length;
+
+ m_parser_state.named_capture_group_minimum_lengths.set(name, length);
+ return true;
+ }
+
+ set_error(Error::InvalidCaptureGroup);
+ return false;
+ }
+
+ auto group_index = ++m_parser_state.capture_groups_count;
+ stack.insert_bytecode_group_capture_left(group_index);
+
+ ByteCode capture_group_bytecode;
+ size_t length = 0;
+
+ if (!parse_disjunction(capture_group_bytecode, length, unicode, named))
+ return set_error(Error::InvalidPattern);
+
+ stack.append(move(capture_group_bytecode));
+
+ m_parser_state.capture_group_minimum_lengths.set(group_index, length);
+
+ consume(TokenType::RightParen, Error::MismatchingParen);
+
+ stack.insert_bytecode_group_capture_right(group_index);
+
+ match_length_minimum += length;
+
+ return true;
+}
+}
diff --git a/Userland/Libraries/LibRegex/RegexParser.h b/Userland/Libraries/LibRegex/RegexParser.h
new file mode 100644
index 0000000000..eb80c6f583
--- /dev/null
+++ b/Userland/Libraries/LibRegex/RegexParser.h
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "RegexByteCode.h"
+#include "RegexError.h"
+#include "RegexLexer.h"
+#include "RegexOptions.h"
+
+#include <AK/Forward.h>
+#include <AK/StringBuilder.h>
+#include <AK/Types.h>
+#include <AK/Vector.h>
+
+namespace regex {
+
+class PosixExtendedParser;
+class ECMA262Parser;
+
+template<typename T>
+struct GenericParserTraits {
+ using OptionsType = T;
+};
+
+template<typename T>
+struct ParserTraits : public GenericParserTraits<T> {
+};
+
+template<>
+struct ParserTraits<PosixExtendedParser> : public GenericParserTraits<PosixOptions> {
+};
+
+template<>
+struct ParserTraits<ECMA262Parser> : public GenericParserTraits<ECMAScriptOptions> {
+};
+
+class Parser {
+public:
+ struct Result {
+ ByteCode bytecode;
+ size_t capture_groups_count;
+ size_t named_capture_groups_count;
+ size_t match_length_minimum;
+ Error error;
+ Token error_token;
+ };
+
+ explicit Parser(Lexer& lexer)
+ : m_parser_state(lexer)
+ {
+ }
+
+ Parser(Lexer& lexer, AllOptions regex_options)
+ : m_parser_state(lexer, regex_options)
+ {
+ }
+
+ virtual ~Parser() = default;
+
+ Result parse(Optional<AllOptions> regex_options = {});
+ bool has_error() const { return m_parser_state.error != Error::NoError; }
+ Error error() const { return m_parser_state.error; }
+
+protected:
+ virtual bool parse_internal(ByteCode&, size_t& match_length_minimum) = 0;
+
+ ALWAYS_INLINE bool match(TokenType type) const;
+ ALWAYS_INLINE bool match(char ch) const;
+ ALWAYS_INLINE bool match_ordinary_characters();
+ ALWAYS_INLINE Token consume();
+ ALWAYS_INLINE Token consume(TokenType type, Error error);
+ ALWAYS_INLINE bool consume(const String&);
+ ALWAYS_INLINE bool try_skip(StringView);
+ ALWAYS_INLINE char skip();
+ ALWAYS_INLINE void reset();
+ ALWAYS_INLINE bool done() const;
+ ALWAYS_INLINE bool set_error(Error error);
+
+ struct ParserState {
+ Lexer& lexer;
+ Token current_token;
+ Error error = Error::NoError;
+ Token error_token { TokenType::Eof, 0, StringView(nullptr) };
+ ByteCode bytecode;
+ size_t capture_groups_count { 0 };
+ size_t named_capture_groups_count { 0 };
+ size_t match_length_minimum { 0 };
+ AllOptions regex_options;
+ HashMap<int, size_t> capture_group_minimum_lengths;
+ HashMap<FlyString, size_t> named_capture_group_minimum_lengths;
+ HashMap<size_t, FlyString> named_capture_groups;
+
+ explicit ParserState(Lexer& lexer)
+ : lexer(lexer)
+ , current_token(lexer.next())
+ {
+ }
+ explicit ParserState(Lexer& lexer, AllOptions regex_options)
+ : lexer(lexer)
+ , current_token(lexer.next())
+ , regex_options(regex_options)
+ {
+ }
+ };
+
+ ParserState m_parser_state;
+};
+
+class PosixExtendedParser final : public Parser {
+public:
+ explicit PosixExtendedParser(Lexer& lexer)
+ : Parser(lexer)
+ {
+ }
+
+ PosixExtendedParser(Lexer& lexer, Optional<typename ParserTraits<PosixExtendedParser>::OptionsType> regex_options)
+ : Parser(lexer, regex_options.value_or({}))
+ {
+ }
+
+ ~PosixExtendedParser() = default;
+
+private:
+ ALWAYS_INLINE bool match_repetition_symbol();
+
+ bool parse_internal(ByteCode&, size_t&) override;
+
+ bool parse_root(ByteCode&, size_t&);
+ ALWAYS_INLINE bool parse_sub_expression(ByteCode&, size_t&);
+ ALWAYS_INLINE bool parse_bracket_expression(ByteCode&, size_t&);
+ ALWAYS_INLINE bool parse_repetition_symbol(ByteCode&, size_t&);
+};
+
+class ECMA262Parser final : public Parser {
+public:
+ explicit ECMA262Parser(Lexer& lexer)
+ : Parser(lexer)
+ {
+ }
+
+ ECMA262Parser(Lexer& lexer, Optional<typename ParserTraits<ECMA262Parser>::OptionsType> regex_options)
+ : Parser(lexer, regex_options.value_or({}))
+ {
+ }
+
+ ~ECMA262Parser() = default;
+
+private:
+ bool parse_internal(ByteCode&, size_t&) override;
+
+ enum class ReadDigitsInitialZeroState {
+ Allow,
+ Disallow,
+ Require,
+ };
+ enum class ReadDigitFollowPolicy {
+ Any,
+ DisallowDigit,
+ DisallowNonDigit,
+ };
+ Optional<unsigned> read_digits(ReadDigitsInitialZeroState initial_zero = ReadDigitsInitialZeroState::Allow, ReadDigitFollowPolicy follow_policy = ReadDigitFollowPolicy::Any, bool hex = false, int max_count = -1);
+ StringView read_capture_group_specifier(bool take_starting_angle_bracket = false);
+
+ bool parse_pattern(ByteCode&, size_t&, bool unicode, bool named);
+ bool parse_disjunction(ByteCode&, size_t&, bool unicode, bool named);
+ bool parse_alternative(ByteCode&, size_t&, bool unicode, bool named);
+ bool parse_term(ByteCode&, size_t&, bool unicode, bool named);
+ bool parse_assertion(ByteCode&, size_t&, bool unicode, bool named);
+ bool parse_atom(ByteCode&, size_t&, bool unicode, bool named);
+ bool parse_quantifier(ByteCode&, size_t&, bool unicode, bool named);
+ bool parse_atom_escape(ByteCode&, size_t&, bool unicode, bool named);
+ bool parse_character_class(ByteCode&, size_t&, bool unicode, bool named);
+ bool parse_capture_group(ByteCode&, size_t&, bool unicode, bool named);
+ Optional<CharClass> parse_character_class_escape(bool& out_inverse, bool expect_backslash = false);
+ bool parse_nonempty_class_ranges(Vector<CompareTypeAndValuePair>&, bool unicode);
+};
+
+using PosixExtended = PosixExtendedParser;
+using ECMA262 = ECMA262Parser;
+
+}
+
+using regex::ECMA262;
+using regex::PosixExtended;
diff --git a/Userland/Libraries/LibRegex/Tests/Benchmark.cpp b/Userland/Libraries/LibRegex/Tests/Benchmark.cpp
new file mode 100644
index 0000000000..0a25ee1847
--- /dev/null
+++ b/Userland/Libraries/LibRegex/Tests/Benchmark.cpp
@@ -0,0 +1,991 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/TestSuite.h> // import first, to prevent warning of ASSERT* redefinition
+
+#include <LibRegex/Regex.h>
+#include <stdio.h>
+
+#ifndef REGEX_DEBUG
+
+# define BENCHMARK_LOOP_ITERATIONS 100000
+
+//# define REGEX_BENCHMARK_OUR
+# ifndef __serenity__
+//# define REGEX_BENCHMARK_OTHER
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+# include <regex>
+# endif
+
+# if not(defined(REGEX_BENCHMARK_OUR) && defined(REGEX_BENCHMARK_OUR))
+BENCHMARK_CASE(dummy_benchmark)
+{
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(catch_all_benchmark)
+{
+ Regex<PosixExtended> re("^.*$");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT(re.match("Hello World", m));
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(catch_all_benchmark_reference_stdcpp)
+{
+ std::regex re("^.*$");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("Hello World", m, re), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_start_benchmark)
+{
+ Regex<PosixExtended> re("^hello friends");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("Hello!", m), false);
+ EXPECT_EQ(re.match("hello friends", m), true);
+ EXPECT_EQ(re.match("Well, hello friends", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_start_benchmark_reference_stdcpp)
+{
+ std::regex re("^hello friends");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("Hello", m, re), false);
+ EXPECT_EQ(std::regex_match("hello friends", m, re), true);
+ EXPECT_EQ(std::regex_match("Well, hello friends", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_end_benchmark)
+{
+ Regex<PosixExtended> re(".*hello\\.\\.\\. there$");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("Hallo", m), false);
+ EXPECT_EQ(re.match("I said fyhello... there", m), true);
+ EXPECT_EQ(re.match("ahello... therea", m), false);
+ EXPECT_EQ(re.match("hello.. there", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_end_benchmark_reference_stdcpp)
+{
+ std::regex re(".*hello\\.\\.\\. there$");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_search("Hallo", m, re), false);
+ EXPECT_EQ(std::regex_search("I said fyhello... there", m, re), true);
+ EXPECT_EQ(std::regex_search("ahello... therea", m, re), false);
+ EXPECT_EQ(std::regex_search("hello.. there", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_period_benchmark)
+{
+ Regex<PosixExtended> re("hello.");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("Hello1", m), false);
+ EXPECT_EQ(re.match("hello1", m), true);
+ EXPECT_EQ(re.match("hello2", m), true);
+ EXPECT_EQ(re.match("hello?", m), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_period_benchmark_reference_stdcpp)
+{
+ std::regex re("hello.");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("Hello1", m, re), false);
+ EXPECT_EQ(std::regex_match("hello1", m, re), true);
+ EXPECT_EQ(std::regex_match("hello2", m, re), true);
+ EXPECT_EQ(std::regex_match("hello?", m, re), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_period_end_benchmark)
+{
+ Regex<PosixExtended> re("hello.$");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.search("Hello1", m), false);
+ EXPECT_EQ(re.search("hello1hello1", m), true);
+ EXPECT_EQ(re.search("hello2hell", m), false);
+ EXPECT_EQ(re.search("hello?", m), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_period_end_benchmark_reference_stdcpp)
+{
+ std::regex re("hello.$");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_search("Hello1", m, re), false);
+ EXPECT_EQ(std::regex_search("hello1hello1", m, re), true);
+ EXPECT_EQ(std::regex_search("hello2hell", m, re), false);
+ EXPECT_EQ(std::regex_search("hello?", m, re), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_escaped_benchmark)
+{
+ Regex<PosixExtended> re("hello\\.");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("hello", m), false);
+ EXPECT_EQ(re.match("hello.", m), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_escaped_benchmark_reference_stdcpp)
+{
+ std::regex re("hello\\.");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("hello", m, re), false);
+ EXPECT_EQ(std::regex_match("hello.", m, re), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_period2_end_benchmark)
+{
+ Regex<PosixExtended> re(".*hi... there$");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.search("Hello there", m), false);
+ EXPECT_EQ(re.search("I said fyhi... there", m), true);
+ EXPECT_EQ(re.search("....hi... ", m), false);
+ EXPECT_EQ(re.search("I said fyhihii there", m), true);
+ EXPECT_EQ(re.search("I said fyhihi there", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_period2_end_benchmark_reference_stdcpp)
+{
+ std::regex re(".*hi... there$");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_search("Hello there", m, re), false);
+ EXPECT_EQ(std::regex_search("I said fyhi... there", m, re), true);
+ EXPECT_EQ(std::regex_search("....hi... ", m, re), false);
+ EXPECT_EQ(std::regex_search("I said fyhihii there", m, re), true);
+ EXPECT_EQ(std::regex_search("I said fyhihi there", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_plus_benchmark)
+{
+ Regex<PosixExtended> re("a+");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.search("b", m), false);
+ EXPECT_EQ(re.search("a", m), true);
+ EXPECT_EQ(re.search("aaaaaabbbbb", m), true);
+ EXPECT_EQ(re.search("aaaaaaaaaaa", m), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_plus_benchmark_reference_stdcpp)
+{
+ std::regex re("a+");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_search("b", m, re), false);
+ EXPECT_EQ(std::regex_search("a", m, re), true);
+ EXPECT_EQ(std::regex_search("aaaaaabbbbb", m, re), true);
+ EXPECT_EQ(std::regex_search("aaaaaaaaaaa", m, re), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_questionmark_benchmark)
+{
+ Regex<PosixExtended> re("da?d");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.search("a", m), false);
+ EXPECT_EQ(re.search("daa", m), false);
+ EXPECT_EQ(re.search("ddddd", m), true);
+ EXPECT_EQ(re.search("dd", m), true);
+ EXPECT_EQ(re.search("dad", m), true);
+ EXPECT_EQ(re.search("dada", m), true);
+ EXPECT_EQ(re.search("adadaa", m), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_questionmark_benchmark_reference_stdcpp)
+{
+ std::regex re("da?d");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_search("a", m, re), false);
+ EXPECT_EQ(std::regex_search("daa", m, re), false);
+ EXPECT_EQ(std::regex_search("ddddd", m, re), true);
+ EXPECT_EQ(std::regex_search("dd", m, re), true);
+ EXPECT_EQ(std::regex_search("dad", m, re), true);
+ EXPECT_EQ(std::regex_search("dada", m, re), true);
+ EXPECT_EQ(std::regex_search("adadaa", m, re), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(character_class_benchmark)
+{
+ Regex<PosixExtended> re("[[:alpha:]]");
+ RegexResult m;
+ String haystack = "[Window]\nOpacity=255\nAudibleBeep=0\n";
+
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match(haystack.characters(), m), false);
+ EXPECT_EQ(re.search(haystack.characters(), m), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(character_class_benchmark_reference_stdcpp)
+{
+ std::regex re("[[:alpha:]]");
+ std::cmatch m;
+ String haystack = "[Window]\nOpacity=255\nAudibleBeep=0\n";
+
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match(haystack.characters(), m, re), false);
+ EXPECT_EQ(std::regex_search(haystack.characters(), m, re), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(escaped_char_questionmark_benchmark)
+{
+ Regex<PosixExtended> re("This\\.?And\\.?That");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("ThisAndThat", m), true);
+ EXPECT_EQ(re.match("This.And.That", m), true);
+ EXPECT_EQ(re.match("This And That", m), false);
+ EXPECT_EQ(re.match("This..And..That", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(escaped_char_questionmark_benchmark_reference_stdcpp)
+{
+ std::regex re("This\\.?And\\.?That");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("ThisAndThat", m, re), true);
+ EXPECT_EQ(std::regex_match("This.And.That", m, re), true);
+ EXPECT_EQ(std::regex_match("This And That", m, re), false);
+ EXPECT_EQ(std::regex_match("This..And..That", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(char_qualifier_asterisk_benchmark)
+{
+ Regex<PosixExtended> re("regex*");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.search("#include <regex.h>", m), true);
+ EXPECT_EQ(re.search("#include <stdio.h>", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(char_qualifier_asterisk_benchmark_reference_stdcpp)
+{
+ std::regex re("regex*");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_search("#include <regex.h>", m, re), true);
+ EXPECT_EQ(std::regex_search("#include <stdio.h>", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(parens_qualifier_questionmark_benchmark)
+{
+ Regex<PosixExtended> re("test(hello)?test");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("testtest", m), true);
+ EXPECT_EQ(re.match("testhellotest", m), true);
+ EXPECT_EQ(re.match("testasfdtest", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(parens_qualifier_questionmark_benchmark_reference_stdcpp)
+{
+ std::regex re("test(hello)?test");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("testtest", m, re), true);
+ EXPECT_EQ(std::regex_match("testhellotest", m, re), true);
+ EXPECT_EQ(std::regex_match("testasfdtest", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(parens_qualifier_asterisk_benchmark)
+{
+ Regex<PosixExtended> re("test(hello)*test");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("testtest", m), true);
+ EXPECT_EQ(re.match("testhellohellotest", m), true);
+ EXPECT_EQ(re.search("testhellohellotest, testhellotest", m), true);
+ EXPECT_EQ(re.match("aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbb", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(parens_qualifier_asterisk_benchmark_reference_stdcpp)
+{
+ std::regex re("test(hello)*test");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("testtest", m, re), true);
+ EXPECT_EQ(std::regex_match("testhellohellotest", m, re), true);
+ EXPECT_EQ(std::regex_search("testhellohellotest, testhellotest", m, re), true);
+ EXPECT_EQ(std::regex_match("aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbb", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(parens_qualifier_asterisk_2_benchmark)
+{
+ Regex<PosixExtended> re("test(.*)test");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("testasdftest", m), true);
+ EXPECT_EQ(re.match("testasdfasdftest", m), true);
+ EXPECT_EQ(re.search("testaaaatest, testbbbtest, testtest", m), true);
+ EXPECT_EQ(re.match("aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbb", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(parens_qualifier_asterisk_2_benchmark_reference_stdcpp)
+{
+ std::regex re("test(.*)test");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("testasdftest", m, re), true);
+ EXPECT_EQ(std::regex_match("testasdfasdftest", m, re), true);
+ EXPECT_EQ(std::regex_search("testaaaatest, testbbbtest, testtest", m, re), true);
+ EXPECT_EQ(std::regex_match("aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbb", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(multi_parens_qualifier_questionmark_benchmark)
+{
+ Regex<PosixExtended> re("test(a)?(b)?(c)?test");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("testtest", m), true);
+ EXPECT_EQ(re.match("testabctest", m), true);
+ EXPECT_EQ(re.search("testabctest, testactest", m), true);
+ EXPECT_EQ(re.match("aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbb", m), false);
+ EXPECT_EQ(re.match("test", m), false);
+ EXPECT_EQ(re.match("whaaaaat", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(multi_parens_qualifier_questionmark_benchmark_reference_stdcpp)
+{
+ std::regex re("test(a)?(b)?(c)?test");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("testtest", m, re), true);
+ EXPECT_EQ(std::regex_match("testabctest", m, re), true);
+ EXPECT_EQ(std::regex_search("testabctest, testactest", m, re), true);
+ EXPECT_EQ(std::regex_match("aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbb", m, re), false);
+ EXPECT_EQ(std::regex_match("test", m, re), false);
+ EXPECT_EQ(std::regex_match("whaaaaat", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_alternative_benchmark)
+{
+ Regex<PosixExtended> re("test|hello|friends");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("test", m), true);
+ EXPECT_EQ(re.match("hello", m), true);
+ EXPECT_EQ(re.match("friends", m), true);
+ EXPECT_EQ(re.match("whaaaaat", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_alternative_benchmark_reference_stdcpp)
+{
+ std::regex re("test|hello|friends");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("test", m, re), true);
+ EXPECT_EQ(std::regex_match("hello", m, re), true);
+ EXPECT_EQ(std::regex_match("friends", m, re), true);
+ EXPECT_EQ(std::regex_match("whaaaaat", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(alternative_match_groups_benchmark)
+{
+ Regex<PosixExtended> re("test(a)?(b)?|hello ?(dear|my)? friends");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("test", m), true);
+ EXPECT_EQ(re.match("testa", m), true);
+ EXPECT_EQ(re.match("testb", m), true);
+ EXPECT_EQ(re.match("hello friends", m), true);
+ EXPECT_EQ(re.match("hello dear friends", m), true);
+ EXPECT_EQ(re.match("hello my friends", m), true);
+ EXPECT_EQ(re.match("testabc", m), false);
+ EXPECT_EQ(re.match("hello test friends", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(alternative_match_groups_benchmark_reference_stdcpp)
+{
+ std::regex re("test(a)?(b)?|hello ?(dear|my)? friends");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("test", m, re), true);
+ EXPECT_EQ(std::regex_match("testa", m, re), true);
+ EXPECT_EQ(std::regex_match("testb", m, re), true);
+ EXPECT_EQ(std::regex_match("hello friends", m, re), true);
+ EXPECT_EQ(std::regex_match("hello dear friends", m, re), true);
+ EXPECT_EQ(std::regex_match("hello my friends", m, re), true);
+ EXPECT_EQ(std::regex_match("testabc", m, re), false);
+ EXPECT_EQ(std::regex_match("hello test friends", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(parens_qualifier_exact_benchmark)
+{
+ Regex<PosixExtended> re("(hello){3}");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("hello", m), false);
+ EXPECT_EQ(re.match("hellohellohello", m), true);
+ EXPECT_EQ(re.search("hellohellohellohello", m), true);
+ EXPECT_EQ(re.search("test hellohellohello", m), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(parens_qualifier_exact_benchmark_reference_stdcpp)
+{
+ std::regex re("(hello){3}");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("hello", m, re), false);
+ EXPECT_EQ(std::regex_match("hellohellohello", m, re), true);
+ EXPECT_EQ(std::regex_search("hellohellohellohello", m, re), true);
+ EXPECT_EQ(std::regex_search("test hellohellohello", m, re), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(parens_qualifier_minimum_benchmark)
+{
+ Regex<PosixExtended> re("(hello){3,}");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("hello", m), false);
+ EXPECT_EQ(re.match("hellohellohello", m), true);
+ EXPECT_EQ(re.search("hellohellohellohello", m), true);
+ EXPECT_EQ(re.search("test hellohellohello", m), true);
+ EXPECT_EQ(re.search("test hellohellohellohello", m), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(parens_qualifier_minimum_benchmark_reference_stdcpp)
+{
+ std::regex re("(hello){3,}");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("hello", m, re), false);
+ EXPECT_EQ(std::regex_match("hellohellohello", m, re), true);
+ EXPECT_EQ(std::regex_search("hellohellohellohello", m, re), true);
+ EXPECT_EQ(std::regex_search("test hellohellohello", m, re), true);
+ EXPECT_EQ(std::regex_search("test hellohellohellohello", m, re), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(parens_qualifier_maximum_benchmark)
+{
+ Regex<PosixExtended> re("(hello){2,3}");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("hello", m), false);
+ EXPECT_EQ(re.match("hellohellohello", m), true);
+ EXPECT_EQ(re.search("hellohellohellohello", m), true);
+ EXPECT_EQ(re.search("test hellohellohello", m), true);
+ EXPECT_EQ(re.search("test hellohellohellohello", m), true);
+ EXPECT_EQ(re.match("test hellohellohellohello", m), false);
+ EXPECT_EQ(re.search("test hellohellohellohello", m), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(parens_qualifier_maximum_benchmark_reference_stdcpp)
+{
+ std::regex re("(hello){2,3}");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("hello", m, re), false);
+ EXPECT_EQ(std::regex_match("hellohellohello", m, re), true);
+ EXPECT_EQ(std::regex_search("hellohellohellohello", m, re), true);
+ EXPECT_EQ(std::regex_search("test hellohellohello", m, re), true);
+ EXPECT_EQ(std::regex_search("test hellohellohellohello", m, re), true);
+ EXPECT_EQ(std::regex_match("test hellohellohellohello", m, re), false);
+ EXPECT_EQ(std::regex_search("test hellohellohellohello", m, re), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(char_qualifier_min_max_benchmark)
+{
+ Regex<PosixExtended> re("c{3,30}");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("cc", m), false);
+ EXPECT_EQ(re.match("ccc", m), true);
+ EXPECT_EQ(re.match("cccccccccccccccccccccccccccccc", m), true);
+ EXPECT_EQ(re.match("ccccccccccccccccccccccccccccccc", m), false);
+ EXPECT_EQ(re.search("ccccccccccccccccccccccccccccccc", m), true);
+ EXPECT_EQ(re.match("cccccccccccccccccccccccccccccccc", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(char_qualifier_min_max_benchmark_reference_stdcpp)
+{
+ std::regex re("c{3,30}");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("cc", m, re), false);
+ EXPECT_EQ(std::regex_match("ccc", m, re), true);
+ EXPECT_EQ(std::regex_match("cccccccccccccccccccccccccccccc", m, re), true);
+ EXPECT_EQ(std::regex_match("ccccccccccccccccccccccccccccccc", m, re), false);
+ EXPECT_EQ(std::regex_search("ccccccccccccccccccccccccccccccc", m, re), true);
+ EXPECT_EQ(std::regex_match("cccccccccccccccccccccccccccccccc", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_bracket_chars_benchmark)
+{
+ Regex<PosixExtended> re("[abc]");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("a", m), true);
+ EXPECT_EQ(re.match("b", m), true);
+ EXPECT_EQ(re.match("c", m), true);
+ EXPECT_EQ(re.match("d", m), false);
+ EXPECT_EQ(re.match("e", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_bracket_chars_benchmark_reference_stdcpp)
+{
+ std::regex re("[abc]");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("a", m, re), true);
+ EXPECT_EQ(std::regex_match("b", m, re), true);
+ EXPECT_EQ(std::regex_match("c", m, re), true);
+ EXPECT_EQ(std::regex_match("d", m, re), false);
+ EXPECT_EQ(std::regex_match("e", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_bracket_chars_inverse_benchmark)
+{
+ Regex<PosixExtended> re("[^abc]");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("a", m), false);
+ EXPECT_EQ(re.match("b", m), false);
+ EXPECT_EQ(re.match("c", m), false);
+ EXPECT_EQ(re.match("d", m), true);
+ EXPECT_EQ(re.match("e", m), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_bracket_chars_inverse_benchmark_reference_stdcpp)
+{
+ std::regex re("[^abc]");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("a", m, re), false);
+ EXPECT_EQ(std::regex_match("b", m, re), false);
+ EXPECT_EQ(std::regex_match("c", m, re), false);
+ EXPECT_EQ(std::regex_match("d", m, re), true);
+ EXPECT_EQ(std::regex_match("e", m, re), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_bracket_chars_range_benchmark)
+{
+ Regex<PosixExtended> re("[a-d]");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("a", m), true);
+ EXPECT_EQ(re.match("b", m), true);
+ EXPECT_EQ(re.match("c", m), true);
+ EXPECT_EQ(re.match("d", m), true);
+ EXPECT_EQ(re.match("e", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_bracket_chars_range_benchmark_reference_stdcpp)
+{
+ std::regex re("[a-d]");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("a", m, re), true);
+ EXPECT_EQ(std::regex_match("b", m, re), true);
+ EXPECT_EQ(std::regex_match("c", m, re), true);
+ EXPECT_EQ(std::regex_match("d", m, re), true);
+ EXPECT_EQ(std::regex_match("e", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_bracket_chars_range_inverse_benchmark)
+{
+ Regex<PosixExtended> re("[^a-df-z]");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("a", m), false);
+ EXPECT_EQ(re.match("b", m), false);
+ EXPECT_EQ(re.match("c", m), false);
+ EXPECT_EQ(re.match("d", m), false);
+ EXPECT_EQ(re.match("e", m), true);
+ EXPECT_EQ(re.match("k", m), false);
+ EXPECT_EQ(re.match("z", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_bracket_chars_range_inverse_benchmark_reference_stdcpp)
+{
+ std::regex re("[^a-df-z]");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("a", m, re), false);
+ EXPECT_EQ(std::regex_match("b", m, re), false);
+ EXPECT_EQ(std::regex_match("c", m, re), false);
+ EXPECT_EQ(std::regex_match("d", m, re), false);
+ EXPECT_EQ(std::regex_match("e", m, re), true);
+ EXPECT_EQ(std::regex_match("k", m, re), false);
+ EXPECT_EQ(std::regex_match("z", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(bracket_character_class_uuid_benchmark)
+{
+ Regex<PosixExtended> re("^([[:xdigit:]]{8})-([[:xdigit:]]{4})-([[:xdigit:]]{4})-([[:xdigit:]]{4})-([[:xdigit:]]{12})$");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("fb9b62a2-1579-4e3a-afba-76239ccb6583", m), true);
+ EXPECT_EQ(re.match("fb9b62a2", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(bracket_character_class_uuid_benchmark_reference_stdcpp)
+{
+ std::regex re("^([[:xdigit:]]{8})-([[:xdigit:]]{4})-([[:xdigit:]]{4})-([[:xdigit:]]{4})-([[:xdigit:]]{12})$");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("fb9b62a2-1579-4e3a-afba-76239ccb6583", m, re), true);
+ EXPECT_EQ(std::regex_match("fb9b62a2", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_bracket_character_class_inverse_benchmark)
+{
+ Regex<PosixExtended> re("[^[:digit:]]");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("1", m), false);
+ EXPECT_EQ(re.match("2", m), false);
+ EXPECT_EQ(re.match("3", m), false);
+ EXPECT_EQ(re.match("d", m), true);
+ EXPECT_EQ(re.match("e", m), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_bracket_character_class_inverse_benchmark_reference_stdcpp)
+{
+ std::regex re("[^[:digit:]]");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("1", m, re), false);
+ EXPECT_EQ(std::regex_match("2", m, re), false);
+ EXPECT_EQ(std::regex_match("3", m, re), false);
+ EXPECT_EQ(std::regex_match("d", m, re), true);
+ EXPECT_EQ(std::regex_match("e", m, re), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(email_address_benchmark)
+{
+ Regex<PosixExtended> re("^[A-Z0-9a-z._%+-]{1,64}@(?:[A-Za-z0-9-]{1,63}\\.){1,125}[A-Za-z]{2,63}$");
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("hello.world@domain.tld", m), true);
+ EXPECT_EQ(re.match("this.is.a.very_long_email_address@world.wide.web", m), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(email_address_benchmark_reference_stdcpp)
+{
+ std::regex re("^[A-Z0-9a-z._%+-]{1,64}@(?:[A-Za-z0-9-]{1,63}\\.){1,125}[A-Za-z]{2,63}$");
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("hello.world@domain.tld", m, re), true);
+ EXPECT_EQ(std::regex_match("this.is.a.very_long_email_address@world.wide.web", m, re), true);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_ignorecase_benchmark)
+{
+ Regex<PosixExtended> re("^hello friends", PosixFlags::Insensitive);
+ RegexResult m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(re.match("Hello Friends", m), true);
+ EXPECT_EQ(re.match("hello Friends", m), true);
+
+ EXPECT_EQ(re.match("hello Friends!", m), false);
+ EXPECT_EQ(re.search("hello Friends", m), true);
+
+ EXPECT_EQ(re.match("hell Friends", m), false);
+ EXPECT_EQ(re.search("hell Friends", m), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_ignorecase_benchmark_reference_stdcpp)
+{
+ std::regex re("^hello friends", std::regex_constants::icase);
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("Hello Friends", m, re), true);
+ EXPECT_EQ(std::regex_match("hello Friends", m, re), true);
+
+ EXPECT_EQ(std::regex_match("hello Friends!", m, re), false);
+ EXPECT_EQ(std::regex_search("hello Friends", m, re), true);
+
+ EXPECT_EQ(std::regex_match("hell Friends", m, re), false);
+ EXPECT_EQ(std::regex_search("hell Friends", m, re), false);
+ }
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OUR)
+BENCHMARK_CASE(simple_notbol_noteol_benchmark)
+{
+ String pattern = "^hello friends$";
+ String pattern2 = "hello friends";
+ regex_t regex, regex2;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED | REG_NOSUB | REG_ICASE), REG_NOERR);
+ EXPECT_EQ(regcomp(&regex2, pattern2.characters(), REG_EXTENDED | REG_NOSUB | REG_ICASE), REG_NOERR);
+
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+
+ EXPECT_EQ(regexec(&regex, "hello friends", 0, NULL, REG_NOTBOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hello friends", 0, NULL, REG_NOTEOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hello friends", 0, NULL, REG_NOTBOL | REG_NOTEOL), REG_NOMATCH);
+
+ EXPECT_EQ(regexec(&regex, "a hello friends b", 0, NULL, REG_NOTBOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "a hello friends", 0, NULL, REG_NOTBOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "a hello friends", 0, NULL, REG_NOTBOL | REG_SEARCH), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "a hello friends b", 0, NULL, REG_NOTBOL | REG_SEARCH), REG_NOERR);
+
+ EXPECT_EQ(regexec(&regex, "a hello friends b", 0, NULL, REG_NOTEOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hello friends b", 0, NULL, REG_NOTEOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hello friends b", 0, NULL, REG_NOTEOL | REG_SEARCH), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "a hello friends b", 0, NULL, REG_NOTEOL | REG_SEARCH), REG_NOMATCH);
+
+ EXPECT_EQ(regexec(&regex, "a hello friends b", 0, NULL, REG_NOTBOL | REG_NOTEOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "a hello friends b", 0, NULL, REG_NOTBOL | REG_NOTEOL | REG_SEARCH), REG_NOMATCH);
+
+ EXPECT_EQ(regexec(&regex2, "hello friends", 0, NULL, REG_NOTBOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex2, "hello friends", 0, NULL, REG_NOTEOL), REG_NOMATCH);
+ }
+
+ regfree(&regex);
+}
+# endif
+
+# if defined(REGEX_BENCHMARK_OTHER)
+BENCHMARK_CASE(simple_notbol_noteol_benchmark_reference_stdcpp)
+{
+ std::regex re1("^hello friends$", std::regex_constants::match_not_bol);
+ std::regex re2("^hello friends$", std::regex_constants::match_not_eol);
+ std::regex re3("^hello friends$", std::regex_constants::match_not_bol | std::regex_constants::match_not_eol);
+ std::regex re4("hello friends", std::regex_constants::match_not_bol);
+ std::regex re5("hello friends", std::regex_constants::match_not_eol);
+ std::cmatch m;
+ for (size_t i = 0; i < BENCHMARK_LOOP_ITERATIONS; ++i) {
+ EXPECT_EQ(std::regex_match("hello friends", m, re1), false);
+ EXPECT_EQ(std::regex_match("hello friends", m, re2), false);
+ EXPECT_EQ(std::regex_match("hello friends", m, re3), false);
+
+ EXPECT_EQ(std::regex_match("a hello friends b", m, re1), false);
+ EXPECT_EQ(std::regex_match("a hello friends", m, re1), false);
+ EXPECT_EQ(std::regex_search("a hello friends", m, re1), true);
+ EXPECT_EQ(std::regex_search("a hello friends b", m, re1), true);
+
+ EXPECT_EQ(std::regex_match("a hello friends b", m, re2), false);
+ EXPECT_EQ(std::regex_match("hello friends b", m, re2), false);
+ EXPECT_EQ(std::regex_search("hello friends b", m, re2), true);
+ EXPECT_EQ(std::regex_search("a hello friends b", m, re2), false);
+
+ EXPECT_EQ(std::regex_match("a hello friends b", m, re3), false);
+ EXPECT_EQ(std::regex_search("a hello friends b", m, re3), false);
+
+ EXPECT_EQ(std::regex_match("hello friends", m, re4), false);
+ EXPECT_EQ(std::regex_match("hello friends", m, re5), false);
+ }
+}
+# endif
+
+#endif
+
+TEST_MAIN(Regex)
diff --git a/Userland/Libraries/LibRegex/Tests/CMakeLists.txt b/Userland/Libraries/LibRegex/Tests/CMakeLists.txt
new file mode 100644
index 0000000000..ae1e399e18
--- /dev/null
+++ b/Userland/Libraries/LibRegex/Tests/CMakeLists.txt
@@ -0,0 +1,20 @@
+file(GLOB TEST_SOURCES CONFIGURE_DEPENDS "*.cpp")
+file(GLOB REGEX_SOURCES CONFIGURE_DEPENDS "../*.cpp" "../C/*.cpp")
+
+foreach(source ${TEST_SOURCES})
+ get_filename_component(name ${source} NAME_WE)
+ add_executable(${name} ${source} ${REGEX_SOURCES})
+ target_link_libraries(${name} LagomCore)
+ add_test(
+ NAME ${name}
+ COMMAND ${name}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+
+ set_tests_properties(
+ ${name}
+ PROPERTIES
+ FAIL_REGULAR_EXPRESSION
+ "FAIL"
+ )
+endforeach()
diff --git a/Userland/Libraries/LibRegex/Tests/Regex.cpp b/Userland/Libraries/LibRegex/Tests/Regex.cpp
new file mode 100644
index 0000000000..fc920140d0
--- /dev/null
+++ b/Userland/Libraries/LibRegex/Tests/Regex.cpp
@@ -0,0 +1,600 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/TestSuite.h> // import first, to prevent warning of ASSERT* redefinition
+
+#include <AK/StringBuilder.h>
+#include <LibRegex/Regex.h>
+#include <LibRegex/RegexDebug.h>
+#include <stdio.h>
+
+static ECMAScriptOptions match_test_api_options(const ECMAScriptOptions options)
+{
+ return options;
+}
+
+static PosixOptions match_test_api_options(const PosixOptions options)
+{
+ return options;
+}
+
+TEST_CASE(regex_options_ecmascript)
+{
+ ECMAScriptOptions eo;
+ eo |= ECMAScriptFlags::Global;
+
+ EXPECT(eo & ECMAScriptFlags::Global);
+ EXPECT(!(eo & ECMAScriptFlags::Insensitive));
+
+ eo = match_test_api_options(ECMAScriptFlags::Global | ECMAScriptFlags::Insensitive | ECMAScriptFlags::Sticky);
+ EXPECT(eo & ECMAScriptFlags::Global);
+ EXPECT(eo & ECMAScriptFlags::Insensitive);
+ EXPECT(eo & ECMAScriptFlags::Sticky);
+ EXPECT(!(eo & ECMAScriptFlags::Unicode));
+ EXPECT(!(eo & ECMAScriptFlags::Multiline));
+ EXPECT(!(eo & ECMAScriptFlags::SingleLine));
+
+ eo &= ECMAScriptFlags::Insensitive;
+ EXPECT(!(eo & ECMAScriptFlags::Global));
+ EXPECT(eo & ECMAScriptFlags::Insensitive);
+ EXPECT(!(eo & ECMAScriptFlags::Multiline));
+
+ eo &= ECMAScriptFlags::Sticky;
+ EXPECT(!(eo & ECMAScriptFlags::Global));
+ EXPECT(!(eo & ECMAScriptFlags::Insensitive));
+ EXPECT(!(eo & ECMAScriptFlags::Multiline));
+ EXPECT(!(eo & ECMAScriptFlags::Sticky));
+
+ eo = ~ECMAScriptFlags::Insensitive;
+ EXPECT(eo & ECMAScriptFlags::Global);
+ EXPECT(!(eo & ECMAScriptFlags::Insensitive));
+ EXPECT(eo & ECMAScriptFlags::Multiline);
+ EXPECT(eo & ECMAScriptFlags::Sticky);
+}
+
+TEST_CASE(regex_options_posix)
+{
+ PosixOptions eo;
+ eo |= PosixFlags::Global;
+
+ EXPECT(eo & PosixFlags::Global);
+ EXPECT(!(eo & PosixFlags::Insensitive));
+
+ eo = match_test_api_options(PosixFlags::Global | PosixFlags::Insensitive | PosixFlags::MatchNotBeginOfLine);
+ EXPECT(eo & PosixFlags::Global);
+ EXPECT(eo & PosixFlags::Insensitive);
+ EXPECT(eo & PosixFlags::MatchNotBeginOfLine);
+ EXPECT(!(eo & PosixFlags::Unicode));
+ EXPECT(!(eo & PosixFlags::Multiline));
+
+ eo &= PosixFlags::Insensitive;
+ EXPECT(!(eo & PosixFlags::Global));
+ EXPECT(eo & PosixFlags::Insensitive);
+ EXPECT(!(eo & PosixFlags::Multiline));
+
+ eo &= PosixFlags::MatchNotBeginOfLine;
+ EXPECT(!(eo & PosixFlags::Global));
+ EXPECT(!(eo & PosixFlags::Insensitive));
+ EXPECT(!(eo & PosixFlags::Multiline));
+
+ eo = ~PosixFlags::Insensitive;
+ EXPECT(eo & PosixFlags::Global);
+ EXPECT(!(eo & PosixFlags::Insensitive));
+ EXPECT(eo & PosixFlags::Multiline);
+}
+
+TEST_CASE(regex_lexer)
+{
+ Lexer l("/[.*+?^${}()|[\\]\\\\]/g");
+ EXPECT(l.next().type() == regex::TokenType::Slash);
+ EXPECT(l.next().type() == regex::TokenType::LeftBracket);
+ EXPECT(l.next().type() == regex::TokenType::Period);
+ EXPECT(l.next().type() == regex::TokenType::Asterisk);
+ EXPECT(l.next().type() == regex::TokenType::Plus);
+ EXPECT(l.next().type() == regex::TokenType::Questionmark);
+ EXPECT(l.next().type() == regex::TokenType::Circumflex);
+ EXPECT(l.next().type() == regex::TokenType::Dollar);
+ EXPECT(l.next().type() == regex::TokenType::LeftCurly);
+ EXPECT(l.next().type() == regex::TokenType::RightCurly);
+ EXPECT(l.next().type() == regex::TokenType::LeftParen);
+ EXPECT(l.next().type() == regex::TokenType::RightParen);
+ EXPECT(l.next().type() == regex::TokenType::Pipe);
+ EXPECT(l.next().type() == regex::TokenType::LeftBracket);
+ EXPECT(l.next().type() == regex::TokenType::EscapeSequence);
+ EXPECT(l.next().type() == regex::TokenType::EscapeSequence);
+ EXPECT(l.next().type() == regex::TokenType::RightBracket);
+ EXPECT(l.next().type() == regex::TokenType::Slash);
+ EXPECT(l.next().type() == regex::TokenType::Char);
+}
+
+TEST_CASE(parser_error_parens)
+{
+ String pattern = "test()test";
+ Lexer l(pattern);
+ PosixExtendedParser p(l);
+ p.parse();
+ EXPECT(p.has_error());
+ EXPECT(p.error() == Error::EmptySubExpression);
+}
+
+TEST_CASE(parser_error_special_characters_used_at_wrong_place)
+{
+ String pattern;
+ Vector<char, 5> chars = { '*', '+', '?', '{' };
+ StringBuilder b;
+
+ Lexer l;
+ PosixExtended p(l);
+
+ for (auto& ch : chars) {
+ // First in ere
+ b.clear();
+ b.append(ch);
+ pattern = b.build();
+ l.set_source(pattern);
+ p.parse();
+ EXPECT(p.has_error());
+ EXPECT(p.error() == Error::InvalidRepetitionMarker);
+
+ // After vertical line
+ b.clear();
+ b.append("a|");
+ b.append(ch);
+ pattern = b.build();
+ l.set_source(pattern);
+ p.parse();
+ EXPECT(p.has_error());
+ EXPECT(p.error() == Error::InvalidRepetitionMarker);
+
+ // After circumflex
+ b.clear();
+ b.append("^");
+ b.append(ch);
+ pattern = b.build();
+ l.set_source(pattern);
+ p.parse();
+ EXPECT(p.has_error());
+ EXPECT(p.error() == Error::InvalidRepetitionMarker);
+
+ // After dollar
+ b.clear();
+ b.append("$");
+ b.append(ch);
+ pattern = b.build();
+ l.set_source(pattern);
+ p.parse();
+ EXPECT(p.has_error());
+ EXPECT(p.error() == Error::InvalidRepetitionMarker);
+
+ // After left parens
+ b.clear();
+ b.append("(");
+ b.append(ch);
+ b.append(")");
+ pattern = b.build();
+ l.set_source(pattern);
+ p.parse();
+ EXPECT(p.has_error());
+ EXPECT(p.error() == Error::InvalidRepetitionMarker);
+ }
+}
+
+TEST_CASE(parser_error_vertical_line_used_at_wrong_place)
+{
+ Lexer l;
+ PosixExtended p(l);
+
+ // First in ere
+ l.set_source("|asdf");
+ p.parse();
+ EXPECT(p.has_error());
+ EXPECT(p.error() == Error::EmptySubExpression);
+
+ // Last in ere
+ l.set_source("asdf|");
+ p.parse();
+ EXPECT(p.has_error());
+ EXPECT(p.error() == Error::EmptySubExpression);
+
+ // After left parens
+ l.set_source("(|asdf)");
+ p.parse();
+ EXPECT(p.has_error());
+ EXPECT(p.error() == Error::EmptySubExpression);
+
+ // Proceed right parens
+ l.set_source("(asdf)|");
+ p.parse();
+ EXPECT(p.has_error());
+ EXPECT(p.error() == Error::EmptySubExpression);
+}
+
+TEST_CASE(catch_all_first)
+{
+ Regex<PosixExtended> re("^.*$");
+ RegexResult m;
+ re.match("Hello World", m);
+ EXPECT(m.count == 1);
+ EXPECT(re.match("Hello World", m));
+}
+
+TEST_CASE(catch_all)
+{
+ Regex<PosixExtended> re("^.*$", PosixFlags::Global);
+
+ EXPECT(re.has_match("Hello World"));
+ EXPECT(re.match("Hello World").success);
+ EXPECT(re.match("Hello World").count == 1);
+
+ EXPECT(has_match("Hello World", re));
+ auto res = match("Hello World", re);
+ EXPECT(res.success);
+ EXPECT(res.count == 1);
+ EXPECT(res.matches.size() == 1);
+ EXPECT(res.matches.first().view == "Hello World");
+}
+
+TEST_CASE(catch_all_again)
+{
+ Regex<PosixExtended> re("^.*$", PosixFlags::Extra);
+ EXPECT_EQ(has_match("Hello World", re), true);
+}
+
+TEST_CASE(char_utf8)
+{
+ Regex<PosixExtended> re("😀");
+ RegexResult result;
+
+ EXPECT_EQ((result = match("Привет, мир! 😀 γειά σου κόσμος 😀 こんにちは世界", re, PosixFlags::Global)).success, true);
+ EXPECT_EQ(result.count, 2u);
+}
+
+TEST_CASE(catch_all_newline)
+{
+ Regex<PosixExtended> re("^.*$", PosixFlags::Multiline | PosixFlags::StringCopyMatches);
+ RegexResult result;
+ auto lambda = [&result, &re]() {
+ String aaa = "Hello World\nTest\n1234\n";
+ result = match(aaa, re);
+ EXPECT_EQ(result.success, true);
+ };
+ lambda();
+ EXPECT_EQ(result.count, 3u);
+ EXPECT_EQ(result.matches.at(0).view, "Hello World");
+ EXPECT_EQ(result.matches.at(1).view, "Test");
+ EXPECT_EQ(result.matches.at(2).view, "1234");
+}
+
+TEST_CASE(catch_all_newline_view)
+{
+ Regex<PosixExtended> re("^.*$", PosixFlags::Multiline);
+ RegexResult result;
+
+ String aaa = "Hello World\nTest\n1234\n";
+ result = match(aaa, re);
+ EXPECT_EQ(result.success, true);
+ EXPECT_EQ(result.count, 3u);
+ String str = "Hello World";
+ EXPECT_EQ(result.matches.at(0).view, str.view());
+ EXPECT_EQ(result.matches.at(1).view, "Test");
+ EXPECT_EQ(result.matches.at(2).view, "1234");
+}
+
+TEST_CASE(catch_all_newline_2)
+{
+ Regex<PosixExtended> re("^.*$");
+ RegexResult result;
+ result = match("Hello World\nTest\n1234\n", re, PosixFlags::Multiline | PosixFlags::StringCopyMatches);
+ EXPECT_EQ(result.success, true);
+ EXPECT_EQ(result.count, 3u);
+ EXPECT_EQ(result.matches.at(0).view, "Hello World");
+ EXPECT_EQ(result.matches.at(1).view, "Test");
+ EXPECT_EQ(result.matches.at(2).view, "1234");
+
+ result = match("Hello World\nTest\n1234\n", re);
+ EXPECT_EQ(result.success, true);
+ EXPECT_EQ(result.count, 1u);
+ EXPECT_EQ(result.matches.at(0).view, "Hello World\nTest\n1234\n");
+}
+
+TEST_CASE(match_all_character_class)
+{
+ Regex<PosixExtended> re("[[:alpha:]]");
+ String str = "[Window]\nOpacity=255\nAudibleBeep=0\n";
+ RegexResult result = match(str, re, PosixFlags::Global | PosixFlags::StringCopyMatches);
+
+ EXPECT_EQ(result.success, true);
+ EXPECT_EQ(result.count, 24u);
+ EXPECT_EQ(result.matches.at(0).view, "W");
+ EXPECT_EQ(result.matches.at(1).view, "i");
+ EXPECT_EQ(result.matches.at(2).view, "n");
+ EXPECT(&result.matches.at(0).view.characters_without_null_termination()[0] != &str.view().characters_without_null_termination()[1]);
+}
+
+TEST_CASE(match_character_class_with_assertion)
+{
+ Regex<PosixExtended> re("[[:alpha:]]+$");
+ String str = "abcdef";
+ RegexResult result = match(str, re);
+
+ EXPECT_EQ(result.success, true);
+ EXPECT_EQ(result.count, 1u);
+}
+
+TEST_CASE(example_for_git_commit)
+{
+ Regex<PosixExtended> re("^.*$");
+ auto result = re.match("Well, hello friends!\nHello World!");
+
+ EXPECT(result.success);
+ EXPECT(result.count == 1);
+ EXPECT(result.matches.at(0).view.starts_with("Well"));
+ EXPECT(result.matches.at(0).view.length() == 33);
+
+ EXPECT(re.has_match("Well,...."));
+
+ result = re.match("Well, hello friends!\nHello World!", PosixFlags::Multiline);
+
+ EXPECT(result.success);
+ EXPECT(result.count == 2);
+ EXPECT(result.matches.at(0).view == "Well, hello friends!");
+ EXPECT(result.matches.at(1).view == "Hello World!");
+}
+
+TEST_CASE(email_address)
+{
+ Regex<PosixExtended> re("^[A-Z0-9a-z._%+-]{1,64}@([A-Za-z0-9-]{1,63}\\.){1,125}[A-Za-z]{2,63}$");
+ EXPECT(re.has_match("hello.world@domain.tld"));
+ EXPECT(re.has_match("this.is.a.very_long_email_address@world.wide.web"));
+}
+
+TEST_CASE(ini_file_entries)
+{
+ Regex<PosixExtended> re("[[:alpha:]]*=([[:digit:]]*)|\\[(.*)\\]");
+ RegexResult result;
+
+#ifdef REGEX_DEBUG
+ RegexDebug regex_dbg(stderr);
+ regex_dbg.print_raw_bytecode(re);
+ regex_dbg.print_header();
+ regex_dbg.print_bytecode(re);
+#endif
+
+ String haystack = "[Window]\nOpacity=255\nAudibleBeep=0\n";
+ EXPECT_EQ(re.search(haystack.view(), result, PosixFlags::Multiline), true);
+ EXPECT_EQ(result.count, 3u);
+
+#ifdef REGEX_DEBUG
+ for (auto& v : result.matches)
+ fprintf(stderr, "%s\n", v.view.to_string().characters());
+#endif
+
+ EXPECT_EQ(result.matches.at(0).view, "[Window]");
+ EXPECT_EQ(result.capture_group_matches.at(0).at(0).view, "Window");
+ EXPECT_EQ(result.matches.at(1).view, "Opacity=255");
+ EXPECT_EQ(result.matches.at(1).line, 1u);
+ EXPECT_EQ(result.matches.at(1).column, 0u);
+ EXPECT_EQ(result.capture_group_matches.at(1).at(0).view, "255");
+ EXPECT_EQ(result.capture_group_matches.at(1).at(0).line, 1u);
+ EXPECT_EQ(result.capture_group_matches.at(1).at(0).column, 8u);
+ EXPECT_EQ(result.matches.at(2).view, "AudibleBeep=0");
+ EXPECT_EQ(result.capture_group_matches.at(2).at(0).view, "0");
+ EXPECT_EQ(result.capture_group_matches.at(2).at(0).line, 2u);
+ EXPECT_EQ(result.capture_group_matches.at(2).at(0).column, 12u);
+}
+
+TEST_CASE(ini_file_entries2)
+{
+ Regex<PosixExtended> re("[[:alpha:]]*=([[:digit:]]*)");
+ RegexResult result;
+
+ String haystack = "ViewMode=Icon";
+
+ EXPECT_EQ(re.match(haystack.view(), result), false);
+ EXPECT_EQ(result.count, 0u);
+
+ EXPECT_EQ(re.search(haystack.view(), result), true);
+ EXPECT_EQ(result.count, 1u);
+}
+
+TEST_CASE(named_capture_group)
+{
+ Regex<PosixExtended> re("[[:alpha:]]*=(?<Test>[[:digit:]]*)");
+ RegexResult result;
+
+#ifdef REGEX_DEBUG
+ RegexDebug regex_dbg(stderr);
+ regex_dbg.print_raw_bytecode(re);
+ regex_dbg.print_header();
+ regex_dbg.print_bytecode(re);
+#endif
+
+ String haystack = "[Window]\nOpacity=255\nAudibleBeep=0\n";
+ EXPECT_EQ(re.search(haystack, result, PosixFlags::Multiline), true);
+ EXPECT_EQ(result.count, 2u);
+ EXPECT_EQ(result.matches.at(0).view, "Opacity=255");
+ EXPECT_EQ(result.named_capture_group_matches.at(0).ensure("Test").view, "255");
+ EXPECT_EQ(result.matches.at(1).view, "AudibleBeep=0");
+ EXPECT_EQ(result.named_capture_group_matches.at(1).ensure("Test").view, "0");
+}
+
+TEST_CASE(a_star)
+{
+ Regex<PosixExtended> re("a*");
+ RegexResult result;
+
+#ifdef REGEX_DEBUG
+ RegexDebug regex_dbg(stderr);
+ regex_dbg.print_raw_bytecode(re);
+ regex_dbg.print_header();
+ regex_dbg.print_bytecode(re);
+#endif
+
+ String haystack = "[Window]\nOpacity=255\nAudibleBeep=0\n";
+ EXPECT_EQ(re.search(haystack.view(), result, PosixFlags::Multiline), true);
+ EXPECT_EQ(result.count, 32u);
+ EXPECT_EQ(result.matches.at(0).view.length(), 0u);
+ EXPECT_EQ(result.matches.at(10).view.length(), 1u);
+ EXPECT_EQ(result.matches.at(10).view, "a");
+ EXPECT_EQ(result.matches.at(31).view.length(), 0u);
+}
+
+TEST_CASE(simple_period_end_benchmark)
+{
+ Regex<PosixExtended> re("hello.$");
+ RegexResult m;
+ EXPECT_EQ(re.search("Hello1", m), false);
+ EXPECT_EQ(re.search("hello1hello1", m), true);
+ EXPECT_EQ(re.search("hello2hell", m), false);
+ EXPECT_EQ(re.search("hello?", m), true);
+}
+
+TEST_CASE(ECMA262_parse)
+{
+ struct _test {
+ const char* pattern;
+ regex::Error expected_error { regex::Error::NoError };
+ regex::ECMAScriptFlags flags {};
+ };
+
+ constexpr _test tests[] {
+ { "^hello.$" },
+ { "^(hello.)$" },
+ { "^h{0,1}ello.$" },
+ { "^hello\\W$" },
+ { "^hell\\w.$" },
+ { "^hell\\x6f1$" }, // ^hello1$
+ { "^hel(?:l\\w).$" },
+ { "^hel(?<LO>l\\w).$" },
+ { "^[-a-zA-Z\\w\\s]+$" },
+ { "\\bhello\\B" },
+ { "^[\\w+/_-]+[=]{0,2}$" }, // #4189
+ { "^(?:[^<]*(<[\\w\\W]+>)[^>]*$|#([\\w\\-]*)$)" }, // #4189
+ { "\\/" }, // #4189
+ { ",/=-:" }, // #4243
+ { "\\x" }, // Even invalid escapes are allowed if ~unicode.
+ { "\\", regex::Error::InvalidTrailingEscape },
+ { "(?", regex::Error::InvalidCaptureGroup },
+ { "\\u1234", regex::Error::NoError, regex::ECMAScriptFlags::Unicode },
+ { "[\\u1234]", regex::Error::NoError, regex::ECMAScriptFlags::Unicode },
+ { ",(?", regex::Error::InvalidCaptureGroup }, // #4583
+ };
+
+ for (auto& test : tests) {
+ Regex<ECMA262> re(test.pattern);
+ EXPECT_EQ(re.parser_result.error, test.expected_error);
+#ifdef REGEX_DEBUG
+ dbgln("\n");
+ RegexDebug regex_dbg(stderr);
+ regex_dbg.print_raw_bytecode(re);
+ regex_dbg.print_header();
+ regex_dbg.print_bytecode(re);
+ dbgln("\n");
+#endif
+ }
+}
+
+TEST_CASE(ECMA262_match)
+{
+ struct _test {
+ const char* pattern;
+ const char* subject;
+ bool matches { true };
+ ECMAScriptFlags options {};
+ };
+
+ constexpr _test tests[] {
+ { "^hello.$", "hello1" },
+ { "^(hello.)$", "hello1" },
+ { "^h{0,1}ello.$", "ello1" },
+ { "^hello\\W$", "hello!" },
+ { "^hell\\w.$", "hellx!" },
+ { "^hell\\x6f1$", "hello1" },
+ { "^hel(?<LO>l.)1$", "hello1" },
+ { "^hel(?<LO>l.)1*\\k<LO>.$", "hello1lo1" },
+ { "^[-a-z1-3\\s]+$", "hell2 o1" },
+ { .pattern = "\\bhello\\B", .subject = "hello1", .options = ECMAScriptFlags::Global },
+ { "\\b.*\\b", "hello1" },
+ { "[^\\D\\S]{2}", "1 " },
+ { "bar(?=f.)foo", "barfoo" },
+ { "bar(?=foo)bar", "barbar", false },
+ { "bar(?!foo)bar", "barbar", true },
+ { "bar(?!bar)bar", "barbar", false },
+ { "bar.*(?<=foo)", "barbar", false },
+ { "bar.*(?<!foo)", "barbar", true },
+ { "((...)X)+", "fooXbarXbazX", true },
+ { "(?:)", "", true },
+ };
+
+ for (auto& test : tests) {
+ Regex<ECMA262> re(test.pattern, test.options);
+#ifdef REGEX_DEBUG
+ dbgln("\n");
+ RegexDebug regex_dbg(stderr);
+ regex_dbg.print_raw_bytecode(re);
+ regex_dbg.print_header();
+ regex_dbg.print_bytecode(re);
+ dbgln("\n");
+#endif
+ EXPECT_EQ(re.parser_result.error, Error::NoError);
+ EXPECT_EQ(re.match(test.subject).success, test.matches);
+ }
+}
+
+TEST_CASE(replace)
+{
+ struct _test {
+ const char* pattern;
+ const char* replacement;
+ const char* subject;
+ const char* expected;
+ ECMAScriptFlags options {};
+ };
+
+ constexpr _test tests[] {
+ { "foo(.+)", "aaa", "test", "test" },
+ { "foo(.+)", "test\\1", "foobar", "testbar" },
+ { "foo(.+)", "\\2\\1", "foobar", "\\2bar" },
+ { "foo(.+)", "\\\\\\1", "foobar", "\\bar" },
+ { "foo(.)", "a\\1", "fooxfooy", "axay", ECMAScriptFlags::Multiline },
+ };
+
+ for (auto& test : tests) {
+ Regex<ECMA262> re(test.pattern, test.options);
+#ifdef REGEX_DEBUG
+ dbgln("\n");
+ RegexDebug regex_dbg(stderr);
+ regex_dbg.print_raw_bytecode(re);
+ regex_dbg.print_header();
+ regex_dbg.print_bytecode(re);
+ dbgln("\n");
+#endif
+ EXPECT_EQ(re.parser_result.error, Error::NoError);
+ EXPECT_EQ(re.replace(test.subject, test.replacement), test.expected);
+ }
+}
+
+TEST_MAIN(Regex)
diff --git a/Userland/Libraries/LibRegex/Tests/RegexLibC.cpp b/Userland/Libraries/LibRegex/Tests/RegexLibC.cpp
new file mode 100644
index 0000000000..51390bea5b
--- /dev/null
+++ b/Userland/Libraries/LibRegex/Tests/RegexLibC.cpp
@@ -0,0 +1,1140 @@
+/*
+ * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/TestSuite.h>
+
+#include <AK/StringBuilder.h>
+#include <LibC/regex.h>
+#include <stdio.h>
+
+TEST_CASE(catch_all)
+{
+ String pattern = "^.*$";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "Hello World", 0, NULL, 0), REG_NOERR);
+
+ regfree(&regex);
+}
+
+TEST_CASE(simple_start)
+{
+ String pattern = "^hello friends";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "Hello!", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hello friends", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "Well, hello friends", 0, NULL, 0), REG_NOMATCH);
+
+ regfree(&regex);
+}
+
+TEST_CASE(simple_end)
+{
+ String pattern = ".*hello\\.\\.\\. there$";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "Hallo", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "I said fyhello... there", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "ahello... therea", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hello.. there", 0, NULL, 0), REG_NOMATCH);
+
+ regfree(&regex);
+}
+
+TEST_CASE(simple_period)
+{
+ String pattern = "hello.";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "Hello1", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hello1", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "hello2", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "hello?", 0, NULL, 0), REG_NOERR);
+
+ regfree(&regex);
+}
+
+TEST_CASE(simple_period_end)
+{
+ String pattern = "hello.$";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED | REG_NOSUB), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "Hello1", 0, NULL, REG_NOSUB), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hello1hello1", 0, NULL, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "hello2hell", 0, NULL, REG_GLOBAL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hello?", 0, NULL, REG_NOSUB), REG_NOERR);
+
+ regfree(&regex);
+}
+
+TEST_CASE(simple_escaped)
+{
+ String pattern = "hello\\.";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "hello", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hello.", 0, NULL, 0), REG_NOERR);
+
+ regfree(&regex);
+}
+
+TEST_CASE(simple_period2_end)
+{
+ String pattern = ".*hi... there$";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "Hello there", 0, NULL, REG_GLOBAL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "I said fyhi... there", 0, NULL, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "....hi... ", 0, NULL, REG_GLOBAL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "I said fyhihii there", 0, NULL, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "I said fyhihi there", 0, NULL, REG_GLOBAL), REG_NOMATCH);
+
+ regfree(&regex);
+}
+
+TEST_CASE(simple_plus)
+{
+ String pattern = "a+";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED | REG_NOSUB), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "b", 0, NULL, REG_NOSUB), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "a", 0, NULL, REG_NOSUB), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "aaaaaabbbbb", 0, NULL, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "aaaaaaaaaaa", 0, NULL, REG_GLOBAL), REG_NOERR);
+
+ regfree(&regex);
+}
+
+TEST_CASE(simple_questionmark)
+{
+ String pattern = "da?d";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "a", 0, NULL, REG_GLOBAL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "daa", 0, NULL, REG_GLOBAL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "ddddd", 0, NULL, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "dd", 0, NULL, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "dad", 0, NULL, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "dada", 0, NULL, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "adadaa", 0, NULL, REG_GLOBAL), REG_NOERR);
+
+ regfree(&regex);
+}
+
+TEST_CASE(simple_questionmark_matchall)
+{
+ String pattern = "da?d";
+ regex_t regex;
+ static constexpr int num_matches { 5 };
+ regmatch_t matches[num_matches];
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "a", num_matches, matches, REG_GLOBAL), REG_NOMATCH);
+ EXPECT_EQ(matches[0].rm_cnt, 0u);
+ EXPECT_EQ(regexec(&regex, "daa", num_matches, matches, REG_GLOBAL), REG_NOMATCH);
+ EXPECT_EQ(matches[0].rm_cnt, 0u);
+
+ EXPECT_EQ(regexec(&regex, "ddddd", num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 2u);
+
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 2u);
+ EXPECT_EQ(matches[1].rm_so, 2u);
+ EXPECT_EQ(matches[1].rm_eo, 4u);
+
+ EXPECT_EQ(regexec(&regex, "dd", num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(regexec(&regex, "dad", num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(regexec(&regex, "dada", num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(regexec(&regex, "adadaa", num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+
+ regfree(&regex);
+}
+
+TEST_CASE(character_class)
+{
+ String pattern = "[[:alpha:]]";
+ regex_t regex;
+ static constexpr int num_matches { 5 };
+ regmatch_t matches[num_matches];
+
+ String haystack = "[Window]\nOpacity=255\nAudibleBeep=0\n";
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, haystack.characters(), num_matches, matches, 0), REG_NOMATCH);
+ EXPECT_EQ(matches[0].rm_cnt, 0u);
+ EXPECT_EQ(regexec(&regex, haystack.characters(), num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 24u);
+ EXPECT_EQ(haystack.substring_view(matches[0].rm_so, matches[0].rm_eo - matches[0].rm_so), "W");
+ EXPECT_EQ(haystack.substring_view(matches[1].rm_so, matches[1].rm_eo - matches[1].rm_so), "i");
+
+ regfree(&regex);
+}
+
+TEST_CASE(character_class2)
+{
+ String pattern = "[[:alpha:]]*=([[:digit:]]*)|\\[(.*)\\]";
+ regex_t regex;
+ static constexpr int num_matches { 9 };
+ regmatch_t matches[num_matches];
+
+ String haystack = "[Window]\nOpacity=255\nAudibleBeep=0\n";
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED | REG_NEWLINE), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, haystack.characters(), num_matches, matches, 0), REG_NOERR);
+
+ EXPECT_EQ(matches[0].rm_cnt, 3u);
+#if 0
+ for (int i = 0; i < num_matches; ++i) {
+ fprintf(stderr, "Matches[%i].rm_so: %li, .rm_eo: %li .rm_cnt: %li: ", i, matches[i].rm_so, matches[i].rm_eo, matches[i].rm_cnt);
+ fprintf(stderr, "haystack length: %lu\n", haystack.length());
+ if (matches[i].rm_so != -1)
+ fprintf(stderr, "%s\n", haystack.substring_view(matches[i].rm_so, matches[i].rm_eo - matches[i].rm_so).to_string().characters());
+ }
+#endif
+
+ EXPECT_EQ(haystack.substring_view(matches[0].rm_so, matches[0].rm_eo - matches[0].rm_so), "[Window]");
+
+ EXPECT_EQ(matches[1].rm_so, -1);
+ EXPECT_EQ(matches[1].rm_eo, -1);
+ EXPECT_EQ(matches[1].rm_cnt, 0);
+
+ EXPECT_EQ(haystack.substring_view(matches[2].rm_so, matches[2].rm_eo - matches[2].rm_so), "Window");
+ EXPECT_EQ(haystack.substring_view(matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so), "Opacity=255");
+
+ EXPECT_EQ(haystack.substring_view(matches[4].rm_so, matches[4].rm_eo - matches[4].rm_so), "255");
+
+ EXPECT_EQ(matches[5].rm_so, -1);
+ EXPECT_EQ(matches[5].rm_eo, -1);
+ EXPECT_EQ(matches[5].rm_cnt, 0);
+
+ EXPECT_EQ(haystack.substring_view(matches[6].rm_so, matches[6].rm_eo - matches[6].rm_so), "AudibleBeep=0");
+ EXPECT_EQ(haystack.substring_view(matches[7].rm_so, matches[7].rm_eo - matches[7].rm_so), "0");
+
+ EXPECT_EQ(matches[8].rm_so, -1);
+ EXPECT_EQ(matches[8].rm_eo, -1);
+ EXPECT_EQ(matches[8].rm_cnt, 0);
+
+ regfree(&regex);
+}
+
+TEST_CASE(escaped_char_questionmark)
+{
+ String pattern = "This\\.?And\\.?That";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "ThisAndThat", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "This.And.That", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "This And That", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "This..And..That", 0, NULL, 0), REG_NOMATCH);
+
+ regfree(&regex);
+}
+
+TEST_CASE(char_qualifier_asterisk)
+{
+ String pattern = "regex*";
+ regex_t regex;
+ static constexpr int num_matches { 5 };
+ regmatch_t matches[num_matches];
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "#include <regex.h>", num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+
+ regfree(&regex);
+}
+
+TEST_CASE(char_utf8)
+{
+ String pattern = "😀";
+ regex_t regex;
+ static constexpr int num_matches { 5 };
+ regmatch_t matches[num_matches];
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "Привет, мир! 😀 γειά σου κόσμος 😀 こんにちは世界", num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 2u);
+
+ regfree(&regex);
+}
+
+TEST_CASE(parens)
+{
+ String pattern = "test(hello)test";
+ regex_t regex;
+ static constexpr int num_matches { 5 };
+ regmatch_t matches[num_matches];
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+
+ EXPECT_EQ(regexec(&regex, "testhellotest", num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 13u);
+ EXPECT_EQ(matches[1].rm_so, 4u);
+ EXPECT_EQ(matches[1].rm_eo, 9u);
+
+ regfree(&regex);
+}
+
+TEST_CASE(parser_error_parens)
+{
+ String pattern = "test()test";
+ regex_t regex;
+ static constexpr int num_matches { 5 };
+ regmatch_t matches[num_matches];
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_EMPTY_EXPR);
+ EXPECT_EQ(regexec(&regex, "testhellotest", num_matches, matches, 0), REG_EMPTY_EXPR);
+
+ regfree(&regex);
+}
+
+TEST_CASE(parser_error_special_characters_used_at_wrong_place)
+{
+ String pattern;
+ regex_t regex;
+ static constexpr int num_matches { 5 };
+ regmatch_t matches[num_matches];
+
+ Vector<char, 4> chars = { '*', '+', '?', '}' };
+ StringBuilder b;
+
+ for (auto& ch : chars) {
+
+ auto error_code_to_check = ch == '}' ? REG_EBRACE : REG_BADRPT;
+
+ // First in ere
+ b.clear();
+ b.append(ch);
+ pattern = b.build();
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), error_code_to_check);
+ EXPECT_EQ(regexec(&regex, "test", num_matches, matches, 0), error_code_to_check);
+
+ // After vertical line
+ b.clear();
+ b.append("a|");
+ b.append(ch);
+ pattern = b.build();
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), error_code_to_check);
+ EXPECT_EQ(regexec(&regex, "test", num_matches, matches, 0), error_code_to_check);
+
+ // After circumflex
+ b.clear();
+ b.append("^");
+ b.append(ch);
+ pattern = b.build();
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), error_code_to_check);
+ EXPECT_EQ(regexec(&regex, "test", num_matches, matches, 0), error_code_to_check);
+
+ // After dollar
+ b.clear();
+ b.append("$");
+ b.append(ch);
+ pattern = b.build();
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), error_code_to_check);
+ EXPECT_EQ(regexec(&regex, "test", num_matches, matches, 0), error_code_to_check);
+
+ // After left parens
+ b.clear();
+ b.append("(");
+ b.append(ch);
+ b.append(")");
+ pattern = b.build();
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), error_code_to_check);
+ EXPECT_EQ(regexec(&regex, "test", num_matches, matches, 0), error_code_to_check);
+ }
+
+ regfree(&regex);
+}
+
+TEST_CASE(parser_error_vertical_line_used_at_wrong_place)
+{
+ String pattern;
+ regex_t regex;
+ static constexpr int num_matches { 5 };
+ regmatch_t matches[num_matches];
+
+ // First in ere
+ pattern = "|asdf";
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_EMPTY_EXPR);
+ EXPECT_EQ(regexec(&regex, "test", num_matches, matches, 0), REG_EMPTY_EXPR);
+
+ // Last in ere
+ pattern = "asdf|";
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_EMPTY_EXPR);
+ EXPECT_EQ(regexec(&regex, "test", num_matches, matches, 0), REG_EMPTY_EXPR);
+
+ // After left parens
+ pattern = "(|asdf)";
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_EMPTY_EXPR);
+ EXPECT_EQ(regexec(&regex, "test", num_matches, matches, 0), REG_EMPTY_EXPR);
+
+ // Proceed right parens
+ pattern = "(asdf)|";
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_EMPTY_EXPR);
+ EXPECT_EQ(regexec(&regex, "test", num_matches, matches, 0), REG_EMPTY_EXPR);
+
+ regfree(&regex);
+}
+
+TEST_CASE(parens_qualifier_questionmark)
+{
+ String pattern = "test(hello)?test";
+ regex_t regex;
+ static constexpr int num_matches { 5 };
+ regmatch_t matches[num_matches];
+ const char* match_str;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+
+ match_str = "testtest";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 8u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testtest");
+
+ match_str = "testhellotest";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 13u);
+ EXPECT_EQ(matches[1].rm_so, 4u);
+ EXPECT_EQ(matches[1].rm_eo, 9u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testhellotest");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "hello");
+
+ regfree(&regex);
+}
+
+TEST_CASE(parens_qualifier_asterisk)
+{
+ String pattern = "test(hello)*test";
+ regex_t regex;
+ static constexpr int num_matches { 6 };
+ regmatch_t matches[num_matches];
+ const char* match_str;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+
+ match_str = "testtest";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 8u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testtest");
+
+ match_str = "testhellohellotest";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 18u);
+ EXPECT_EQ(matches[1].rm_so, 9u);
+ EXPECT_EQ(matches[1].rm_eo, 14u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testhellohellotest");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "hello");
+
+ match_str = "testhellohellotest, testhellotest";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 2u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 18u);
+ EXPECT_EQ(matches[1].rm_so, 9u);
+ EXPECT_EQ(matches[1].rm_eo, 14u);
+ EXPECT_EQ(matches[2].rm_so, 20u);
+ EXPECT_EQ(matches[2].rm_eo, 33u);
+ EXPECT_EQ(matches[3].rm_so, 24u);
+ EXPECT_EQ(matches[3].rm_eo, 29u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testhellohellotest");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "hello");
+ EXPECT_EQ(StringView(&match_str[matches[2].rm_so], matches[2].rm_eo - matches[2].rm_so), "testhellotest");
+ EXPECT_EQ(StringView(&match_str[matches[3].rm_so], matches[3].rm_eo - matches[3].rm_so), "hello");
+
+ regfree(&regex);
+}
+
+TEST_CASE(parens_qualifier_asterisk_2)
+{
+ String pattern = "test(.*)test";
+ regex_t regex;
+ static constexpr int num_matches { 6 };
+ regmatch_t matches[num_matches];
+ const char* match_str;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+
+ match_str = "testasdftest";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 12u);
+ EXPECT_EQ(matches[1].rm_so, 4u);
+ EXPECT_EQ(matches[1].rm_eo, 8u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testasdftest");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "asdf");
+
+ match_str = "testasdfasdftest";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 16u);
+ EXPECT_EQ(matches[1].rm_so, 4u);
+ EXPECT_EQ(matches[1].rm_eo, 12u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testasdfasdftest");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "asdfasdf");
+
+ match_str = "testaaaatest, testbbbtest, testtest";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 35u);
+ EXPECT_EQ(matches[1].rm_so, 4u);
+ EXPECT_EQ(matches[1].rm_eo, 31u);
+
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testaaaatest, testbbbtest, testtest");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "aaaatest, testbbbtest, test");
+
+ regfree(&regex);
+}
+
+TEST_CASE(mulit_parens_qualifier_too_less_result_values)
+{
+ String pattern = "test(a)?(b)?(c)?test";
+ regex_t regex;
+ static constexpr int num_matches { 4 };
+ regmatch_t matches[num_matches];
+ const char* match_str;
+
+ matches[3] = { -2, -2, 100 };
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+
+ match_str = "testabtest";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches - 1, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 10u);
+ EXPECT_EQ(matches[1].rm_so, 4u);
+ EXPECT_EQ(matches[1].rm_eo, 5u);
+ EXPECT_EQ(matches[2].rm_so, 5u);
+ EXPECT_EQ(matches[2].rm_eo, 6u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testabtest");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "a");
+ EXPECT_EQ(StringView(&match_str[matches[2].rm_so], matches[2].rm_eo - matches[2].rm_so), "b");
+ EXPECT_EQ(matches[3].rm_so, -2);
+ EXPECT_EQ(matches[3].rm_eo, -2);
+ EXPECT_EQ(matches[3].rm_cnt, 100u);
+
+ match_str = "testabctest";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches - 1, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 11u);
+ EXPECT_EQ(matches[1].rm_so, 4u);
+ EXPECT_EQ(matches[1].rm_eo, 5u);
+ EXPECT_EQ(matches[2].rm_so, 5u);
+ EXPECT_EQ(matches[2].rm_eo, 6u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testabctest");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "a");
+ EXPECT_EQ(StringView(&match_str[matches[2].rm_so], matches[2].rm_eo - matches[2].rm_so), "b");
+ EXPECT_EQ(matches[3].rm_so, -2);
+ EXPECT_EQ(matches[3].rm_eo, -2);
+ EXPECT_EQ(matches[3].rm_cnt, 100u);
+
+ match_str = "testabctest, testabctest";
+
+ EXPECT_EQ(regexec(&regex, match_str, num_matches - 1, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 2u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 11u);
+ EXPECT_EQ(matches[1].rm_so, 4u);
+ EXPECT_EQ(matches[1].rm_eo, 5u);
+ EXPECT_EQ(matches[2].rm_so, 5u);
+ EXPECT_EQ(matches[2].rm_eo, 6u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testabctest");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "a");
+ EXPECT_EQ(StringView(&match_str[matches[2].rm_so], matches[2].rm_eo - matches[2].rm_so), "b");
+ EXPECT_EQ(matches[3].rm_so, -2);
+ EXPECT_EQ(matches[3].rm_eo, -2);
+ EXPECT_EQ(matches[3].rm_cnt, 100u);
+
+ regfree(&regex);
+}
+
+TEST_CASE(multi_parens_qualifier_questionmark)
+{
+ String pattern = "test(a)?(b)?(c)?test";
+ regex_t regex;
+ static constexpr int num_matches { 8 };
+ regmatch_t matches[num_matches];
+ const char* match_str;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+
+ match_str = "testtest";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 8u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testtest");
+
+ match_str = "testabctest";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 11u);
+ EXPECT_EQ(matches[1].rm_so, 4u);
+ EXPECT_EQ(matches[1].rm_eo, 5u);
+ EXPECT_EQ(matches[2].rm_so, 5u);
+ EXPECT_EQ(matches[2].rm_eo, 6u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testabctest");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "a");
+ EXPECT_EQ(StringView(&match_str[matches[2].rm_so], matches[2].rm_eo - matches[2].rm_so), "b");
+
+ match_str = "testabctest, testactest";
+
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 2u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 11u);
+ EXPECT_EQ(matches[1].rm_so, 4u);
+ EXPECT_EQ(matches[1].rm_eo, 5u);
+ EXPECT_EQ(matches[2].rm_so, 5u);
+ EXPECT_EQ(matches[2].rm_eo, 6u);
+ EXPECT_EQ(matches[3].rm_so, 6u);
+ EXPECT_EQ(matches[3].rm_eo, 7u);
+
+ EXPECT_EQ(matches[4].rm_so, 13u);
+ EXPECT_EQ(matches[4].rm_eo, 23u);
+ EXPECT_EQ(matches[5].rm_so, 17u);
+ EXPECT_EQ(matches[5].rm_eo, 18u);
+ EXPECT_EQ(matches[6].rm_so, -1);
+ EXPECT_EQ(matches[6].rm_eo, -1);
+ EXPECT_EQ(matches[7].rm_so, 18u);
+ EXPECT_EQ(matches[7].rm_eo, 19u);
+
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testabctest");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "a");
+ EXPECT_EQ(StringView(&match_str[matches[2].rm_so], matches[2].rm_eo - matches[2].rm_so), "b");
+ EXPECT_EQ(StringView(&match_str[matches[3].rm_so], matches[3].rm_eo - matches[3].rm_so), "c");
+ EXPECT_EQ(StringView(&match_str[matches[4].rm_so], matches[4].rm_eo - matches[4].rm_so), "testactest");
+ EXPECT_EQ(StringView(&match_str[matches[5].rm_so], matches[5].rm_eo - matches[5].rm_so), "a");
+ EXPECT_EQ(StringView(&match_str[matches[6].rm_so], matches[6].rm_eo - matches[6].rm_so), "");
+ EXPECT_EQ(StringView(&match_str[matches[7].rm_so], matches[7].rm_eo - matches[7].rm_so), "c");
+
+ regfree(&regex);
+}
+
+TEST_CASE(simple_alternative)
+{
+ String pattern = "test|hello|friends";
+ regex_t regex;
+ static constexpr int num_matches { 1 };
+ regmatch_t matches[num_matches];
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+
+ EXPECT_EQ(regexec(&regex, "test", num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 4u);
+
+ EXPECT_EQ(regexec(&regex, "hello", num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 5u);
+
+ EXPECT_EQ(regexec(&regex, "friends", num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 7u);
+
+ regfree(&regex);
+}
+
+TEST_CASE(alternative_match_groups)
+{
+ String pattern = "test(a)?(b)?|hello ?(dear|my)? friends";
+ regex_t regex;
+ static constexpr int num_matches { 8 };
+ regmatch_t matches[num_matches];
+ const char* match_str;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+
+ match_str = "test";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0);
+ EXPECT_EQ(matches[0].rm_eo, 4);
+ EXPECT_EQ(matches[1].rm_so, -1);
+ EXPECT_EQ(matches[1].rm_eo, -1);
+ EXPECT_EQ(matches[2].rm_so, -1);
+ EXPECT_EQ(matches[2].rm_eo, -1);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "test");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "");
+ EXPECT_EQ(StringView(&match_str[matches[2].rm_so], matches[2].rm_eo - matches[2].rm_so), "");
+
+ match_str = "testa";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 5u);
+ EXPECT_EQ(matches[1].rm_so, 4u);
+ EXPECT_EQ(matches[1].rm_eo, 5u);
+ EXPECT_EQ(matches[2].rm_so, -1);
+ EXPECT_EQ(matches[2].rm_eo, -1);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testa");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "a");
+ EXPECT_EQ(StringView(&match_str[matches[2].rm_so], matches[2].rm_eo - matches[2].rm_so), "");
+
+ match_str = "testb";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 5u);
+ EXPECT_EQ(matches[1].rm_so, -1);
+ EXPECT_EQ(matches[1].rm_eo, -1);
+ EXPECT_EQ(matches[2].rm_so, 4u);
+ EXPECT_EQ(matches[2].rm_eo, 5u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "testb");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "");
+ EXPECT_EQ(StringView(&match_str[matches[2].rm_so], matches[2].rm_eo - matches[2].rm_so), "b");
+
+ match_str = "hello friends";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 13u);
+ EXPECT_EQ(matches[1].rm_so, -1);
+ EXPECT_EQ(matches[1].rm_eo, -1);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "hello friends");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "");
+
+ match_str = "hello dear friends";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 18u);
+ EXPECT_EQ(matches[1].rm_so, -1);
+ EXPECT_EQ(matches[1].rm_eo, -1);
+ EXPECT_EQ(matches[2].rm_so, -1);
+ EXPECT_EQ(matches[2].rm_eo, -1);
+ EXPECT_EQ(matches[3].rm_so, 6);
+ EXPECT_EQ(matches[3].rm_eo, 10);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "hello dear friends");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "");
+ EXPECT_EQ(StringView(&match_str[matches[2].rm_so], matches[2].rm_eo - matches[2].rm_so), "");
+ EXPECT_EQ(StringView(&match_str[matches[3].rm_so], matches[3].rm_eo - matches[3].rm_so), "dear");
+
+ match_str = "hello my friends";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 16u);
+ EXPECT_EQ(matches[1].rm_so, -1);
+ EXPECT_EQ(matches[1].rm_eo, -1);
+ EXPECT_EQ(matches[2].rm_so, -1);
+ EXPECT_EQ(matches[2].rm_eo, -1);
+ EXPECT_EQ(matches[3].rm_so, 6);
+ EXPECT_EQ(matches[3].rm_eo, 8);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "hello my friends");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "");
+ EXPECT_EQ(StringView(&match_str[matches[2].rm_so], matches[2].rm_eo - matches[2].rm_so), "");
+ EXPECT_EQ(StringView(&match_str[matches[3].rm_so], matches[3].rm_eo - matches[3].rm_so), "my");
+
+ match_str = "testabc";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOMATCH);
+ EXPECT_EQ(matches[0].rm_cnt, 0u);
+ EXPECT_EQ(matches[0].rm_so, -1);
+ EXPECT_EQ(matches[0].rm_eo, -1);
+
+ match_str = "hello test friends";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOMATCH);
+ EXPECT_EQ(matches[0].rm_cnt, 0u);
+ EXPECT_EQ(matches[0].rm_so, -1);
+ EXPECT_EQ(matches[0].rm_eo, -1);
+
+ regfree(&regex);
+}
+
+TEST_CASE(parens_qualifier_exact)
+{
+ String pattern = "(hello){3}";
+ regex_t regex;
+ static constexpr int num_matches { 5 };
+ regmatch_t matches[num_matches];
+ const char* match_str;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+
+ match_str = "hello";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOMATCH);
+ EXPECT_EQ(matches[0].rm_cnt, 0u);
+
+ match_str = "hellohellohello";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 15u);
+ EXPECT_EQ(matches[1].rm_so, 10u);
+ EXPECT_EQ(matches[1].rm_eo, 15u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "hellohellohello");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "hello");
+
+ match_str = "hellohellohellohello";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 15u);
+ EXPECT_EQ(matches[1].rm_so, 10u);
+ EXPECT_EQ(matches[1].rm_eo, 15u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "hellohellohello");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "hello");
+
+ match_str = "test hellohellohello";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 5u);
+ EXPECT_EQ(matches[0].rm_eo, 20u);
+ EXPECT_EQ(matches[1].rm_so, 15u);
+ EXPECT_EQ(matches[1].rm_eo, 20u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "hellohellohello");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "hello");
+
+ regfree(&regex);
+}
+
+TEST_CASE(parens_qualifier_minimum)
+{
+ String pattern = "(hello){3,}";
+ regex_t regex;
+ static constexpr int num_matches { 5 };
+ regmatch_t matches[num_matches];
+ const char* match_str;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+
+ match_str = "hello";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOMATCH);
+ EXPECT_EQ(matches[0].rm_cnt, 0u);
+
+ match_str = "hellohellohello";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 15u);
+ EXPECT_EQ(matches[1].rm_so, 10u);
+ EXPECT_EQ(matches[1].rm_eo, 15u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "hellohellohello");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "hello");
+
+ match_str = "hellohellohellohello";
+
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, REG_SEARCH), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 20u);
+ EXPECT_EQ(matches[1].rm_so, 15u);
+ EXPECT_EQ(matches[1].rm_eo, 20u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "hellohellohellohello");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "hello");
+
+ match_str = "test hellohellohello";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 5u);
+ EXPECT_EQ(matches[0].rm_eo, 20u);
+ EXPECT_EQ(matches[1].rm_so, 15u);
+ EXPECT_EQ(matches[1].rm_eo, 20u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "hellohellohello");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "hello");
+
+ match_str = "test hellohellohellohello";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 5u);
+ EXPECT_EQ(matches[0].rm_eo, 25u);
+ EXPECT_EQ(matches[1].rm_so, 20u);
+ EXPECT_EQ(matches[1].rm_eo, 25u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "hellohellohellohello");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "hello");
+
+ regfree(&regex);
+}
+
+TEST_CASE(parens_qualifier_maximum)
+{
+ String pattern = "(hello){2,3}";
+ regex_t regex;
+ static constexpr int num_matches { 5 };
+ regmatch_t matches[num_matches];
+ const char* match_str;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+
+ match_str = "hello";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOMATCH);
+ EXPECT_EQ(matches[0].rm_cnt, 0u);
+
+ match_str = "hellohellohello";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 15u);
+ EXPECT_EQ(matches[1].rm_so, 10u);
+ EXPECT_EQ(matches[1].rm_eo, 15u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "hellohellohello");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "hello");
+
+ match_str = "hellohellohellohello";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 0u);
+ EXPECT_EQ(matches[0].rm_eo, 15u);
+ EXPECT_EQ(matches[1].rm_so, 10u);
+ EXPECT_EQ(matches[1].rm_eo, 15u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "hellohellohello");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "hello");
+
+ match_str = "test hellohellohello";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 5u);
+ EXPECT_EQ(matches[0].rm_eo, 20u);
+ EXPECT_EQ(matches[1].rm_so, 15u);
+ EXPECT_EQ(matches[1].rm_eo, 20u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "hellohellohello");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "hello");
+
+ match_str = "test hellohellohellohello";
+ EXPECT_EQ(regexec(&regex, match_str, num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(matches[0].rm_so, 5u);
+ EXPECT_EQ(matches[0].rm_eo, 20u);
+ EXPECT_EQ(matches[1].rm_so, 15u);
+ EXPECT_EQ(matches[1].rm_eo, 20u);
+ EXPECT_EQ(StringView(&match_str[matches[0].rm_so], matches[0].rm_eo - matches[0].rm_so), "hellohellohello");
+ EXPECT_EQ(StringView(&match_str[matches[1].rm_so], matches[1].rm_eo - matches[1].rm_so), "hello");
+
+ regfree(&regex);
+}
+
+TEST_CASE(char_qualifier_min_max)
+{
+ String pattern = "c{3,30}";
+ regex_t regex;
+ static constexpr int num_matches { 5 };
+ regmatch_t matches[num_matches];
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+
+ EXPECT_EQ(regexec(&regex, "cc", num_matches, matches, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "ccc", num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "cccccccccccccccccccccccccccccc", num_matches, matches, 0), REG_NOERR);
+ EXPECT_EQ(matches[0].rm_cnt, 1u);
+ EXPECT_EQ(regexec(&regex, "ccccccccccccccccccccccccccccccc", num_matches, matches, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "ccccccccccccccccccccccccccccccc", num_matches, matches, REG_GLOBAL), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "cccccccccccccccccccccccccccccccc", num_matches, matches, 0), REG_NOMATCH);
+
+ regfree(&regex);
+}
+
+TEST_CASE(simple_bracket_chars)
+{
+ String pattern = "[abc]";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "a", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "b", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "c", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "d", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "e", 0, NULL, 0), REG_NOMATCH);
+}
+
+TEST_CASE(simple_bracket_chars_inverse)
+{
+ String pattern = "[^abc]";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "a", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "b", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "c", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "d", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "e", 0, NULL, 0), REG_NOERR);
+}
+
+TEST_CASE(simple_bracket_chars_range)
+{
+ String pattern = "[a-d]";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "a", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "b", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "c", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "d", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "e", 0, NULL, 0), REG_NOMATCH);
+}
+
+TEST_CASE(simple_bracket_chars_range_inverse)
+{
+ String pattern = "[^a-df-z]";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "a", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "b", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "c", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "d", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "e", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "k", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "z", 0, NULL, 0), REG_NOMATCH);
+}
+
+TEST_CASE(bracket_character_class_uuid)
+{
+ String pattern = "^([[:xdigit:]]{8})-([[:xdigit:]]{4})-([[:xdigit:]]{4})-([[:xdigit:]]{4})-([[:xdigit:]]{12})$";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "fb9b62a2-1579-4e3a-afba-76239ccb6583", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "fb9b62a2", 0, NULL, 0), REG_NOMATCH);
+
+ regfree(&regex);
+}
+
+TEST_CASE(simple_bracket_character_class_inverse)
+{
+ String pattern = "[^[:digit:]]";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "1", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "2", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "3", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "d", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "e", 0, NULL, 0), REG_NOERR);
+}
+
+TEST_CASE(email_address)
+{
+ String pattern = "^[A-Z0-9a-z._%+-]{1,64}@(?:[A-Za-z0-9-]{1,63}\\.){1,125}[A-Za-z]{2,63}$";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "emanuel.sprung@gmail.com", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "kling@serenityos.org", 0, NULL, 0), REG_NOERR);
+
+ regfree(&regex);
+}
+
+TEST_CASE(error_message)
+{
+ String pattern = "^[A-Z0-9[a-z._%+-]{1,64}@[A-Za-z0-9-]{1,63}\\.{1,125}[A-Za-z]{2,63}$";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED), REG_EBRACK);
+ EXPECT_EQ(regexec(&regex, "asdf@asdf.com", 0, NULL, 0), REG_EBRACK);
+ char buf[1024];
+ size_t buflen = 1024;
+ auto len = regerror(0, &regex, buf, buflen);
+ String expected = "Error during parsing of regular expression:\n ^[A-Z0-9[a-z._%+-]{1,64}@[A-Za-z0-9-]{1,63}\\.{1,125}[A-Za-z]{2,63}$\n ^---- [ ] imbalance.";
+ for (size_t i = 0; i < len; ++i) {
+ EXPECT_EQ(buf[i], expected[i]);
+ }
+
+ regfree(&regex);
+}
+
+TEST_CASE(simple_ignorecase)
+{
+ String pattern = "^hello friends";
+ regex_t regex;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED | REG_NOSUB | REG_ICASE), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "Hello Friends", 0, NULL, 0), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "hello Friends", 0, NULL, 0), REG_NOERR);
+
+ EXPECT_EQ(regexec(&regex, "hello Friends!", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hello Friends!", 0, NULL, REG_GLOBAL), REG_NOERR);
+
+ EXPECT_EQ(regexec(&regex, "hell Friends", 0, NULL, 0), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hell Friends", 0, NULL, REG_GLOBAL), REG_NOMATCH);
+
+ regfree(&regex);
+}
+
+TEST_CASE(simple_notbol_noteol)
+{
+ String pattern = "^hello friends$";
+ String pattern2 = "hello friends";
+ regex_t regex, regex2;
+
+ EXPECT_EQ(regcomp(&regex, pattern.characters(), REG_EXTENDED | REG_NOSUB | REG_ICASE), REG_NOERR);
+ EXPECT_EQ(regcomp(&regex2, pattern2.characters(), REG_EXTENDED | REG_NOSUB | REG_ICASE), REG_NOERR);
+
+ EXPECT_EQ(regexec(&regex, "hello friends", 0, NULL, REG_NOTBOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hello friends", 0, NULL, REG_NOTEOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hello friends", 0, NULL, REG_NOTBOL | REG_NOTEOL), REG_NOMATCH);
+
+ EXPECT_EQ(regexec(&regex, "a hello friends b", 0, NULL, REG_NOTBOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "a hello friends", 0, NULL, REG_NOTBOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "a hello friends", 0, NULL, REG_NOTBOL | REG_SEARCH), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "a hello friends b", 0, NULL, REG_NOTBOL | REG_SEARCH), REG_NOERR);
+
+ EXPECT_EQ(regexec(&regex, "a hello friends b", 0, NULL, REG_NOTEOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hello friends b", 0, NULL, REG_NOTEOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "hello friends b", 0, NULL, REG_NOTEOL | REG_SEARCH), REG_NOERR);
+ EXPECT_EQ(regexec(&regex, "a hello friends b", 0, NULL, REG_NOTEOL | REG_SEARCH), REG_NOMATCH);
+
+ EXPECT_EQ(regexec(&regex, "a hello friends b", 0, NULL, REG_NOTBOL | REG_NOTEOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex, "a hello friends b", 0, NULL, REG_NOTBOL | REG_NOTEOL | REG_SEARCH), REG_NOMATCH);
+
+ EXPECT_EQ(regexec(&regex2, "hello friends", 0, NULL, REG_NOTBOL), REG_NOMATCH);
+ EXPECT_EQ(regexec(&regex2, "hello friends", 0, NULL, REG_NOTEOL), REG_NOMATCH);
+
+ regfree(&regex);
+}
+
+TEST_MAIN(Regex)
diff --git a/Userland/Libraries/LibTLS/CMakeLists.txt b/Userland/Libraries/LibTLS/CMakeLists.txt
new file mode 100644
index 0000000000..e4194ac456
--- /dev/null
+++ b/Userland/Libraries/LibTLS/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(SOURCES
+ ClientHandshake.cpp
+ Exchange.cpp
+ Handshake.cpp
+ Record.cpp
+ Socket.cpp
+ TLSv12.cpp
+)
+
+serenity_lib(LibTLS tls)
+target_link_libraries(LibTLS LibCore LibCrypto)
diff --git a/Userland/Libraries/LibTLS/Certificate.h b/Userland/Libraries/LibTLS/Certificate.h
new file mode 100644
index 0000000000..22953092d4
--- /dev/null
+++ b/Userland/Libraries/LibTLS/Certificate.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/Forward.h>
+#include <AK/Singleton.h>
+#include <AK/Types.h>
+#include <LibCrypto/BigInt/UnsignedBigInteger.h>
+#include <LibCrypto/PK/RSA.h>
+
+namespace TLS {
+
+enum class CertificateKeyAlgorithm {
+ Unsupported = 0x00,
+ RSA_RSA = 0x01,
+ RSA_MD5 = 0x04,
+ RSA_SHA1 = 0x05,
+ RSA_SHA256 = 0x0b,
+ RSA_SHA512 = 0x0d,
+};
+
+struct Certificate {
+ u16 version;
+ CertificateKeyAlgorithm algorithm;
+ CertificateKeyAlgorithm key_algorithm;
+ CertificateKeyAlgorithm ec_algorithm;
+ ByteBuffer exponent;
+ Crypto::PK::RSAPublicKey<Crypto::UnsignedBigInteger> public_key;
+ Crypto::PK::RSAPrivateKey<Crypto::UnsignedBigInteger> private_key;
+ String issuer_country;
+ String issuer_state;
+ String issuer_location;
+ String issuer_entity;
+ String issuer_subject;
+ String issuer_unit;
+ String not_before;
+ String not_after;
+ String country;
+ String state;
+ String location;
+ String entity;
+ String subject;
+ String unit;
+ Vector<String> SAN;
+ u8* ocsp;
+ Crypto::UnsignedBigInteger serial_number;
+ ByteBuffer sign_key;
+ ByteBuffer fingerprint;
+ ByteBuffer der;
+ ByteBuffer data;
+
+ bool is_valid() const;
+};
+
+class DefaultRootCACertificates {
+public:
+ DefaultRootCACertificates();
+
+ const Vector<Certificate>& certificates() const { return m_ca_certificates; }
+
+ static DefaultRootCACertificates& the() { return s_the; }
+
+private:
+ static AK::Singleton<DefaultRootCACertificates> s_the;
+
+ Vector<Certificate> m_ca_certificates;
+};
+
+}
+
+using TLS::Certificate;
+using TLS::DefaultRootCACertificates;
diff --git a/Userland/Libraries/LibTLS/ClientHandshake.cpp b/Userland/Libraries/LibTLS/ClientHandshake.cpp
new file mode 100644
index 0000000000..b1fbaa2391
--- /dev/null
+++ b/Userland/Libraries/LibTLS/ClientHandshake.cpp
@@ -0,0 +1,667 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Endian.h>
+#include <AK/Random.h>
+
+#include <LibCore/Timer.h>
+#include <LibCrypto/ASN1/DER.h>
+#include <LibCrypto/PK/Code/EMSA_PSS.h>
+#include <LibTLS/TLSv12.h>
+
+namespace TLS {
+
+ssize_t TLSv12::handle_server_hello_done(ReadonlyBytes buffer)
+{
+ if (buffer.size() < 3)
+ return (i8)Error::NeedMoreData;
+
+ size_t size = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2];
+
+ if (buffer.size() - 3 < size)
+ return (i8)Error::NeedMoreData;
+
+ return size + 3;
+}
+
+ssize_t TLSv12::handle_hello(ReadonlyBytes buffer, WritePacketStage& write_packets)
+{
+ write_packets = WritePacketStage::Initial;
+ if (m_context.connection_status != ConnectionStatus::Disconnected && m_context.connection_status != ConnectionStatus::Renegotiating) {
+ dbgln("unexpected hello message");
+ return (i8)Error::UnexpectedMessage;
+ }
+ ssize_t res = 0;
+ size_t min_hello_size = 41;
+
+ if (min_hello_size > buffer.size()) {
+ dbgln("need more data");
+ return (i8)Error::NeedMoreData;
+ }
+ size_t following_bytes = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2];
+ res += 3;
+ if (buffer.size() - res < following_bytes) {
+ dbg() << "not enough data after header: " << buffer.size() - res << " < " << following_bytes;
+ return (i8)Error::NeedMoreData;
+ }
+
+ if (buffer.size() - res < 2) {
+ dbgln("not enough data for version");
+ return (i8)Error::NeedMoreData;
+ }
+ auto version = (Version)AK::convert_between_host_and_network_endian(*(const u16*)buffer.offset_pointer(res));
+
+ res += 2;
+ if (!supports_version(version))
+ return (i8)Error::NotSafe;
+
+ memcpy(m_context.remote_random, buffer.offset_pointer(res), sizeof(m_context.remote_random));
+ res += sizeof(m_context.remote_random);
+
+ u8 session_length = buffer[res++];
+ if (buffer.size() - res < session_length) {
+ dbgln("not enough data for session id");
+ return (i8)Error::NeedMoreData;
+ }
+
+ if (session_length && session_length <= 32) {
+ memcpy(m_context.session_id, buffer.offset_pointer(res), session_length);
+ m_context.session_id_size = session_length;
+#ifdef TLS_DEBUG
+ dbgln("Remote session ID:");
+ print_buffer(ReadonlyBytes { m_context.session_id, session_length });
+#endif
+ } else {
+ m_context.session_id_size = 0;
+ }
+ res += session_length;
+
+ if (buffer.size() - res < 2) {
+ dbgln("not enough data for cipher suite listing");
+ return (i8)Error::NeedMoreData;
+ }
+ auto cipher = (CipherSuite)AK::convert_between_host_and_network_endian(*(const u16*)buffer.offset_pointer(res));
+ res += 2;
+ if (!supports_cipher(cipher)) {
+ m_context.cipher = CipherSuite::Invalid;
+ dbgln("No supported cipher could be agreed upon");
+ return (i8)Error::NoCommonCipher;
+ }
+ m_context.cipher = cipher;
+#ifdef TLS_DEBUG
+ dbg() << "Cipher: " << (u16)cipher;
+#endif
+
+ // The handshake hash function is _always_ SHA256
+ m_context.handshake_hash.initialize(Crypto::Hash::HashKind::SHA256);
+
+ if (buffer.size() - res < 1) {
+ dbgln("not enough data for compression spec");
+ return (i8)Error::NeedMoreData;
+ }
+ u8 compression = buffer[res++];
+ if (compression != 0) {
+ dbgln("Server told us to compress, we will not!");
+ return (i8)Error::CompressionNotSupported;
+ }
+
+ if (res > 0) {
+ if (m_context.connection_status != ConnectionStatus::Renegotiating)
+ m_context.connection_status = ConnectionStatus::Negotiating;
+ if (m_context.is_server) {
+ dbgln("unsupported: server mode");
+ write_packets = WritePacketStage::ServerHandshake;
+ }
+ }
+
+ if (res > 2) {
+ res += 2;
+ }
+
+ while ((ssize_t)buffer.size() - res >= 4) {
+ auto extension_type = (HandshakeExtension)AK::convert_between_host_and_network_endian(*(const u16*)buffer.offset_pointer(res));
+ res += 2;
+ u16 extension_length = AK::convert_between_host_and_network_endian(*(const u16*)buffer.offset_pointer(res));
+ res += 2;
+
+#ifdef TLS_DEBUG
+ dbg() << "extension " << (u16)extension_type << " with length " << extension_length;
+#endif
+ if (extension_length) {
+ if (buffer.size() - res < extension_length) {
+ dbgln("not enough data for extension");
+ return (i8)Error::NeedMoreData;
+ }
+
+ // SNI
+ if (extension_type == HandshakeExtension::ServerName) {
+ u16 sni_host_length = AK::convert_between_host_and_network_endian(*(const u16*)buffer.offset_pointer(res + 3));
+ if (buffer.size() - res - 5 < sni_host_length) {
+ dbg() << "Not enough data for sni " << (buffer.size() - res - 5) << " < " << sni_host_length;
+ return (i8)Error::NeedMoreData;
+ }
+
+ if (sni_host_length) {
+ m_context.SNI = String { (const char*)buffer.offset_pointer(res + 5), sni_host_length };
+ dbg() << "server name indicator: " << m_context.SNI;
+ }
+ } else if (extension_type == HandshakeExtension::ApplicationLayerProtocolNegotiation && m_context.alpn.size()) {
+ if (buffer.size() - res > 2) {
+ auto alpn_length = AK::convert_between_host_and_network_endian(*(const u16*)buffer.offset_pointer(res));
+ if (alpn_length && alpn_length <= extension_length - 2) {
+ const u8* alpn = buffer.offset_pointer(res + 2);
+ size_t alpn_position = 0;
+ while (alpn_position < alpn_length) {
+ u8 alpn_size = alpn[alpn_position++];
+ if (alpn_size + alpn_position >= extension_length)
+ break;
+ String alpn_str { (const char*)alpn + alpn_position, alpn_length };
+ if (alpn_size && m_context.alpn.contains_slow(alpn_str)) {
+ m_context.negotiated_alpn = alpn_str;
+ dbg() << "negotiated alpn: " << alpn_str;
+ break;
+ }
+ alpn_position += alpn_length;
+ if (!m_context.is_server) // server hello must contain one ALPN
+ break;
+ }
+ }
+ }
+ } else if (extension_type == HandshakeExtension::SignatureAlgorithms) {
+ dbgln("supported signatures: ");
+ print_buffer(buffer.slice(res, extension_length));
+ // FIXME: what are we supposed to do here?
+ }
+ res += extension_length;
+ }
+ }
+
+ return res;
+}
+
+ssize_t TLSv12::handle_finished(ReadonlyBytes buffer, WritePacketStage& write_packets)
+{
+ if (m_context.connection_status < ConnectionStatus::KeyExchange || m_context.connection_status == ConnectionStatus::Established) {
+ dbgln("unexpected finished message");
+ return (i8)Error::UnexpectedMessage;
+ }
+
+ write_packets = WritePacketStage::Initial;
+
+ if (buffer.size() < 3) {
+ return (i8)Error::NeedMoreData;
+ }
+
+ size_t index = 3;
+
+ u32 size = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2];
+
+ if (size < 12) {
+#ifdef TLS_DEBUG
+ dbg() << "finished packet smaller than minimum size: " << size;
+#endif
+ return (i8)Error::BrokenPacket;
+ }
+
+ if (size < buffer.size() - index) {
+#ifdef TLS_DEBUG
+ dbg() << "not enough data after length: " << size << " > " << buffer.size() - index;
+#endif
+ return (i8)Error::NeedMoreData;
+ }
+
+// TODO: Compare Hashes
+#ifdef TLS_DEBUG
+ dbgln("FIXME: handle_finished :: Check message validity");
+#endif
+ m_context.connection_status = ConnectionStatus::Established;
+
+ if (m_handshake_timeout_timer) {
+ // Disable the handshake timeout timer as handshake has been established.
+ m_handshake_timeout_timer->stop();
+ m_handshake_timeout_timer->remove_from_parent();
+ m_handshake_timeout_timer = nullptr;
+ }
+
+ if (on_tls_ready_to_write)
+ on_tls_ready_to_write(*this);
+
+ return index + size;
+}
+
+void TLSv12::build_random(PacketBuilder& builder)
+{
+ u8 random_bytes[48];
+ size_t bytes = 48;
+
+ AK::fill_with_random(random_bytes, bytes);
+
+ // remove zeros from the random bytes
+ for (size_t i = 0; i < bytes; ++i) {
+ if (!random_bytes[i])
+ random_bytes[i--] = AK::get_random<u8>();
+ }
+
+ if (m_context.is_server) {
+ dbgln("Server mode not supported");
+ return;
+ } else {
+ *(u16*)random_bytes = AK::convert_between_host_and_network_endian((u16)Version::V12);
+ }
+
+ m_context.premaster_key = ByteBuffer::copy(random_bytes, bytes);
+
+ const auto& certificate_option = verify_chain_and_get_matching_certificate(m_context.SNI); // if the SNI is empty, we'll make a special case and match *a* leaf certificate.
+ if (!certificate_option.has_value()) {
+ dbgln("certificate verification failed :(");
+ alert(AlertLevel::Critical, AlertDescription::BadCertificate);
+ return;
+ }
+
+ auto& certificate = m_context.certificates[certificate_option.value()];
+#ifdef TLS_DEBUG
+ dbgln("PreMaster secret");
+ print_buffer(m_context.premaster_key);
+#endif
+
+ Crypto::PK::RSA_PKCS1_EME rsa(certificate.public_key.modulus(), 0, certificate.public_key.public_exponent());
+
+ u8 out[rsa.output_size()];
+ auto outbuf = Bytes { out, rsa.output_size() };
+ rsa.encrypt(m_context.premaster_key, outbuf);
+
+#ifdef TLS_DEBUG
+ dbgln("Encrypted: ");
+ print_buffer(outbuf);
+#endif
+
+ if (!compute_master_secret(bytes)) {
+ dbgln("oh noes we could not derive a master key :(");
+ return;
+ }
+
+ builder.append_u24(outbuf.size() + 2);
+ builder.append((u16)outbuf.size());
+ builder.append(outbuf);
+}
+
+ssize_t TLSv12::handle_payload(ReadonlyBytes vbuffer)
+{
+ if (m_context.connection_status == ConnectionStatus::Established) {
+#ifdef TLS_DEBUG
+ dbgln("Renegotiation attempt ignored");
+#endif
+ // FIXME: We should properly say "NoRenegotiation", but that causes a handshake failure
+ // so we just roll with it and pretend that we _did_ renegotiate
+ // This will cause issues when we decide to have long-lasting connections, but
+ // we do not have those at the moment :^)
+ return 1;
+ }
+ auto buffer = vbuffer;
+ auto buffer_length = buffer.size();
+ auto original_length = buffer_length;
+ while (buffer_length >= 4 && !m_context.critical_error) {
+ ssize_t payload_res = 0;
+ if (buffer_length < 1)
+ return (i8)Error::NeedMoreData;
+ auto type = buffer[0];
+ auto write_packets { WritePacketStage::Initial };
+ size_t payload_size = buffer[1] * 0x10000 + buffer[2] * 0x100 + buffer[3] + 3;
+#ifdef TLS_DEBUG
+ dbg() << "payload size: " << payload_size << " buffer length: " << buffer_length;
+#endif
+ if (payload_size + 1 > buffer_length)
+ return (i8)Error::NeedMoreData;
+
+ switch (type) {
+ case HelloRequest:
+ if (m_context.handshake_messages[0] >= 1) {
+ dbgln("unexpected hello request message");
+ payload_res = (i8)Error::UnexpectedMessage;
+ break;
+ }
+ ++m_context.handshake_messages[0];
+ dbgln("hello request (renegotiation?)");
+ if (m_context.connection_status == ConnectionStatus::Established) {
+ // renegotiation
+ payload_res = (i8)Error::NoRenegotiation;
+ } else {
+ // :shrug:
+ payload_res = (i8)Error::UnexpectedMessage;
+ }
+ break;
+ case ClientHello:
+ // FIXME: We only support client mode right now
+ if (m_context.is_server) {
+ ASSERT_NOT_REACHED();
+ }
+ payload_res = (i8)Error::UnexpectedMessage;
+ break;
+ case ServerHello:
+ if (m_context.handshake_messages[2] >= 1) {
+ dbgln("unexpected server hello message");
+ payload_res = (i8)Error::UnexpectedMessage;
+ break;
+ }
+ ++m_context.handshake_messages[2];
+#ifdef TLS_DEBUG
+ dbgln("server hello");
+#endif
+ if (m_context.is_server) {
+ dbgln("unsupported: server mode");
+ ASSERT_NOT_REACHED();
+ } else {
+ payload_res = handle_hello(buffer.slice(1, payload_size), write_packets);
+ }
+ break;
+ case HelloVerifyRequest:
+ dbgln("unsupported: DTLS");
+ payload_res = (i8)Error::UnexpectedMessage;
+ break;
+ case CertificateMessage:
+ if (m_context.handshake_messages[4] >= 1) {
+ dbgln("unexpected certificate message");
+ payload_res = (i8)Error::UnexpectedMessage;
+ break;
+ }
+ ++m_context.handshake_messages[4];
+#ifdef TLS_DEBUG
+ dbgln("certificate");
+#endif
+ if (m_context.connection_status == ConnectionStatus::Negotiating) {
+ if (m_context.is_server) {
+ dbgln("unsupported: server mode");
+ ASSERT_NOT_REACHED();
+ }
+ payload_res = handle_certificate(buffer.slice(1, payload_size));
+ if (m_context.certificates.size()) {
+ auto it = m_context.certificates.find_if([](const auto& cert) { return cert.is_valid(); });
+
+ if (it.is_end()) {
+ // no valid certificates
+ dbgln("No valid certificates found");
+ payload_res = (i8)Error::BadCertificate;
+ m_context.critical_error = payload_res;
+ break;
+ }
+
+ // swap the first certificate with the valid one
+ if (it.index() != 0)
+ swap(m_context.certificates[0], m_context.certificates[it.index()]);
+ }
+ } else {
+ payload_res = (i8)Error::UnexpectedMessage;
+ }
+ break;
+ case ServerKeyExchange:
+ if (m_context.handshake_messages[5] >= 1) {
+ dbgln("unexpected server key exchange message");
+ payload_res = (i8)Error::UnexpectedMessage;
+ break;
+ }
+ ++m_context.handshake_messages[5];
+#ifdef TLS_DEBUG
+ dbgln("server key exchange");
+#endif
+ if (m_context.is_server) {
+ dbgln("unsupported: server mode");
+ ASSERT_NOT_REACHED();
+ } else {
+ payload_res = handle_server_key_exchange(buffer.slice(1, payload_size));
+ }
+ break;
+ case CertificateRequest:
+ if (m_context.handshake_messages[6] >= 1) {
+ dbgln("unexpected certificate request message");
+ payload_res = (i8)Error::UnexpectedMessage;
+ break;
+ }
+ ++m_context.handshake_messages[6];
+ if (m_context.is_server) {
+ dbgln("invalid request");
+ dbgln("unsupported: server mode");
+ ASSERT_NOT_REACHED();
+ } else {
+ // we do not support "certificate request"
+ dbgln("certificate request");
+ if (on_tls_certificate_request)
+ on_tls_certificate_request(*this);
+ m_context.client_verified = VerificationNeeded;
+ }
+ break;
+ case ServerHelloDone:
+ if (m_context.handshake_messages[7] >= 1) {
+ dbgln("unexpected server hello done message");
+ payload_res = (i8)Error::UnexpectedMessage;
+ break;
+ }
+ ++m_context.handshake_messages[7];
+#ifdef TLS_DEBUG
+ dbgln("server hello done");
+#endif
+ if (m_context.is_server) {
+ dbgln("unsupported: server mode");
+ ASSERT_NOT_REACHED();
+ } else {
+ payload_res = handle_server_hello_done(buffer.slice(1, payload_size));
+ if (payload_res > 0)
+ write_packets = WritePacketStage::ClientHandshake;
+ }
+ break;
+ case CertificateVerify:
+ if (m_context.handshake_messages[8] >= 1) {
+ dbgln("unexpected certificate verify message");
+ payload_res = (i8)Error::UnexpectedMessage;
+ break;
+ }
+ ++m_context.handshake_messages[8];
+#ifdef TLS_DEBUG
+ dbgln("certificate verify");
+#endif
+ if (m_context.connection_status == ConnectionStatus::KeyExchange) {
+ payload_res = handle_verify(buffer.slice(1, payload_size));
+ } else {
+ payload_res = (i8)Error::UnexpectedMessage;
+ }
+ break;
+ case ClientKeyExchange:
+ if (m_context.handshake_messages[9] >= 1) {
+ dbgln("unexpected client key exchange message");
+ payload_res = (i8)Error::UnexpectedMessage;
+ break;
+ }
+ ++m_context.handshake_messages[9];
+#ifdef TLS_DEBUG
+ dbgln("client key exchange");
+#endif
+ if (m_context.is_server) {
+ dbgln("unsupported: server mode");
+ ASSERT_NOT_REACHED();
+ } else {
+ payload_res = (i8)Error::UnexpectedMessage;
+ }
+ break;
+ case Finished:
+ if (m_context.cached_handshake) {
+ m_context.cached_handshake.clear();
+ }
+ if (m_context.handshake_messages[10] >= 1) {
+ dbgln("unexpected finished message");
+ payload_res = (i8)Error::UnexpectedMessage;
+ break;
+ }
+ ++m_context.handshake_messages[10];
+#ifdef TLS_DEBUG
+ dbgln("finished");
+#endif
+ payload_res = handle_finished(buffer.slice(1, payload_size), write_packets);
+ if (payload_res > 0) {
+ memset(m_context.handshake_messages, 0, sizeof(m_context.handshake_messages));
+ }
+ break;
+ default:
+ dbg() << "message type not understood: " << type;
+ return (i8)Error::NotUnderstood;
+ }
+
+ if (type != HelloRequest) {
+ update_hash(buffer.slice(0, payload_size + 1));
+ }
+
+ // if something went wrong, send an alert about it
+ if (payload_res < 0) {
+ switch ((Error)payload_res) {
+ case Error::UnexpectedMessage: {
+ auto packet = build_alert(true, (u8)AlertDescription::UnexpectedMessage);
+ write_packet(packet);
+ break;
+ }
+ case Error::CompressionNotSupported: {
+ auto packet = build_alert(true, (u8)AlertDescription::DecompressionFailure);
+ write_packet(packet);
+ break;
+ }
+ case Error::BrokenPacket: {
+ auto packet = build_alert(true, (u8)AlertDescription::DecodeError);
+ write_packet(packet);
+ break;
+ }
+ case Error::NotVerified: {
+ auto packet = build_alert(true, (u8)AlertDescription::BadRecordMAC);
+ write_packet(packet);
+ break;
+ }
+ case Error::BadCertificate: {
+ auto packet = build_alert(true, (u8)AlertDescription::BadCertificate);
+ write_packet(packet);
+ break;
+ }
+ case Error::UnsupportedCertificate: {
+ auto packet = build_alert(true, (u8)AlertDescription::UnsupportedCertificate);
+ write_packet(packet);
+ break;
+ }
+ case Error::NoCommonCipher: {
+ auto packet = build_alert(true, (u8)AlertDescription::InsufficientSecurity);
+ write_packet(packet);
+ break;
+ }
+ case Error::NotUnderstood: {
+ auto packet = build_alert(true, (u8)AlertDescription::InternalError);
+ write_packet(packet);
+ break;
+ }
+ case Error::NoRenegotiation: {
+ auto packet = build_alert(true, (u8)AlertDescription::NoRenegotiation);
+ write_packet(packet);
+ break;
+ }
+ case Error::DecryptionFailed: {
+ auto packet = build_alert(true, (u8)AlertDescription::DecryptionFailed);
+ write_packet(packet);
+ break;
+ }
+ case Error::NeedMoreData:
+ // Ignore this, as it's not an "error"
+ break;
+ default:
+ dbg() << "Unknown TLS::Error with value " << payload_res;
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ if (payload_res < 0)
+ return payload_res;
+ }
+ switch (write_packets) {
+ case WritePacketStage::Initial:
+ // nothing to write
+ break;
+ case WritePacketStage::ClientHandshake:
+ if (m_context.client_verified == VerificationNeeded) {
+#ifdef TLS_DEBUG
+ dbgln("> Client Certificate");
+#endif
+ auto packet = build_certificate();
+ write_packet(packet);
+ m_context.client_verified = Verified;
+ }
+ {
+#ifdef TLS_DEBUG
+ dbgln("> Key exchange");
+#endif
+ auto packet = build_client_key_exchange();
+ write_packet(packet);
+ }
+ {
+#ifdef TLS_DEBUG
+ dbgln("> change cipher spec");
+#endif
+ auto packet = build_change_cipher_spec();
+ write_packet(packet);
+ }
+ m_context.cipher_spec_set = 1;
+ m_context.local_sequence_number = 0;
+ {
+#ifdef TLS_DEBUG
+ dbgln("> client finished");
+#endif
+ auto packet = build_finished();
+ write_packet(packet);
+ }
+ m_context.cipher_spec_set = 0;
+ break;
+ case WritePacketStage::ServerHandshake:
+ // server handshake
+ dbgln("UNSUPPORTED: Server mode");
+ ASSERT_NOT_REACHED();
+ break;
+ case WritePacketStage::Finished:
+ // finished
+ {
+#ifdef TLS_DEBUG
+ dbgln("> change cipher spec");
+#endif
+ auto packet = build_change_cipher_spec();
+ write_packet(packet);
+ }
+ {
+#ifdef TLS_DEBUG
+ dbgln("> client finished");
+#endif
+ auto packet = build_finished();
+ write_packet(packet);
+ }
+ m_context.connection_status = ConnectionStatus::Established;
+ break;
+ }
+ payload_size++;
+ buffer_length -= payload_size;
+ buffer = buffer.slice(payload_size, buffer_length);
+ }
+ return original_length;
+}
+
+}
diff --git a/Userland/Libraries/LibTLS/Exchange.cpp b/Userland/Libraries/LibTLS/Exchange.cpp
new file mode 100644
index 0000000000..0765bc5e9d
--- /dev/null
+++ b/Userland/Libraries/LibTLS/Exchange.cpp
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCrypto/ASN1/DER.h>
+#include <LibCrypto/PK/Code/EMSA_PSS.h>
+#include <LibTLS/TLSv12.h>
+
+namespace TLS {
+
+bool TLSv12::expand_key()
+{
+ u8 key[192]; // soooooooo many constants
+ auto key_buffer = Bytes { key, sizeof(key) };
+
+ auto is_aead = this->is_aead();
+
+ if (m_context.master_key.size() == 0) {
+ dbgln("expand_key() with empty master key");
+ return false;
+ }
+
+ auto key_size = key_length();
+ auto mac_size = mac_length();
+ auto iv_size = iv_length();
+
+ pseudorandom_function(
+ key_buffer,
+ m_context.master_key,
+ (const u8*)"key expansion", 13,
+ ReadonlyBytes { m_context.remote_random, sizeof(m_context.remote_random) },
+ ReadonlyBytes { m_context.local_random, sizeof(m_context.local_random) });
+
+ size_t offset = 0;
+ if (is_aead) {
+ iv_size = 4; // Explicit IV size.
+ } else {
+ memcpy(m_context.crypto.local_mac, key + offset, mac_size);
+ offset += mac_size;
+ memcpy(m_context.crypto.remote_mac, key + offset, mac_size);
+ offset += mac_size;
+ }
+
+ auto client_key = key + offset;
+ offset += key_size;
+ auto server_key = key + offset;
+ offset += key_size;
+ auto client_iv = key + offset;
+ offset += iv_size;
+ auto server_iv = key + offset;
+ offset += iv_size;
+
+#ifdef TLS_DEBUG
+ dbgln("client key");
+ print_buffer(client_key, key_size);
+ dbgln("server key");
+ print_buffer(server_key, key_size);
+ dbgln("client iv");
+ print_buffer(client_iv, iv_size);
+ dbgln("server iv");
+ print_buffer(server_iv, iv_size);
+ if (!is_aead) {
+ dbgln("client mac key");
+ print_buffer(m_context.crypto.local_mac, mac_size);
+ dbgln("server mac key");
+ print_buffer(m_context.crypto.remote_mac, mac_size);
+ }
+#endif
+
+ if (is_aead) {
+ memcpy(m_context.crypto.local_aead_iv, client_iv, iv_size);
+ memcpy(m_context.crypto.remote_aead_iv, server_iv, iv_size);
+
+ m_aes_local.gcm = make<Crypto::Cipher::AESCipher::GCMMode>(ReadonlyBytes { client_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Encryption, Crypto::Cipher::PaddingMode::RFC5246);
+ m_aes_remote.gcm = make<Crypto::Cipher::AESCipher::GCMMode>(ReadonlyBytes { server_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Decryption, Crypto::Cipher::PaddingMode::RFC5246);
+ } else {
+ memcpy(m_context.crypto.local_iv, client_iv, iv_size);
+ memcpy(m_context.crypto.remote_iv, server_iv, iv_size);
+
+ m_aes_local.cbc = make<Crypto::Cipher::AESCipher::CBCMode>(ReadonlyBytes { client_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Encryption, Crypto::Cipher::PaddingMode::RFC5246);
+ m_aes_remote.cbc = make<Crypto::Cipher::AESCipher::CBCMode>(ReadonlyBytes { server_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Decryption, Crypto::Cipher::PaddingMode::RFC5246);
+ }
+
+ m_context.crypto.created = 1;
+
+ return true;
+}
+
+void TLSv12::pseudorandom_function(Bytes output, ReadonlyBytes secret, const u8* label, size_t label_length, ReadonlyBytes seed, ReadonlyBytes seed_b)
+{
+ if (!secret.size()) {
+ dbgln("null secret");
+ return;
+ }
+
+ // RFC 5246: "In this section, we define one PRF, based on HMAC. This PRF with the
+ // SHA-256 hash function is used for all cipher suites defined in this
+ // document and in TLS documents published prior to this document when
+ // TLS 1.2 is negotiated."
+ // Apparently this PRF _always_ uses SHA256
+ Crypto::Authentication::HMAC<Crypto::Hash::SHA256> hmac(secret);
+
+ auto l_seed_size = label_length + seed.size() + seed_b.size();
+ u8 l_seed[l_seed_size];
+ auto label_seed_buffer = Bytes { l_seed, l_seed_size };
+ label_seed_buffer.overwrite(0, label, label_length);
+ label_seed_buffer.overwrite(label_length, seed.data(), seed.size());
+ label_seed_buffer.overwrite(label_length + seed.size(), seed_b.data(), seed_b.size());
+
+ auto digest_size = hmac.digest_size();
+
+ u8 digest[digest_size];
+
+ auto digest_0 = Bytes { digest, digest_size };
+
+ digest_0.overwrite(0, hmac.process(label_seed_buffer).immutable_data(), digest_size);
+
+ size_t index = 0;
+ while (index < output.size()) {
+ hmac.update(digest_0);
+ hmac.update(label_seed_buffer);
+ auto digest_1 = hmac.digest();
+
+ auto copy_size = min(digest_size, output.size() - index);
+
+ output.overwrite(index, digest_1.immutable_data(), copy_size);
+ index += copy_size;
+
+ digest_0.overwrite(0, hmac.process(digest_0).immutable_data(), digest_size);
+ }
+}
+
+bool TLSv12::compute_master_secret(size_t length)
+{
+ if (m_context.premaster_key.size() == 0 || length < 48) {
+ dbgln("there's no way I can make a master secret like this");
+ dbg() << "I'd like to talk to your manager about this length of " << length;
+ return false;
+ }
+
+ m_context.master_key.clear();
+ m_context.master_key.grow(length);
+
+ pseudorandom_function(
+ m_context.master_key,
+ m_context.premaster_key,
+ (const u8*)"master secret", 13,
+ ReadonlyBytes { m_context.local_random, sizeof(m_context.local_random) },
+ ReadonlyBytes { m_context.remote_random, sizeof(m_context.remote_random) });
+
+ m_context.premaster_key.clear();
+#ifdef TLS_DEBUG
+ dbgln("master key:");
+ print_buffer(m_context.master_key);
+#endif
+ expand_key();
+ return true;
+}
+
+ByteBuffer TLSv12::build_certificate()
+{
+ PacketBuilder builder { MessageType::Handshake, m_context.version };
+
+ Vector<const Certificate*> certificates;
+ Vector<Certificate>* local_certificates = nullptr;
+
+ if (m_context.is_server) {
+ dbgln("Unsupported: Server mode");
+ ASSERT_NOT_REACHED();
+ } else {
+ local_certificates = &m_context.client_certificates;
+ }
+
+ constexpr size_t der_length_delta = 3;
+ constexpr size_t certificate_vector_header_size = 3;
+
+ size_t total_certificate_size = 0;
+
+ for (size_t i = 0; i < local_certificates->size(); ++i) {
+ auto& certificate = local_certificates->at(i);
+ if (!certificate.der.is_empty()) {
+ total_certificate_size += certificate.der.size() + der_length_delta;
+
+ // FIXME: Check for and respond with only the requested certificate types.
+ if (true) {
+ certificates.append(&certificate);
+ }
+ }
+ }
+
+ builder.append((u8)HandshakeType::CertificateMessage);
+
+ if (!total_certificate_size) {
+#ifdef TLS_DEBUG
+ dbgln("No certificates, sending empty certificate message");
+#endif
+ builder.append_u24(certificate_vector_header_size);
+ builder.append_u24(total_certificate_size);
+ } else {
+ builder.append_u24(total_certificate_size + certificate_vector_header_size); // 3 bytes for header
+ builder.append_u24(total_certificate_size);
+
+ for (auto& certificate : certificates) {
+ if (!certificate->der.is_empty()) {
+ builder.append_u24(certificate->der.size());
+ builder.append(certificate->der.bytes());
+ }
+ }
+ }
+ auto packet = builder.build();
+ update_packet(packet);
+ return packet;
+}
+
+ByteBuffer TLSv12::build_change_cipher_spec()
+{
+ PacketBuilder builder { MessageType::ChangeCipher, m_context.version, 64 };
+ builder.append((u8)1);
+ auto packet = builder.build();
+ update_packet(packet);
+ m_context.local_sequence_number = 0;
+ return packet;
+}
+
+ByteBuffer TLSv12::build_server_key_exchange()
+{
+ dbgln("FIXME: build_server_key_exchange");
+ return {};
+}
+
+ByteBuffer TLSv12::build_client_key_exchange()
+{
+ PacketBuilder builder { MessageType::Handshake, m_context.version };
+ builder.append((u8)HandshakeType::ClientKeyExchange);
+ build_random(builder);
+
+ m_context.connection_status = ConnectionStatus::KeyExchange;
+
+ auto packet = builder.build();
+
+ update_packet(packet);
+
+ return packet;
+}
+
+ssize_t TLSv12::handle_server_key_exchange(ReadonlyBytes)
+{
+ dbgln("FIXME: parse_server_key_exchange");
+ return 0;
+}
+
+ssize_t TLSv12::handle_verify(ReadonlyBytes)
+{
+ dbgln("FIXME: parse_verify");
+ return 0;
+}
+
+}
diff --git a/Userland/Libraries/LibTLS/Handshake.cpp b/Userland/Libraries/LibTLS/Handshake.cpp
new file mode 100644
index 0000000000..1fa0f01fca
--- /dev/null
+++ b/Userland/Libraries/LibTLS/Handshake.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Random.h>
+#include <LibCrypto/ASN1/DER.h>
+#include <LibCrypto/PK/Code/EMSA_PSS.h>
+#include <LibTLS/TLSv12.h>
+
+namespace TLS {
+
+ByteBuffer TLSv12::build_hello()
+{
+ AK::fill_with_random(&m_context.local_random, 32);
+
+ auto packet_version = (u16)m_context.version;
+ auto version = (u16)m_context.version;
+ PacketBuilder builder { MessageType::Handshake, packet_version };
+
+ builder.append((u8)ClientHello);
+
+ // hello length (for later)
+ u8 dummy[3] = {};
+ builder.append(dummy, 3);
+
+ auto start_length = builder.length();
+
+ builder.append(version);
+ builder.append(m_context.local_random, sizeof(m_context.local_random));
+
+ builder.append(m_context.session_id_size);
+ if (m_context.session_id_size)
+ builder.append(m_context.session_id, m_context.session_id_size);
+
+ size_t extension_length = 0;
+ size_t alpn_length = 0;
+ size_t alpn_negotiated_length = 0;
+
+ // ALPN
+ if (!m_context.negotiated_alpn.is_null()) {
+ alpn_negotiated_length = m_context.negotiated_alpn.length();
+ alpn_length = alpn_negotiated_length + 1;
+ extension_length += alpn_length + 6;
+ } else if (m_context.alpn.size()) {
+ for (auto& alpn : m_context.alpn) {
+ size_t length = alpn.length();
+ alpn_length += length + 1;
+ }
+ if (alpn_length)
+ extension_length += alpn_length + 6;
+ }
+
+ // Ciphers
+ builder.append((u16)(5 * sizeof(u16)));
+ builder.append((u16)CipherSuite::RSA_WITH_AES_128_CBC_SHA256);
+ builder.append((u16)CipherSuite::RSA_WITH_AES_256_CBC_SHA256);
+ builder.append((u16)CipherSuite::RSA_WITH_AES_128_CBC_SHA);
+ builder.append((u16)CipherSuite::RSA_WITH_AES_256_CBC_SHA);
+ builder.append((u16)CipherSuite::RSA_WITH_AES_128_GCM_SHA256);
+
+ // we don't like compression
+ builder.append((u8)1);
+ builder.append((u8)0);
+
+ // set SNI if we have one
+ auto sni_length = 0;
+ if (!m_context.SNI.is_null())
+ sni_length = m_context.SNI.length();
+
+ if (sni_length)
+ extension_length += sni_length + 9;
+
+ builder.append((u16)extension_length);
+
+ if (sni_length) {
+ // SNI extension
+ builder.append((u16)HandshakeExtension::ServerName);
+ // extension length
+ builder.append((u16)(sni_length + 5));
+ // SNI length
+ builder.append((u16)(sni_length + 3));
+ // SNI type
+ builder.append((u8)0);
+ // SNI host length + value
+ builder.append((u16)sni_length);
+ builder.append((const u8*)m_context.SNI.characters(), sni_length);
+ }
+
+ if (alpn_length) {
+ // TODO
+ ASSERT_NOT_REACHED();
+ }
+
+ // set the "length" field of the packet
+ size_t remaining = builder.length() - start_length;
+ size_t payload_position = 6;
+ builder.set(payload_position, remaining / 0x10000);
+ remaining %= 0x10000;
+ builder.set(payload_position + 1, remaining / 0x100);
+ remaining %= 0x100;
+ builder.set(payload_position + 2, remaining);
+
+ auto packet = builder.build();
+ update_packet(packet);
+
+ return packet;
+}
+
+ByteBuffer TLSv12::build_alert(bool critical, u8 code)
+{
+ PacketBuilder builder(MessageType::Alert, (u16)m_context.version);
+ builder.append((u8)(critical ? AlertLevel::Critical : AlertLevel::Warning));
+ builder.append(code);
+
+ if (critical)
+ m_context.critical_error = code;
+
+ auto packet = builder.build();
+ update_packet(packet);
+
+ return packet;
+}
+
+ByteBuffer TLSv12::build_finished()
+{
+ PacketBuilder builder { MessageType::Handshake, m_context.version, 12 + 64 };
+ builder.append((u8)HandshakeType::Finished);
+
+ u32 out_size = 12;
+
+ builder.append_u24(out_size);
+
+ u8 out[out_size];
+ auto outbuffer = Bytes { out, out_size };
+ auto dummy = ByteBuffer::create_zeroed(0);
+
+ auto digest = m_context.handshake_hash.digest();
+ auto hashbuf = ReadonlyBytes { digest.immutable_data(), m_context.handshake_hash.digest_size() };
+ pseudorandom_function(outbuffer, m_context.master_key, (const u8*)"client finished", 15, hashbuf, dummy);
+
+ builder.append(outbuffer);
+ auto packet = builder.build();
+ update_packet(packet);
+
+ return packet;
+}
+
+void TLSv12::alert(AlertLevel level, AlertDescription code)
+{
+ auto the_alert = build_alert(level == AlertLevel::Critical, (u8)code);
+ write_packet(the_alert);
+ flush();
+}
+
+}
diff --git a/Userland/Libraries/LibTLS/Record.cpp b/Userland/Libraries/LibTLS/Record.cpp
new file mode 100644
index 0000000000..1f2e875926
--- /dev/null
+++ b/Userland/Libraries/LibTLS/Record.cpp
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Endian.h>
+
+#include <AK/MemoryStream.h>
+#include <LibCore/Timer.h>
+#include <LibCrypto/ASN1/DER.h>
+#include <LibCrypto/PK/Code/EMSA_PSS.h>
+#include <LibTLS/TLSv12.h>
+
+namespace TLS {
+
+void TLSv12::write_packet(ByteBuffer& packet)
+{
+ m_context.tls_buffer.append(packet.data(), packet.size());
+ if (m_context.connection_status > ConnectionStatus::Disconnected) {
+ if (!m_has_scheduled_write_flush) {
+#ifdef TLS_DEBUG
+ dbg() << "Scheduling write of " << m_context.tls_buffer.size();
+#endif
+ deferred_invoke([this](auto&) { write_into_socket(); });
+ m_has_scheduled_write_flush = true;
+ } else {
+ // multiple packet are available, let's flush some out
+#ifdef TLS_DEBUG
+ dbg() << "Flushing scheduled write of " << m_context.tls_buffer.size();
+#endif
+ write_into_socket();
+ // the deferred invoke is still in place
+ m_has_scheduled_write_flush = true;
+ }
+ }
+}
+
+void TLSv12::update_packet(ByteBuffer& packet)
+{
+ u32 header_size = 5;
+ *(u16*)packet.offset_pointer(3) = AK::convert_between_host_and_network_endian((u16)(packet.size() - header_size));
+
+ if (packet[0] != (u8)MessageType::ChangeCipher) {
+ if (packet[0] == (u8)MessageType::Handshake && packet.size() > header_size) {
+ u8 handshake_type = packet[header_size];
+ if (handshake_type != HandshakeType::HelloRequest && handshake_type != HandshakeType::HelloVerifyRequest) {
+ update_hash(packet.bytes().slice(header_size, packet.size() - header_size));
+ }
+ }
+ if (m_context.cipher_spec_set && m_context.crypto.created) {
+ size_t length = packet.size() - header_size;
+ size_t block_size, padding, mac_size;
+
+ if (!is_aead()) {
+ block_size = m_aes_local.cbc->cipher().block_size();
+ // If the length is already a multiple a block_size,
+ // an entire block of padding is added.
+ // In short, we _never_ have no padding.
+ mac_size = mac_length();
+ length += mac_size;
+ padding = block_size - length % block_size;
+ length += padding;
+ } else {
+ block_size = m_aes_local.gcm->cipher().block_size();
+ padding = 0;
+ mac_size = 0; // AEAD provides its own authentication scheme.
+ }
+
+ if (m_context.crypto.created == 1) {
+ // `buffer' will continue to be encrypted
+ auto buffer = ByteBuffer::create_uninitialized(length);
+ size_t buffer_position = 0;
+ auto iv_size = iv_length();
+
+ // copy the packet, sans the header
+ buffer.overwrite(buffer_position, packet.offset_pointer(header_size), packet.size() - header_size);
+ buffer_position += packet.size() - header_size;
+
+ ByteBuffer ct;
+
+ if (is_aead()) {
+ // We need enough space for a header, the data, a tag, and the IV
+ ct = ByteBuffer::create_uninitialized(length + header_size + iv_size + 16);
+
+ // copy the header over
+ ct.overwrite(0, packet.data(), header_size - 2);
+
+ // AEAD AAD (13)
+ // Seq. no (8)
+ // content type (1)
+ // version (2)
+ // length (2)
+ u8 aad[13];
+ Bytes aad_bytes { aad, 13 };
+ OutputMemoryStream aad_stream { aad_bytes };
+
+ u64 seq_no = AK::convert_between_host_and_network_endian(m_context.local_sequence_number);
+ u16 len = AK::convert_between_host_and_network_endian((u16)(packet.size() - header_size));
+
+ aad_stream.write({ &seq_no, sizeof(seq_no) });
+ aad_stream.write(packet.bytes().slice(0, 3)); // content-type + version
+ aad_stream.write({ &len, sizeof(len) }); // length
+ ASSERT(aad_stream.is_end());
+
+ // AEAD IV (12)
+ // IV (4)
+ // (Nonce) (8)
+ // -- Our GCM impl takes 16 bytes
+ // zero (4)
+ u8 iv[16];
+ Bytes iv_bytes { iv, 16 };
+ Bytes { m_context.crypto.local_aead_iv, 4 }.copy_to(iv_bytes);
+ AK::fill_with_random(iv_bytes.offset(4), 8);
+ memset(iv_bytes.offset(12), 0, 4);
+
+ // write the random part of the iv out
+ iv_bytes.slice(4, 8).copy_to(ct.bytes().slice(header_size));
+
+ // Write the encrypted data and the tag
+ m_aes_local.gcm->encrypt(
+ packet.bytes().slice(header_size, length),
+ ct.bytes().slice(header_size + 8, length),
+ iv_bytes,
+ aad_bytes,
+ ct.bytes().slice(header_size + 8 + length, 16));
+
+ ASSERT(header_size + 8 + length + 16 == ct.size());
+
+ } else {
+ // We need enough space for a header, iv_length bytes of IV and whatever the packet contains
+ ct = ByteBuffer::create_uninitialized(length + header_size + iv_size);
+
+ // copy the header over
+ ct.overwrite(0, packet.data(), header_size - 2);
+
+ // get the appropricate HMAC value for the entire packet
+ auto mac = hmac_message(packet, {}, mac_size, true);
+
+ // write the MAC
+ buffer.overwrite(buffer_position, mac.data(), mac.size());
+ buffer_position += mac.size();
+
+ // Apply the padding (a packet MUST always be padded)
+ memset(buffer.offset_pointer(buffer_position), padding - 1, padding);
+ buffer_position += padding;
+
+ ASSERT(buffer_position == buffer.size());
+
+ auto iv = ByteBuffer::create_uninitialized(iv_size);
+ AK::fill_with_random(iv.data(), iv.size());
+
+ // write it into the ciphertext portion of the message
+ ct.overwrite(header_size, iv.data(), iv.size());
+
+ ASSERT(header_size + iv_size + length == ct.size());
+ ASSERT(length % block_size == 0);
+
+ // get a block to encrypt into
+ auto view = ct.bytes().slice(header_size + iv_size, length);
+ m_aes_local.cbc->encrypt(buffer, view, iv);
+ }
+
+ // store the correct ciphertext length into the packet
+ u16 ct_length = (u16)ct.size() - header_size;
+
+ *(u16*)ct.offset_pointer(header_size - 2) = AK::convert_between_host_and_network_endian(ct_length);
+
+ // replace the packet with the ciphertext
+ packet = ct;
+ }
+ }
+ }
+ ++m_context.local_sequence_number;
+}
+
+void TLSv12::update_hash(ReadonlyBytes message)
+{
+ m_context.handshake_hash.update(message);
+}
+
+ByteBuffer TLSv12::hmac_message(const ReadonlyBytes& buf, const Optional<ReadonlyBytes> buf2, size_t mac_length, bool local)
+{
+ u64 sequence_number = AK::convert_between_host_and_network_endian(local ? m_context.local_sequence_number : m_context.remote_sequence_number);
+ ensure_hmac(mac_length, local);
+ auto& hmac = local ? *m_hmac_local : *m_hmac_remote;
+#ifdef TLS_DEBUG
+ dbgln("========================= PACKET DATA ==========================");
+ print_buffer((const u8*)&sequence_number, sizeof(u64));
+ print_buffer(buf.data(), buf.size());
+ if (buf2.has_value())
+ print_buffer(buf2.value().data(), buf2.value().size());
+ dbgln("========================= PACKET DATA ==========================");
+#endif
+ hmac.update((const u8*)&sequence_number, sizeof(u64));
+ hmac.update(buf);
+ if (buf2.has_value() && buf2.value().size()) {
+ hmac.update(buf2.value());
+ }
+ auto digest = hmac.digest();
+ auto mac = ByteBuffer::copy(digest.immutable_data(), digest.data_length());
+#ifdef TLS_DEBUG
+ dbg() << "HMAC of the block for sequence number " << sequence_number;
+ print_buffer(mac);
+#endif
+ return mac;
+}
+
+ssize_t TLSv12::handle_message(ReadonlyBytes buffer)
+{
+ auto res { 5ll };
+ size_t header_size = res;
+ ssize_t payload_res = 0;
+
+#ifdef TLS_DEBUG
+ dbg() << "buffer size: " << buffer.size();
+#endif
+ if (buffer.size() < 5) {
+ return (i8)Error::NeedMoreData;
+ }
+
+ auto type = (MessageType)buffer[0];
+ size_t buffer_position { 1 };
+
+ // FIXME: Read the version and verify it
+#ifdef TLS_DEBUG
+ auto version = (Version) * (const u16*)buffer.offset_pointer(buffer_position);
+ dbg() << "type: " << (u8)type << " version: " << (u16)version;
+#endif
+ buffer_position += 2;
+
+ auto length = AK::convert_between_host_and_network_endian(*(const u16*)buffer.offset_pointer(buffer_position));
+#ifdef TLS_DEBUG
+ dbg() << "record length: " << length << " at offset: " << buffer_position;
+#endif
+ buffer_position += 2;
+
+ if (buffer_position + length > buffer.size()) {
+#ifdef TLS_DEBUG
+ dbg() << "record length more than what we have: " << buffer.size();
+#endif
+ return (i8)Error::NeedMoreData;
+ }
+
+#ifdef TLS_DEBUG
+ dbg() << "message type: " << (u8)type << ", length: " << length;
+#endif
+ auto plain = buffer.slice(buffer_position, buffer.size() - buffer_position);
+
+ ByteBuffer decrypted;
+
+ if (m_context.cipher_spec_set && type != MessageType::ChangeCipher) {
+#ifdef TLS_DEBUG
+ dbgln("Encrypted: ");
+ print_buffer(buffer.slice(header_size, length));
+#endif
+
+ if (is_aead()) {
+ ASSERT(m_aes_remote.gcm);
+
+ if (length < 24) {
+ dbgln("Invalid packet length");
+ auto packet = build_alert(true, (u8)AlertDescription::DecryptError);
+ write_packet(packet);
+ return (i8)Error::BrokenPacket;
+ }
+
+ auto packet_length = length - iv_length() - 16;
+ auto payload = plain;
+ decrypted = ByteBuffer::create_uninitialized(packet_length);
+
+ // AEAD AAD (13)
+ // Seq. no (8)
+ // content type (1)
+ // version (2)
+ // length (2)
+ u8 aad[13];
+ Bytes aad_bytes { aad, 13 };
+ OutputMemoryStream aad_stream { aad_bytes };
+
+ u64 seq_no = AK::convert_between_host_and_network_endian(m_context.remote_sequence_number);
+ u16 len = AK::convert_between_host_and_network_endian((u16)packet_length);
+
+ aad_stream.write({ &seq_no, sizeof(seq_no) }); // Sequence number
+ aad_stream.write(buffer.slice(0, header_size - 2)); // content-type + version
+ aad_stream.write({ &len, sizeof(u16) });
+ ASSERT(aad_stream.is_end());
+
+ auto nonce = payload.slice(0, iv_length());
+ payload = payload.slice(iv_length());
+
+ // AEAD IV (12)
+ // IV (4)
+ // (Nonce) (8)
+ // -- Our GCM impl takes 16 bytes
+ // zero (4)
+ u8 iv[16];
+ Bytes iv_bytes { iv, 16 };
+ Bytes { m_context.crypto.remote_aead_iv, 4 }.copy_to(iv_bytes);
+ nonce.copy_to(iv_bytes.slice(4));
+ memset(iv_bytes.offset(12), 0, 4);
+
+ auto ciphertext = payload.slice(0, payload.size() - 16);
+ auto tag = payload.slice(ciphertext.size());
+
+ auto consistency = m_aes_remote.gcm->decrypt(
+ ciphertext,
+ decrypted,
+ iv_bytes,
+ aad_bytes,
+ tag);
+
+ if (consistency != Crypto::VerificationConsistency::Consistent) {
+ dbg() << "integrity check failed (tag length " << tag.size() << ")";
+ auto packet = build_alert(true, (u8)AlertDescription::BadRecordMAC);
+ write_packet(packet);
+
+ return (i8)Error::IntegrityCheckFailed;
+ }
+
+ plain = decrypted;
+ } else {
+ ASSERT(m_aes_remote.cbc);
+ auto iv_size = iv_length();
+
+ decrypted = m_aes_remote.cbc->create_aligned_buffer(length - iv_size);
+ auto iv = buffer.slice(header_size, iv_size);
+
+ Bytes decrypted_span = decrypted;
+ m_aes_remote.cbc->decrypt(buffer.slice(header_size + iv_size, length - iv_size), decrypted_span, iv);
+
+ length = decrypted_span.size();
+
+#ifdef TLS_DEBUG
+ dbgln("Decrypted: ");
+ print_buffer(decrypted);
+#endif
+
+ auto mac_size = mac_length();
+ if (length < mac_size) {
+ dbgln("broken packet");
+ auto packet = build_alert(true, (u8)AlertDescription::DecryptError);
+ write_packet(packet);
+ return (i8)Error::BrokenPacket;
+ }
+
+ length -= mac_size;
+
+ const u8* message_hmac = decrypted_span.offset(length);
+ u8 temp_buf[5];
+ memcpy(temp_buf, buffer.offset_pointer(0), 3);
+ *(u16*)(temp_buf + 3) = AK::convert_between_host_and_network_endian(length);
+ auto hmac = hmac_message({ temp_buf, 5 }, decrypted_span.slice(0, length), mac_size);
+ auto message_mac = ReadonlyBytes { message_hmac, mac_size };
+ if (hmac != message_mac) {
+ dbg() << "integrity check failed (mac length " << mac_size << ")";
+ dbgln("mac received:");
+ print_buffer(message_mac);
+ dbgln("mac computed:");
+ print_buffer(hmac);
+ auto packet = build_alert(true, (u8)AlertDescription::BadRecordMAC);
+ write_packet(packet);
+
+ return (i8)Error::IntegrityCheckFailed;
+ }
+ plain = decrypted.bytes().slice(0, length);
+ }
+ }
+ m_context.remote_sequence_number++;
+
+ switch (type) {
+ case MessageType::ApplicationData:
+ if (m_context.connection_status != ConnectionStatus::Established) {
+ dbgln("unexpected application data");
+ payload_res = (i8)Error::UnexpectedMessage;
+ auto packet = build_alert(true, (u8)AlertDescription::UnexpectedMessage);
+ write_packet(packet);
+ } else {
+#ifdef TLS_DEBUG
+ dbg() << "application data message of size " << plain.size();
+#endif
+
+ m_context.application_buffer.append(plain.data(), plain.size());
+ }
+ break;
+ case MessageType::Handshake:
+#ifdef TLS_DEBUG
+ dbgln("tls handshake message");
+#endif
+ payload_res = handle_payload(plain);
+ break;
+ case MessageType::ChangeCipher:
+ if (m_context.connection_status != ConnectionStatus::KeyExchange) {
+ dbgln("unexpected change cipher message");
+ auto packet = build_alert(true, (u8)AlertDescription::UnexpectedMessage);
+ payload_res = (i8)Error::UnexpectedMessage;
+ } else {
+#ifdef TLS_DEBUG
+ dbgln("change cipher spec message");
+#endif
+ m_context.cipher_spec_set = true;
+ m_context.remote_sequence_number = 0;
+ }
+ break;
+ case MessageType::Alert:
+#ifdef TLS_DEBUG
+ dbg() << "alert message of length " << length;
+#endif
+ if (length >= 2) {
+#ifdef TLS_DEBUG
+ print_buffer(plain);
+#endif
+ auto level = plain[0];
+ auto code = plain[1];
+ if (level == (u8)AlertLevel::Critical) {
+ dbg() << "We were alerted of a critical error: " << code << " (" << alert_name((AlertDescription)code) << ")";
+ m_context.critical_error = code;
+ try_disambiguate_error();
+ res = (i8)Error::UnknownError;
+ } else {
+ dbg() << "Alert: " << code;
+ }
+ if (code == 0) {
+ // close notify
+ res += 2;
+ alert(AlertLevel::Critical, AlertDescription::CloseNotify);
+ m_context.connection_finished = true;
+ if (!m_context.cipher_spec_set) {
+ // AWS CloudFront hits this.
+ dbgln("Server sent a close notify and we haven't agreed on a cipher suite. Treating it as a handshake failure.");
+ m_context.critical_error = (u8)AlertDescription::HandshakeFailure;
+ try_disambiguate_error();
+ }
+ }
+ m_context.error_code = (Error)code;
+ }
+ break;
+ default:
+ dbgln("message not understood");
+ return (i8)Error::NotUnderstood;
+ }
+
+ if (payload_res < 0)
+ return payload_res;
+
+ if (res > 0)
+ return header_size + length;
+
+ return res;
+}
+
+}
diff --git a/Userland/Libraries/LibTLS/Socket.cpp b/Userland/Libraries/LibTLS/Socket.cpp
new file mode 100644
index 0000000000..fd7c97761f
--- /dev/null
+++ b/Userland/Libraries/LibTLS/Socket.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/DateTime.h>
+#include <LibCore/Timer.h>
+#include <LibCrypto/ASN1/DER.h>
+#include <LibCrypto/PK/Code/EMSA_PSS.h>
+#include <LibTLS/TLSv12.h>
+
+namespace TLS {
+
+Optional<ByteBuffer> TLSv12::read()
+{
+ if (m_context.application_buffer.size()) {
+ auto buf = m_context.application_buffer.slice(0, m_context.application_buffer.size());
+ m_context.application_buffer.clear();
+ return buf;
+ }
+ return {};
+}
+
+ByteBuffer TLSv12::read(size_t max_size)
+{
+ if (m_context.application_buffer.size()) {
+ auto length = min(m_context.application_buffer.size(), max_size);
+ auto buf = m_context.application_buffer.slice(0, length);
+ m_context.application_buffer = m_context.application_buffer.slice(length, m_context.application_buffer.size() - length);
+ return buf;
+ }
+ return {};
+}
+
+String TLSv12::read_line(size_t max_size)
+{
+ if (!can_read_line())
+ return {};
+
+ auto* start = m_context.application_buffer.data();
+ auto* newline = (u8*)memchr(m_context.application_buffer.data(), '\n', m_context.application_buffer.size());
+ ASSERT(newline);
+
+ size_t offset = newline - start;
+
+ if (offset > max_size)
+ return {};
+
+ auto buffer = ByteBuffer::copy(start, offset);
+ m_context.application_buffer = m_context.application_buffer.slice(offset + 1, m_context.application_buffer.size() - offset - 1);
+
+ return String::copy(buffer, Chomp);
+}
+
+bool TLSv12::write(ReadonlyBytes buffer)
+{
+ if (m_context.connection_status != ConnectionStatus::Established) {
+#ifdef TLS_DEBUG
+ dbgln("write request while not connected");
+#endif
+ return false;
+ }
+
+ PacketBuilder builder { MessageType::ApplicationData, m_context.version, buffer.size() };
+ builder.append(buffer);
+ auto packet = builder.build();
+
+ update_packet(packet);
+ write_packet(packet);
+
+ return true;
+}
+
+bool TLSv12::connect(const String& hostname, int port)
+{
+ set_sni(hostname);
+ return Core::Socket::connect(hostname, port);
+}
+
+bool TLSv12::common_connect(const struct sockaddr* saddr, socklen_t length)
+{
+ if (m_context.critical_error)
+ return false;
+
+ if (Core::Socket::is_connected()) {
+ if (is_established()) {
+ ASSERT_NOT_REACHED();
+ } else {
+ Core::Socket::close(); // reuse?
+ }
+ }
+
+ Core::Socket::on_connected = [this] {
+ Core::Socket::on_ready_to_read = [this] {
+ read_from_socket();
+ };
+
+ auto packet = build_hello();
+ write_packet(packet);
+
+ deferred_invoke([&](auto&) {
+ m_handshake_timeout_timer = Core::Timer::create_single_shot(
+ m_max_wait_time_for_handshake_in_seconds * 1000, [&] {
+ auto timeout_diff = Core::DateTime::now().timestamp() - m_context.handshake_initiation_timestamp;
+ // If the timeout duration was actually within the max wait time (with a margin of error),
+ // we're not operating slow, so the server timed out.
+ // otherwise, it's our fault that the negotiation is taking too long, so extend the timer :P
+ if (timeout_diff < m_max_wait_time_for_handshake_in_seconds + 1) {
+ // The server did not respond fast enough,
+ // time the connection out.
+ alert(AlertLevel::Critical, AlertDescription::UserCanceled);
+ m_context.connection_finished = true;
+ m_context.tls_buffer.clear();
+ m_context.error_code = Error::TimedOut;
+ m_context.critical_error = (u8)Error::TimedOut;
+ check_connection_state(false); // Notify the client.
+ } else {
+ // Extend the timer, we are too slow.
+ m_handshake_timeout_timer->restart(m_max_wait_time_for_handshake_in_seconds * 1000);
+ }
+ },
+ this);
+ write_into_socket();
+ m_handshake_timeout_timer->start();
+ m_context.handshake_initiation_timestamp = Core::DateTime::now().timestamp();
+ });
+ m_has_scheduled_write_flush = true;
+
+ if (on_tls_connected)
+ on_tls_connected();
+ };
+ bool success = Core::Socket::common_connect(saddr, length);
+ if (!success)
+ return false;
+
+ return true;
+}
+
+void TLSv12::read_from_socket()
+{
+ if (m_context.application_buffer.size() > 0) {
+ deferred_invoke([&](auto&) { read_from_socket(); });
+ if (on_tls_ready_to_read)
+ on_tls_ready_to_read(*this);
+ }
+
+ if (!check_connection_state(true))
+ return;
+
+ consume(Core::Socket::read(4096));
+}
+
+void TLSv12::write_into_socket()
+{
+#ifdef TLS_DEBUG
+ dbg() << "Flushing cached records: " << m_context.tls_buffer.size() << " established? " << is_established();
+#endif
+ m_has_scheduled_write_flush = false;
+ if (!check_connection_state(false))
+ return;
+ flush();
+
+ if (!is_established())
+ return;
+
+ if (!m_context.application_buffer.size()) // hey client, you still have stuff to read...
+ if (on_tls_ready_to_write)
+ on_tls_ready_to_write(*this);
+}
+
+bool TLSv12::check_connection_state(bool read)
+{
+ if (!Core::Socket::is_open() || !Core::Socket::is_connected() || Core::Socket::eof()) {
+ // an abrupt closure (the server is a jerk)
+#ifdef TLS_DEBUG
+ dbgln("Socket not open, assuming abrupt closure");
+#endif
+ m_context.connection_finished = true;
+ }
+ if (m_context.critical_error) {
+#ifdef TLS_DEBUG
+ dbg() << "CRITICAL ERROR " << m_context.critical_error << " :(";
+#endif
+ if (on_tls_error)
+ on_tls_error((AlertDescription)m_context.critical_error);
+ return false;
+ }
+ if (((read && m_context.application_buffer.size() == 0) || !read) && m_context.connection_finished) {
+ if (m_context.application_buffer.size() == 0) {
+ if (on_tls_finished)
+ on_tls_finished();
+ }
+ if (m_context.tls_buffer.size()) {
+#ifdef TLS_DEBUG
+ dbg() << "connection closed without finishing data transfer, " << m_context.tls_buffer.size() << " bytes still in buffer & " << m_context.application_buffer.size() << " bytes in application buffer";
+#endif
+ } else {
+ m_context.connection_finished = false;
+#ifdef TLS_DEBUG
+ dbgln("FINISHED");
+#endif
+ }
+ if (!m_context.application_buffer.size()) {
+ m_context.connection_status = ConnectionStatus::Disconnected;
+ return false;
+ }
+ }
+ return true;
+}
+
+bool TLSv12::flush()
+{
+ auto out_buffer = write_buffer().data();
+ size_t out_buffer_index { 0 };
+ size_t out_buffer_length = write_buffer().size();
+
+ if (out_buffer_length == 0)
+ return true;
+
+#ifdef TLS_DEBUG
+ dbgln("SENDING...");
+ print_buffer(out_buffer, out_buffer_length);
+#endif
+ if (Core::Socket::write(&out_buffer[out_buffer_index], out_buffer_length)) {
+ write_buffer().clear();
+ return true;
+ }
+ if (m_context.send_retries++ == 10) {
+ // drop the records, we can't send
+#ifdef TLS_DEBUG
+ dbg() << "Dropping " << write_buffer().size() << " bytes worth of TLS records as max retries has been reached";
+#endif
+ write_buffer().clear();
+ m_context.send_retries = 0;
+ }
+ return false;
+}
+
+}
diff --git a/Userland/Libraries/LibTLS/TLSPacketBuilder.h b/Userland/Libraries/LibTLS/TLSPacketBuilder.h
new file mode 100644
index 0000000000..2994ed27ab
--- /dev/null
+++ b/Userland/Libraries/LibTLS/TLSPacketBuilder.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/Endian.h>
+#include <AK/Types.h>
+
+namespace TLS {
+
+enum class MessageType : u8 {
+ ChangeCipher = 0x14,
+ Alert = 0x15,
+ Handshake = 0x16,
+ ApplicationData = 0x17,
+};
+
+enum class Version : u16 {
+ V10 = 0x0301,
+ V11 = 0x0302,
+ V12 = 0x0303,
+ V13 = 0x0304
+};
+
+class PacketBuilder {
+public:
+ PacketBuilder(MessageType type, u16 version, size_t size_hint = 0xfdf)
+ : PacketBuilder(type, (Version)version, size_hint)
+ {
+ }
+
+ PacketBuilder(MessageType type, Version version, size_t size_hint = 0xfdf)
+ {
+ m_packet_data = ByteBuffer::create_uninitialized(size_hint + 16);
+ m_current_length = 5;
+ m_packet_data[0] = (u8)type;
+ *(u16*)m_packet_data.offset_pointer(1) = AK::convert_between_host_and_network_endian((u16)version);
+ }
+
+ inline void append(u16 value)
+ {
+ value = AK::convert_between_host_and_network_endian(value);
+ append((const u8*)&value, sizeof(value));
+ }
+ inline void append(u8 value)
+ {
+ append((const u8*)&value, sizeof(value));
+ }
+ inline void append(ReadonlyBytes data)
+ {
+ append(data.data(), data.size());
+ }
+ inline void append_u24(u32 value)
+ {
+ u8 buf[3];
+ buf[0] = value / 0x10000;
+ value %= 0x10000;
+ buf[1] = value / 0x100;
+ value %= 0x100;
+ buf[2] = value;
+
+ append(buf, 3);
+ }
+ inline void append(const u8* data, size_t bytes)
+ {
+ if (bytes == 0)
+ return;
+
+ auto old_length = m_current_length;
+ m_current_length += bytes;
+
+ if (m_packet_data.size() < m_current_length) {
+ m_packet_data.grow(m_current_length);
+ }
+
+ m_packet_data.overwrite(old_length, data, bytes);
+ }
+ inline ByteBuffer build()
+ {
+ auto length = m_current_length;
+ m_current_length = 0;
+ return m_packet_data.slice(0, length);
+ }
+ inline void set(size_t offset, u8 value)
+ {
+ ASSERT(offset < m_current_length);
+ m_packet_data[offset] = value;
+ }
+ size_t length() const { return m_current_length; }
+
+private:
+ ByteBuffer m_packet_data;
+ size_t m_current_length;
+};
+
+}
diff --git a/Userland/Libraries/LibTLS/TLSv12.cpp b/Userland/Libraries/LibTLS/TLSv12.cpp
new file mode 100644
index 0000000000..272f12b961
--- /dev/null
+++ b/Userland/Libraries/LibTLS/TLSv12.cpp
@@ -0,0 +1,887 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Endian.h>
+#include <LibCore/ConfigFile.h>
+#include <LibCore/DateTime.h>
+#include <LibCore/Timer.h>
+#include <LibCrypto/ASN1/DER.h>
+#include <LibCrypto/ASN1/PEM.h>
+#include <LibCrypto/PK/Code/EMSA_PSS.h>
+#include <LibTLS/TLSv12.h>
+
+#ifndef SOCK_NONBLOCK
+# include <sys/ioctl.h>
+#endif
+
+//#define TLS_DEBUG
+
+namespace {
+struct OIDChain {
+ OIDChain* root { nullptr };
+ u8* oid { nullptr };
+};
+}
+
+namespace TLS {
+
+// "for now" q&d implementation of ASN1
+namespace {
+
+static bool _asn1_is_field_present(const u32* fields, const u32* prefix)
+{
+ size_t i = 0;
+ while (prefix[i]) {
+ if (fields[i] != prefix[i])
+ return false;
+ ++i;
+ }
+ return true;
+}
+
+static bool _asn1_is_oid(const u8* oid, const u8* compare, size_t length = 3)
+{
+ size_t i = 0;
+ while (oid[i] && i < length) {
+ if (oid[i] != compare[i])
+ return false;
+ ++i;
+ }
+ return true;
+}
+
+static bool _asn1_is_oid_in_chain(OIDChain* reference_chain, const u8* lookup, size_t lookup_length = 3)
+{
+ auto is_oid = [](const u8* oid, size_t oid_length, const u8* compare, size_t compare_length) {
+ if (oid_length < compare_length)
+ compare_length = oid_length;
+ for (size_t i = 0; i < compare_length; i++) {
+ if (oid[i] != compare[i])
+ return false;
+ }
+ return true;
+ };
+ for (; reference_chain; reference_chain = reference_chain->root) {
+ if (reference_chain->oid)
+ if (is_oid(reference_chain->oid, 16, lookup, lookup_length))
+ return true;
+ }
+ return false;
+}
+
+static bool _set_algorithm(CertificateKeyAlgorithm& algorithm, const u8* value, size_t length)
+{
+ if (length == 7) {
+ // Elliptic Curve pubkey
+ dbgln("Cert.algorithm: EC, unsupported");
+ return false;
+ }
+
+ if (length == 8) {
+ // named EC key
+ dbg() << "Cert.algorithm: Named EC (" << *value << "), unsupported";
+ return false;
+ }
+
+ if (length == 5) {
+ // named EC SECP key
+ dbg() << "Cert.algorithm: Named EC secp (" << *value << "), unsupported";
+ return false;
+ }
+
+ if (length != 9) {
+ dbgln("Invalid certificate algorithm");
+ return false;
+ }
+
+ if (_asn1_is_oid(value, Constants::RSA_SIGN_RSA_OID, 9)) {
+ algorithm = CertificateKeyAlgorithm::RSA_RSA;
+ return true;
+ }
+
+ if (_asn1_is_oid(value, Constants::RSA_SIGN_SHA256_OID, 9)) {
+ algorithm = CertificateKeyAlgorithm::RSA_SHA256;
+ return true;
+ }
+
+ if (_asn1_is_oid(value, Constants::RSA_SIGN_SHA512_OID, 9)) {
+ algorithm = CertificateKeyAlgorithm::RSA_SHA512;
+ return true;
+ }
+
+ if (_asn1_is_oid(value, Constants::RSA_SIGN_SHA1_OID, 9)) {
+ algorithm = CertificateKeyAlgorithm::RSA_SHA1;
+ return true;
+ }
+
+ if (_asn1_is_oid(value, Constants::RSA_SIGN_MD5_OID, 9)) {
+ algorithm = CertificateKeyAlgorithm::RSA_MD5;
+ return true;
+ }
+
+ dbg() << "Unsupported RSA Signature mode " << value[8];
+ return false;
+}
+
+static size_t _get_asn1_length(const u8* buffer, size_t length, size_t& octets)
+{
+ octets = 0;
+ if (length < 1)
+ return 0;
+
+ u8 size = buffer[0];
+ if (size & 0x80) {
+ octets = size & 0x7f;
+ if (octets > length - 1) {
+ return 0;
+ }
+ auto reference_octets = octets;
+ if (octets > 4)
+ reference_octets = 4;
+ size_t long_size = 0, coeff = 1;
+ for (auto i = reference_octets; i > 0; --i) {
+ long_size += buffer[i] * coeff;
+ coeff *= 0x100;
+ }
+ ++octets;
+ return long_size;
+ }
+ ++octets;
+ return size;
+}
+
+static ssize_t _parse_asn1(const Context& context, Certificate& cert, const u8* buffer, size_t size, int level, u32* fields, u8* has_key, int client_cert, u8* root_oid, OIDChain* chain)
+{
+ OIDChain local_chain;
+ local_chain.root = chain;
+ size_t position = 0;
+
+ // parse DER...again
+ size_t index = 0;
+ u8 oid[16] { 0 };
+
+ local_chain.oid = oid;
+ if (has_key)
+ *has_key = 0;
+
+ u8 local_has_key = 0;
+ const u8* cert_data = nullptr;
+ size_t cert_length = 0;
+ while (position < size) {
+ size_t start_position = position;
+ if (size - position < 2) {
+ dbgln("not enough data for certificate size");
+ return (i8)Error::NeedMoreData;
+ }
+ u8 first = buffer[position++];
+ u8 type = first & 0x1f;
+ u8 constructed = first & 0x20;
+ size_t octets = 0;
+ u32 temp;
+ index++;
+
+ if (level <= 0xff)
+ fields[level - 1] = index;
+
+ size_t length = _get_asn1_length((const u8*)&buffer[position], size - position, octets);
+
+ if (octets > 4 || octets > size - position) {
+#ifdef TLS_DEBUG
+ dbgln("could not read the certificate");
+#endif
+ return position;
+ }
+
+ position += octets;
+ if (size - position < length) {
+#ifdef TLS_DEBUG
+ dbgln("not enough data for sequence");
+#endif
+ return (i8)Error::NeedMoreData;
+ }
+
+ if (length && constructed) {
+ switch (type) {
+ case 0x03:
+ break;
+ case 0x10:
+ if (level == 2 && index == 1) {
+ cert_length = length + position - start_position;
+ cert_data = buffer + start_position;
+ }
+ // public key data
+ if (!cert.version && _asn1_is_field_present(fields, Constants::priv_der_id)) {
+ temp = length + position - start_position;
+ if (cert.der.size() < temp) {
+ cert.der.grow(temp);
+ } else {
+ cert.der.trim(temp);
+ }
+ cert.der.overwrite(0, buffer + start_position, temp);
+ }
+ break;
+
+ default:
+ break;
+ }
+ local_has_key = false;
+ _parse_asn1(context, cert, buffer + position, length, level + 1, fields, &local_has_key, client_cert, root_oid, &local_chain);
+ if ((local_has_key && (!context.is_server || client_cert)) || (client_cert || _asn1_is_field_present(fields, Constants::pk_id))) {
+ temp = length + position - start_position;
+ if (cert.der.size() < temp) {
+ cert.der.grow(temp);
+ } else {
+ cert.der.trim(temp);
+ }
+ cert.der.overwrite(0, buffer + start_position, temp);
+ }
+ } else {
+ switch (type) {
+ case 0x00:
+ return position;
+ break;
+ case 0x01:
+ temp = buffer[position];
+ break;
+ case 0x02:
+ if (_asn1_is_field_present(fields, Constants::pk_id)) {
+ if (has_key)
+ *has_key = true;
+
+ if (index == 1)
+ cert.public_key.set(
+ Crypto::UnsignedBigInteger::import_data(buffer + position, length),
+ cert.public_key.public_exponent());
+ else if (index == 2)
+ cert.public_key.set(
+ cert.public_key.modulus(),
+ Crypto::UnsignedBigInteger::import_data(buffer + position, length));
+ } else if (_asn1_is_field_present(fields, Constants::serial_id)) {
+ cert.serial_number = Crypto::UnsignedBigInteger::import_data(buffer + position, length);
+ }
+ if (_asn1_is_field_present(fields, Constants::version_id)) {
+ if (length == 1)
+ cert.version = buffer[position];
+ }
+ if (chain && length > 2) {
+ if (_asn1_is_oid_in_chain(chain, Constants::san_oid)) {
+ StringView alt_name { &buffer[position], length };
+ cert.SAN.append(alt_name);
+ }
+ }
+ // print_buffer(ReadonlyBytes { buffer + position, length });
+ break;
+ case 0x03:
+ if (_asn1_is_field_present(fields, Constants::pk_id)) {
+ if (has_key)
+ *has_key = true;
+ }
+ if (_asn1_is_field_present(fields, Constants::sign_id)) {
+ auto* value = buffer + position;
+ auto len = length;
+ if (!value[0] && len % 2) {
+ ++value;
+ --len;
+ }
+ cert.sign_key = ByteBuffer::copy(value, len);
+ } else {
+ if (buffer[position] == 0 && length > 256) {
+ _parse_asn1(context, cert, buffer + position + 1, length - 1, level + 1, fields, &local_has_key, client_cert, root_oid, &local_chain);
+ } else {
+ _parse_asn1(context, cert, buffer + position, length, level + 1, fields, &local_has_key, client_cert, root_oid, &local_chain);
+ }
+ }
+ break;
+ case 0x04:
+ _parse_asn1(context, cert, buffer + position, length, level + 1, fields, &local_has_key, client_cert, root_oid, &local_chain);
+ break;
+ case 0x05:
+ break;
+ case 0x06:
+ if (_asn1_is_field_present(fields, Constants::pk_id)) {
+ _set_algorithm(cert.key_algorithm, buffer + position, length);
+ }
+ if (_asn1_is_field_present(fields, Constants::algorithm_id)) {
+ _set_algorithm(cert.algorithm, buffer + position, length);
+ }
+
+ if (length < 16)
+ memcpy(oid, buffer + position, length);
+ else
+ memcpy(oid, buffer + position, 16);
+ if (root_oid)
+ memcpy(root_oid, oid, 16);
+ break;
+ case 0x09:
+ break;
+ case 0x17:
+ case 0x018:
+ // time
+ // ignore
+ break;
+ case 0x013:
+ case 0x0c:
+ case 0x14:
+ case 0x15:
+ case 0x16:
+ case 0x19:
+ case 0x1a:
+ case 0x1b:
+ case 0x1c:
+ case 0x1d:
+ case 0x1e:
+ // printable string and such
+ if (_asn1_is_field_present(fields, Constants::issurer_id)) {
+ if (_asn1_is_oid(oid, Constants::country_oid)) {
+ cert.issuer_country = String { (const char*)buffer + position, length };
+ } else if (_asn1_is_oid(oid, Constants::state_oid)) {
+ cert.issuer_state = String { (const char*)buffer + position, length };
+ } else if (_asn1_is_oid(oid, Constants::location_oid)) {
+ cert.issuer_location = String { (const char*)buffer + position, length };
+ } else if (_asn1_is_oid(oid, Constants::entity_oid)) {
+ cert.issuer_entity = String { (const char*)buffer + position, length };
+ } else if (_asn1_is_oid(oid, Constants::subject_oid)) {
+ cert.issuer_subject = String { (const char*)buffer + position, length };
+ } else if (_asn1_is_oid(oid, Constants::unit_oid)) {
+ cert.issuer_unit = String { (const char*)buffer + position, length };
+ }
+ } else if (_asn1_is_field_present(fields, Constants::owner_id)) {
+ if (_asn1_is_oid(oid, Constants::country_oid)) {
+ cert.country = String { (const char*)buffer + position, length };
+ } else if (_asn1_is_oid(oid, Constants::state_oid)) {
+ cert.state = String { (const char*)buffer + position, length };
+ } else if (_asn1_is_oid(oid, Constants::location_oid)) {
+ cert.location = String { (const char*)buffer + position, length };
+ } else if (_asn1_is_oid(oid, Constants::entity_oid)) {
+ cert.entity = String { (const char*)buffer + position, length };
+ } else if (_asn1_is_oid(oid, Constants::subject_oid)) {
+ cert.subject = String { (const char*)buffer + position, length };
+ } else if (_asn1_is_oid(oid, Constants::unit_oid)) {
+ cert.unit = String { (const char*)buffer + position, length };
+ }
+ }
+ break;
+ default:
+ // dbg() << "unused field " << type;
+ break;
+ }
+ }
+ position += length;
+ }
+ if (level == 2 && cert.sign_key.size() && cert_length && cert_data) {
+ cert.fingerprint.clear();
+ Crypto::Hash::Manager hash;
+ switch (cert.key_algorithm) {
+ case CertificateKeyAlgorithm::RSA_MD5:
+ hash.initialize(Crypto::Hash::HashKind::MD5);
+ break;
+ case CertificateKeyAlgorithm::RSA_SHA1:
+ hash.initialize(Crypto::Hash::HashKind::SHA1);
+ break;
+ case CertificateKeyAlgorithm::RSA_SHA256:
+ hash.initialize(Crypto::Hash::HashKind::SHA256);
+ break;
+ case CertificateKeyAlgorithm::RSA_SHA512:
+ hash.initialize(Crypto::Hash::HashKind::SHA512);
+ break;
+ default:
+#ifdef TLS_DEBUG
+ dbg() << "Unsupported hash mode " << (u32)cert.key_algorithm;
+#endif
+ // fallback to md5, it will fail later
+ hash.initialize(Crypto::Hash::HashKind::MD5);
+ break;
+ }
+ hash.update(cert_data, cert_length);
+ auto fingerprint = hash.digest();
+ cert.fingerprint.grow(fingerprint.data_length());
+ cert.fingerprint.overwrite(0, fingerprint.immutable_data(), fingerprint.data_length());
+#ifdef TLS_DEBUG
+ dbgln("Certificate fingerprint:");
+ print_buffer(cert.fingerprint);
+#endif
+ }
+ return position;
+}
+}
+
+Optional<Certificate> TLSv12::parse_asn1(ReadonlyBytes buffer, bool) const
+{
+ // FIXME: Our ASN.1 parser is not quite up to the task of
+ // parsing this X.509 certificate, so for the
+ // time being, we will "parse" the certificate
+ // manually right here.
+
+ Certificate cert;
+ u32 fields[0xff];
+
+ _parse_asn1(m_context, cert, buffer.data(), buffer.size(), 1, fields, nullptr, 0, nullptr, nullptr);
+
+#ifdef TLS_DEBUG
+ dbg() << "Certificate issued for " << cert.subject << " by " << cert.issuer_subject;
+#endif
+
+ return cert;
+}
+
+ssize_t TLSv12::handle_certificate(ReadonlyBytes buffer)
+{
+ ssize_t res = 0;
+
+ if (buffer.size() < 3) {
+#ifdef TLS_DEBUG
+ dbgln("not enough certificate header data");
+#endif
+ return (i8)Error::NeedMoreData;
+ }
+
+ u32 certificate_total_length = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2];
+
+#ifdef TLS_DEBUG
+ dbg() << "total length: " << certificate_total_length;
+#endif
+
+ if (certificate_total_length <= 4)
+ return 3 * certificate_total_length;
+
+ res += 3;
+
+ if (certificate_total_length > buffer.size() - res) {
+#ifdef TLS_DEBUG
+ dbgln("not enough data for claimed total cert length");
+#endif
+ return (i8)Error::NeedMoreData;
+ }
+ size_t size = certificate_total_length;
+
+ size_t index = 0;
+ bool valid_certificate = false;
+
+ while (size > 0) {
+ ++index;
+ if (buffer.size() - res < 3) {
+#ifdef TLS_DEBUG
+ dbgln("not enough data for certificate length");
+#endif
+ return (i8)Error::NeedMoreData;
+ }
+ size_t certificate_size = buffer[res] * 0x10000 + buffer[res + 1] * 0x100 + buffer[res + 2];
+ res += 3;
+
+ if (buffer.size() - res < certificate_size) {
+#ifdef TLS_DEBUG
+ dbgln("not enough data for certificate body");
+#endif
+ return (i8)Error::NeedMoreData;
+ }
+
+ auto res_cert = res;
+ auto remaining = certificate_size;
+ size_t certificates_in_chain = 0;
+
+ do {
+ if (remaining <= 3) {
+ dbgln("Ran out of data");
+ break;
+ }
+ ++certificates_in_chain;
+ if (buffer.size() < (size_t)res_cert + 3) {
+ dbg() << "not enough data to read cert size (" << buffer.size() << " < " << res_cert + 3 << ")";
+ break;
+ }
+ size_t certificate_size_specific = buffer[res_cert] * 0x10000 + buffer[res_cert + 1] * 0x100 + buffer[res_cert + 2];
+ res_cert += 3;
+ remaining -= 3;
+
+ if (certificate_size_specific > remaining) {
+ dbg() << "invalid certificate size (expected " << remaining << " but got " << certificate_size_specific << ")";
+ break;
+ }
+ remaining -= certificate_size_specific;
+
+ auto certificate = parse_asn1(buffer.slice(res_cert, certificate_size_specific), false);
+ if (certificate.has_value()) {
+ if (certificate.value().is_valid()) {
+ m_context.certificates.append(certificate.value());
+ valid_certificate = true;
+ }
+ }
+ res_cert += certificate_size_specific;
+ } while (remaining > 0);
+ if (remaining) {
+ dbg() << "extraneous " << remaining << " bytes left over after parsing certificates";
+ }
+ size -= certificate_size + 3;
+ res += certificate_size;
+ }
+ if (!valid_certificate)
+ return (i8)Error::UnsupportedCertificate;
+
+ if ((size_t)res != buffer.size())
+ dbg() << "some data left unread: " << (size_t)res << " bytes out of " << buffer.size();
+
+ return res;
+}
+
+void TLSv12::consume(ReadonlyBytes record)
+{
+ if (m_context.critical_error) {
+ dbg() << "There has been a critical error (" << (i8)m_context.critical_error << "), refusing to continue";
+ return;
+ }
+
+ if (record.size() == 0) {
+ return;
+ }
+
+#ifdef TLS_DEBUG
+ dbg() << "Consuming " << record.size() << " bytes";
+#endif
+
+ m_context.message_buffer.append(record.data(), record.size());
+
+ size_t index { 0 };
+ size_t buffer_length = m_context.message_buffer.size();
+
+ size_t size_offset { 3 }; // read the common record header
+ size_t header_size { 5 };
+#ifdef TLS_DEBUG
+ dbg() << "message buffer length " << buffer_length;
+#endif
+ while (buffer_length >= 5) {
+ auto length = AK::convert_between_host_and_network_endian(*(u16*)m_context.message_buffer.offset_pointer(index + size_offset)) + header_size;
+ if (length > buffer_length) {
+#ifdef TLS_DEBUG
+ dbg() << "Need more data: " << length << " | " << buffer_length;
+#endif
+ break;
+ }
+ auto consumed = handle_message(m_context.message_buffer.bytes().slice(index, length));
+
+#ifdef TLS_DEBUG
+ if (consumed > 0)
+ dbg() << "consumed " << (size_t)consumed << " bytes";
+ else
+ dbg() << "error: " << (int)consumed;
+#endif
+
+ if (consumed != (i8)Error::NeedMoreData) {
+ if (consumed < 0) {
+ dbg() << "Consumed an error: " << (int)consumed;
+ if (!m_context.critical_error)
+ m_context.critical_error = (i8)consumed;
+ m_context.error_code = (Error)consumed;
+ break;
+ }
+ } else {
+ continue;
+ }
+
+ index += length;
+ buffer_length -= length;
+ if (m_context.critical_error) {
+ dbgln("Broken connection");
+ m_context.error_code = Error::BrokenConnection;
+ break;
+ }
+ }
+ if (m_context.error_code != Error::NoError && m_context.error_code != Error::NeedMoreData) {
+ dbg() << "consume error: " << (i8)m_context.error_code;
+ m_context.message_buffer.clear();
+ return;
+ }
+
+ if (index) {
+ m_context.message_buffer = m_context.message_buffer.slice(index, m_context.message_buffer.size() - index);
+ }
+}
+
+void TLSv12::ensure_hmac(size_t digest_size, bool local)
+{
+ if (local && m_hmac_local)
+ return;
+
+ if (!local && m_hmac_remote)
+ return;
+
+ auto hash_kind = Crypto::Hash::HashKind::None;
+
+ switch (digest_size) {
+ case Crypto::Hash::SHA1::DigestSize:
+ hash_kind = Crypto::Hash::HashKind::SHA1;
+ break;
+ case Crypto::Hash::SHA256::DigestSize:
+ hash_kind = Crypto::Hash::HashKind::SHA256;
+ break;
+ case Crypto::Hash::SHA512::DigestSize:
+ hash_kind = Crypto::Hash::HashKind::SHA512;
+ break;
+ default:
+ dbg() << "Failed to find a suitable hash for size " << digest_size;
+ break;
+ }
+
+ auto hmac = make<Crypto::Authentication::HMAC<Crypto::Hash::Manager>>(ReadonlyBytes { local ? m_context.crypto.local_mac : m_context.crypto.remote_mac, digest_size }, hash_kind);
+ if (local)
+ m_hmac_local = move(hmac);
+ else
+ m_hmac_remote = move(hmac);
+}
+
+bool Certificate::is_valid() const
+{
+ auto now = Core::DateTime::now();
+
+ if (!not_before.is_empty()) {
+ if (now.is_before(not_before)) {
+ dbg() << "certificate expired (not yet valid, signed for " << not_before << ")";
+ return false;
+ }
+ }
+
+ if (!not_after.is_empty()) {
+ if (!now.is_before(not_after)) {
+ dbg() << "certificate expired (expiry date " << not_after << ")";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void TLSv12::try_disambiguate_error() const
+{
+ dbgln("Possible failure cause(s): ");
+ switch ((AlertDescription)m_context.critical_error) {
+ case AlertDescription::HandshakeFailure:
+ if (!m_context.cipher_spec_set) {
+ dbg() << "- No cipher suite in common with " << m_context.SNI;
+ } else {
+ dbgln("- Unknown internal issue");
+ }
+ break;
+ case AlertDescription::InsufficientSecurity:
+ dbg() << "- No cipher suite in common with " << m_context.SNI << " (the server is oh so secure)";
+ break;
+ case AlertDescription::ProtocolVersion:
+ dbgln("- The server refused to negotiate with TLS 1.2 :(");
+ break;
+ case AlertDescription::UnexpectedMessage:
+ dbgln("- We sent an invalid message for the state we're in.");
+ break;
+ case AlertDescription::BadRecordMAC:
+ dbgln("- Bad MAC record from our side.");
+ dbgln("- Ciphertext wasn't an even multiple of the block length.");
+ dbgln("- Bad block cipher padding.");
+ dbgln("- If both sides are compliant, the only cause is messages being corrupted in the network.");
+ break;
+ case AlertDescription::RecordOverflow:
+ dbgln("- Sent a ciphertext record which has a length bigger than 18432 bytes.");
+ dbgln("- Sent record decrypted to a compressed record that has a length bigger than 18432 bytes.");
+ dbgln("- If both sides are compliant, the only cause is messages being corrupted in the network.");
+ break;
+ case AlertDescription::DecompressionFailure:
+ dbgln("- We sent invalid input for decompression (e.g. data that would expand to excessive length)");
+ break;
+ case AlertDescription::IllegalParameter:
+ dbgln("- We sent a parameter in the handshake that is out of range or inconsistent with the other parameters.");
+ break;
+ case AlertDescription::DecodeError:
+ dbgln("- The message we sent cannot be decoded because a field was out of range or the length was incorrect.");
+ dbgln("- If both sides are compliant, the only cause is messages being corrupted in the network.");
+ break;
+ case AlertDescription::DecryptError:
+ dbgln("- A handshake crypto operation failed. This includes signature verification and validating Finished.");
+ break;
+ case AlertDescription::AccessDenied:
+ dbgln("- The certificate is valid, but once access control was applied, the sender decided to stop negotiation.");
+ break;
+ case AlertDescription::InternalError:
+ dbgln("- No one knows, but it isn't a protocol failure.");
+ break;
+ case AlertDescription::DecryptionFailed:
+ case AlertDescription::NoCertificate:
+ case AlertDescription::ExportRestriction:
+ dbgln("- No one knows, the server sent a non-compliant alert.");
+ break;
+ default:
+ dbgln("- No one knows.");
+ break;
+ }
+}
+
+void TLSv12::set_root_certificates(Vector<Certificate> certificates)
+{
+ if (!m_context.root_ceritificates.is_empty())
+ dbgln("TLS warn: resetting root certificates!");
+
+ for (auto& cert : certificates) {
+ if (!cert.is_valid())
+ dbg() << "Certificate for " << cert.subject << " by " << cert.issuer_subject << " is invalid, things may or may not work!";
+ // FIXME: Figure out what we should do when our root certs are invalid.
+ }
+ m_context.root_ceritificates = move(certificates);
+}
+
+bool Context::verify_chain() const
+{
+ const Vector<Certificate>* local_chain = nullptr;
+ if (is_server) {
+ dbgln("Unsupported: Server mode");
+ TODO();
+ } else {
+ local_chain = &certificates;
+ }
+
+ // FIXME: Actually verify the signature, instead of just checking the name.
+ HashMap<String, String> chain;
+ HashTable<String> roots;
+ // First, walk the root certs.
+ for (auto& cert : root_ceritificates) {
+ roots.set(cert.subject);
+ chain.set(cert.subject, cert.issuer_subject);
+ }
+
+ // Then, walk the local certs.
+ for (auto& cert : *local_chain) {
+ auto& issuer_unique_name = cert.issuer_unit.is_empty() ? cert.issuer_subject : cert.issuer_unit;
+ chain.set(cert.subject, issuer_unique_name);
+ }
+
+ // Then verify the chain.
+ for (auto& it : chain) {
+ if (it.key == it.value) { // Allow self-signed certificates.
+ if (!roots.contains(it.key))
+ dbg() << "Self-signed warning: Certificate for " << it.key << " is self-signed";
+ continue;
+ }
+
+ auto ref = chain.get(it.value);
+ if (!ref.has_value()) {
+ dbg() << "Certificate for " << it.key << " is not signed by anyone we trust (" << it.value << ")";
+ return false;
+ }
+
+ if (ref.value() == it.key) // Allow (but warn about) mutually recursively signed cert A <-> B.
+ dbg() << "Co-dependency warning: Certificate for " << ref.value() << " is issued by " << it.key << ", which itself is issued by " << ref.value();
+ }
+
+ return true;
+}
+
+static bool wildcard_matches(const StringView& host, const StringView& subject)
+{
+ if (host.matches(subject))
+ return true;
+
+ if (subject.starts_with("*."))
+ return wildcard_matches(host, subject.substring_view(2));
+
+ return false;
+}
+
+Optional<size_t> TLSv12::verify_chain_and_get_matching_certificate(const StringView& host) const
+{
+ if (m_context.certificates.is_empty() || !m_context.verify_chain())
+ return {};
+
+ if (host.is_empty())
+ return 0;
+
+ for (size_t i = 0; i < m_context.certificates.size(); ++i) {
+ auto& cert = m_context.certificates[i];
+ if (wildcard_matches(host, cert.subject))
+ return i;
+ for (auto& san : cert.SAN) {
+ if (wildcard_matches(host, san))
+ return i;
+ }
+ }
+
+ return {};
+}
+
+TLSv12::TLSv12(Core::Object* parent, Version version)
+ : Core::Socket(Core::Socket::Type::TCP, parent)
+{
+ m_context.version = version;
+ m_context.is_server = false;
+ m_context.tls_buffer = ByteBuffer::create_uninitialized(0);
+#ifdef SOCK_NONBLOCK
+ int fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+#else
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
+ int option = 1;
+ ioctl(fd, FIONBIO, &option);
+#endif
+ if (fd < 0) {
+ set_error(errno);
+ } else {
+ set_fd(fd);
+ set_mode(IODevice::ReadWrite);
+ set_error(0);
+ }
+}
+
+bool TLSv12::add_client_key(ReadonlyBytes certificate_pem_buffer, ReadonlyBytes rsa_key) // FIXME: This should not be bound to RSA
+{
+ if (certificate_pem_buffer.is_empty() || rsa_key.is_empty()) {
+ return true;
+ }
+ auto decoded_certificate = Crypto::decode_pem(certificate_pem_buffer, 0);
+ if (decoded_certificate.is_empty()) {
+ dbgln("Certificate not PEM");
+ return false;
+ }
+
+ auto maybe_certificate = parse_asn1(decoded_certificate);
+ if (!maybe_certificate.has_value()) {
+ dbgln("Invalid certificate");
+ return false;
+ }
+
+ Crypto::PK::RSA rsa(rsa_key);
+ auto certificate = maybe_certificate.value();
+ certificate.private_key = rsa.private_key();
+
+ return add_client_key(certificate);
+}
+
+AK::Singleton<DefaultRootCACertificates> DefaultRootCACertificates::s_the;
+DefaultRootCACertificates::DefaultRootCACertificates()
+{
+ // FIXME: This might not be the best format, find a better way to represent CA certificates.
+ auto config = Core::ConfigFile::get_for_system("ca_certs");
+ for (auto& entity : config->groups()) {
+ Certificate cert;
+ cert.subject = entity;
+ cert.issuer_subject = config->read_entry(entity, "issuer_subject", entity);
+ cert.country = config->read_entry(entity, "country");
+ m_ca_certificates.append(move(cert));
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibTLS/TLSv12.h b/Userland/Libraries/LibTLS/TLSv12.h
new file mode 100644
index 0000000000..219b9f55d4
--- /dev/null
+++ b/Userland/Libraries/LibTLS/TLSv12.h
@@ -0,0 +1,509 @@
+/*
+ * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Certificate.h"
+#include <AK/IPv4Address.h>
+#include <AK/WeakPtr.h>
+#include <LibCore/Notifier.h>
+#include <LibCore/Socket.h>
+#include <LibCore/TCPSocket.h>
+#include <LibCrypto/Authentication/HMAC.h>
+#include <LibCrypto/BigInt/UnsignedBigInteger.h>
+#include <LibCrypto/Cipher/AES.h>
+#include <LibCrypto/Hash/HashManager.h>
+#include <LibCrypto/PK/RSA.h>
+#include <LibTLS/TLSPacketBuilder.h>
+
+namespace TLS {
+
+inline void print_buffer(ReadonlyBytes buffer)
+{
+ for (size_t i { 0 }; i < buffer.size(); ++i)
+ dbgprintf("%02x ", buffer[i]);
+ dbgprintf("\n");
+}
+
+inline void print_buffer(const ByteBuffer& buffer)
+{
+ print_buffer(buffer.bytes());
+}
+
+inline void print_buffer(const u8* buffer, size_t size)
+{
+ print_buffer(ReadonlyBytes { buffer, size });
+}
+
+class Socket;
+
+enum class CipherSuite {
+ Invalid = 0,
+ AES_128_GCM_SHA256 = 0x1301,
+ AES_256_GCM_SHA384 = 0x1302,
+ AES_128_CCM_SHA256 = 0x1304,
+ AES_128_CCM_8_SHA256 = 0x1305,
+
+ // We support these
+ RSA_WITH_AES_128_CBC_SHA = 0x002F,
+ RSA_WITH_AES_256_CBC_SHA = 0x0035,
+ RSA_WITH_AES_128_CBC_SHA256 = 0x003C,
+ RSA_WITH_AES_256_CBC_SHA256 = 0x003D,
+ // TODO
+ RSA_WITH_AES_128_GCM_SHA256 = 0x009C,
+ RSA_WITH_AES_256_GCM_SHA384 = 0x009D,
+};
+
+#define ENUMERATE_ALERT_DESCRIPTIONS \
+ ENUMERATE_ALERT_DESCRIPTION(CloseNotify, 0) \
+ ENUMERATE_ALERT_DESCRIPTION(UnexpectedMessage, 10) \
+ ENUMERATE_ALERT_DESCRIPTION(BadRecordMAC, 20) \
+ ENUMERATE_ALERT_DESCRIPTION(DecryptionFailed, 21) \
+ ENUMERATE_ALERT_DESCRIPTION(RecordOverflow, 22) \
+ ENUMERATE_ALERT_DESCRIPTION(DecompressionFailure, 30) \
+ ENUMERATE_ALERT_DESCRIPTION(HandshakeFailure, 40) \
+ ENUMERATE_ALERT_DESCRIPTION(NoCertificate, 41) \
+ ENUMERATE_ALERT_DESCRIPTION(BadCertificate, 42) \
+ ENUMERATE_ALERT_DESCRIPTION(UnsupportedCertificate, 43) \
+ ENUMERATE_ALERT_DESCRIPTION(CertificateRevoked, 44) \
+ ENUMERATE_ALERT_DESCRIPTION(CertificateExpired, 45) \
+ ENUMERATE_ALERT_DESCRIPTION(CertificateUnknown, 46) \
+ ENUMERATE_ALERT_DESCRIPTION(IllegalParameter, 47) \
+ ENUMERATE_ALERT_DESCRIPTION(UnknownCA, 48) \
+ ENUMERATE_ALERT_DESCRIPTION(AccessDenied, 49) \
+ ENUMERATE_ALERT_DESCRIPTION(DecodeError, 50) \
+ ENUMERATE_ALERT_DESCRIPTION(DecryptError, 51) \
+ ENUMERATE_ALERT_DESCRIPTION(ExportRestriction, 60) \
+ ENUMERATE_ALERT_DESCRIPTION(ProtocolVersion, 70) \
+ ENUMERATE_ALERT_DESCRIPTION(InsufficientSecurity, 71) \
+ ENUMERATE_ALERT_DESCRIPTION(InternalError, 80) \
+ ENUMERATE_ALERT_DESCRIPTION(InappropriateFallback, 86) \
+ ENUMERATE_ALERT_DESCRIPTION(UserCanceled, 90) \
+ ENUMERATE_ALERT_DESCRIPTION(NoRenegotiation, 100) \
+ ENUMERATE_ALERT_DESCRIPTION(UnsupportedExtension, 110) \
+ ENUMERATE_ALERT_DESCRIPTION(NoError, 255)
+
+enum class AlertDescription : u8 {
+#define ENUMERATE_ALERT_DESCRIPTION(name, value) name = value,
+ ENUMERATE_ALERT_DESCRIPTIONS
+#undef ENUMERATE_ALERT_DESCRIPTION
+};
+
+constexpr static const char* alert_name(AlertDescription descriptor)
+{
+#define ENUMERATE_ALERT_DESCRIPTION(name, value) \
+ case AlertDescription::name: \
+ return #name;
+
+ switch (descriptor) {
+ ENUMERATE_ALERT_DESCRIPTIONS
+ }
+
+ return "Unknown";
+#undef ENUMERATE_ALERT_DESCRIPTION
+}
+
+enum class Error : i8 {
+ NoError = 0,
+ UnknownError = -1,
+ BrokenPacket = -2,
+ NotUnderstood = -3,
+ NoCommonCipher = -5,
+ UnexpectedMessage = -6,
+ CloseConnection = -7,
+ CompressionNotSupported = -8,
+ NotVerified = -9,
+ NotSafe = -10,
+ IntegrityCheckFailed = -11,
+ ErrorAlert = -12,
+ BrokenConnection = -13,
+ BadCertificate = -14,
+ UnsupportedCertificate = -15,
+ NoRenegotiation = -16,
+ FeatureNotSupported = -17,
+ DecryptionFailed = -20,
+ NeedMoreData = -21,
+ TimedOut = -22,
+};
+
+enum class AlertLevel : u8 {
+ Warning = 0x01,
+ Critical = 0x02
+};
+
+enum HandshakeType {
+ HelloRequest = 0x00,
+ ClientHello = 0x01,
+ ServerHello = 0x02,
+ HelloVerifyRequest = 0x03,
+ CertificateMessage = 0x0b,
+ ServerKeyExchange = 0x0c,
+ CertificateRequest = 0x0d,
+ ServerHelloDone = 0x0e,
+ CertificateVerify = 0x0f,
+ ClientKeyExchange = 0x10,
+ Finished = 0x14
+};
+
+enum class HandshakeExtension : u16 {
+ ServerName = 0x00,
+ ApplicationLayerProtocolNegotiation = 0x10,
+ SignatureAlgorithms = 0x0d,
+};
+
+enum class WritePacketStage {
+ Initial = 0,
+ ClientHandshake = 1,
+ ServerHandshake = 2,
+ Finished = 3,
+};
+
+enum class ConnectionStatus {
+ Disconnected,
+ Negotiating,
+ KeyExchange,
+ Renegotiating,
+ Established,
+};
+
+enum ClientVerificationStaus {
+ Verified,
+ VerificationNeeded,
+};
+
+struct Context {
+ String to_string() const;
+ bool verify() const;
+ bool verify_chain() const;
+
+ static void print_file(const StringView& fname);
+
+ u8 remote_random[32];
+ // To be predictable
+ u8 local_random[32];
+ u8 session_id[32];
+ u8 session_id_size { 0 };
+ CipherSuite cipher;
+ Version version;
+ bool is_server { false };
+ Vector<Certificate> certificates;
+ Certificate private_key;
+ Vector<Certificate> client_certificates;
+ ByteBuffer master_key;
+ ByteBuffer premaster_key;
+ u8 cipher_spec_set { 0 };
+ struct {
+ int created { 0 };
+ u8 remote_mac[32];
+ u8 local_mac[32];
+ u8 local_iv[16];
+ u8 remote_iv[16];
+ u8 local_aead_iv[4];
+ u8 remote_aead_iv[4];
+ } crypto;
+
+ Crypto::Hash::Manager handshake_hash;
+
+ ByteBuffer message_buffer;
+ u64 remote_sequence_number { 0 };
+ u64 local_sequence_number { 0 };
+
+ ConnectionStatus connection_status { ConnectionStatus::Disconnected };
+ u8 critical_error { 0 };
+ Error error_code { Error::NoError };
+
+ ByteBuffer tls_buffer;
+
+ ByteBuffer application_buffer;
+
+ bool is_child { false };
+
+ String SNI; // I hate your existence
+
+ u8 request_client_certificate { 0 };
+
+ ByteBuffer cached_handshake;
+
+ ClientVerificationStaus client_verified { Verified };
+
+ bool connection_finished { false };
+
+ // message flags
+ u8 handshake_messages[11] { 0 };
+ ByteBuffer user_data;
+ Vector<Certificate> root_ceritificates;
+
+ Vector<String> alpn;
+ StringView negotiated_alpn;
+
+ size_t send_retries { 0 };
+
+ time_t handshake_initiation_timestamp { 0 };
+};
+
+class TLSv12 : public Core::Socket {
+ C_OBJECT(TLSv12)
+public:
+ ByteBuffer& write_buffer() { return m_context.tls_buffer; }
+ bool is_established() const { return m_context.connection_status == ConnectionStatus::Established; }
+ virtual bool connect(const String&, int) override;
+
+ void set_sni(const StringView& sni)
+ {
+ if (m_context.is_server || m_context.critical_error || m_context.connection_status != ConnectionStatus::Disconnected) {
+ dbgln("invalid state for set_sni");
+ return;
+ }
+ m_context.SNI = sni;
+ }
+
+ Optional<Certificate> parse_asn1(ReadonlyBytes, bool client_cert = false) const;
+ bool load_certificates(ReadonlyBytes pem_buffer);
+ bool load_private_key(ReadonlyBytes pem_buffer);
+
+ void set_root_certificates(Vector<Certificate>);
+
+ bool add_client_key(ReadonlyBytes certificate_pem_buffer, ReadonlyBytes key_pem_buffer);
+ bool add_client_key(Certificate certificate)
+ {
+ m_context.client_certificates.append(move(certificate));
+ return true;
+ }
+
+ ByteBuffer finish_build();
+
+ const StringView& alpn() const { return m_context.negotiated_alpn; }
+ void add_alpn(const StringView& alpn);
+ bool has_alpn(const StringView& alpn) const;
+
+ bool supports_cipher(CipherSuite suite) const
+ {
+ return suite == CipherSuite::RSA_WITH_AES_128_CBC_SHA256
+ || suite == CipherSuite::RSA_WITH_AES_256_CBC_SHA256
+ || suite == CipherSuite::RSA_WITH_AES_128_CBC_SHA
+ || suite == CipherSuite::RSA_WITH_AES_256_CBC_SHA
+ || suite == CipherSuite::RSA_WITH_AES_128_GCM_SHA256;
+ }
+
+ bool supports_version(Version v) const
+ {
+ return v == Version::V12;
+ }
+
+ Optional<ByteBuffer> read();
+ ByteBuffer read(size_t max_size);
+
+ bool write(ReadonlyBytes);
+ void alert(AlertLevel, AlertDescription);
+
+ bool can_read_line() const { return m_context.application_buffer.size() && memchr(m_context.application_buffer.data(), '\n', m_context.application_buffer.size()); }
+ bool can_read() const { return m_context.application_buffer.size() > 0; }
+ String read_line(size_t max_size);
+
+ Function<void(TLSv12&)> on_tls_ready_to_read;
+ Function<void(TLSv12&)> on_tls_ready_to_write;
+ Function<void(AlertDescription)> on_tls_error;
+ Function<void()> on_tls_connected;
+ Function<void()> on_tls_finished;
+ Function<void(TLSv12&)> on_tls_certificate_request;
+
+private:
+ explicit TLSv12(Core::Object* parent, Version version = Version::V12);
+
+ virtual bool common_connect(const struct sockaddr*, socklen_t) override;
+
+ void consume(ReadonlyBytes record);
+
+ ByteBuffer hmac_message(const ReadonlyBytes& buf, const Optional<ReadonlyBytes> buf2, size_t mac_length, bool local = false);
+ void ensure_hmac(size_t digest_size, bool local);
+
+ void update_packet(ByteBuffer& packet);
+ void update_hash(ReadonlyBytes in);
+
+ void write_packet(ByteBuffer& packet);
+
+ ByteBuffer build_client_key_exchange();
+ ByteBuffer build_server_key_exchange();
+
+ ByteBuffer build_hello();
+ ByteBuffer build_finished();
+ ByteBuffer build_certificate();
+ ByteBuffer build_done();
+ ByteBuffer build_alert(bool critical, u8 code);
+ ByteBuffer build_change_cipher_spec();
+ ByteBuffer build_verify_request();
+ void build_random(PacketBuilder&);
+
+ bool flush();
+ void write_into_socket();
+ void read_from_socket();
+
+ bool check_connection_state(bool read);
+
+ ssize_t handle_hello(ReadonlyBytes, WritePacketStage&);
+ ssize_t handle_finished(ReadonlyBytes, WritePacketStage&);
+ ssize_t handle_certificate(ReadonlyBytes);
+ ssize_t handle_server_key_exchange(ReadonlyBytes);
+ ssize_t handle_server_hello_done(ReadonlyBytes);
+ ssize_t handle_verify(ReadonlyBytes);
+ ssize_t handle_payload(ReadonlyBytes);
+ ssize_t handle_message(ReadonlyBytes);
+ ssize_t handle_random(ReadonlyBytes);
+
+ size_t asn1_length(ReadonlyBytes, size_t* octets);
+
+ void pseudorandom_function(Bytes output, ReadonlyBytes secret, const u8* label, size_t label_length, ReadonlyBytes seed, ReadonlyBytes seed_b);
+
+ size_t key_length() const
+ {
+ switch (m_context.cipher) {
+ case CipherSuite::AES_128_CCM_8_SHA256:
+ case CipherSuite::AES_128_CCM_SHA256:
+ case CipherSuite::AES_128_GCM_SHA256:
+ case CipherSuite::Invalid:
+ case CipherSuite::RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite::RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite::RSA_WITH_AES_128_GCM_SHA256:
+ default:
+ return 128 / 8;
+ case CipherSuite::AES_256_GCM_SHA384:
+ case CipherSuite::RSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite::RSA_WITH_AES_256_CBC_SHA256:
+ case CipherSuite::RSA_WITH_AES_256_GCM_SHA384:
+ return 256 / 8;
+ }
+ }
+ size_t mac_length() const
+ {
+ switch (m_context.cipher) {
+ case CipherSuite::RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite::RSA_WITH_AES_256_CBC_SHA:
+ return Crypto::Hash::SHA1::digest_size();
+ case CipherSuite::AES_256_GCM_SHA384:
+ case CipherSuite::RSA_WITH_AES_256_GCM_SHA384:
+ return Crypto::Hash::SHA512::digest_size();
+ case CipherSuite::AES_128_CCM_8_SHA256:
+ case CipherSuite::AES_128_CCM_SHA256:
+ case CipherSuite::AES_128_GCM_SHA256:
+ case CipherSuite::Invalid:
+ case CipherSuite::RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite::RSA_WITH_AES_128_GCM_SHA256:
+ case CipherSuite::RSA_WITH_AES_256_CBC_SHA256:
+ default:
+ return Crypto::Hash::SHA256::digest_size();
+ }
+ }
+ size_t iv_length() const
+ {
+ switch (m_context.cipher) {
+ case CipherSuite::AES_128_CCM_8_SHA256:
+ case CipherSuite::AES_128_CCM_SHA256:
+ case CipherSuite::Invalid:
+ case CipherSuite::RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite::RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite::RSA_WITH_AES_256_CBC_SHA256:
+ case CipherSuite::RSA_WITH_AES_256_CBC_SHA:
+ default:
+ return 16;
+ case CipherSuite::AES_128_GCM_SHA256:
+ case CipherSuite::AES_256_GCM_SHA384:
+ case CipherSuite::RSA_WITH_AES_128_GCM_SHA256:
+ case CipherSuite::RSA_WITH_AES_256_GCM_SHA384:
+ return 8; // 4 bytes of fixed IV, 8 random (nonce) bytes, 4 bytes for counter
+ // GCM specifically asks us to transmit only the nonce, the counter is zero
+ // and the fixed IV is derived from the premaster key.
+ }
+ }
+
+ bool is_aead() const
+ {
+ switch (m_context.cipher) {
+ case CipherSuite::AES_128_GCM_SHA256:
+ case CipherSuite::AES_256_GCM_SHA384:
+ case CipherSuite::RSA_WITH_AES_128_GCM_SHA256:
+ case CipherSuite::RSA_WITH_AES_256_GCM_SHA384:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ bool expand_key();
+
+ bool compute_master_secret(size_t length);
+
+ Optional<size_t> verify_chain_and_get_matching_certificate(const StringView& host) const;
+
+ void try_disambiguate_error() const;
+
+ Context m_context;
+
+ OwnPtr<Crypto::Authentication::HMAC<Crypto::Hash::Manager>> m_hmac_local;
+ OwnPtr<Crypto::Authentication::HMAC<Crypto::Hash::Manager>> m_hmac_remote;
+
+ struct {
+ OwnPtr<Crypto::Cipher::AESCipher::CBCMode> cbc;
+ OwnPtr<Crypto::Cipher::AESCipher::GCMMode> gcm;
+ } m_aes_local, m_aes_remote;
+
+ bool m_has_scheduled_write_flush { false };
+ i32 m_max_wait_time_for_handshake_in_seconds { 10 };
+
+ RefPtr<Core::Timer> m_handshake_timeout_timer;
+};
+
+namespace Constants {
+constexpr static const u32 version_id[] { 1, 1, 1, 0 };
+constexpr static const u32 pk_id[] { 1, 1, 7, 0 };
+constexpr static const u32 serial_id[] { 1, 1, 2, 1, 0 };
+constexpr static const u32 issurer_id[] { 1, 1, 4, 0 };
+constexpr static const u32 owner_id[] { 1, 1, 6, 0 };
+constexpr static const u32 validity_id[] { 1, 1, 5, 0 };
+constexpr static const u32 algorithm_id[] { 1, 1, 3, 0 };
+constexpr static const u32 sign_id[] { 1, 3, 2, 1, 0 };
+constexpr static const u32 priv_id[] { 1, 4, 0 };
+constexpr static const u32 priv_der_id[] { 1, 3, 1, 0 };
+constexpr static const u32 ecc_priv_id[] { 1, 2, 0 };
+
+constexpr static const u8 country_oid[] { 0x55, 0x04, 0x06, 0x00 };
+constexpr static const u8 state_oid[] { 0x55, 0x04, 0x08, 0x00 };
+constexpr static const u8 location_oid[] { 0x55, 0x04, 0x07, 0x00 };
+constexpr static const u8 entity_oid[] { 0x55, 0x04, 0x0A, 0x00 };
+constexpr static const u8 subject_oid[] { 0x55, 0x04, 0x03, 0x00 };
+constexpr static const u8 unit_oid[] { 0x55, 0x04, 0x0B, 0x00 };
+constexpr static const u8 san_oid[] { 0x55, 0x1D, 0x11, 0x00 };
+constexpr static const u8 ocsp_oid[] { 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x00 };
+
+static constexpr const u8 RSA_SIGN_RSA_OID[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x00 };
+static constexpr const u8 RSA_SIGN_MD5_OID[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04, 0x00 };
+static constexpr const u8 RSA_SIGN_SHA1_OID[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x00 };
+static constexpr const u8 RSA_SIGN_SHA256_OID[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x00 };
+static constexpr const u8 RSA_SIGN_SHA384_OID[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c, 0x00 };
+static constexpr const u8 RSA_SIGN_SHA512_OID[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d, 0x00 };
+
+}
+
+}
diff --git a/Userland/Libraries/LibTTF/CMakeLists.txt b/Userland/Libraries/LibTTF/CMakeLists.txt
new file mode 100644
index 0000000000..96918db743
--- /dev/null
+++ b/Userland/Libraries/LibTTF/CMakeLists.txt
@@ -0,0 +1,8 @@
+set(SOURCES
+ Cmap.cpp
+ Font.cpp
+ Glyf.cpp
+)
+
+serenity_lib(LibTTF ttf)
+target_link_libraries(LibTTF LibGfx LibM LibCore)
diff --git a/Userland/Libraries/LibTTF/Cmap.cpp b/Userland/Libraries/LibTTF/Cmap.cpp
new file mode 100644
index 0000000000..ce651e5385
--- /dev/null
+++ b/Userland/Libraries/LibTTF/Cmap.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Optional.h>
+#include <LibTTF/Cmap.h>
+
+namespace TTF {
+
+extern u16 be_u16(const u8* ptr);
+extern u32 be_u32(const u8* ptr);
+extern i16 be_i16(const u8* ptr);
+
+Cmap::Subtable::Platform Cmap::Subtable::platform_id() const
+{
+ switch (m_raw_platform_id) {
+ case 0:
+ return Platform::Unicode;
+ case 1:
+ return Platform::Macintosh;
+ case 3:
+ return Platform::Windows;
+ case 4:
+ return Platform::Custom;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+Cmap::Subtable::Format Cmap::Subtable::format() const
+{
+ switch (be_u16(m_slice.offset_pointer(0))) {
+ case 0:
+ return Format::ByteEncoding;
+ case 2:
+ return Format::HighByte;
+ case 4:
+ return Format::SegmentToDelta;
+ case 6:
+ return Format::TrimmedTable;
+ case 8:
+ return Format::Mixed16And32;
+ case 10:
+ return Format::TrimmedArray;
+ case 12:
+ return Format::SegmentedCoverage;
+ case 13:
+ return Format::ManyToOneRange;
+ case 14:
+ return Format::UnicodeVariationSequences;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+u32 Cmap::num_subtables() const
+{
+ return be_u16(m_slice.offset_pointer((u32)Offsets::NumTables));
+}
+
+Optional<Cmap::Subtable> Cmap::subtable(u32 index) const
+{
+ if (index >= num_subtables()) {
+ return {};
+ }
+ u32 record_offset = (u32)Sizes::TableHeader + index * (u32)Sizes::EncodingRecord;
+ u16 platform_id = be_u16(m_slice.offset_pointer(record_offset));
+ u16 encoding_id = be_u16(m_slice.offset_pointer(record_offset + (u32)Offsets::EncodingRecord_EncodingID));
+ u32 subtable_offset = be_u32(m_slice.offset_pointer(record_offset + (u32)Offsets::EncodingRecord_Offset));
+ ASSERT(subtable_offset < m_slice.size());
+ auto subtable_slice = ReadonlyBytes(m_slice.offset_pointer(subtable_offset), m_slice.size() - subtable_offset);
+ return Subtable(subtable_slice, platform_id, encoding_id);
+}
+
+// FIXME: This only handles formats 4 (SegmentToDelta) and 12 (SegmentedCoverage) for now.
+u32 Cmap::Subtable::glyph_id_for_codepoint(u32 codepoint) const
+{
+ switch (format()) {
+ case Format::SegmentToDelta:
+ return glyph_id_for_codepoint_table_4(codepoint);
+ case Format::SegmentedCoverage:
+ return glyph_id_for_codepoint_table_12(codepoint);
+ default:
+ return 0;
+ }
+}
+
+u32 Cmap::Subtable::glyph_id_for_codepoint_table_4(u32 codepoint) const
+{
+ u32 segcount_x2 = be_u16(m_slice.offset_pointer((u32)Table4Offsets::SegCountX2));
+ if (m_slice.size() < segcount_x2 * (u32)Table4Sizes::NonConstMultiplier + (u32)Table4Sizes::Constant) {
+ return 0;
+ }
+ for (u32 offset = 0; offset < segcount_x2; offset += 2) {
+ u32 end_codepoint = be_u16(m_slice.offset_pointer((u32)Table4Offsets::EndConstBase + offset));
+ if (codepoint > end_codepoint) {
+ continue;
+ }
+ u32 start_codepoint = be_u16(m_slice.offset_pointer((u32)Table4Offsets::StartConstBase + segcount_x2 + offset));
+ if (codepoint < start_codepoint) {
+ break;
+ }
+ u32 delta = be_u16(m_slice.offset_pointer((u32)Table4Offsets::DeltaConstBase + segcount_x2 * 2 + offset));
+ u32 range = be_u16(m_slice.offset_pointer((u32)Table4Offsets::RangeConstBase + segcount_x2 * 3 + offset));
+ if (range == 0) {
+ return (codepoint + delta) & 0xffff;
+ }
+ u32 glyph_offset = (u32)Table4Offsets::GlyphOffsetConstBase + segcount_x2 * 3 + offset + range + (codepoint - start_codepoint) * 2;
+ ASSERT(glyph_offset + 2 <= m_slice.size());
+ return (be_u16(m_slice.offset_pointer(glyph_offset)) + delta) & 0xffff;
+ }
+ return 0;
+}
+
+u32 Cmap::Subtable::glyph_id_for_codepoint_table_12(u32 codepoint) const
+{
+ u32 num_groups = be_u32(m_slice.offset_pointer((u32)Table12Offsets::NumGroups));
+ ASSERT(m_slice.size() >= (u32)Table12Sizes::Header + (u32)Table12Sizes::Record * num_groups);
+ for (u32 offset = 0; offset < num_groups * (u32)Table12Sizes::Record; offset += (u32)Table12Sizes::Record) {
+ u32 start_codepoint = be_u32(m_slice.offset_pointer((u32)Table12Offsets::Record_StartCode + offset));
+ if (codepoint < start_codepoint) {
+ break;
+ }
+ u32 end_codepoint = be_u32(m_slice.offset_pointer((u32)Table12Offsets::Record_EndCode + offset));
+ if (codepoint > end_codepoint) {
+ continue;
+ }
+ u32 glyph_offset = be_u32(m_slice.offset_pointer((u32)Table12Offsets::Record_StartGlyph + offset));
+ return codepoint - start_codepoint + glyph_offset;
+ }
+ return 0;
+}
+
+u32 Cmap::glyph_id_for_codepoint(u32 codepoint) const
+{
+ auto opt_subtable = subtable(m_active_index);
+ if (!opt_subtable.has_value()) {
+ return 0;
+ }
+ auto subtable = opt_subtable.value();
+ return subtable.glyph_id_for_codepoint(codepoint);
+}
+
+Optional<Cmap> Cmap::from_slice(const ReadonlyBytes& slice)
+{
+ if (slice.size() < (size_t)Sizes::TableHeader) {
+ return {};
+ }
+ return Cmap(slice);
+}
+
+}
diff --git a/Userland/Libraries/LibTTF/Cmap.h b/Userland/Libraries/LibTTF/Cmap.h
new file mode 100644
index 0000000000..2133a72f91
--- /dev/null
+++ b/Userland/Libraries/LibTTF/Cmap.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Span.h>
+
+namespace TTF {
+
+class Cmap {
+public:
+ class Subtable {
+ public:
+ enum class Platform {
+ Unicode = 0,
+ Macintosh = 1,
+ Windows = 3,
+ Custom = 4,
+ };
+ enum class Format {
+ ByteEncoding = 0,
+ HighByte = 2,
+ SegmentToDelta = 4,
+ TrimmedTable = 6,
+ Mixed16And32 = 8,
+ TrimmedArray = 10,
+ SegmentedCoverage = 12,
+ ManyToOneRange = 13,
+ UnicodeVariationSequences = 14,
+ };
+ enum class WindowsEncoding {
+ UnicodeBMP = 1,
+ UnicodeFullRepertoire = 10,
+ };
+
+ Subtable(const ReadonlyBytes& slice, u16 platform_id, u16 encoding_id)
+ : m_slice(slice)
+ , m_raw_platform_id(platform_id)
+ , m_encoding_id(encoding_id)
+ {
+ }
+ // Returns 0 if glyph not found. This corresponds to the "missing glyph"
+ u32 glyph_id_for_codepoint(u32 codepoint) const;
+ Platform platform_id() const;
+ u16 encoding_id() const { return m_encoding_id; }
+ Format format() const;
+
+ private:
+ enum class Table4Offsets {
+ SegCountX2 = 6,
+ EndConstBase = 14,
+ StartConstBase = 16,
+ DeltaConstBase = 16,
+ RangeConstBase = 16,
+ GlyphOffsetConstBase = 16,
+ };
+ enum class Table4Sizes {
+ Constant = 16,
+ NonConstMultiplier = 4,
+ };
+ enum class Table12Offsets {
+ NumGroups = 12,
+ Record_StartCode = 16,
+ Record_EndCode = 20,
+ Record_StartGlyph = 24,
+ };
+ enum class Table12Sizes {
+ Header = 16,
+ Record = 12,
+ };
+
+ u32 glyph_id_for_codepoint_table_4(u32 codepoint) const;
+ u32 glyph_id_for_codepoint_table_12(u32 codepoint) const;
+
+ ReadonlyBytes m_slice;
+ u16 m_raw_platform_id { 0 };
+ u16 m_encoding_id { 0 };
+ };
+
+ static Optional<Cmap> from_slice(const ReadonlyBytes&);
+ u32 num_subtables() const;
+ Optional<Subtable> subtable(u32 index) const;
+ void set_active_index(u32 index) { m_active_index = index; }
+ // Returns 0 if glyph not found. This corresponds to the "missing glyph"
+ u32 glyph_id_for_codepoint(u32 codepoint) const;
+
+private:
+ enum class Offsets {
+ NumTables = 2,
+ EncodingRecord_EncodingID = 2,
+ EncodingRecord_Offset = 4,
+ };
+ enum class Sizes {
+ TableHeader = 4,
+ EncodingRecord = 8,
+ };
+
+ Cmap(const ReadonlyBytes& slice)
+ : m_slice(slice)
+ {
+ }
+
+ ReadonlyBytes m_slice;
+ u32 m_active_index { UINT32_MAX };
+};
+
+}
diff --git a/Userland/Libraries/LibTTF/Font.cpp b/Userland/Libraries/LibTTF/Font.cpp
new file mode 100644
index 0000000000..de3ef7a1bf
--- /dev/null
+++ b/Userland/Libraries/LibTTF/Font.cpp
@@ -0,0 +1,469 @@
+/*
+ * Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "AK/ByteBuffer.h"
+#include <AK/Checked.h>
+#include <AK/LogStream.h>
+#include <AK/Utf32View.h>
+#include <AK/Utf8View.h>
+#include <LibCore/File.h>
+#include <LibTTF/Cmap.h>
+#include <LibTTF/Font.h>
+#include <LibTTF/Glyf.h>
+#include <LibTTF/Tables.h>
+#include <math.h>
+
+namespace TTF {
+
+u16 be_u16(const u8* ptr);
+u32 be_u32(const u8* ptr);
+i16 be_i16(const u8* ptr);
+float be_fword(const u8* ptr);
+u32 tag_from_str(const char* str);
+
+u16 be_u16(const u8* ptr)
+{
+ return (((u16)ptr[0]) << 8) | ((u16)ptr[1]);
+}
+
+u32 be_u32(const u8* ptr)
+{
+ return (((u32)ptr[0]) << 24) | (((u32)ptr[1]) << 16) | (((u32)ptr[2]) << 8) | ((u32)ptr[3]);
+}
+
+i16 be_i16(const u8* ptr)
+{
+ return (((i16)ptr[0]) << 8) | ((i16)ptr[1]);
+}
+
+float be_fword(const u8* ptr)
+{
+ return (float)be_i16(ptr) / (float)(1 << 14);
+}
+
+u32 tag_from_str(const char* str)
+{
+ return be_u32((const u8*)str);
+}
+
+Optional<Head> Head::from_slice(const ReadonlyBytes& slice)
+{
+ if (slice.size() < (size_t)Sizes::Table) {
+ return {};
+ }
+ return Head(slice);
+}
+
+u16 Head::units_per_em() const
+{
+ return be_u16(m_slice.offset_pointer((u32)Offsets::UnitsPerEM));
+}
+
+i16 Head::xmin() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::XMin));
+}
+
+i16 Head::ymin() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::YMin));
+}
+
+i16 Head::xmax() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::XMax));
+}
+
+i16 Head::ymax() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::YMax));
+}
+
+u16 Head::lowest_recommended_ppem() const
+{
+ return be_u16(m_slice.offset_pointer((u32)Offsets::LowestRecPPEM));
+}
+
+IndexToLocFormat Head::index_to_loc_format() const
+{
+ i16 raw = be_i16(m_slice.offset_pointer((u32)Offsets::IndexToLocFormat));
+ switch (raw) {
+ case 0:
+ return IndexToLocFormat::Offset16;
+ case 1:
+ return IndexToLocFormat::Offset32;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+Optional<Hhea> Hhea::from_slice(const ReadonlyBytes& slice)
+{
+ if (slice.size() < (size_t)Sizes::Table) {
+ return {};
+ }
+ return Hhea(slice);
+}
+
+i16 Hhea::ascender() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::Ascender));
+}
+
+i16 Hhea::descender() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::Descender));
+}
+
+i16 Hhea::line_gap() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::LineGap));
+}
+
+u16 Hhea::advance_width_max() const
+{
+ return be_u16(m_slice.offset_pointer((u32)Offsets::AdvanceWidthMax));
+}
+
+u16 Hhea::number_of_h_metrics() const
+{
+ return be_u16(m_slice.offset_pointer((u32)Offsets::NumberOfHMetrics));
+}
+
+Optional<Maxp> Maxp::from_slice(const ReadonlyBytes& slice)
+{
+ if (slice.size() < (size_t)Sizes::TableV0p5) {
+ return {};
+ }
+ return Maxp(slice);
+}
+
+u16 Maxp::num_glyphs() const
+{
+ return be_u16(m_slice.offset_pointer((u32)Offsets::NumGlyphs));
+}
+
+Optional<Hmtx> Hmtx::from_slice(const ReadonlyBytes& slice, u32 num_glyphs, u32 number_of_h_metrics)
+{
+ if (slice.size() < number_of_h_metrics * (u32)Sizes::LongHorMetric + (num_glyphs - number_of_h_metrics) * (u32)Sizes::LeftSideBearing) {
+ return {};
+ }
+ return Hmtx(slice, num_glyphs, number_of_h_metrics);
+}
+
+GlyphHorizontalMetrics Hmtx::get_glyph_horizontal_metrics(u32 glyph_id) const
+{
+ ASSERT(glyph_id < m_num_glyphs);
+ if (glyph_id < m_number_of_h_metrics) {
+ auto offset = glyph_id * (u32)Sizes::LongHorMetric;
+ u16 advance_width = be_u16(m_slice.offset_pointer(offset));
+ i16 left_side_bearing = be_i16(m_slice.offset_pointer(offset + 2));
+ return GlyphHorizontalMetrics {
+ .advance_width = advance_width,
+ .left_side_bearing = left_side_bearing,
+ };
+ }
+ auto offset = m_number_of_h_metrics * (u32)Sizes::LongHorMetric + (glyph_id - m_number_of_h_metrics) * (u32)Sizes::LeftSideBearing;
+ u16 advance_width = be_u16(m_slice.offset_pointer((m_number_of_h_metrics - 1) * (u32)Sizes::LongHorMetric));
+ i16 left_side_bearing = be_i16(m_slice.offset_pointer(offset));
+ return GlyphHorizontalMetrics {
+ .advance_width = advance_width,
+ .left_side_bearing = left_side_bearing,
+ };
+}
+
+RefPtr<Font> Font::load_from_file(const StringView& path, unsigned index)
+{
+ auto file_or_error = Core::File::open(String(path), Core::IODevice::ReadOnly);
+ if (file_or_error.is_error()) {
+ dbg() << "Could not open file: " << file_or_error.error();
+ return nullptr;
+ }
+ auto file = file_or_error.value();
+ if (!file->open(Core::IODevice::ReadOnly)) {
+ dbgln("Could not open file");
+ return nullptr;
+ }
+ auto buffer = file->read_all();
+ return load_from_memory(buffer, index);
+}
+
+RefPtr<Font> Font::load_from_memory(ByteBuffer& buffer, unsigned index)
+{
+ if (buffer.size() < 4) {
+ dbgln("Font file too small");
+ return nullptr;
+ }
+ u32 tag = be_u32(buffer.data());
+ if (tag == tag_from_str("ttcf")) {
+ // It's a font collection
+ if (buffer.size() < (u32)Sizes::TTCHeaderV1 + sizeof(u32) * (index + 1)) {
+ dbgln("Font file too small");
+ return nullptr;
+ }
+ u32 offset = be_u32(buffer.offset_pointer((u32)Sizes::TTCHeaderV1 + sizeof(u32) * index));
+ return load_from_offset(move(buffer), offset);
+ }
+ if (tag == tag_from_str("OTTO")) {
+ dbgln("CFF fonts not supported yet");
+ return nullptr;
+ }
+ if (tag != 0x00010000) {
+ dbgln("Not a valid font");
+ return nullptr;
+ }
+ return load_from_offset(move(buffer), 0);
+}
+
+// FIXME: "loca" and "glyf" are not available for CFF fonts.
+RefPtr<Font> Font::load_from_offset(ByteBuffer&& buffer, u32 offset)
+{
+ if (buffer.size() < offset + (u32)Sizes::OffsetTable) {
+ dbgln("Font file too small");
+ return nullptr;
+ }
+
+ Optional<ReadonlyBytes> opt_head_slice = {};
+ Optional<ReadonlyBytes> opt_hhea_slice = {};
+ Optional<ReadonlyBytes> opt_maxp_slice = {};
+ Optional<ReadonlyBytes> opt_hmtx_slice = {};
+ Optional<ReadonlyBytes> opt_cmap_slice = {};
+ Optional<ReadonlyBytes> opt_loca_slice = {};
+ Optional<ReadonlyBytes> opt_glyf_slice = {};
+
+ Optional<Head> opt_head = {};
+ Optional<Hhea> opt_hhea = {};
+ Optional<Maxp> opt_maxp = {};
+ Optional<Hmtx> opt_hmtx = {};
+ Optional<Cmap> opt_cmap = {};
+ Optional<Loca> opt_loca = {};
+
+ auto num_tables = be_u16(buffer.offset_pointer(offset + (u32)Offsets::NumTables));
+ if (buffer.size() < offset + (u32)Sizes::OffsetTable + num_tables * (u32)Sizes::TableRecord) {
+ dbgln("Font file too small");
+ return nullptr;
+ }
+
+ for (auto i = 0; i < num_tables; i++) {
+ u32 record_offset = offset + (u32)Sizes::OffsetTable + i * (u32)Sizes::TableRecord;
+ u32 tag = be_u32(buffer.offset_pointer(record_offset));
+ u32 table_offset = be_u32(buffer.offset_pointer(record_offset + (u32)Offsets::TableRecord_Offset));
+ u32 table_length = be_u32(buffer.offset_pointer(record_offset + (u32)Offsets::TableRecord_Length));
+
+ if (Checked<u32>::addition_would_overflow(table_offset, table_length)) {
+ dbgln("Invalid table offset/length in font.");
+ return nullptr;
+ }
+
+ if (buffer.size() < table_offset + table_length) {
+ dbgln("Font file too small");
+ return nullptr;
+ }
+ auto buffer_here = ReadonlyBytes(buffer.offset_pointer(table_offset), table_length);
+
+ // Get the table offsets we need.
+ if (tag == tag_from_str("head")) {
+ opt_head_slice = buffer_here;
+ } else if (tag == tag_from_str("hhea")) {
+ opt_hhea_slice = buffer_here;
+ } else if (tag == tag_from_str("maxp")) {
+ opt_maxp_slice = buffer_here;
+ } else if (tag == tag_from_str("hmtx")) {
+ opt_hmtx_slice = buffer_here;
+ } else if (tag == tag_from_str("cmap")) {
+ opt_cmap_slice = buffer_here;
+ } else if (tag == tag_from_str("loca")) {
+ opt_loca_slice = buffer_here;
+ } else if (tag == tag_from_str("glyf")) {
+ opt_glyf_slice = buffer_here;
+ }
+ }
+
+ if (!opt_head_slice.has_value() || !(opt_head = Head::from_slice(opt_head_slice.value())).has_value()) {
+ dbgln("Could not load Head");
+ return nullptr;
+ }
+ auto head = opt_head.value();
+
+ if (!opt_hhea_slice.has_value() || !(opt_hhea = Hhea::from_slice(opt_hhea_slice.value())).has_value()) {
+ dbgln("Could not load Hhea");
+ return nullptr;
+ }
+ auto hhea = opt_hhea.value();
+
+ if (!opt_maxp_slice.has_value() || !(opt_maxp = Maxp::from_slice(opt_maxp_slice.value())).has_value()) {
+ dbgln("Could not load Maxp");
+ return nullptr;
+ }
+ auto maxp = opt_maxp.value();
+
+ if (!opt_hmtx_slice.has_value() || !(opt_hmtx = Hmtx::from_slice(opt_hmtx_slice.value(), maxp.num_glyphs(), hhea.number_of_h_metrics())).has_value()) {
+ dbgln("Could not load Hmtx");
+ return nullptr;
+ }
+ auto hmtx = opt_hmtx.value();
+
+ if (!opt_cmap_slice.has_value() || !(opt_cmap = Cmap::from_slice(opt_cmap_slice.value())).has_value()) {
+ dbgln("Could not load Cmap");
+ return nullptr;
+ }
+ auto cmap = opt_cmap.value();
+
+ if (!opt_loca_slice.has_value() || !(opt_loca = Loca::from_slice(opt_loca_slice.value(), maxp.num_glyphs(), head.index_to_loc_format())).has_value()) {
+ dbgln("Could not load Loca");
+ return nullptr;
+ }
+ auto loca = opt_loca.value();
+
+ if (!opt_glyf_slice.has_value()) {
+ dbgln("Could not load Glyf");
+ return nullptr;
+ }
+ auto glyf = Glyf(opt_glyf_slice.value());
+
+ // Select cmap table. FIXME: Do this better. Right now, just looks for platform "Windows"
+ // and corresponding encoding "Unicode full repertoire", or failing that, "Unicode BMP"
+ for (u32 i = 0; i < cmap.num_subtables(); i++) {
+ auto opt_subtable = cmap.subtable(i);
+ if (!opt_subtable.has_value()) {
+ continue;
+ }
+ auto subtable = opt_subtable.value();
+ if (subtable.platform_id() == Cmap::Subtable::Platform::Windows) {
+ if (subtable.encoding_id() == (u16)Cmap::Subtable::WindowsEncoding::UnicodeFullRepertoire) {
+ cmap.set_active_index(i);
+ break;
+ }
+ if (subtable.encoding_id() == (u16)Cmap::Subtable::WindowsEncoding::UnicodeBMP) {
+ cmap.set_active_index(i);
+ break;
+ }
+ }
+ }
+
+ return adopt(*new Font(move(buffer), move(head), move(hhea), move(maxp), move(hmtx), move(cmap), move(loca), move(glyf)));
+}
+
+ScaledFontMetrics Font::metrics(float x_scale, float y_scale) const
+{
+ auto ascender = m_hhea.ascender() * y_scale;
+ auto descender = m_hhea.descender() * y_scale;
+ auto line_gap = m_hhea.line_gap() * y_scale;
+ auto advance_width_max = m_hhea.advance_width_max() * x_scale;
+ return ScaledFontMetrics {
+ .ascender = (int)roundf(ascender),
+ .descender = (int)roundf(descender),
+ .line_gap = (int)roundf(line_gap),
+ .advance_width_max = (int)roundf(advance_width_max),
+ };
+}
+
+// FIXME: "loca" and "glyf" are not available for CFF fonts.
+ScaledGlyphMetrics Font::glyph_metrics(u32 glyph_id, float x_scale, float y_scale) const
+{
+ if (glyph_id >= glyph_count()) {
+ glyph_id = 0;
+ }
+ auto horizontal_metrics = m_hmtx.get_glyph_horizontal_metrics(glyph_id);
+ auto glyph_offset = m_loca.get_glyph_offset(glyph_id);
+ auto glyph = m_glyf.glyph(glyph_offset);
+ int ascender = glyph.ascender();
+ int descender = glyph.descender();
+ return ScaledGlyphMetrics {
+ .ascender = (int)roundf(ascender * y_scale),
+ .descender = (int)roundf(descender * y_scale),
+ .advance_width = (int)roundf(horizontal_metrics.advance_width * x_scale),
+ .left_side_bearing = (int)roundf(horizontal_metrics.left_side_bearing * x_scale),
+ };
+}
+
+// FIXME: "loca" and "glyf" are not available for CFF fonts.
+RefPtr<Gfx::Bitmap> Font::raster_glyph(u32 glyph_id, float x_scale, float y_scale) const
+{
+ if (glyph_id >= glyph_count()) {
+ glyph_id = 0;
+ }
+ auto glyph_offset = m_loca.get_glyph_offset(glyph_id);
+ auto glyph = m_glyf.glyph(glyph_offset);
+ return glyph.raster(x_scale, y_scale, [&](u16 glyph_id) {
+ if (glyph_id >= glyph_count()) {
+ glyph_id = 0;
+ }
+ auto glyph_offset = m_loca.get_glyph_offset(glyph_id);
+ return m_glyf.glyph(glyph_offset);
+ });
+}
+
+u32 Font::glyph_count() const
+{
+ return m_maxp.num_glyphs();
+}
+
+u16 Font::units_per_em() const
+{
+ return m_head.units_per_em();
+}
+
+int ScaledFont::width(const StringView& string) const
+{
+ Utf8View utf8 { string };
+ return width(utf8);
+}
+
+int ScaledFont::width(const Utf8View& utf8) const
+{
+ int width = 0;
+ for (u32 codepoint : utf8) {
+ u32 glyph_id = glyph_id_for_codepoint(codepoint);
+ auto metrics = glyph_metrics(glyph_id);
+ width += metrics.advance_width;
+ }
+ return width;
+}
+
+int ScaledFont::width(const Utf32View& utf32) const
+{
+ int width = 0;
+ for (size_t i = 0; i < utf32.length(); i++) {
+ u32 glyph_id = glyph_id_for_codepoint(utf32.code_points()[i]);
+ auto metrics = glyph_metrics(glyph_id);
+ width += metrics.advance_width;
+ }
+ return width;
+}
+
+RefPtr<Gfx::Bitmap> ScaledFont::raster_glyph(u32 glyph_id) const
+{
+ auto glyph_iterator = m_cached_glyph_bitmaps.find(glyph_id);
+ if (glyph_iterator != m_cached_glyph_bitmaps.end())
+ return glyph_iterator->value;
+
+ auto glyph_bitmap = m_font->raster_glyph(glyph_id, m_x_scale, m_y_scale);
+ m_cached_glyph_bitmaps.set(glyph_id, glyph_bitmap);
+ return glyph_bitmap;
+}
+
+}
diff --git a/Userland/Libraries/LibTTF/Font.h b/Userland/Libraries/LibTTF/Font.h
new file mode 100644
index 0000000000..79185411c0
--- /dev/null
+++ b/Userland/Libraries/LibTTF/Font.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/HashMap.h>
+#include <AK/Noncopyable.h>
+#include <AK/RefCounted.h>
+#include <AK/StringView.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Size.h>
+#include <LibTTF/Cmap.h>
+#include <LibTTF/Glyf.h>
+#include <LibTTF/Tables.h>
+
+#define POINTS_PER_INCH 72.0f
+#define DEFAULT_DPI 96
+
+namespace TTF {
+
+struct ScaledFontMetrics {
+ int ascender;
+ int descender;
+ int line_gap;
+ int advance_width_max;
+
+ int height() const
+ {
+ return ascender - descender;
+ }
+};
+
+struct ScaledGlyphMetrics {
+ int ascender;
+ int descender;
+ int advance_width;
+ int left_side_bearing;
+};
+
+class Font : public RefCounted<Font> {
+ AK_MAKE_NONCOPYABLE(Font);
+
+public:
+ static RefPtr<Font> load_from_file(const StringView& path, unsigned index = 0);
+ static RefPtr<Font> load_from_memory(ByteBuffer&, unsigned index = 0);
+
+ ScaledFontMetrics metrics(float x_scale, float y_scale) const;
+ ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale) const;
+ RefPtr<Gfx::Bitmap> raster_glyph(u32 glyph_id, float x_scale, float y_scale) const;
+ u32 glyph_count() const;
+ u16 units_per_em() const;
+ u32 glyph_id_for_codepoint(u32 codepoint) const { return m_cmap.glyph_id_for_codepoint(codepoint); }
+
+private:
+ enum class Offsets {
+ NumTables = 4,
+ TableRecord_Offset = 8,
+ TableRecord_Length = 12,
+ };
+ enum class Sizes {
+ TTCHeaderV1 = 12,
+ OffsetTable = 12,
+ TableRecord = 16,
+ };
+
+ static RefPtr<Font> load_from_offset(ByteBuffer&&, unsigned index = 0);
+ Font(ByteBuffer&& buffer, Head&& head, Hhea&& hhea, Maxp&& maxp, Hmtx&& hmtx, Cmap&& cmap, Loca&& loca, Glyf&& glyf)
+ : m_buffer(move(buffer))
+ , m_head(move(head))
+ , m_hhea(move(hhea))
+ , m_maxp(move(maxp))
+ , m_hmtx(move(hmtx))
+ , m_loca(move(loca))
+ , m_glyf(move(glyf))
+ , m_cmap(move(cmap))
+ {
+ }
+
+ // This owns the font data
+ ByteBuffer m_buffer;
+ // These are stateful wrappers around non-owning slices
+ Head m_head;
+ Hhea m_hhea;
+ Maxp m_maxp;
+ Hmtx m_hmtx;
+ Loca m_loca;
+ Glyf m_glyf;
+ Cmap m_cmap;
+};
+
+class ScaledFont {
+public:
+ ScaledFont(RefPtr<Font> font, float point_width, float point_height, unsigned dpi_x = DEFAULT_DPI, unsigned dpi_y = DEFAULT_DPI)
+ : m_font(font)
+ {
+ float units_per_em = m_font->units_per_em();
+ m_x_scale = (point_width * dpi_x) / (POINTS_PER_INCH * units_per_em);
+ m_y_scale = (point_height * dpi_y) / (POINTS_PER_INCH * units_per_em);
+ }
+ u32 glyph_id_for_codepoint(u32 codepoint) const { return m_font->glyph_id_for_codepoint(codepoint); }
+ ScaledFontMetrics metrics() const { return m_font->metrics(m_x_scale, m_y_scale); }
+ ScaledGlyphMetrics glyph_metrics(u32 glyph_id) const { return m_font->glyph_metrics(glyph_id, m_x_scale, m_y_scale); }
+ RefPtr<Gfx::Bitmap> raster_glyph(u32 glyph_id) const;
+ u32 glyph_count() const { return m_font->glyph_count(); }
+ int width(const StringView&) const;
+ int width(const Utf8View&) const;
+ int width(const Utf32View&) const;
+
+private:
+ RefPtr<Font> m_font;
+ float m_x_scale { 0.0 };
+ float m_y_scale { 0.0 };
+ mutable AK::HashMap<u32, RefPtr<Gfx::Bitmap>> m_cached_glyph_bitmaps;
+};
+
+}
diff --git a/Userland/Libraries/LibTTF/Glyf.cpp b/Userland/Libraries/LibTTF/Glyf.cpp
new file mode 100644
index 0000000000..7ad25cd4e1
--- /dev/null
+++ b/Userland/Libraries/LibTTF/Glyf.cpp
@@ -0,0 +1,519 @@
+/*
+ * Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/Path.h>
+#include <LibGfx/Point.h>
+#include <LibTTF/Glyf.h>
+
+namespace TTF {
+
+extern u16 be_u16(const u8* ptr);
+extern u32 be_u32(const u8* ptr);
+extern i16 be_i16(const u8* ptr);
+extern float be_fword(const u8* ptr);
+
+enum class SimpleGlyfFlags {
+ // From spec.
+ OnCurve = 0x01,
+ XShortVector = 0x02,
+ YShortVector = 0x04,
+ RepeatFlag = 0x08,
+ XIsSameOrPositiveXShortVector = 0x10,
+ YIsSameOrPositiveYShortVector = 0x20,
+ // Combinations
+ XMask = 0x12,
+ YMask = 0x24,
+ XLongVector = 0x00,
+ YLongVector = 0x00,
+ XNegativeShortVector = 0x02,
+ YNegativeShortVector = 0x04,
+ XPositiveShortVector = 0x12,
+ YPositiveShortVector = 0x24,
+};
+
+enum class CompositeGlyfFlags {
+ Arg1AndArg2AreWords = 0x0001,
+ ArgsAreXYValues = 0x0002,
+ RoundXYToGrid = 0x0004,
+ WeHaveAScale = 0x0008,
+ MoreComponents = 0x0020,
+ WeHaveAnXAndYScale = 0x0040,
+ WeHaveATwoByTwo = 0x0080,
+ WeHaveInstructions = 0x0100,
+ UseMyMetrics = 0x0200,
+ OverlapCompound = 0x0400, // Not relevant - can overlap without this set
+ ScaledComponentOffset = 0x0800,
+ UnscaledComponentOffset = 0x1000,
+};
+
+class PointIterator {
+public:
+ struct Item {
+ bool on_curve;
+ Gfx::FloatPoint point;
+ };
+
+ PointIterator(const ReadonlyBytes& slice, u16 num_points, u32 flags_offset, u32 x_offset, u32 y_offset, Gfx::AffineTransform affine)
+ : m_slice(slice)
+ , m_points_remaining(num_points)
+ , m_flags_offset(flags_offset)
+ , m_x_offset(x_offset)
+ , m_y_offset(y_offset)
+ , m_affine(affine)
+ {
+ }
+
+ Optional<Item> next()
+ {
+ if (m_points_remaining == 0) {
+ return {};
+ }
+ if (m_flags_remaining > 0) {
+ m_flags_remaining--;
+ } else {
+ m_flag = m_slice[m_flags_offset++];
+ if (m_flag & (u8)SimpleGlyfFlags::RepeatFlag) {
+ m_flags_remaining = m_slice[m_flags_offset++];
+ }
+ }
+ switch (m_flag & (u8)SimpleGlyfFlags::XMask) {
+ case (u8)SimpleGlyfFlags::XLongVector:
+ m_last_point.set_x(m_last_point.x() + be_i16(m_slice.offset_pointer(m_x_offset)));
+ m_x_offset += 2;
+ break;
+ case (u8)SimpleGlyfFlags::XNegativeShortVector:
+ m_last_point.set_x(m_last_point.x() - m_slice[m_x_offset++]);
+ break;
+ case (u8)SimpleGlyfFlags::XPositiveShortVector:
+ m_last_point.set_x(m_last_point.x() + m_slice[m_x_offset++]);
+ break;
+ default:
+ break;
+ }
+ switch (m_flag & (u8)SimpleGlyfFlags::YMask) {
+ case (u8)SimpleGlyfFlags::YLongVector:
+ m_last_point.set_y(m_last_point.y() + be_i16(m_slice.offset_pointer(m_y_offset)));
+ m_y_offset += 2;
+ break;
+ case (u8)SimpleGlyfFlags::YNegativeShortVector:
+ m_last_point.set_y(m_last_point.y() - m_slice[m_y_offset++]);
+ break;
+ case (u8)SimpleGlyfFlags::YPositiveShortVector:
+ m_last_point.set_y(m_last_point.y() + m_slice[m_y_offset++]);
+ break;
+ default:
+ break;
+ }
+ m_points_remaining--;
+ Item ret = {
+ .on_curve = (m_flag & (u8)SimpleGlyfFlags::OnCurve) != 0,
+ .point = m_affine.map(m_last_point),
+ };
+ return ret;
+ }
+
+private:
+ ReadonlyBytes m_slice;
+ u16 m_points_remaining;
+ u8 m_flag { 0 };
+ Gfx::FloatPoint m_last_point = { 0.0f, 0.0f };
+ u32 m_flags_remaining = { 0 };
+ u32 m_flags_offset;
+ u32 m_x_offset;
+ u32 m_y_offset;
+ Gfx::AffineTransform m_affine;
+};
+
+Optional<Glyf::Glyph::ComponentIterator::Item> Glyf::Glyph::ComponentIterator::next()
+{
+ if (!m_has_more) {
+ return {};
+ }
+ u16 flags = be_u16(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ u16 glyph_id = be_u16(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ i16 arg1 = 0, arg2 = 0;
+ if (flags & (u16)CompositeGlyfFlags::Arg1AndArg2AreWords) {
+ arg1 = be_i16(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ arg2 = be_i16(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ } else {
+ arg1 = (i8)m_slice[m_offset++];
+ arg2 = (i8)m_slice[m_offset++];
+ }
+ float a = 1.0, b = 0.0, c = 0.0, d = 1.0, e = 0.0, f = 0.0;
+ if (flags & (u16)CompositeGlyfFlags::WeHaveATwoByTwo) {
+ a = be_fword(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ b = be_fword(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ c = be_fword(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ d = be_fword(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ } else if (flags & (u16)CompositeGlyfFlags::WeHaveAnXAndYScale) {
+ a = be_fword(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ d = be_fword(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ } else if (flags & (u16)CompositeGlyfFlags::WeHaveAScale) {
+ a = be_fword(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ d = a;
+ }
+ // FIXME: Handle UseMyMetrics, ScaledComponentOffset, UnscaledComponentOffset, non-ArgsAreXYValues
+ if (flags & (u16)CompositeGlyfFlags::ArgsAreXYValues) {
+ e = arg1;
+ f = arg2;
+ } else {
+ TODO();
+ }
+ if (flags & (u16)CompositeGlyfFlags::UseMyMetrics) {
+ TODO();
+ }
+ if (flags & (u16)CompositeGlyfFlags::ScaledComponentOffset) {
+ TODO();
+ }
+ if (flags & (u16)CompositeGlyfFlags::UnscaledComponentOffset) {
+ TODO();
+ }
+ m_has_more = (flags & (u16)CompositeGlyfFlags::MoreComponents);
+ return Item {
+ .glyph_id = glyph_id,
+ .affine = Gfx::AffineTransform(a, b, c, d, e, f),
+ };
+}
+
+Rasterizer::Rasterizer(Gfx::IntSize size)
+ : m_size(size)
+{
+ m_data.resize(m_size.width() * m_size.height());
+ for (int i = 0; i < m_size.width() * m_size.height(); i++) {
+ m_data[i] = 0.0;
+ }
+}
+
+void Rasterizer::draw_path(Gfx::Path& path)
+{
+ for (auto& line : path.split_lines()) {
+ draw_line(line.from, line.to);
+ }
+}
+
+RefPtr<Gfx::Bitmap> Rasterizer::accumulate()
+{
+ auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, m_size);
+ Color base_color = Color::from_rgb(0xffffff);
+ for (int y = 0; y < m_size.height(); y++) {
+ float accumulator = 0.0;
+ for (int x = 0; x < m_size.width(); x++) {
+ accumulator += m_data[y * m_size.width() + x];
+ float value = accumulator;
+ if (value < 0.0) {
+ value = -value;
+ }
+ if (value > 1.0) {
+ value = 1.0;
+ }
+ u8 alpha = value * 255.0;
+ bitmap->set_pixel(x, y, base_color.with_alpha(alpha));
+ }
+ }
+ return bitmap;
+}
+
+void Rasterizer::draw_line(Gfx::FloatPoint p0, Gfx::FloatPoint p1)
+{
+ // FIXME: Shift x and y according to dy/dx
+ if (p0.x() < 0.0) {
+ p0.set_x(roundf(p0.x()));
+ }
+ if (p0.y() < 0.0) {
+ p0.set_y(roundf(p0.y()));
+ }
+ if (p1.x() < 0.0) {
+ p1.set_x(roundf(p1.x()));
+ }
+ if (p1.y() < 0.0) {
+ p1.set_y(roundf(p1.y()));
+ }
+
+ if (!(p0.x() >= 0.0 && p0.y() >= 0.0 && p0.x() <= m_size.width() && p0.y() <= m_size.height())) {
+ dbgln("!P0({},{})", p0.x(), p0.y());
+ return;
+ }
+
+ if (!(p1.x() >= 0.0 && p1.y() >= 0.0 && p1.x() <= m_size.width() && p1.y() <= m_size.height())) {
+ dbgln("!P1({},{})", p1.x(), p1.y());
+ return;
+ }
+
+ ASSERT(p0.x() >= 0.0 && p0.y() >= 0.0 && p0.x() <= m_size.width() && p0.y() <= m_size.height());
+ ASSERT(p1.x() >= 0.0 && p1.y() >= 0.0 && p1.x() <= m_size.width() && p1.y() <= m_size.height());
+
+ // If we're on the same Y, there's no need to draw
+ if (p0.y() == p1.y()) {
+ return;
+ }
+
+ float direction = -1.0;
+ if (p1.y() < p0.y()) {
+ direction = 1.0;
+ auto tmp = p0;
+ p0 = p1;
+ p1 = tmp;
+ }
+
+ float dxdy = (p1.x() - p0.x()) / (p1.y() - p0.y());
+ u32 y0 = floor(p0.y());
+ u32 y1 = ceil(p1.y());
+ float x_cur = p0.x();
+
+ for (u32 y = y0; y < y1; y++) {
+ u32 line_offset = m_size.width() * y;
+
+ float dy = min(y + 1.0f, p1.y()) - max((float)y, p0.y());
+ float directed_dy = dy * direction;
+ float x_next = x_cur + dy * dxdy;
+ if (x_next < 0.0) {
+ x_next = 0.0;
+ }
+ float x0 = x_cur;
+ float x1 = x_next;
+ if (x1 < x0) {
+ x1 = x_cur;
+ x0 = x_next;
+ }
+ float x0_floor = floor(x0);
+ float x1_ceil = ceil(x1);
+ u32 x0i = x0_floor;
+
+ if (x1_ceil <= x0_floor + 1.0) {
+ // If x0 and x1 are within the same pixel, then area to the right is (1 - (mid(x0, x1) - x0_floor)) * dy
+ float area = ((x0 + x1) * 0.5) - x0_floor;
+ m_data[line_offset + x0i] += directed_dy * (1.0 - area);
+ m_data[line_offset + x0i + 1] += directed_dy * area;
+ } else {
+ float dydx = 1.0 / dxdy;
+ float x0_right = 1.0 - (x0 - x0_floor);
+ u32 x1_floor_i = floor(x1);
+ float area_upto_here = 0.5 * x0_right * x0_right * dydx;
+ m_data[line_offset + x0i] += direction * area_upto_here;
+ for (u32 x = x0i + 1; x < x1_floor_i; x++) {
+ x0_right += 1.0;
+ float total_area_here = 0.5 * x0_right * x0_right * dydx;
+ m_data[line_offset + x] += direction * (total_area_here - area_upto_here);
+ area_upto_here = total_area_here;
+ }
+ m_data[line_offset + x1_floor_i] += direction * (dy - area_upto_here);
+ }
+
+ x_cur = x_next;
+ }
+}
+
+Optional<Loca> Loca::from_slice(const ReadonlyBytes& slice, u32 num_glyphs, IndexToLocFormat index_to_loc_format)
+{
+ switch (index_to_loc_format) {
+ case IndexToLocFormat::Offset16:
+ if (slice.size() < num_glyphs * 2) {
+ return {};
+ }
+ break;
+ case IndexToLocFormat::Offset32:
+ if (slice.size() < num_glyphs * 4) {
+ return {};
+ }
+ break;
+ }
+ return Loca(slice, num_glyphs, index_to_loc_format);
+}
+
+u32 Loca::get_glyph_offset(u32 glyph_id) const
+{
+ ASSERT(glyph_id < m_num_glyphs);
+ switch (m_index_to_loc_format) {
+ case IndexToLocFormat::Offset16:
+ return ((u32)be_u16(m_slice.offset_pointer(glyph_id * 2))) * 2;
+ case IndexToLocFormat::Offset32:
+ return be_u32(m_slice.offset_pointer(glyph_id * 4));
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+static void get_ttglyph_offsets(const ReadonlyBytes& slice, u32 num_points, u32 flags_offset, u32* x_offset, u32* y_offset)
+{
+ u32 flags_size = 0;
+ u32 x_size = 0;
+ u32 repeat_count;
+ while (num_points > 0) {
+ u8 flag = slice[flags_offset + flags_size];
+ if (flag & (u8)SimpleGlyfFlags::RepeatFlag) {
+ flags_size++;
+ repeat_count = slice[flags_offset + flags_size] + 1;
+ } else {
+ repeat_count = 1;
+ }
+ flags_size++;
+ switch (flag & (u8)SimpleGlyfFlags::XMask) {
+ case (u8)SimpleGlyfFlags::XLongVector:
+ x_size += repeat_count * 2;
+ break;
+ case (u8)SimpleGlyfFlags::XNegativeShortVector:
+ case (u8)SimpleGlyfFlags::XPositiveShortVector:
+ x_size += repeat_count;
+ break;
+ default:
+ break;
+ }
+ num_points -= repeat_count;
+ }
+ *x_offset = flags_offset + flags_size;
+ *y_offset = *x_offset + x_size;
+}
+
+void Glyf::Glyph::raster_inner(Rasterizer& rasterizer, Gfx::AffineTransform& affine) const
+{
+ // Get offset for flags, x, and y.
+ u16 num_points = be_u16(m_slice.offset_pointer((m_num_contours - 1) * 2)) + 1;
+ u16 num_instructions = be_u16(m_slice.offset_pointer(m_num_contours * 2));
+ u32 flags_offset = m_num_contours * 2 + 2 + num_instructions;
+ u32 x_offset = 0;
+ u32 y_offset = 0;
+ get_ttglyph_offsets(m_slice, num_points, flags_offset, &x_offset, &y_offset);
+
+ // Prepare to render glyph.
+ Gfx::Path path;
+ PointIterator point_iterator(m_slice, num_points, flags_offset, x_offset, y_offset, affine);
+
+ int last_contour_end = -1;
+ i32 contour_index = 0;
+ u32 contour_size = 0;
+ Optional<Gfx::FloatPoint> contour_start = {};
+ Optional<Gfx::FloatPoint> last_offcurve_point = {};
+
+ // Render glyph
+ while (true) {
+ if (!contour_start.has_value()) {
+ if (contour_index >= m_num_contours) {
+ break;
+ }
+ int current_contour_end = be_u16(m_slice.offset_pointer(contour_index++ * 2));
+ contour_size = current_contour_end - last_contour_end;
+ last_contour_end = current_contour_end;
+ auto opt_item = point_iterator.next();
+ ASSERT(opt_item.has_value());
+ contour_start = opt_item.value().point;
+ path.move_to(contour_start.value());
+ contour_size--;
+ } else if (!last_offcurve_point.has_value()) {
+ if (contour_size > 0) {
+ auto opt_item = point_iterator.next();
+ // FIXME: Should we draw a line to the first point here?
+ if (!opt_item.has_value()) {
+ break;
+ }
+ auto item = opt_item.value();
+ contour_size--;
+ if (item.on_curve) {
+ path.line_to(item.point);
+ } else if (contour_size > 0) {
+ auto opt_next_item = point_iterator.next();
+ // FIXME: Should we draw a quadratic bezier to the first point here?
+ if (!opt_next_item.has_value()) {
+ break;
+ }
+ auto next_item = opt_next_item.value();
+ contour_size--;
+ if (next_item.on_curve) {
+ path.quadratic_bezier_curve_to(item.point, next_item.point);
+ } else {
+ auto mid_point = (item.point + next_item.point) * 0.5f;
+ path.quadratic_bezier_curve_to(item.point, mid_point);
+ last_offcurve_point = next_item.point;
+ }
+ } else {
+ path.quadratic_bezier_curve_to(item.point, contour_start.value());
+ contour_start = {};
+ }
+ } else {
+ path.line_to(contour_start.value());
+ contour_start = {};
+ }
+ } else {
+ auto point0 = last_offcurve_point.value();
+ last_offcurve_point = {};
+ if (contour_size > 0) {
+ auto opt_item = point_iterator.next();
+ // FIXME: Should we draw a quadratic bezier to the first point here?
+ if (!opt_item.has_value()) {
+ break;
+ }
+ auto item = opt_item.value();
+ contour_size--;
+ if (item.on_curve) {
+ path.quadratic_bezier_curve_to(point0, item.point);
+ } else {
+ auto mid_point = (point0 + item.point) * 0.5f;
+ path.quadratic_bezier_curve_to(point0, mid_point);
+ last_offcurve_point = item.point;
+ }
+ } else {
+ path.quadratic_bezier_curve_to(point0, contour_start.value());
+ contour_start = {};
+ }
+ }
+ }
+
+ rasterizer.draw_path(path);
+}
+
+RefPtr<Gfx::Bitmap> Glyf::Glyph::raster_simple(float x_scale, float y_scale) const
+{
+ u32 width = (u32)(ceil((m_xmax - m_xmin) * x_scale)) + 2;
+ u32 height = (u32)(ceil((m_ymax - m_ymin) * y_scale)) + 2;
+ Rasterizer rasterizer(Gfx::IntSize(width, height));
+ auto affine = Gfx::AffineTransform().scale(x_scale, -y_scale).translate(-m_xmin, -m_ymax);
+ raster_inner(rasterizer, affine);
+ return rasterizer.accumulate();
+}
+
+Glyf::Glyph Glyf::glyph(u32 offset) const
+{
+ ASSERT(m_slice.size() >= offset + (u32)Sizes::GlyphHeader);
+ i16 num_contours = be_i16(m_slice.offset_pointer(offset));
+ i16 xmin = be_i16(m_slice.offset_pointer(offset + (u32)Offsets::XMin));
+ i16 ymin = be_i16(m_slice.offset_pointer(offset + (u32)Offsets::YMin));
+ i16 xmax = be_i16(m_slice.offset_pointer(offset + (u32)Offsets::XMax));
+ i16 ymax = be_i16(m_slice.offset_pointer(offset + (u32)Offsets::YMax));
+ auto slice = ReadonlyBytes(m_slice.offset_pointer(offset + (u32)Sizes::GlyphHeader), m_slice.size() - offset - (u32)Sizes::GlyphHeader);
+ return Glyph(slice, xmin, ymin, xmax, ymax, num_contours);
+}
+
+}
diff --git a/Userland/Libraries/LibTTF/Glyf.h b/Userland/Libraries/LibTTF/Glyf.h
new file mode 100644
index 0000000000..c7beb43644
--- /dev/null
+++ b/Userland/Libraries/LibTTF/Glyf.h
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Span.h>
+#include <AK/Vector.h>
+#include <LibGfx/AffineTransform.h>
+#include <LibGfx/Bitmap.h>
+#include <LibTTF/Tables.h>
+#include <math.h>
+
+namespace TTF {
+
+class Rasterizer {
+public:
+ Rasterizer(Gfx::IntSize);
+ void draw_path(Gfx::Path&);
+ RefPtr<Gfx::Bitmap> accumulate();
+
+private:
+ void draw_line(Gfx::FloatPoint, Gfx::FloatPoint);
+
+ Gfx::IntSize m_size;
+ AK::Vector<float> m_data;
+};
+
+class Loca {
+public:
+ static Optional<Loca> from_slice(const ReadonlyBytes&, u32 num_glyphs, IndexToLocFormat);
+ u32 get_glyph_offset(u32 glyph_id) const;
+
+private:
+ Loca(const ReadonlyBytes& slice, u32 num_glyphs, IndexToLocFormat index_to_loc_format)
+ : m_slice(slice)
+ , m_num_glyphs(num_glyphs)
+ , m_index_to_loc_format(index_to_loc_format)
+ {
+ }
+
+ ReadonlyBytes m_slice;
+ u32 m_num_glyphs { 0 };
+ IndexToLocFormat m_index_to_loc_format;
+};
+
+class Glyf {
+public:
+ class Glyph {
+ public:
+ Glyph(const ReadonlyBytes& slice, i16 xmin, i16 ymin, i16 xmax, i16 ymax, i16 num_contours = -1)
+ : m_xmin(xmin)
+ , m_ymin(ymin)
+ , m_xmax(xmax)
+ , m_ymax(ymax)
+ , m_num_contours(num_contours)
+ , m_slice(slice)
+ {
+ if (m_num_contours >= 0) {
+ m_type = Type::Simple;
+ }
+ }
+ template<typename GlyphCb>
+ RefPtr<Gfx::Bitmap> raster(float x_scale, float y_scale, GlyphCb glyph_callback) const
+ {
+ switch (m_type) {
+ case Type::Simple:
+ return raster_simple(x_scale, y_scale);
+ case Type::Composite:
+ return raster_composite(x_scale, y_scale, glyph_callback);
+ }
+ ASSERT_NOT_REACHED();
+ }
+ int ascender() const { return m_ymax; }
+ int descender() const { return m_ymin; }
+
+ private:
+ enum class Type {
+ Simple,
+ Composite,
+ };
+
+ class ComponentIterator {
+ public:
+ struct Item {
+ u16 glyph_id;
+ Gfx::AffineTransform affine;
+ };
+
+ ComponentIterator(const ReadonlyBytes& slice)
+ : m_slice(slice)
+ {
+ }
+ Optional<Item> next();
+
+ private:
+ ReadonlyBytes m_slice;
+ bool m_has_more { true };
+ u32 m_offset { 0 };
+ };
+
+ void raster_inner(Rasterizer&, Gfx::AffineTransform&) const;
+ RefPtr<Gfx::Bitmap> raster_simple(float x_scale, float y_scale) const;
+ template<typename GlyphCb>
+ RefPtr<Gfx::Bitmap> raster_composite(float x_scale, float y_scale, GlyphCb glyph_callback) const
+ {
+ u32 width = (u32)(ceil((m_xmax - m_xmin) * x_scale)) + 1;
+ u32 height = (u32)(ceil((m_ymax - m_ymin) * y_scale)) + 1;
+ Rasterizer rasterizer(Gfx::IntSize(width, height));
+ auto affine = Gfx::AffineTransform().scale(x_scale, -y_scale).translate(-m_xmin, -m_ymax);
+ ComponentIterator component_iterator(m_slice);
+ while (true) {
+ auto opt_item = component_iterator.next();
+ if (!opt_item.has_value()) {
+ break;
+ }
+ auto item = opt_item.value();
+ auto affine_here = affine.multiply(item.affine);
+ auto glyph = glyph_callback(item.glyph_id);
+ glyph.raster_inner(rasterizer, affine_here);
+ }
+ return rasterizer.accumulate();
+ }
+
+ Type m_type { Type::Composite };
+ i16 m_xmin { 0 };
+ i16 m_ymin { 0 };
+ i16 m_xmax { 0 };
+ i16 m_ymax { 0 };
+ i16 m_num_contours { -1 };
+ ReadonlyBytes m_slice;
+ };
+
+ Glyf(const ReadonlyBytes& slice)
+ : m_slice(slice)
+ {
+ }
+ Glyph glyph(u32 offset) const;
+
+private:
+ enum class Offsets {
+ XMin = 2,
+ YMin = 4,
+ XMax = 6,
+ YMax = 8,
+ };
+ enum class Sizes {
+ GlyphHeader = 10,
+ };
+
+ ReadonlyBytes m_slice;
+};
+
+}
diff --git a/Userland/Libraries/LibTTF/Tables.h b/Userland/Libraries/LibTTF/Tables.h
new file mode 100644
index 0000000000..5264911ad9
--- /dev/null
+++ b/Userland/Libraries/LibTTF/Tables.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Span.h>
+
+namespace TTF {
+
+enum class IndexToLocFormat {
+ Offset16,
+ Offset32,
+};
+
+class Head {
+public:
+ static Optional<Head> from_slice(const ReadonlyBytes&);
+ u16 units_per_em() const;
+ i16 xmin() const;
+ i16 ymin() const;
+ i16 xmax() const;
+ i16 ymax() const;
+ u16 lowest_recommended_ppem() const;
+ IndexToLocFormat index_to_loc_format() const;
+
+private:
+ enum class Offsets {
+ UnitsPerEM = 18,
+ XMin = 36,
+ YMin = 38,
+ XMax = 40,
+ YMax = 42,
+ LowestRecPPEM = 46,
+ IndexToLocFormat = 50,
+ };
+ enum class Sizes {
+ Table = 54,
+ };
+
+ Head(const ReadonlyBytes& slice)
+ : m_slice(slice)
+ {
+ }
+
+ ReadonlyBytes m_slice;
+};
+
+class Hhea {
+public:
+ static Optional<Hhea> from_slice(const ReadonlyBytes&);
+ i16 ascender() const;
+ i16 descender() const;
+ i16 line_gap() const;
+ u16 advance_width_max() const;
+ u16 number_of_h_metrics() const;
+
+private:
+ enum class Offsets {
+ Ascender = 4,
+ Descender = 6,
+ LineGap = 8,
+ AdvanceWidthMax = 10,
+ NumberOfHMetrics = 34,
+ };
+ enum class Sizes {
+ Table = 36,
+ };
+
+ Hhea(const ReadonlyBytes& slice)
+ : m_slice(slice)
+ {
+ }
+
+ ReadonlyBytes m_slice;
+};
+
+class Maxp {
+public:
+ static Optional<Maxp> from_slice(const ReadonlyBytes&);
+ u16 num_glyphs() const;
+
+private:
+ enum class Offsets {
+ NumGlyphs = 4
+ };
+ enum class Sizes {
+ TableV0p5 = 6,
+ };
+
+ Maxp(const ReadonlyBytes& slice)
+ : m_slice(slice)
+ {
+ }
+
+ ReadonlyBytes m_slice;
+};
+
+struct GlyphHorizontalMetrics {
+ u16 advance_width;
+ i16 left_side_bearing;
+};
+
+class Hmtx {
+public:
+ static Optional<Hmtx> from_slice(const ReadonlyBytes&, u32 num_glyphs, u32 number_of_h_metrics);
+ GlyphHorizontalMetrics get_glyph_horizontal_metrics(u32 glyph_id) const;
+
+private:
+ enum class Sizes {
+ LongHorMetric = 4,
+ LeftSideBearing = 2
+ };
+
+ Hmtx(const ReadonlyBytes& slice, u32 num_glyphs, u32 number_of_h_metrics)
+ : m_slice(slice)
+ , m_num_glyphs(num_glyphs)
+ , m_number_of_h_metrics(number_of_h_metrics)
+ {
+ }
+
+ ReadonlyBytes m_slice;
+ u32 m_num_glyphs { 0 };
+ u32 m_number_of_h_metrics { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibTar/CMakeLists.txt b/Userland/Libraries/LibTar/CMakeLists.txt
new file mode 100644
index 0000000000..94c0420438
--- /dev/null
+++ b/Userland/Libraries/LibTar/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(SOURCES
+ TarStream.cpp
+)
+
+serenity_lib(LibTar tar)
+target_link_libraries(LibTar LibCore)
diff --git a/Userland/Libraries/LibTar/Tar.h b/Userland/Libraries/LibTar/Tar.h
new file mode 100644
index 0000000000..70f1764344
--- /dev/null
+++ b/Userland/Libraries/LibTar/Tar.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/StringView.h>
+#include <sys/types.h>
+
+namespace Tar {
+
+enum FileType {
+ NormalFile = '0',
+ AlternateNormalFile = '\0',
+ HardLink = '1',
+ SymLink = '2',
+ CharacterSpecialFile = '3',
+ BlockSpecialFile = '4',
+ Directory = '5',
+ FIFO = '6',
+ ContiguousFile = '7',
+ GlobalExtendedHeader = 'g',
+ ExtendedHeader = 'x'
+};
+
+constexpr size_t block_size = 512;
+constexpr const char* ustar_magic = "ustar ";
+
+class Header {
+public:
+ // FIXME: support ustar filename prefix
+ const StringView file_name() const { return m_file_name; }
+ mode_t mode() const { return get_tar_field(m_mode); }
+ uid_t uid() const { return get_tar_field(m_uid); }
+ gid_t gid() const { return get_tar_field(m_gid); }
+ // FIXME: support 2001-star size encoding
+ size_t size() const { return get_tar_field(m_size); }
+ time_t timestamp() const { return get_tar_field(m_timestamp); }
+ FileType type_flag() const { return FileType(m_type_flag); }
+ const StringView link_name() const { return m_link_name; }
+ const StringView magic() const { return StringView(m_magic, sizeof(m_magic)); }
+ const StringView version() const { return StringView(m_version, sizeof(m_version)); }
+ const StringView owner_name() const { return m_owner_name; }
+ const StringView group_name() const { return m_group_name; }
+ int major() const { return get_tar_field(m_major); }
+ int minor() const { return get_tar_field(m_minor); }
+
+private:
+ char m_file_name[100];
+ char m_mode[8];
+ char m_uid[8];
+ char m_gid[8];
+ char m_size[12];
+ char m_timestamp[12];
+ char m_checksum[8];
+ char m_type_flag;
+ char m_link_name[100];
+ char m_magic[6];
+ char m_version[2];
+ char m_owner_name[32];
+ char m_group_name[32];
+ char m_major[8];
+ char m_minor[8];
+ char m_prefix[155];
+
+ template<size_t N>
+ static size_t get_tar_field(const char (&field)[N]);
+};
+
+template<size_t N>
+size_t Header::get_tar_field(const char (&field)[N])
+{
+ size_t value = 0;
+ for (size_t i = 0; i < N; ++i) {
+ if (field[i] == 0)
+ break;
+
+ ASSERT(field[i] >= '0' && field[i] <= '7');
+ value *= 8;
+ value += field[i] - '0';
+ }
+ return value;
+}
+
+}
diff --git a/Userland/Libraries/LibTar/TarStream.cpp b/Userland/Libraries/LibTar/TarStream.cpp
new file mode 100644
index 0000000000..533a01e932
--- /dev/null
+++ b/Userland/Libraries/LibTar/TarStream.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LogStream.h>
+#include <LibTar/TarStream.h>
+
+namespace Tar {
+TarFileStream::TarFileStream(TarStream& tar_stream)
+ : m_tar_stream(tar_stream)
+ , m_generation(tar_stream.m_generation)
+{
+}
+
+size_t TarFileStream::read(Bytes bytes)
+{
+ // verify that the stream has not advanced
+ ASSERT(m_tar_stream.m_generation == m_generation);
+
+ if (has_any_error())
+ return 0;
+
+ auto to_read = min(bytes.size(), m_tar_stream.header().size() - m_tar_stream.m_file_offset);
+
+ auto nread = m_tar_stream.m_stream.read(bytes.trim(to_read));
+ m_tar_stream.m_file_offset += nread;
+ return nread;
+}
+
+bool TarFileStream::unreliable_eof() const
+{
+ // verify that the stream has not advanced
+ ASSERT(m_tar_stream.m_generation == m_generation);
+
+ return m_tar_stream.m_stream.unreliable_eof()
+ || m_tar_stream.m_file_offset >= m_tar_stream.header().size();
+}
+
+bool TarFileStream::read_or_error(Bytes bytes)
+{
+ // verify that the stream has not advanced
+ ASSERT(m_tar_stream.m_generation == m_generation);
+
+ if (read(bytes) < bytes.size()) {
+ set_fatal_error();
+ return false;
+ }
+
+ return true;
+}
+
+bool TarFileStream::discard_or_error(size_t count)
+{
+ // verify that the stream has not advanced
+ ASSERT(m_tar_stream.m_generation == m_generation);
+
+ if (count > m_tar_stream.header().size() - m_tar_stream.m_file_offset) {
+ return false;
+ }
+ m_tar_stream.m_file_offset += count;
+ return m_tar_stream.m_stream.discard_or_error(count);
+}
+
+TarStream::TarStream(InputStream& stream)
+ : m_stream(stream)
+{
+ if (!m_stream.read_or_error(Bytes(&m_header, sizeof(m_header)))) {
+ m_finished = true;
+ return;
+ }
+ ASSERT(m_stream.discard_or_error(block_size - sizeof(Header)));
+}
+
+static constexpr unsigned long block_ceiling(unsigned long offset)
+{
+ return block_size * (1 + ((offset - 1) / block_size));
+}
+
+void TarStream::advance()
+{
+ if (m_finished)
+ return;
+
+ m_generation++;
+ ASSERT(m_stream.discard_or_error(block_ceiling(m_header.size()) - m_file_offset));
+ m_file_offset = 0;
+
+ if (!m_stream.read_or_error(Bytes(&m_header, sizeof(m_header)))) {
+ m_finished = true;
+ return;
+ }
+ if (!valid()) {
+ m_finished = true;
+ return;
+ }
+
+ ASSERT(m_stream.discard_or_error(block_size - sizeof(Header)));
+}
+
+bool TarStream::valid() const
+{
+ return header().magic() == ustar_magic;
+}
+
+TarFileStream TarStream::file_contents()
+{
+ ASSERT(!m_finished);
+ return TarFileStream(*this);
+}
+
+}
diff --git a/Userland/Libraries/LibTar/TarStream.h b/Userland/Libraries/LibTar/TarStream.h
new file mode 100644
index 0000000000..adcf0cdb5f
--- /dev/null
+++ b/Userland/Libraries/LibTar/TarStream.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Span.h>
+#include <AK/Stream.h>
+#include <LibTar/Tar.h>
+
+namespace Tar {
+
+class TarStream;
+
+class TarFileStream : public InputStream {
+public:
+ size_t read(Bytes) override;
+ bool unreliable_eof() const override;
+
+ bool read_or_error(Bytes) override;
+ bool discard_or_error(size_t count) override;
+
+private:
+ TarFileStream(TarStream& stream);
+ TarStream& m_tar_stream;
+ int m_generation;
+
+ friend class TarStream;
+};
+
+class TarStream {
+public:
+ TarStream(InputStream&);
+ void advance();
+ bool finished() const { return m_finished; }
+ bool valid() const;
+ const Header& header() const { return m_header; }
+ TarFileStream file_contents();
+
+private:
+ Header m_header;
+ InputStream& m_stream;
+ unsigned long m_file_offset { 0 };
+ int m_generation { 0 };
+ bool m_finished { false };
+
+ friend class TarFileStream;
+};
+
+}
diff --git a/Userland/Libraries/LibTextCodec/CMakeLists.txt b/Userland/Libraries/LibTextCodec/CMakeLists.txt
new file mode 100644
index 0000000000..f99a6caa03
--- /dev/null
+++ b/Userland/Libraries/LibTextCodec/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(SOURCES
+ Decoder.cpp
+)
+
+serenity_lib(LibTextCodec textcodec)
+target_link_libraries(LibTextCodec LibC)
diff --git a/Userland/Libraries/LibTextCodec/Decoder.cpp b/Userland/Libraries/LibTextCodec/Decoder.cpp
new file mode 100644
index 0000000000..6d4f2790f0
--- /dev/null
+++ b/Userland/Libraries/LibTextCodec/Decoder.cpp
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <LibTextCodec/Decoder.h>
+
+namespace TextCodec {
+
+namespace {
+Latin1Decoder& latin1_decoder()
+{
+ static Latin1Decoder* decoder;
+ if (!decoder)
+ decoder = new Latin1Decoder;
+ return *decoder;
+}
+
+UTF8Decoder& utf8_decoder()
+{
+ static UTF8Decoder* decoder;
+ if (!decoder)
+ decoder = new UTF8Decoder;
+ return *decoder;
+}
+
+Latin2Decoder& latin2_decoder()
+{
+ static Latin2Decoder* decoder = nullptr;
+ if (!decoder)
+ decoder = new Latin2Decoder;
+ return *decoder;
+}
+
+}
+
+Decoder* decoder_for(const String& a_encoding)
+{
+ auto encoding = get_standardized_encoding(a_encoding);
+ if (encoding.equals_ignoring_case("windows-1252"))
+ return &latin1_decoder();
+ if (encoding.equals_ignoring_case("utf-8"))
+ return &utf8_decoder();
+ if (encoding.equals_ignoring_case("iso-8859-2"))
+ return &latin2_decoder();
+ dbgln("TextCodec: No decoder implemented for encoding '{}'", a_encoding);
+ return nullptr;
+}
+
+// https://encoding.spec.whatwg.org/#concept-encoding-get
+String get_standardized_encoding(const String& encoding)
+{
+ String trimmed_lowercase_encoding = encoding.trim_whitespace().to_lowercase();
+
+ if (trimmed_lowercase_encoding.is_one_of("unicode-1-1-utf-8", "unicode11utf8", "unicode20utf8", "utf-8", "utf8", "x-unicode20utf8"))
+ return "UTF-8";
+ if (trimmed_lowercase_encoding.is_one_of("866", "cp866", "csibm866", "ibm666"))
+ return "IBM666";
+ if (trimmed_lowercase_encoding.is_one_of("csisolatin2", "iso-8859-2", "iso-ir-101", "iso8859-2", "iso88592", "iso_8859-2", "iso_8859-2:1987", "l2", "latin2"))
+ return "ISO-8859-2";
+ if (trimmed_lowercase_encoding.is_one_of("csisolatin3", "iso-8859-3", "iso-ir-109", "iso8859-3", "iso88593", "iso_8859-3", "iso_8859-3:1988", "l3", "latin3"))
+ return "ISO-8859-3";
+ if (trimmed_lowercase_encoding.is_one_of("csisolatin4", "iso-8859-4", "iso-ir-110", "iso8859-4", "iso88594", "iso_8859-4", "iso_8859-4:1989", "l4", "latin4"))
+ return "ISO-8859-4";
+ if (trimmed_lowercase_encoding.is_one_of("csisolatincyrillic", "cyrillic", "iso-8859-5", "iso-ir-144", "iso8859-5", "iso88595", "iso_8859-5", "iso_8859-5:1988"))
+ return "ISO-8859-5";
+ if (trimmed_lowercase_encoding.is_one_of("arabic", "asmo-708", "csiso88596e", "csiso88596i", "csisolatinarabic", "ecma-114", "iso-8859-6", "iso-8859-6-e", "iso-8859-6-i", "iso-ir-127", "iso8859-6", "iso88596", "iso_8859-6", "iso_8859-6:1987"))
+ return "ISO-8859-6";
+ if (trimmed_lowercase_encoding.is_one_of("csisolatingreek", "ecma-118", "elot_928", "greek", "greek8", "iso-8859-7", "iso-ir-126", "iso8859-7", "iso88597", "iso_8859-7", "iso_8859-7:1987", "sun_eu_greek"))
+ return "ISO-8859-7";
+ if (trimmed_lowercase_encoding.is_one_of("csiso88598e", "csisolatinhebrew", "hebrew", "iso-8859-8", "iso-8859-8-e", "iso-ir-138", "iso8859-8", "iso88598", "iso_8859-8", "iso_8859-8:1988", "visual"))
+ return "ISO-8859-8";
+ if (trimmed_lowercase_encoding.is_one_of("csiso88598i", "iso-8859-8-i", "logical"))
+ return "ISO-8859-8-I";
+ if (trimmed_lowercase_encoding.is_one_of("csisolatin6", "iso8859-10", "iso-ir-157", "iso8859-10", "iso885910", "l6", "latin6"))
+ return "ISO-8859-10";
+ if (trimmed_lowercase_encoding.is_one_of("iso-8859-13", "iso8859-13", "iso885913"))
+ return "ISO-8859-13";
+ if (trimmed_lowercase_encoding.is_one_of("iso-8859-14", "iso8859-14", "iso885914"))
+ return "ISO-8859-14";
+ if (trimmed_lowercase_encoding.is_one_of("csisolatin9", "iso-8859-15", "iso8859-15", "iso885915", "iso_8859-15", "l9"))
+ return "ISO-8859-15";
+ if (trimmed_lowercase_encoding == "iso-8859-16")
+ return "ISO-8859-16";
+ if (trimmed_lowercase_encoding.is_one_of("cskoi8r", "koi", "koi8", "koi8-r", "koi8_r"))
+ return "KOI8-R";
+ if (trimmed_lowercase_encoding.is_one_of("koi8-ru", "koi8-u"))
+ return "KOI8-U";
+ if (trimmed_lowercase_encoding.is_one_of("csmacintosh", "mac", "macintosh", "x-mac-roman"))
+ return "macintosh";
+ if (trimmed_lowercase_encoding.is_one_of("dos-874", "iso-8859-11", "iso8859-11", "iso885911", "tis-620", "windows-874"))
+ return "windows-874";
+ if (trimmed_lowercase_encoding.is_one_of("cp1250", "windows-1250", "x-cp1250"))
+ return "windows-1250";
+ if (trimmed_lowercase_encoding.is_one_of("cp1251", "windows-1251", "x-cp1251"))
+ return "windows-1251";
+ if (trimmed_lowercase_encoding.is_one_of("ansi_x3.4-1968", "ascii", "cp1252", "cp819", "csisolatin1", "ibm819", "iso-8859-1", "iso-ir-100", "iso8859-1", "iso88591", "iso_8859-1", "iso_8859-1:1987", "l1", "latin1", "us-ascii", "windows-1252", "x-cp1252"))
+ return "windows-1252";
+ if (trimmed_lowercase_encoding.is_one_of("cp1253", "windows-1253", "x-cp1253"))
+ return "windows-1253";
+ if (trimmed_lowercase_encoding.is_one_of("cp1254", "csisolatin5", "iso-8859-9", "iso-ir-148", "iso-8859-9", "iso-88599", "iso_8859-9", "iso_8859-9:1989", "l5", "latin5", "windows-1254", "x-cp1254"))
+ return "windows-1254";
+ if (trimmed_lowercase_encoding.is_one_of("cp1255", "windows-1255", "x-cp1255"))
+ return "windows-1255";
+ if (trimmed_lowercase_encoding.is_one_of("cp1256", "windows-1256", "x-cp1256"))
+ return "windows-1256";
+ if (trimmed_lowercase_encoding.is_one_of("cp1257", "windows-1257", "x-cp1257"))
+ return "windows-1257";
+ if (trimmed_lowercase_encoding.is_one_of("cp1258", "windows-1258", "x-cp1258"))
+ return "windows-1258";
+ if (trimmed_lowercase_encoding.is_one_of("x-mac-cyrillic", "x-mac-ukrainian"))
+ return "x-mac-cyrillic";
+ if (trimmed_lowercase_encoding.is_one_of("chinese", "csgb2312", "csiso58gb231280", "gb2312", "gb_2312", "gb_2312-80", "gbk", "iso-ir-58", "x-gbk"))
+ return "GBK";
+ if (trimmed_lowercase_encoding == "gb18030")
+ return "gb18030";
+ if (trimmed_lowercase_encoding.is_one_of("big5", "big5-hkscs", "cn-big5", "csbig5", "x-x-big5"))
+ return "Big5";
+ if (trimmed_lowercase_encoding.is_one_of("cseucpkdfmtjapanese", "euc-jp", "x-euc-jp"))
+ return "EUC-JP";
+ if (trimmed_lowercase_encoding.is_one_of("csiso2022jp", "iso-2022-jp"))
+ return "ISO-2022-JP";
+ if (trimmed_lowercase_encoding.is_one_of("csshiftjis", "ms932", "ms_kanji", "shift-jis", "shift_jis", "sjis", "windows-31j", "x-sjis"))
+ return "Shift_JIS";
+ if (trimmed_lowercase_encoding.is_one_of("cseuckr", "csksc56011987", "euc-kr", "iso-ir-149", "korean", "ks_c_5601-1987", "ks_c_5601-1989", "ksc5601", "ksc_5601", "windows-949"))
+ return "EUC-KR";
+ if (trimmed_lowercase_encoding.is_one_of("csiso2022kr", "hz-gb-2312", "iso-2022-cn", "iso-2022-cn-ext", "iso-2022-kr", "replacement"))
+ return "replacement";
+ if (trimmed_lowercase_encoding.is_one_of("unicodefffe", "utf-16be"))
+ return "UTF-16BE";
+ if (trimmed_lowercase_encoding.is_one_of("csunicode", "iso-10646-ucs-2", "ucs-2", "unicode", "unicodefeff", "utf-16", "utf-16le"))
+ return "UTF-16LE";
+ if (trimmed_lowercase_encoding == "x-user-defined")
+ return "x-user-defined";
+
+ dbg() << "TextCodec: Unrecognized encoding: " << encoding;
+ return {};
+}
+
+bool is_standardized_encoding(const String& encoding)
+{
+ auto lowercase_encoding = encoding.to_lowercase();
+ return lowercase_encoding.is_one_of("utf-8", "ibm666", "iso-8859-2", "iso-8859-3", "iso-8859-4", "iso-8859-5", "iso-8859-6", "iso-8859-7", "iso-8859-8", "iso-8859-8-I", "iso-8859-10", "iso-8859-13", "iso-8859-14", "iso-8859-15", "iso-8859-16", "koi8-r", "koi8-u", "macintosh", "windows-874", "windows-1250", "windows-1251", "windows-1252", "windows-1253", "windows-1254", "windows-1255", "windows-1256", "windows-1257", "windows-1258", "x-mac-cyrillic", "gbk", "gb18030", "big5", "euc-jp", "iso-2022-JP", "shift_jis", "euc-kr", "replacement", "utf-16be", "utf-16le", "x-user-defined");
+}
+
+String UTF8Decoder::to_utf8(const StringView& input)
+{
+ return input;
+}
+
+String Latin1Decoder::to_utf8(const StringView& input)
+{
+ StringBuilder builder(input.length());
+ for (size_t i = 0; i < input.length(); ++i) {
+ u8 ch = input[i];
+ // Latin1 is the same as the first 256 Unicode code_points, so no mapping is needed, just utf-8 encoding.
+ builder.append_code_point(ch);
+ }
+ return builder.to_string();
+}
+
+namespace {
+u32 convert_latin2_to_utf8(u8 in)
+{
+ switch (in) {
+
+#define MAP(X, Y) \
+ case X: \
+ return Y
+
+ MAP(0xA1, 0x104);
+ MAP(0xA2, 0x2D8);
+ MAP(0xA3, 0x141);
+ MAP(0xA5, 0x13D);
+ MAP(0xA6, 0x15A);
+ MAP(0xA9, 0x160);
+ MAP(0xAA, 0x15E);
+ MAP(0xAB, 0x164);
+ MAP(0xAC, 0x179);
+ MAP(0xAE, 0x17D);
+ MAP(0xAF, 0x17B);
+
+ MAP(0xB1, 0x105);
+ MAP(0xB2, 0x2DB);
+ MAP(0xB3, 0x142);
+ MAP(0xB5, 0x13E);
+ MAP(0xB6, 0x15B);
+ MAP(0xB7, 0x2C7);
+ MAP(0xB9, 0x161);
+ MAP(0xBA, 0x15F);
+ MAP(0xBB, 0x165);
+ MAP(0xBC, 0x17A);
+ MAP(0xBD, 0x2DD);
+ MAP(0xBE, 0x17E);
+ MAP(0xBF, 0x17C);
+
+ MAP(0xC0, 0x154);
+ MAP(0xC3, 0x102);
+ MAP(0xC5, 0x139);
+ MAP(0xC6, 0x106);
+ MAP(0xC8, 0x10C);
+ MAP(0xCA, 0x118);
+ MAP(0xCC, 0x11A);
+ MAP(0xCF, 0x10E);
+
+ MAP(0xD0, 0x110);
+ MAP(0xD1, 0x143);
+ MAP(0xD2, 0x147);
+ MAP(0xD5, 0x150);
+ MAP(0xD8, 0x158);
+ MAP(0xD9, 0x16E);
+ MAP(0xDB, 0x170);
+ MAP(0xDE, 0x162);
+
+ MAP(0xE0, 0x155);
+ MAP(0xE3, 0x103);
+ MAP(0xE5, 0x13A);
+ MAP(0xE6, 0x107);
+ MAP(0xE8, 0x10D);
+ MAP(0xEA, 0x119);
+ MAP(0xEC, 0x11B);
+ MAP(0xEF, 0x10F);
+
+ MAP(0xF0, 0x111);
+ MAP(0xF1, 0x144);
+ MAP(0xF2, 0x148);
+ MAP(0xF5, 0x151);
+ MAP(0xF8, 0x159);
+ MAP(0xF9, 0x16F);
+ MAP(0xFB, 0x171);
+ MAP(0xFE, 0x163);
+ MAP(0xFF, 0x2D9);
+#undef MAP
+
+ default:
+ return in;
+ }
+}
+}
+
+String Latin2Decoder::to_utf8(const StringView& input)
+{
+ StringBuilder builder(input.length());
+ for (auto c : input) {
+ builder.append_code_point(convert_latin2_to_utf8(c));
+ }
+
+ return builder.to_string();
+}
+
+}
diff --git a/Userland/Libraries/LibTextCodec/Decoder.h b/Userland/Libraries/LibTextCodec/Decoder.h
new file mode 100644
index 0000000000..84dd388ea5
--- /dev/null
+++ b/Userland/Libraries/LibTextCodec/Decoder.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+
+namespace TextCodec {
+
+class Decoder {
+public:
+ virtual String to_utf8(const StringView&) = 0;
+};
+
+class UTF8Decoder final : public Decoder {
+public:
+ virtual String to_utf8(const StringView&) override;
+};
+
+class Latin1Decoder final : public Decoder {
+public:
+ virtual String to_utf8(const StringView&) override;
+};
+
+class Latin2Decoder final : public Decoder {
+public:
+ virtual String to_utf8(const StringView&) override;
+};
+
+Decoder* decoder_for(const String& encoding);
+String get_standardized_encoding(const String& encoding);
+bool is_standardized_encoding(const String& encoding);
+
+}
diff --git a/Userland/Libraries/LibThread/BackgroundAction.cpp b/Userland/Libraries/LibThread/BackgroundAction.cpp
new file mode 100644
index 0000000000..a2880dfb3f
--- /dev/null
+++ b/Userland/Libraries/LibThread/BackgroundAction.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Queue.h>
+#include <LibThread/BackgroundAction.h>
+#include <LibThread/Lock.h>
+#include <LibThread/Thread.h>
+
+static LibThread::Lockable<Queue<Function<void()>>>* s_all_actions;
+static LibThread::Thread* s_background_thread;
+
+static int background_thread_func()
+{
+ while (true) {
+ Function<void()> work_item;
+ {
+ LOCKER(s_all_actions->lock());
+
+ if (!s_all_actions->resource().is_empty())
+ work_item = s_all_actions->resource().dequeue();
+ }
+ if (work_item)
+ work_item();
+ else
+ sleep(1);
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+static void init()
+{
+ s_all_actions = new LibThread::Lockable<Queue<Function<void()>>>();
+ s_background_thread = &LibThread::Thread::construct(background_thread_func).leak_ref();
+ s_background_thread->set_name("Background thread");
+ s_background_thread->start();
+}
+
+LibThread::Lockable<Queue<Function<void()>>>& LibThread::BackgroundActionBase::all_actions()
+{
+ if (s_all_actions == nullptr)
+ init();
+ return *s_all_actions;
+}
+
+LibThread::Thread& LibThread::BackgroundActionBase::background_thread()
+{
+ if (s_background_thread == nullptr)
+ init();
+ return *s_background_thread;
+}
diff --git a/Userland/Libraries/LibThread/BackgroundAction.h b/Userland/Libraries/LibThread/BackgroundAction.h
new file mode 100644
index 0000000000..76de180cd7
--- /dev/null
+++ b/Userland/Libraries/LibThread/BackgroundAction.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/NonnullRefPtr.h>
+#include <AK/Optional.h>
+#include <AK/Queue.h>
+#include <LibCore/Event.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/Object.h>
+#include <LibThread/Lock.h>
+#include <LibThread/Thread.h>
+
+namespace LibThread {
+
+template<typename Result>
+class BackgroundAction;
+
+class BackgroundActionBase {
+ template<typename Result>
+ friend class BackgroundAction;
+
+private:
+ BackgroundActionBase() { }
+
+ static Lockable<Queue<Function<void()>>>& all_actions();
+ static Thread& background_thread();
+};
+
+template<typename Result>
+class BackgroundAction final : public Core::Object
+ , private BackgroundActionBase {
+ C_OBJECT(BackgroundAction);
+
+public:
+ static NonnullRefPtr<BackgroundAction<Result>> create(
+ Function<Result()> action,
+ Function<void(Result)> on_complete = nullptr)
+ {
+ return adopt(*new BackgroundAction(move(action), move(on_complete)));
+ }
+
+ virtual ~BackgroundAction() { }
+
+private:
+ BackgroundAction(Function<Result()> action, Function<void(Result)> on_complete)
+ : Core::Object(&background_thread())
+ , m_action(move(action))
+ , m_on_complete(move(on_complete))
+ {
+ LOCKER(all_actions().lock());
+
+ all_actions().resource().enqueue([this] {
+ m_result = m_action();
+ if (m_on_complete) {
+ Core::EventLoop::current().post_event(*this, make<Core::DeferredInvocationEvent>([this](auto&) {
+ m_on_complete(m_result.release_value());
+ this->remove_from_parent();
+ }));
+ Core::EventLoop::wake();
+ } else {
+ this->remove_from_parent();
+ }
+ });
+ }
+
+ Function<Result()> m_action;
+ Function<void(Result)> m_on_complete;
+ Optional<Result> m_result;
+};
+
+}
diff --git a/Userland/Libraries/LibThread/CMakeLists.txt b/Userland/Libraries/LibThread/CMakeLists.txt
new file mode 100644
index 0000000000..6bfce1c060
--- /dev/null
+++ b/Userland/Libraries/LibThread/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(SOURCES
+ BackgroundAction.cpp
+ Thread.cpp
+)
+
+serenity_lib(LibThread thread)
+target_link_libraries(LibThread LibC LibCore LibPthread)
diff --git a/Userland/Libraries/LibThread/Lock.h b/Userland/Libraries/LibThread/Lock.h
new file mode 100644
index 0000000000..7ab63162c7
--- /dev/null
+++ b/Userland/Libraries/LibThread/Lock.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#ifdef __serenity__
+
+# include <AK/Assertions.h>
+# include <AK/Atomic.h>
+# include <AK/Types.h>
+# include <unistd.h>
+
+namespace LibThread {
+
+class Lock {
+public:
+ Lock() { }
+ ~Lock() { }
+
+ void lock();
+ void unlock();
+
+private:
+ Atomic<pid_t> m_holder { 0 };
+ u32 m_level { 0 };
+};
+
+class Locker {
+public:
+ ALWAYS_INLINE explicit Locker(Lock& l)
+ : m_lock(l)
+ {
+ lock();
+ }
+ ALWAYS_INLINE ~Locker() { unlock(); }
+ ALWAYS_INLINE void unlock() { m_lock.unlock(); }
+ ALWAYS_INLINE void lock() { m_lock.lock(); }
+
+private:
+ Lock& m_lock;
+};
+
+ALWAYS_INLINE void Lock::lock()
+{
+ pid_t tid = gettid();
+ if (m_holder == tid) {
+ ++m_level;
+ return;
+ }
+ for (;;) {
+ int expected = 0;
+ if (m_holder.compare_exchange_strong(expected, tid, AK::memory_order_acq_rel)) {
+ m_level = 1;
+ return;
+ }
+ donate(expected);
+ }
+}
+
+inline void Lock::unlock()
+{
+ ASSERT(m_holder == gettid());
+ ASSERT(m_level);
+ if (m_level == 1)
+ m_holder.store(0, AK::memory_order_release);
+ else
+ --m_level;
+}
+
+# define LOCKER(lock) LibThread::Locker locker(lock)
+
+template<typename T>
+class Lockable {
+public:
+ Lockable() { }
+
+ template<typename... Args>
+ Lockable(Args&&... args)
+ : m_resource(forward(args)...)
+ {
+ }
+
+ Lockable(T&& resource)
+ : m_resource(move(resource))
+ {
+ }
+ Lock& lock() { return m_lock; }
+ T& resource() { return m_resource; }
+
+ T lock_and_copy()
+ {
+ LOCKER(m_lock);
+ return m_resource;
+ }
+
+private:
+ T m_resource;
+ Lock m_lock;
+};
+
+}
+
+#else
+
+namespace LibThread {
+
+class Lock {
+public:
+ Lock() { }
+ ~Lock() { }
+};
+
+}
+
+# define LOCKER(x)
+
+#endif
diff --git a/Userland/Libraries/LibThread/Thread.cpp b/Userland/Libraries/LibThread/Thread.cpp
new file mode 100644
index 0000000000..7d8fea4af4
--- /dev/null
+++ b/Userland/Libraries/LibThread/Thread.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibThread/Thread.h>
+#include <pthread.h>
+#include <string.h>
+#include <unistd.h>
+
+LibThread::Thread::Thread(Function<int()> action, StringView thread_name)
+ : Core::Object(nullptr)
+ , m_action(move(action))
+ , m_thread_name(thread_name.is_null() ? "" : thread_name)
+{
+ register_property("thread_name", [&] { return JsonValue { m_thread_name }; });
+ register_property("tid", [&] { return JsonValue { m_tid }; });
+}
+
+LibThread::Thread::~Thread()
+{
+ if (m_tid) {
+ dbgln("Destroying thread \"{}\"({}) while it is still running!", m_thread_name, m_tid);
+ [[maybe_unused]] auto res = join();
+ }
+}
+
+void LibThread::Thread::start()
+{
+ int rc = pthread_create(
+ &m_tid,
+ nullptr,
+ [](void* arg) -> void* {
+ Thread* self = static_cast<Thread*>(arg);
+ int exit_code = self->m_action();
+ self->m_tid = 0;
+ return (void*)exit_code;
+ },
+ static_cast<void*>(this));
+
+ ASSERT(rc == 0);
+ if (!m_thread_name.is_empty()) {
+ rc = pthread_setname_np(m_tid, m_thread_name.characters());
+ ASSERT(rc == 0);
+ }
+ dbgln("Started thread \"{}\", tid = {}", m_thread_name, m_tid);
+}
diff --git a/Userland/Libraries/LibThread/Thread.h b/Userland/Libraries/LibThread/Thread.h
new file mode 100644
index 0000000000..7fff078827
--- /dev/null
+++ b/Userland/Libraries/LibThread/Thread.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/DistinctNumeric.h>
+#include <AK/Function.h>
+#include <AK/Result.h>
+#include <AK/String.h>
+#include <LibCore/Object.h>
+#include <pthread.h>
+
+namespace LibThread {
+
+TYPEDEF_DISTINCT_ORDERED_ID(int, ThreadError);
+
+class Thread final : public Core::Object {
+ C_OBJECT(Thread);
+
+public:
+ virtual ~Thread();
+
+ void start();
+
+ template<typename T = void>
+ Result<T, ThreadError> join();
+
+ String thread_name() const { return m_thread_name; }
+ pthread_t tid() const { return m_tid; }
+
+private:
+ explicit Thread(Function<int()> action, StringView thread_name = nullptr);
+ Function<int()> m_action;
+ pthread_t m_tid { 0 };
+ String m_thread_name;
+};
+
+template<typename T>
+Result<T, ThreadError> Thread::join()
+{
+ void* thread_return = nullptr;
+ int rc = pthread_join(m_tid, &thread_return);
+ if (rc != 0) {
+ return ThreadError { rc };
+ }
+
+ m_tid = 0;
+ if constexpr (IsVoid<T>::value)
+ return {};
+ else
+ return { static_cast<T>(thread_return) };
+}
+
+}
diff --git a/Userland/Libraries/LibUnwind/UnwindBase.h b/Userland/Libraries/LibUnwind/UnwindBase.h
new file mode 100644
index 0000000000..980c1a0308
--- /dev/null
+++ b/Userland/Libraries/LibUnwind/UnwindBase.h
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2020, Andrew Kaster <andrewdkaster@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+// The following definitions and comments are largely pulled from the Itanium Exception handling ABI
+// Reference: https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.htm, version 1.22
+// This header defines the methods, types, and constants outlined in Level I. Base ABI
+// Also, from the Intel386 psABI version 1.1
+// Reference: https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI
+
+// FIXME: Configure gcc and libgcc with --with-system-libunwind :^)
+
+extern "C" {
+
+/// Return code used by _Unwind family of methods
+enum _Unwind_Reason_Code {
+ _URC_NO_REASON = 0, /// Forced unwind stop routine determined this isn't the correct frame
+ _URC_FOREIGN_EXCEPTION_CAUGHT = 1, /// This indicates that a different runtime caught this exception.
+ /// Nested foreign exceptions, or rethrowing a foreign exception,
+ /// result in undefined behaviour.
+ _URC_FATAL_PHASE2_ERROR = 2, /// The personality routine encountered an error during phase 2,
+ /// for instance a stack corruption (call std::terminate())
+ _URC_FATAL_PHASE1_ERROR = 3, /// The personality routine encountered an error during phase 1,
+ /// other than the specific error codes defined.
+ _URC_NORMAL_STOP = 4, /// Exception handled(?) Spec doesn't say...
+ _URC_END_OF_STACK = 5, /// Reached the top of the stack without a handler :(
+ _URC_HANDLER_FOUND = 6, /// Success! End phase 1 lookup, continue to phase 2
+ _URC_INSTALL_CONTEXT = 7, /// Personality routine wants the context created
+ _URC_CONTINUE_UNWIND = 8 /// Destructors etc called, keep walking up stack until we reach handler
+};
+
+// Forward declaration for cleanup method below
+struct _Unwind_Exception;
+
+/// Method that knows how to destroy a particualar exception. Called when handling a foreign exception
+typedef void (*_Unwind_Exception_Cleanup_Fn)(_Unwind_Reason_Code reason, struct _Unwind_Exception* exc);
+
+/// Exception object, language agnostic
+/// @note: _Unwind_Exception must be double word aligned
+/// @note: the private vars are different sizes on 64-bit...
+struct _Unwind_Exception {
+ uint64_t exception_class; ///< For identification. For c++, the four low bytes are "C++\0"
+ _Unwind_Exception_Cleanup_Fn exception_cleanup; ///< Used if caught by a different runtime
+ uint32_t private_1; ///< Private for system implementation
+ uint32_t private_2; ///< Private for system implementation
+ // Note that for C++, the language specific exception object will be located directly following this header
+};
+
+/// Opaque typedef to system unwinder implementation class
+struct _Unwind_Context;
+
+/// Raise an exception, nominally noreturn (i.e. if we return, we've got trouble)
+/// @param exception_object Exception that was allocated by language specific runtime
+/// It must have its exception_class and cleanup fields set
+/// @returns One of the following reason codes:
+/// * _URC_END_OF_STACK : No handler found during phase 1 lookup. (uncaught_exception())
+/// * _URC_FATAL_PHASE1_ERROR: Stack corruption during phase 1 lookup. (terminate())
+///
+/// @note If the unwinder can't do phase 2 cleanup, it should return _URC_FATAL_PHASE2_ERROR
+/// @note The caller of _Unwind_RaiseException can make no assumptions about the state of its stack or registers.
+///
+_Unwind_Reason_Code _Unwind_RaiseException(struct _Unwind_Exception* exception_object);
+
+/// Resume propagation of an existing exception e.g. after executing cleanup code in a partially unwound stack.
+/// A call to this routine is inserted at the end of a landing pad that performed cleanup, but did not resume normal
+/// execution. It causes unwinding to proceed further.
+///
+/// @note _Unwind_Resume should not be used to implement rethrowing. To the unwinding runtime,/
+/// the catch code that rethrows was a handler, and the previous unwinding session was terminated before entering.
+/// Rethrowing is implemented by calling _Unwind_RaiseException again with the same exception object.
+/// This means that re-throwing an exception causes the exception handing process to begin again at phase 1.
+/// @note This is the only routine in the unwind library which is expected to be called directly by generated code:
+/// it will be called at the end of a landing pad in a "landing-pad" model.
+///
+void _Unwind_Resume(struct _Unwind_Exception* exception_object);
+
+/// Deletes the given exception object.
+/// If a given runtime resumes normal execution after catching a foreign exception, it will not know how to delete
+/// that exception. Such an exception will be deleted by calling _Unwind_DeleteException.
+/// This is a convenience function that calls the function pointed to by the exception_cleanup field of the exception
+/// header.
+///
+void _Unwind_DeleteException(struct _Unwind_Exception* exception_object);
+
+//-------------
+// Context management routines
+//
+// Used for communicating information about the unwind context between the unwind library, personality routine,
+// and compiler-generated landing pad.
+// They include routines to read or set the context record images of registers in the stack frame corresponding
+// to a given unwind context, and to identify the location of the current unwind descriptors and unwind frame.
+//-------------
+
+/// Get the 32-bit value of the given general register
+/// The register is identified by its index in the DWARF register mapping
+///
+/// During the two phases of unwinding, no registers have a guaranteed value.
+///
+uint32_t _Unwind_GetGR(struct _Unwind_Context* context, int index);
+
+/// Set the 32-bit value of the given register
+/// The register identified by its index as for _Unwind_GetGR.
+///
+/// The behaviour is guaranteed only if the function is called during phase 2 of unwinding,
+/// and applied to an unwind context representing a handler frame, for which the personality routine will return
+/// _URC_INSTALL_CONTEXT. In that case, only registers %eax and %edx should be used.
+/// These scratch registers are reserved for passing arguments between the personality routine and the landing pads.
+///
+void _Unwind_SetGR(struct _Unwind_Context* context, int index, uint32_t new_value);
+
+/// Get the 32-bit value of the instruction pointer (IP).
+///
+/// During unwinding, the value is guaranteed to be the address of the instruction immediately following the call site
+/// in the function identified by the unwind context. This value may be outside of the procedure fragment for
+/// a function call that is known to not return (such as _Unwind_Resume).
+///
+uint32_t _Unwind_GetIP(struct _Unwind_Context* context);
+
+/// Set the value of the instruction pointer (IP) for the routine identified by the unwind context.
+///
+/// The behaviour is guaranteed only when this function is called for an unwind context representing a handler frame,
+/// for which the personality routine will return _URC_INSTALL_CONTEXT.
+/// In this case, control will be transferred to the given address, which should be the address of a landing pad.
+///
+void _Unwind_SetIP(struct _Unwind_Context* context, uint32_t new_value);
+
+/// Get language specific data area for the current stack frame.
+/// Useful for retrieving information that was cached after finding the personality routine.
+///
+uint32_t _Unwind_GetLanguageSpecificData(struct _Unwind_Context* context);
+
+/// Get the address of the beginning of the procedure or code fragment described by the current unwind descriptor
+/// block.
+///
+/// This information is required to access any data stored relative to the beginning
+/// of the procedure fragment. For instance, a call site table might be stored relative
+/// to the beginning of the procedure fragment that contains the calls.
+/// During unwinding, the function returns the start of the procedure fragment containing the call site in the current
+/// stack frame.
+///
+uint32_t _Unwind_GetRegionStart(struct _Unwind_Context* context);
+
+/// This function returns the 32-bit Canonical Frame Address which is defined as
+/// the value of %esp at the call site in the previous frame. This value is guaranteed
+/// to be correct any time the context has been passed to a personality routine or a
+/// stop function.
+uint32_t _Unwind_GetCFA(struct _Unwind_Context* context);
+
+/// Personality routine
+/// The personality routine is the function in the C++ (or other language) runtime library which serves
+/// as an interface between the system unwind library and
+/// language-specific exception handling semantics. It is specific to the code fragment
+/// described by an unwind info block, and it is always referenced via the pointer in
+/// the unwind info block, and hence it has no psABI-specified name.
+/// @note Both GCC and Clang will generate .cfi_personality directives for __gxx_personality_v0
+/// Unless we tell the compiler we're using setjump/longjump exceptions (ew, how 90's)
+///
+/// @param version Better be 1
+/// @param actions See below
+/// @param exceptionClass [ vendor ][ language ] e.g. "CLNGC++\0" "GNUCC++\0"
+/// @param exceptionObject language-specific exception
+/// @param context Opaque handle for personality routine + unwinder lib state
+///
+typedef _Unwind_Reason_Code (*__personality_routine)(int version,
+ _Unwind_Action actions,
+ uint64_t exceptionClass,
+ struct _Unwind_Exception* exceptionObject,
+ struct _Unwind_Context* context);
+
+/// Action parameter type for personality routine during unwind. Will be a bit-wise OR of the __UnwindActions below
+typedef int _Unwind_Action;
+
+/// Which action(s) the personality routine should perform duing unwind
+enum __UnwindActions {
+ _UA_SEARCH_PHASE = 1, /// The personality routine should check if the current frame contains a handler,
+ /// and if so return _URC_HANDLER_FOUND, or otherwise return _URC_CONTINUE_UNWIND.
+ /// _UA_SEARCH_PHASE cannot be set at the same time as _UA_CLEANUP_PHASE.
+ _UA_CLEANUP_PHASE = 2, /// The personality routine should perform cleanup for the current frame.
+ /// The personality routine can perform this cleanup itself, by calling nested
+ /// procedures, and return _URC_CONTINUE_UNWIND. Alternatively, it can setup the
+ /// registers (including the IP) for transferring control to a "landing pad", and
+ /// return _URC_INSTALL_CONTEXT.
+ _UA_HANDLER_FRAME = 4, /// During phase 2, indicates to the personality routine that the current frame is the one
+ /// which was flagged as the handler frame during phase 1. The personality routine
+ /// is not allowed to change its mind between phase 1 and phase 2, i.e. it must handle
+ /// the exception in this frame in phase 2.
+ _UA_FORCE_UNWIND = 8 /// During phase 2, indicates that no language is allowed to "catch" the exception.
+ /// This flag is set while unwinding the stack for longjmp or during thread
+ /// cancellation. User-defined code in a catch clause may still be executed, but the
+ /// catch clause must resume unwinding with a call to _Unwind_Resume when finished.
+};
+
+/// Function that knows how to identify a stack frame to stop unwinding for forced unwinding
+/// This is different from the usual personality routine query as it can only say yes or no to each frame
+typedef _Unwind_Reason_Code (*_Unwind_Stop_Fn)(int version,
+ _Unwind_Action actions,
+ uint64_t exceptionClass,
+ struct _Unwind_Exception* exceptionObject,
+ struct _Unwind_Context* context,
+ void* stop_parameter);
+
+/// Raise an exception for forced unwinding, passing along the given exception object, which should have its
+/// exception_class and exception_cleanup fields set. The exception object has been allocated by the language-specific
+/// runtime, and has a language-specific format, except that it must contain an _Unwind_Exception struct.
+///
+/// Forced unwinding is a single-phase process (phase 2 of the normal exceptionhandling process). The stop and
+/// stop_parameter parameters control the termination of the unwind process, instead of the usual personality routine
+/// query. The stop function parameter is called for each unwind frame, with the parameters described for the usual
+/// personality routine below, plus an additional stop_parameter.
+///
+/// When the stop function identifies the destination frame, it transfers control (according to its own, unspecified,
+/// conventions) to the user code as appropriate without returning, normally after calling _Unwind_DeleteException.
+/// If not, it should return an _Unwind_Reason_Code value as follows:
+/// * _URC_NO_REASON: This is not the destination frame. The unwind runtime will
+/// call the frame’s personality routine with the _UA_FORCE_UNWIND and
+// _UA_CLEANUP_PHASE flags set in actions, and then unwind to the next
+/// frame and call the stop function again.
+/// * _URC_END_OF_STACK: In order to allow _Unwind_ForcedUnwind to perform special processing
+/// when it reaches the end of the stack, the unwind runtime will call it after the last frame is rejected,
+/// with a NULL stack pointer in the context, and the stop function must catch this condition
+/// (i.e. by noticing the NULL stack pointer). It may return this reason code if it cannot handle end-of-stack.
+/// * _URC_FATAL_PHASE2_ERROR: The stop function may return this code for other fatal conditions, e.g. stack corruption.
+/// @note: The main reason for this method is to support setjump/longjump exceptions
+///
+_Unwind_Reason_Code _Unwind_ForcedUnwind(struct _Unwind_Exception* exception_object, _Unwind_Stop_Fn stop, void* stop_parameter);
+
+} // extern "C"
diff --git a/Userland/Libraries/LibVT/CMakeLists.txt b/Userland/Libraries/LibVT/CMakeLists.txt
new file mode 100644
index 0000000000..eeca322d28
--- /dev/null
+++ b/Userland/Libraries/LibVT/CMakeLists.txt
@@ -0,0 +1,8 @@
+set(SOURCES
+ Line.cpp
+ Terminal.cpp
+ TerminalWidget.cpp
+)
+
+serenity_lib(LibVT vt)
+target_link_libraries(LibVT LibC LibCore LibGUI LibGfx LibDesktop)
diff --git a/Userland/Libraries/LibVT/Line.cpp b/Userland/Libraries/LibVT/Line.cpp
new file mode 100644
index 0000000000..56f776e9cb
--- /dev/null
+++ b/Userland/Libraries/LibVT/Line.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibVT/Line.h>
+#include <string.h>
+
+namespace VT {
+
+Line::Line(u16 length)
+{
+ set_length(length);
+}
+
+Line::~Line()
+{
+ if (m_utf32)
+ delete[] m_code_points.as_u32;
+ else
+ delete[] m_code_points.as_u8;
+ delete[] m_attributes;
+}
+
+template<typename CodepointType>
+static CodepointType* create_new_code_point_array(size_t new_length, const CodepointType* old_code_points, size_t old_length)
+{
+ auto* new_code_points = new CodepointType[new_length];
+ for (size_t i = 0; i < new_length; ++i)
+ new_code_points[i] = ' ';
+ if (old_code_points) {
+ for (size_t i = 0; i < min(old_length, new_length); ++i) {
+ new_code_points[i] = old_code_points[i];
+ }
+ }
+ delete[] old_code_points;
+ return new_code_points;
+}
+
+void Line::set_length(u16 new_length)
+{
+ if (m_length == new_length)
+ return;
+
+ if (m_utf32)
+ m_code_points.as_u32 = create_new_code_point_array<u32>(new_length, m_code_points.as_u32, m_length);
+ else
+ m_code_points.as_u8 = create_new_code_point_array<u8>(new_length, m_code_points.as_u8, m_length);
+
+ auto* new_attributes = new Attribute[new_length];
+ if (m_attributes) {
+ for (size_t i = 0; i < min(m_length, new_length); ++i)
+ new_attributes[i] = m_attributes[i];
+ }
+ delete[] m_attributes;
+ m_attributes = new_attributes;
+ m_length = new_length;
+}
+
+void Line::clear(Attribute attribute)
+{
+ if (m_dirty) {
+ for (u16 i = 0; i < m_length; ++i) {
+ set_code_point(i, ' ');
+ m_attributes[i] = attribute;
+ }
+ return;
+ }
+ for (unsigned i = 0; i < m_length; ++i) {
+ if (code_point(i) != ' ')
+ m_dirty = true;
+ set_code_point(i, ' ');
+ }
+ for (unsigned i = 0; i < m_length; ++i) {
+ if (m_attributes[i] != attribute)
+ m_dirty = true;
+ m_attributes[i] = attribute;
+ }
+}
+
+bool Line::has_only_one_background_color() const
+{
+ if (!m_length)
+ return true;
+ // FIXME: Cache this result?
+ auto color = m_attributes[0].effective_background_color();
+ for (size_t i = 1; i < m_length; ++i) {
+ if (m_attributes[i].effective_background_color() != color)
+ return false;
+ }
+ return true;
+}
+
+void Line::convert_to_utf32()
+{
+ ASSERT(!m_utf32);
+ auto* new_code_points = new u32[m_length];
+ for (size_t i = 0; i < m_length; ++i) {
+ new_code_points[i] = m_code_points.as_u8[i];
+ }
+ delete m_code_points.as_u8;
+ m_code_points.as_u32 = new_code_points;
+ m_utf32 = true;
+}
+
+}
diff --git a/Userland/Libraries/LibVT/Line.h b/Userland/Libraries/LibVT/Line.h
new file mode 100644
index 0000000000..5b3bcbd5c7
--- /dev/null
+++ b/Userland/Libraries/LibVT/Line.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Noncopyable.h>
+#include <AK/String.h>
+#include <LibVT/XtermColors.h>
+
+namespace VT {
+
+struct Attribute {
+ Attribute() { reset(); }
+
+ static const u32 default_foreground_color = xterm_colors[7];
+ static const u32 default_background_color = xterm_colors[0];
+
+ void reset()
+ {
+ foreground_color = default_foreground_color;
+ background_color = default_background_color;
+ flags = Flags::NoAttributes;
+ }
+ u32 foreground_color;
+ u32 background_color;
+
+ u32 effective_background_color() const { return flags & Negative ? foreground_color : background_color; }
+ u32 effective_foreground_color() const { return flags & Negative ? background_color : foreground_color; }
+
+ String href;
+ String href_id;
+
+ enum Flags : u8 {
+ NoAttributes = 0x00,
+ Bold = 0x01,
+ Italic = 0x02,
+ Underline = 0x04,
+ Negative = 0x08,
+ Blink = 0x10,
+ Touched = 0x20,
+ };
+
+ bool is_untouched() const { return !(flags & Touched); }
+
+ // TODO: it would be really nice if we had a helper for enums that
+ // exposed bit ops for class enums...
+ u8 flags = Flags::NoAttributes;
+
+ bool operator==(const Attribute& other) const
+ {
+ return foreground_color == other.foreground_color && background_color == other.background_color && flags == other.flags;
+ }
+ bool operator!=(const Attribute& other) const
+ {
+ return !(*this == other);
+ }
+};
+
+class Line {
+ AK_MAKE_NONCOPYABLE(Line);
+ AK_MAKE_NONMOVABLE(Line);
+
+public:
+ explicit Line(u16 columns);
+ ~Line();
+
+ void clear(Attribute);
+ bool has_only_one_background_color() const;
+ void set_length(u16);
+
+ u16 length() const { return m_length; }
+
+ u32 code_point(size_t index) const
+ {
+ if (m_utf32)
+ return m_code_points.as_u32[index];
+ return m_code_points.as_u8[index];
+ }
+
+ void set_code_point(size_t index, u32 code_point)
+ {
+ if (!m_utf32 && code_point & 0xffffff80u)
+ convert_to_utf32();
+
+ if (m_utf32)
+ m_code_points.as_u32[index] = code_point;
+ else
+ m_code_points.as_u8[index] = code_point;
+ }
+
+ bool is_dirty() const { return m_dirty; }
+ void set_dirty(bool b) { m_dirty = b; }
+
+ const Attribute* attributes() const { return m_attributes; }
+ Attribute* attributes() { return m_attributes; }
+
+ void convert_to_utf32();
+
+ bool is_utf32() const { return m_utf32; }
+
+private:
+ union {
+ u8* as_u8;
+ u32* as_u32;
+ } m_code_points { nullptr };
+ Attribute* m_attributes { nullptr };
+ bool m_dirty { false };
+ bool m_utf32 { false };
+ u16 m_length { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibVT/Position.h b/Userland/Libraries/LibVT/Position.h
new file mode 100644
index 0000000000..d042ac68e3
--- /dev/null
+++ b/Userland/Libraries/LibVT/Position.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace VT {
+
+class Position {
+public:
+ Position() { }
+ Position(int row, int column)
+ : m_row(row)
+ , m_column(column)
+ {
+ }
+
+ bool is_valid() const { return m_row >= 0 && m_column >= 0; }
+ int row() const { return m_row; }
+ int column() const { return m_column; }
+
+ bool operator<(const Position& other) const
+ {
+ return m_row < other.m_row || (m_row == other.m_row && m_column < other.m_column);
+ }
+
+ bool operator<=(const Position& other) const
+ {
+ return *this < other || *this == other;
+ }
+
+ bool operator>=(const Position& other) const
+ {
+ return !(*this < other);
+ }
+
+ bool operator==(const Position& other) const
+ {
+ return m_row == other.m_row && m_column == other.m_column;
+ }
+
+ bool operator!=(const Position& other) const
+ {
+ return !(*this == other);
+ }
+
+private:
+ int m_row { -1 };
+ int m_column { -1 };
+};
+
+}
diff --git a/Userland/Libraries/LibVT/Range.h b/Userland/Libraries/LibVT/Range.h
new file mode 100644
index 0000000000..51d4efcd8f
--- /dev/null
+++ b/Userland/Libraries/LibVT/Range.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibVT/Position.h>
+
+namespace VT {
+
+class Range {
+public:
+ Range() { }
+ Range(const VT::Position& start, const VT::Position& end)
+ : m_start(start)
+ , m_end(end)
+ {
+ }
+
+ bool is_valid() const { return m_start.is_valid() && m_end.is_valid(); }
+ void clear()
+ {
+ m_start = {};
+ m_end = {};
+ }
+
+ VT::Position& start() { return m_start; }
+ VT::Position& end() { return m_end; }
+ const VT::Position& start() const { return m_start; }
+ const VT::Position& end() const { return m_end; }
+
+ Range normalized() const { return Range(normalized_start(), normalized_end()); }
+
+ void set_start(const VT::Position& position) { m_start = position; }
+ void set_end(const VT::Position& position) { m_end = position; }
+
+ void set(const VT::Position& start, const VT::Position& end)
+ {
+ m_start = start;
+ m_end = end;
+ }
+
+ bool operator==(const Range& other) const
+ {
+ return m_start == other.m_start && m_end == other.m_end;
+ }
+
+ bool contains(const VT::Position& position) const
+ {
+ if (!(position.row() > m_start.row() || (position.row() == m_start.row() && position.column() >= m_start.column())))
+ return false;
+ if (!(position.row() < m_end.row() || (position.row() == m_end.row() && position.column() <= m_end.column())))
+ return false;
+ return true;
+ }
+
+private:
+ VT::Position normalized_start() const { return m_start < m_end ? m_start : m_end; }
+ VT::Position normalized_end() const { return m_start < m_end ? m_end : m_start; }
+
+ VT::Position m_start;
+ VT::Position m_end;
+};
+
+};
diff --git a/Userland/Libraries/LibVT/Terminal.cpp b/Userland/Libraries/LibVT/Terminal.cpp
new file mode 100644
index 0000000000..ce0ec61d6a
--- /dev/null
+++ b/Userland/Libraries/LibVT/Terminal.cpp
@@ -0,0 +1,1235 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <AK/StringView.h>
+#include <LibVT/Terminal.h>
+#include <string.h>
+
+//#define TERMINAL_DEBUG
+
+namespace VT {
+
+Terminal::Terminal(TerminalClient& client)
+ : m_client(client)
+{
+}
+
+Terminal::~Terminal()
+{
+}
+
+void Terminal::clear()
+{
+ for (size_t i = 0; i < rows(); ++i)
+ m_lines[i].clear(m_current_attribute);
+ set_cursor(0, 0);
+}
+
+void Terminal::clear_including_history()
+{
+ m_history.clear();
+ m_history_start = 0;
+
+ clear();
+
+ m_client.terminal_history_changed();
+}
+
+inline bool is_valid_parameter_character(u8 ch)
+{
+ return ch >= 0x30 && ch <= 0x3f;
+}
+
+inline bool is_valid_intermediate_character(u8 ch)
+{
+ return ch >= 0x20 && ch <= 0x2f;
+}
+
+inline bool is_valid_final_character(u8 ch)
+{
+ return ch >= 0x40 && ch <= 0x7e;
+}
+
+void Terminal::alter_mode(bool should_set, bool question_param, const ParamVector& params)
+{
+ int mode = 2;
+ if (params.size() > 0) {
+ mode = params[0];
+ }
+ if (!question_param) {
+ switch (mode) {
+ // FIXME: implement *something* for this
+ default:
+ unimplemented_escape();
+ break;
+ }
+ } else {
+ switch (mode) {
+ case 25:
+ // Hide cursor command, but doesn't need to be run (for now, because
+ // we don't do inverse control codes anyways)
+ if (should_set)
+ dbgprintf("Terminal: Hide Cursor escapecode received. Not needed: ignored.\n");
+ else
+ dbgprintf("Terminal: Show Cursor escapecode received. Not needed: ignored.\n");
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void Terminal::RM(bool question_param, const ParamVector& params)
+{
+ // RM – Reset Mode
+ alter_mode(true, question_param, params);
+}
+
+void Terminal::SM(bool question_param, const ParamVector& params)
+{
+ // SM – Set Mode
+ alter_mode(false, question_param, params);
+}
+
+void Terminal::SGR(const ParamVector& params)
+{
+ // SGR – Select Graphic Rendition
+ if (params.is_empty()) {
+ m_current_attribute.reset();
+ return;
+ }
+ if (params.size() >= 3) {
+ bool should_set = true;
+ auto kind = params[1];
+ u32 color = 0;
+ switch (kind) {
+ case 5: // 8-bit
+ color = xterm_colors[params[2]];
+ break;
+ case 2: // 24-bit
+ for (size_t i = 0; i < 3; ++i) {
+ u8 component = 0;
+ if (params.size() - 2 > i) {
+ component = params[i + 2];
+ }
+ color <<= 8;
+ color |= component;
+ }
+ break;
+ default:
+ should_set = false;
+ break;
+ }
+
+ if (should_set) {
+ if (params[0] == 38) {
+ m_current_attribute.foreground_color = color;
+ return;
+ } else if (params[0] == 48) {
+ m_current_attribute.background_color = color;
+ return;
+ }
+ }
+ }
+ for (auto param : params) {
+ switch (param) {
+ case 0:
+ // Reset
+ m_current_attribute.reset();
+ break;
+ case 1:
+ m_current_attribute.flags |= Attribute::Bold;
+ break;
+ case 3:
+ m_current_attribute.flags |= Attribute::Italic;
+ break;
+ case 4:
+ m_current_attribute.flags |= Attribute::Underline;
+ break;
+ case 5:
+ m_current_attribute.flags |= Attribute::Blink;
+ break;
+ case 7:
+ m_current_attribute.flags |= Attribute::Negative;
+ break;
+ case 22:
+ m_current_attribute.flags &= ~Attribute::Bold;
+ break;
+ case 23:
+ m_current_attribute.flags &= ~Attribute::Italic;
+ break;
+ case 24:
+ m_current_attribute.flags &= ~Attribute::Underline;
+ break;
+ case 25:
+ m_current_attribute.flags &= ~Attribute::Blink;
+ break;
+ case 27:
+ m_current_attribute.flags &= ~Attribute::Negative;
+ break;
+ case 30:
+ case 31:
+ case 32:
+ case 33:
+ case 34:
+ case 35:
+ case 36:
+ case 37:
+ // Foreground color
+ if (m_current_attribute.flags & Attribute::Bold)
+ param += 8;
+ m_current_attribute.foreground_color = xterm_colors[param - 30];
+ break;
+ case 39:
+ // reset foreground
+ m_current_attribute.foreground_color = Attribute::default_foreground_color;
+ break;
+ case 40:
+ case 41:
+ case 42:
+ case 43:
+ case 44:
+ case 45:
+ case 46:
+ case 47:
+ // Background color
+ if (m_current_attribute.flags & Attribute::Bold)
+ param += 8;
+ m_current_attribute.background_color = xterm_colors[param - 40];
+ break;
+ case 49:
+ // reset background
+ m_current_attribute.background_color = Attribute::default_background_color;
+ break;
+ default:
+ dbgprintf("FIXME: SGR: p: %u\n", param);
+ }
+ }
+}
+
+void Terminal::escape$s(const ParamVector&)
+{
+ m_saved_cursor_row = m_cursor_row;
+ m_saved_cursor_column = m_cursor_column;
+}
+
+void Terminal::escape$u(const ParamVector&)
+{
+ set_cursor(m_saved_cursor_row, m_saved_cursor_column);
+}
+
+void Terminal::escape$t(const ParamVector& params)
+{
+ if (params.size() < 1)
+ return;
+ dbgprintf("FIXME: escape$t: Ps: %u (param count: %zu)\n", params[0], params.size());
+}
+
+void Terminal::DECSTBM(const ParamVector& params)
+{
+ // DECSTBM – Set Top and Bottom Margins ("Scrolling Region")
+ unsigned top = 1;
+ unsigned bottom = m_rows;
+ if (params.size() >= 1)
+ top = params[0];
+ if (params.size() >= 2)
+ bottom = params[1];
+ if ((bottom - top) < 2 || bottom > m_rows) {
+ dbgprintf("Error: DECSTBM: scrolling region invalid: %u-%u\n", top, bottom);
+ return;
+ }
+ m_scroll_region_top = top - 1;
+ m_scroll_region_bottom = bottom - 1;
+ set_cursor(0, 0);
+}
+
+void Terminal::CUP(const ParamVector& params)
+{
+ // CUP – Cursor Position
+ unsigned row = 1;
+ unsigned col = 1;
+ if (params.size() >= 1)
+ row = params[0];
+ if (params.size() >= 2)
+ col = params[1];
+ set_cursor(row - 1, col - 1);
+}
+
+void Terminal::HVP(const ParamVector& params)
+{
+ // HVP – Horizontal and Vertical Position
+ unsigned row = 1;
+ unsigned col = 1;
+ if (params.size() >= 1)
+ row = params[0];
+ if (params.size() >= 2)
+ col = params[1];
+ set_cursor(row - 1, col - 1);
+}
+
+void Terminal::CUU(const ParamVector& params)
+{
+ // CUU – Cursor Up
+ int num = 1;
+ if (params.size() >= 1)
+ num = params[0];
+ if (num == 0)
+ num = 1;
+ int new_row = (int)m_cursor_row - num;
+ if (new_row < 0)
+ new_row = 0;
+ set_cursor(new_row, m_cursor_column);
+}
+
+void Terminal::CUD(const ParamVector& params)
+{
+ // CUD – Cursor Down
+ int num = 1;
+ if (params.size() >= 1)
+ num = params[0];
+ if (num == 0)
+ num = 1;
+ int new_row = (int)m_cursor_row + num;
+ if (new_row >= m_rows)
+ new_row = m_rows - 1;
+ set_cursor(new_row, m_cursor_column);
+}
+
+void Terminal::CUF(const ParamVector& params)
+{
+ // CUF – Cursor Forward
+ int num = 1;
+ if (params.size() >= 1)
+ num = params[0];
+ if (num == 0)
+ num = 1;
+ int new_column = (int)m_cursor_column + num;
+ if (new_column >= m_columns)
+ new_column = m_columns - 1;
+ set_cursor(m_cursor_row, new_column);
+}
+
+void Terminal::CUB(const ParamVector& params)
+{
+ // CUB – Cursor Backward
+ int num = 1;
+ if (params.size() >= 1)
+ num = params[0];
+ if (num == 0)
+ num = 1;
+ int new_column = (int)m_cursor_column - num;
+ if (new_column < 0)
+ new_column = 0;
+ set_cursor(m_cursor_row, new_column);
+}
+
+void Terminal::escape$G(const ParamVector& params)
+{
+ int new_column = 1;
+ if (params.size() >= 1)
+ new_column = params[0] - 1;
+ if (new_column < 0)
+ new_column = 0;
+ set_cursor(m_cursor_row, new_column);
+}
+
+void Terminal::escape$b(const ParamVector& params)
+{
+ if (params.size() < 1)
+ return;
+
+ for (unsigned i = 0; i < params[0]; ++i)
+ put_character_at(m_cursor_row, m_cursor_column++, m_last_code_point);
+}
+
+void Terminal::escape$d(const ParamVector& params)
+{
+ int new_row = 1;
+ if (params.size() >= 1)
+ new_row = params[0] - 1;
+ if (new_row < 0)
+ new_row = 0;
+ set_cursor(new_row, m_cursor_column);
+}
+
+void Terminal::escape$X(const ParamVector& params)
+{
+ // Erase characters (without moving cursor)
+ int num = 1;
+ if (params.size() >= 1)
+ num = params[0];
+ if (num == 0)
+ num = 1;
+ // Clear from cursor to end of line.
+ for (int i = m_cursor_column; i < num; ++i) {
+ put_character_at(m_cursor_row, i, ' ');
+ }
+}
+
+void Terminal::EL(const ParamVector& params)
+{
+ int mode = 0;
+ if (params.size() >= 1)
+ mode = params[0];
+ switch (mode) {
+ case 0:
+ // Clear from cursor to end of line.
+ for (int i = m_cursor_column; i < m_columns; ++i) {
+ put_character_at(m_cursor_row, i, ' ');
+ }
+ break;
+ case 1:
+ // Clear from cursor to beginning of line.
+ for (int i = 0; i <= m_cursor_column; ++i) {
+ put_character_at(m_cursor_row, i, ' ');
+ }
+ break;
+ case 2:
+ // Clear the complete line
+ for (int i = 0; i < m_columns; ++i) {
+ put_character_at(m_cursor_row, i, ' ');
+ }
+ break;
+ default:
+ unimplemented_escape();
+ break;
+ }
+}
+
+void Terminal::ED(const ParamVector& params)
+{
+ // ED - Erase in Display
+ int mode = 0;
+ if (params.size() >= 1)
+ mode = params[0];
+ switch (mode) {
+ case 0:
+ // Clear from cursor to end of screen.
+ for (int i = m_cursor_column; i < m_columns; ++i)
+ put_character_at(m_cursor_row, i, ' ');
+ for (int row = m_cursor_row + 1; row < m_rows; ++row) {
+ for (int column = 0; column < m_columns; ++column) {
+ put_character_at(row, column, ' ');
+ }
+ }
+ break;
+ case 1:
+ // Clear from cursor to beginning of screen.
+ for (int i = m_cursor_column; i >= 0; --i)
+ put_character_at(m_cursor_row, i, ' ');
+ for (int row = m_cursor_row - 1; row >= 0; --row) {
+ for (int column = 0; column < m_columns; ++column) {
+ put_character_at(row, column, ' ');
+ }
+ }
+ break;
+ case 2:
+ clear();
+ break;
+ case 3:
+ // FIXME: <esc>[3J should also clear the scrollback buffer.
+ clear();
+ break;
+ default:
+ unimplemented_escape();
+ break;
+ }
+}
+
+void Terminal::escape$S(const ParamVector& params)
+{
+ int count = 1;
+ if (params.size() >= 1)
+ count = params[0];
+
+ for (u16 i = 0; i < count; i++)
+ scroll_up();
+}
+
+void Terminal::escape$T(const ParamVector& params)
+{
+ int count = 1;
+ if (params.size() >= 1)
+ count = params[0];
+
+ for (u16 i = 0; i < count; i++)
+ scroll_down();
+}
+
+void Terminal::escape$L(const ParamVector& params)
+{
+ int count = 1;
+ if (params.size() >= 1)
+ count = params[0];
+ invalidate_cursor();
+ for (; count > 0; --count) {
+ m_lines.insert(m_cursor_row + m_scroll_region_top, make<Line>(m_columns));
+ if (m_scroll_region_bottom + 1 < m_lines.size())
+ m_lines.remove(m_scroll_region_bottom + 1);
+ else
+ m_lines.remove(m_lines.size() - 1);
+ }
+
+ m_need_full_flush = true;
+}
+
+void Terminal::DA(const ParamVector&)
+{
+ // DA - Device Attributes
+ emit_string("\033[?1;0c");
+}
+
+void Terminal::escape$M(const ParamVector& params)
+{
+ int count = 1;
+ if (params.size() >= 1)
+ count = params[0];
+
+ if (count == 1 && m_cursor_row == 0) {
+ scroll_up();
+ return;
+ }
+
+ int max_count = m_rows - (m_scroll_region_top + m_cursor_row);
+ count = min(count, max_count);
+
+ for (int c = count; c > 0; --c) {
+ m_lines.remove(m_cursor_row + m_scroll_region_top);
+ if (m_scroll_region_bottom < m_lines.size())
+ m_lines.insert(m_scroll_region_bottom, make<Line>(m_columns));
+ else
+ m_lines.append(make<Line>(m_columns));
+ }
+}
+
+void Terminal::escape$P(const ParamVector& params)
+{
+ int num = 1;
+ if (params.size() >= 1)
+ num = params[0];
+
+ if (num == 0)
+ num = 1;
+
+ auto& line = m_lines[m_cursor_row];
+
+ // Move n characters of line to the left
+ for (int i = m_cursor_column; i < line.length() - num; i++)
+ line.set_code_point(i, line.code_point(i + num));
+
+ // Fill remainder of line with blanks
+ for (int i = line.length() - num; i < line.length(); i++)
+ line.set_code_point(i, ' ');
+
+ line.set_dirty(true);
+}
+
+void Terminal::execute_xterm_command()
+{
+ ParamVector numeric_params;
+ auto param_string = String::copy(m_xterm_parameters);
+ auto params = param_string.split(';', true);
+ m_xterm_parameters.clear_with_capacity();
+ for (auto& parampart : params)
+ numeric_params.append(parampart.to_uint().value_or(0));
+
+ while (params.size() < 3) {
+ params.append(String::empty());
+ numeric_params.append(0);
+ }
+
+ m_final = '@';
+
+ if (numeric_params.is_empty()) {
+ dbgln("Empty Xterm params?");
+ return;
+ }
+
+ switch (numeric_params[0]) {
+ case 0:
+ case 1:
+ case 2:
+ m_client.set_window_title(params[1]);
+ break;
+ case 8:
+ if (params[2].is_empty()) {
+ m_current_attribute.href = String();
+ m_current_attribute.href_id = String();
+ } else {
+ m_current_attribute.href = params[2];
+ // FIXME: Respect the provided ID
+ m_current_attribute.href_id = String::format("%u", m_next_href_id++);
+ }
+ break;
+ case 9:
+ m_client.set_window_progress(numeric_params[1], numeric_params[2]);
+ break;
+ default:
+ unimplemented_xterm_escape();
+ break;
+ }
+}
+
+void Terminal::execute_escape_sequence(u8 final)
+{
+ bool question_param = false;
+ m_final = final;
+ ParamVector params;
+
+ if (m_parameters.size() > 0 && m_parameters[0] == '?') {
+ question_param = true;
+ m_parameters.remove(0);
+ }
+ auto paramparts = String::copy(m_parameters).split(';');
+ for (auto& parampart : paramparts) {
+ auto value = parampart.to_uint();
+ if (!value.has_value()) {
+ // FIXME: Should we do something else?
+ m_parameters.clear_with_capacity();
+ m_intermediates.clear_with_capacity();
+ return;
+ }
+ params.append(value.value());
+ }
+
+#if defined(TERMINAL_DEBUG)
+ dbgprintf("Terminal::execute_escape_sequence: Handled final '%c'\n", final);
+ dbgprintf("Params: ");
+ for (auto& p : params) {
+ dbgprintf("%d ", p);
+ }
+ dbgprintf("\b\n");
+#endif
+
+ switch (final) {
+ case 'A':
+ CUU(params);
+ break;
+ case 'B':
+ CUD(params);
+ break;
+ case 'C':
+ CUF(params);
+ break;
+ case 'D':
+ CUB(params);
+ break;
+ case 'H':
+ CUP(params);
+ break;
+ case 'J':
+ ED(params);
+ break;
+ case 'K':
+ EL(params);
+ break;
+ case 'M':
+ escape$M(params);
+ break;
+ case 'P':
+ escape$P(params);
+ break;
+ case 'S':
+ escape$S(params);
+ break;
+ case 'T':
+ escape$T(params);
+ break;
+ case 'L':
+ escape$L(params);
+ break;
+ case 'G':
+ escape$G(params);
+ break;
+ case 'X':
+ escape$X(params);
+ break;
+ case 'b':
+ escape$b(params);
+ break;
+ case 'd':
+ escape$d(params);
+ break;
+ case 'm':
+ SGR(params);
+ break;
+ case 's':
+ escape$s(params);
+ break;
+ case 'u':
+ escape$u(params);
+ break;
+ case 't':
+ escape$t(params);
+ break;
+ case 'r':
+ DECSTBM(params);
+ break;
+ case 'l':
+ RM(question_param, params);
+ break;
+ case 'h':
+ SM(question_param, params);
+ break;
+ case 'c':
+ DA(params);
+ break;
+ case 'f':
+ HVP(params);
+ break;
+ case 'n':
+ DSR(params);
+ break;
+ case '@':
+ ICH(params);
+ break;
+ default:
+ dbgprintf("Terminal::execute_escape_sequence: Unhandled final '%c'\n", final);
+ break;
+ }
+
+#if defined(TERMINAL_DEBUG)
+ dbgprintf("\n");
+ for (auto& line : m_lines) {
+ dbgprintf("Terminal: Line: ");
+ for (int i = 0; i < line.length(); i++) {
+ u32 codepoint = line.code_point(i);
+ if (codepoint < 128)
+ dbgprintf("%c", (char)codepoint);
+ else
+ dbgprintf("<U+%04x>", codepoint);
+ }
+ dbgprintf("\n");
+ }
+#endif
+
+ m_parameters.clear_with_capacity();
+ m_intermediates.clear_with_capacity();
+}
+
+void Terminal::newline()
+{
+ u16 new_row = m_cursor_row;
+ if (m_cursor_row == m_scroll_region_bottom) {
+ scroll_up();
+ } else {
+ ++new_row;
+ }
+ set_cursor(new_row, 0);
+}
+
+void Terminal::scroll_up()
+{
+ // NOTE: We have to invalidate the cursor first.
+ invalidate_cursor();
+ if (m_scroll_region_top == 0) {
+ auto line = move(m_lines.ptr_at(m_scroll_region_top));
+ add_line_to_history(move(line));
+ m_client.terminal_history_changed();
+ }
+ m_lines.remove(m_scroll_region_top);
+ m_lines.insert(m_scroll_region_bottom, make<Line>(m_columns));
+ m_need_full_flush = true;
+}
+
+void Terminal::scroll_down()
+{
+ // NOTE: We have to invalidate the cursor first.
+ invalidate_cursor();
+ m_lines.remove(m_scroll_region_bottom);
+ m_lines.insert(m_scroll_region_top, make<Line>(m_columns));
+ m_need_full_flush = true;
+}
+
+void Terminal::set_cursor(unsigned a_row, unsigned a_column)
+{
+ unsigned row = min(a_row, m_rows - 1u);
+ unsigned column = min(a_column, m_columns - 1u);
+ if (row == m_cursor_row && column == m_cursor_column)
+ return;
+ ASSERT(row < rows());
+ ASSERT(column < columns());
+ invalidate_cursor();
+ m_cursor_row = row;
+ m_cursor_column = column;
+ m_stomp = false;
+ invalidate_cursor();
+}
+
+void Terminal::put_character_at(unsigned row, unsigned column, u32 code_point)
+{
+ ASSERT(row < rows());
+ ASSERT(column < columns());
+ auto& line = m_lines[row];
+ line.set_code_point(column, code_point);
+ line.attributes()[column] = m_current_attribute;
+ line.attributes()[column].flags |= Attribute::Touched;
+ line.set_dirty(true);
+
+ m_last_code_point = code_point;
+}
+
+void Terminal::NEL()
+{
+ // NEL - Next Line
+ newline();
+}
+
+void Terminal::IND()
+{
+ // IND - Index (move down)
+ CUD({});
+}
+
+void Terminal::RI()
+{
+ // RI - Reverse Index (move up)
+ CUU({});
+}
+
+void Terminal::DSR(const ParamVector& params)
+{
+ if (params.size() == 1 && params[0] == 5) {
+ // Device status
+ emit_string("\033[0n"); // Terminal status OK!
+ } else if (params.size() == 1 && params[0] == 6) {
+ // Cursor position query
+ emit_string(String::format("\033[%d;%dR", m_cursor_row + 1, m_cursor_column + 1));
+ } else {
+ dbgln("Unknown DSR");
+ }
+}
+
+void Terminal::ICH(const ParamVector& params)
+{
+ int num = 0;
+ if (params.size() >= 1) {
+ num = params[0];
+ }
+ if (num == 0)
+ num = 1;
+
+ auto& line = m_lines[m_cursor_row];
+
+ // Move characters after cursor to the right
+ for (int i = line.length() - num; i >= m_cursor_column; --i)
+ line.set_code_point(i + num, line.code_point(i));
+
+ // Fill n characters after cursor with blanks
+ for (int i = 0; i < num; i++)
+ line.set_code_point(m_cursor_column + i, ' ');
+
+ line.set_dirty(true);
+}
+
+void Terminal::on_input(u8 ch)
+{
+#ifdef TERMINAL_DEBUG
+ dbgln("Terminal::on_input: {:#02x} ({:c}), fg={}, bg={}\n", ch, ch, m_current_attribute.foreground_color, m_current_attribute.background_color);
+#endif
+
+ auto fail_utf8_parse = [this] {
+ m_parser_state = Normal;
+ on_code_point(U'�');
+ };
+
+ auto advance_utf8_parse = [this, ch] {
+ m_parser_code_point <<= 6;
+ m_parser_code_point |= ch & 0x3f;
+ if (m_parser_state == UTF8Needs1Byte) {
+ on_code_point(m_parser_code_point);
+ m_parser_state = Normal;
+ } else {
+ m_parser_state = (ParserState)(m_parser_state + 1);
+ }
+ };
+
+ switch (m_parser_state) {
+ case GotEscape:
+ if (ch == '[') {
+ m_parser_state = ExpectParameter;
+ } else if (ch == '(') {
+ m_swallow_current = true;
+ m_parser_state = ExpectParameter;
+ } else if (ch == ']') {
+ m_parser_state = ExpectXtermParameter;
+ m_xterm_parameters.clear_with_capacity();
+ } else if (ch == '#') {
+ m_parser_state = ExpectHashtagDigit;
+ } else if (ch == 'D') {
+ IND();
+ m_parser_state = Normal;
+ return;
+ } else if (ch == 'M') {
+ RI();
+ m_parser_state = Normal;
+ return;
+ } else if (ch == 'E') {
+ NEL();
+ m_parser_state = Normal;
+ return;
+ } else {
+ dbg() << "Unexpected character in GotEscape '" << (char)ch << "'";
+ m_parser_state = Normal;
+ }
+ return;
+ case ExpectHashtagDigit:
+ if (ch >= '0' && ch <= '9') {
+ execute_hashtag(ch);
+ m_parser_state = Normal;
+ }
+ return;
+ case ExpectXtermParameter:
+ if (ch == 27) {
+ m_parser_state = ExpectStringTerminator;
+ return;
+ }
+ if (ch == 7) {
+ execute_xterm_command();
+ m_parser_state = Normal;
+ return;
+ }
+ m_xterm_parameters.append(ch);
+ return;
+ case ExpectStringTerminator:
+ if (ch == '\\')
+ execute_xterm_command();
+ else
+ dbg() << "Unexpected string terminator: " << String::format("%02x", ch);
+ m_parser_state = Normal;
+ return;
+ case ExpectParameter:
+ if (is_valid_parameter_character(ch)) {
+ m_parameters.append(ch);
+ return;
+ }
+ m_parser_state = ExpectIntermediate;
+ [[fallthrough]];
+ case ExpectIntermediate:
+ if (is_valid_intermediate_character(ch)) {
+ m_intermediates.append(ch);
+ return;
+ }
+ m_parser_state = ExpectFinal;
+ [[fallthrough]];
+ case ExpectFinal:
+ if (is_valid_final_character(ch)) {
+ m_parser_state = Normal;
+ if (!m_swallow_current)
+ execute_escape_sequence(ch);
+ m_swallow_current = false;
+ return;
+ }
+ m_parser_state = Normal;
+ m_swallow_current = false;
+ return;
+ case UTF8Needs1Byte:
+ case UTF8Needs2Bytes:
+ case UTF8Needs3Bytes:
+ if ((ch & 0xc0) != 0x80) {
+ fail_utf8_parse();
+ } else {
+ advance_utf8_parse();
+ }
+ return;
+
+ case Normal:
+ if (!(ch & 0x80))
+ break;
+ if ((ch & 0xe0) == 0xc0) {
+ m_parser_state = UTF8Needs1Byte;
+ m_parser_code_point = ch & 0x1f;
+ return;
+ }
+ if ((ch & 0xf0) == 0xe0) {
+ m_parser_state = UTF8Needs2Bytes;
+ m_parser_code_point = ch & 0x0f;
+ return;
+ }
+ if ((ch & 0xf8) == 0xf0) {
+ m_parser_state = UTF8Needs3Bytes;
+ m_parser_code_point = ch & 0x07;
+ return;
+ }
+ fail_utf8_parse();
+ return;
+ }
+
+ switch (ch) {
+ case '\0':
+ return;
+ case '\033':
+ m_parser_state = GotEscape;
+ m_swallow_current = false;
+ return;
+ case 8: // Backspace
+ if (m_cursor_column) {
+ set_cursor(m_cursor_row, m_cursor_column - 1);
+ return;
+ }
+ return;
+ case '\a':
+ m_client.beep();
+ return;
+ case '\t': {
+ for (unsigned i = m_cursor_column + 1; i < columns(); ++i) {
+ if (m_horizontal_tabs[i]) {
+ set_cursor(m_cursor_row, i);
+ return;
+ }
+ }
+ return;
+ }
+ case '\r':
+ set_cursor(m_cursor_row, 0);
+ return;
+ case '\n':
+ newline();
+ return;
+ }
+
+ on_code_point(ch);
+}
+
+void Terminal::on_code_point(u32 code_point)
+{
+ auto new_column = m_cursor_column + 1;
+ if (new_column < columns()) {
+ put_character_at(m_cursor_row, m_cursor_column, code_point);
+ set_cursor(m_cursor_row, new_column);
+ return;
+ }
+ if (m_stomp) {
+ m_stomp = false;
+ newline();
+ put_character_at(m_cursor_row, m_cursor_column, code_point);
+ set_cursor(m_cursor_row, 1);
+ } else {
+ // Curious: We wait once on the right-hand side
+ m_stomp = true;
+ put_character_at(m_cursor_row, m_cursor_column, code_point);
+ }
+}
+
+void Terminal::inject_string(const StringView& str)
+{
+ for (size_t i = 0; i < str.length(); ++i)
+ on_input(str[i]);
+}
+
+void Terminal::emit_string(const StringView& string)
+{
+ m_client.emit((const u8*)string.characters_without_null_termination(), string.length());
+}
+
+void Terminal::handle_key_press(KeyCode key, u32 code_point, u8 flags)
+{
+ bool ctrl = flags & Mod_Ctrl;
+ bool alt = flags & Mod_Alt;
+ bool shift = flags & Mod_Shift;
+ unsigned modifier_mask = int(shift) + (int(alt) << 1) + (int(ctrl) << 2);
+
+ auto emit_final_with_modifier = [this, modifier_mask](char final) {
+ if (modifier_mask)
+ emit_string(String::format("\e[1;%d%c", modifier_mask + 1, final));
+ else
+ emit_string(String::format("\e[%c", final));
+ };
+ auto emit_tilde_with_modifier = [this, modifier_mask](unsigned num) {
+ if (modifier_mask)
+ emit_string(String::format("\e[%d;%d~", num, modifier_mask + 1));
+ else
+ emit_string(String::format("\e[%d~", num));
+ };
+
+ switch (key) {
+ case KeyCode::Key_Up:
+ emit_final_with_modifier('A');
+ return;
+ case KeyCode::Key_Down:
+ emit_final_with_modifier('B');
+ return;
+ case KeyCode::Key_Right:
+ emit_final_with_modifier('C');
+ return;
+ case KeyCode::Key_Left:
+ emit_final_with_modifier('D');
+ return;
+ case KeyCode::Key_Insert:
+ emit_tilde_with_modifier(2);
+ return;
+ case KeyCode::Key_Delete:
+ emit_tilde_with_modifier(3);
+ return;
+ case KeyCode::Key_Home:
+ emit_final_with_modifier('H');
+ return;
+ case KeyCode::Key_End:
+ emit_final_with_modifier('F');
+ return;
+ case KeyCode::Key_PageUp:
+ emit_tilde_with_modifier(5);
+ return;
+ case KeyCode::Key_PageDown:
+ emit_tilde_with_modifier(6);
+ return;
+ default:
+ break;
+ }
+
+ if (!code_point) {
+ // Probably a modifier being pressed.
+ return;
+ }
+
+ if (shift && key == KeyCode::Key_Tab) {
+ emit_string("\033[Z");
+ return;
+ }
+
+ // Key event was not one of the above special cases,
+ // attempt to treat it as a character...
+ if (ctrl) {
+ if (code_point >= 'a' && code_point <= 'z') {
+ code_point = code_point - 'a' + 1;
+ } else if (code_point == '\\') {
+ code_point = 0x1c;
+ }
+ }
+
+ // Alt modifier sends escape prefix.
+ if (alt)
+ emit_string("\033");
+
+ StringBuilder sb;
+ sb.append_code_point(code_point);
+
+ emit_string(sb.to_string());
+}
+
+void Terminal::unimplemented_escape()
+{
+ StringBuilder builder;
+ builder.appendf("((Unimplemented escape: %c", m_final);
+ if (!m_parameters.is_empty()) {
+ builder.append(" parameters:");
+ for (size_t i = 0; i < m_parameters.size(); ++i)
+ builder.append((char)m_parameters[i]);
+ }
+ if (!m_intermediates.is_empty()) {
+ builder.append(" intermediates:");
+ for (size_t i = 0; i < m_intermediates.size(); ++i)
+ builder.append((char)m_intermediates[i]);
+ }
+ builder.append("))");
+ inject_string(builder.to_string());
+}
+
+void Terminal::unimplemented_xterm_escape()
+{
+ auto message = String::format("((Unimplemented xterm escape: %c))\n", m_final);
+ inject_string(message);
+}
+
+void Terminal::set_size(u16 columns, u16 rows)
+{
+ if (!columns)
+ columns = 1;
+ if (!rows)
+ rows = 1;
+
+ if (columns == m_columns && rows == m_rows)
+ return;
+
+#if defined(TERMINAL_DEBUG)
+ dbgprintf("Terminal: RESIZE to: %d rows\n", rows);
+#endif
+
+ if (rows > m_rows) {
+ while (m_lines.size() < rows)
+ m_lines.append(make<Line>(columns));
+ } else {
+ m_lines.shrink(rows);
+ }
+
+ for (int i = 0; i < rows; ++i)
+ m_lines[i].set_length(columns);
+
+ m_columns = columns;
+ m_rows = rows;
+
+ m_scroll_region_top = 0;
+ m_scroll_region_bottom = rows - 1;
+
+ m_cursor_row = min((int)m_cursor_row, m_rows - 1);
+ m_cursor_column = min((int)m_cursor_column, m_columns - 1);
+ m_saved_cursor_row = min((int)m_saved_cursor_row, m_rows - 1);
+ m_saved_cursor_column = min((int)m_saved_cursor_column, m_columns - 1);
+
+ m_horizontal_tabs.resize(columns);
+ for (unsigned i = 0; i < columns; ++i)
+ m_horizontal_tabs[i] = (i % 8) == 0;
+ // Rightmost column is always last tab on line.
+ m_horizontal_tabs[columns - 1] = 1;
+
+ m_client.terminal_did_resize(m_columns, m_rows);
+}
+
+void Terminal::invalidate_cursor()
+{
+ m_lines[m_cursor_row].set_dirty(true);
+}
+
+void Terminal::execute_hashtag(u8 hashtag)
+{
+ switch (hashtag) {
+ case '8':
+ // Confidence Test - Fill screen with E's
+ for (size_t row = 0; row < m_rows; ++row) {
+ for (size_t column = 0; column < m_columns; ++column) {
+ put_character_at(row, column, 'E');
+ }
+ }
+ break;
+ default:
+ dbg() << "Unknown hashtag: '" << hashtag << "'";
+ }
+}
+
+Attribute Terminal::attribute_at(const Position& position) const
+{
+ if (!position.is_valid())
+ return {};
+ if (position.row() >= static_cast<int>(line_count()))
+ return {};
+ auto& line = this->line(position.row());
+ if (position.column() >= line.length())
+ return {};
+ return line.attributes()[position.column()];
+}
+
+}
diff --git a/Userland/Libraries/LibVT/Terminal.h b/Userland/Libraries/LibVT/Terminal.h
new file mode 100644
index 0000000000..65e276f71d
--- /dev/null
+++ b/Userland/Libraries/LibVT/Terminal.h
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Noncopyable.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <Kernel/API/KeyCode.h>
+#include <LibVT/Line.h>
+#include <LibVT/Position.h>
+
+namespace VT {
+
+class TerminalClient {
+public:
+ virtual ~TerminalClient() { }
+
+ virtual void beep() = 0;
+ virtual void set_window_title(const StringView&) = 0;
+ virtual void set_window_progress(int value, int max) = 0;
+ virtual void terminal_did_resize(u16 columns, u16 rows) = 0;
+ virtual void terminal_history_changed() = 0;
+ virtual void emit(const u8*, size_t) = 0;
+};
+
+class Terminal {
+public:
+ explicit Terminal(TerminalClient&);
+ ~Terminal();
+
+ bool m_need_full_flush { false };
+
+ void invalidate_cursor();
+ void on_input(u8);
+
+ void clear();
+ void clear_including_history();
+
+ void set_size(u16 columns, u16 rows);
+ u16 columns() const { return m_columns; }
+ u16 rows() const { return m_rows; }
+
+ u16 cursor_column() const { return m_cursor_column; }
+ u16 cursor_row() const { return m_cursor_row; }
+
+ size_t line_count() const
+ {
+ return m_history.size() + m_lines.size();
+ }
+
+ Line& line(size_t index)
+ {
+ if (index < m_history.size())
+ return m_history[(m_history_start + index) % m_history.size()];
+ return m_lines[index - m_history.size()];
+ }
+ const Line& line(size_t index) const
+ {
+ return const_cast<Terminal*>(this)->line(index);
+ }
+
+ Line& visible_line(size_t index)
+ {
+ return m_lines[index];
+ }
+ const Line& visible_line(size_t index) const
+ {
+ return m_lines[index];
+ }
+
+ size_t max_history_size() const { return m_max_history_lines; }
+ void set_max_history_size(size_t value)
+ {
+ if (value == 0) {
+ m_max_history_lines = 0;
+ m_history_start = 0;
+ m_history.clear();
+ m_client.terminal_history_changed();
+ return;
+ }
+
+ if (m_max_history_lines > value) {
+ NonnullOwnPtrVector<Line> new_history;
+ new_history.ensure_capacity(value);
+ auto existing_line_count = min(m_history.size(), value);
+ for (size_t i = m_history.size() - existing_line_count; i < m_history.size(); ++i) {
+ auto j = (m_history_start + i) % m_history.size();
+ new_history.unchecked_append(move(static_cast<Vector<NonnullOwnPtr<Line>>&>(m_history).at(j)));
+ }
+ m_history = move(new_history);
+ m_history_start = 0;
+ m_client.terminal_history_changed();
+ }
+ m_max_history_lines = value;
+ }
+ size_t history_size() const { return m_history.size(); }
+
+ void inject_string(const StringView&);
+ void handle_key_press(KeyCode, u32, u8 flags);
+
+ Attribute attribute_at(const Position&) const;
+
+private:
+ typedef Vector<unsigned, 4> ParamVector;
+
+ void on_code_point(u32);
+
+ void scroll_up();
+ void scroll_down();
+ void newline();
+ void set_cursor(unsigned row, unsigned column);
+ void put_character_at(unsigned row, unsigned column, u32 ch);
+ void set_window_title(const String&);
+
+ void unimplemented_escape();
+ void unimplemented_xterm_escape();
+
+ void emit_string(const StringView&);
+
+ void alter_mode(bool should_set, bool question_param, const ParamVector&);
+
+ void CUU(const ParamVector&);
+ void CUD(const ParamVector&);
+ void CUF(const ParamVector&);
+ void CUB(const ParamVector&);
+ void CUP(const ParamVector&);
+ void ED(const ParamVector&);
+ void EL(const ParamVector&);
+ void escape$M(const ParamVector&);
+ void escape$P(const ParamVector&);
+ void escape$G(const ParamVector&);
+ void escape$X(const ParamVector&);
+ void escape$b(const ParamVector&);
+ void escape$d(const ParamVector&);
+ void SGR(const ParamVector&);
+ void escape$s(const ParamVector&);
+ void escape$u(const ParamVector&);
+ void escape$t(const ParamVector&);
+ void DECSTBM(const ParamVector&);
+ void escape$S(const ParamVector&);
+ void escape$T(const ParamVector&);
+ void escape$L(const ParamVector&);
+ void RM(bool question_param, const ParamVector&);
+ void SM(bool question_param, const ParamVector&);
+ void DA(const ParamVector&);
+ void HVP(const ParamVector&);
+ void NEL();
+ void IND();
+ void RI();
+ void DSR(const ParamVector&);
+ void ICH(const ParamVector&);
+
+ TerminalClient& m_client;
+
+ size_t m_history_start = 0;
+ NonnullOwnPtrVector<Line> m_history;
+ void add_line_to_history(NonnullOwnPtr<Line>&& line)
+ {
+ if (max_history_size() == 0)
+ return;
+
+ if (m_history.size() < max_history_size()) {
+ ASSERT(m_history_start == 0);
+ m_history.append(move(line));
+ return;
+ }
+ m_history.ptr_at(m_history_start) = move(line);
+ m_history_start = (m_history_start + 1) % m_history.size();
+ }
+
+ NonnullOwnPtrVector<Line> m_lines;
+
+ size_t m_scroll_region_top { 0 };
+ size_t m_scroll_region_bottom { 0 };
+
+ u16 m_columns { 1 };
+ u16 m_rows { 1 };
+
+ u16 m_cursor_row { 0 };
+ u16 m_cursor_column { 0 };
+ u16 m_saved_cursor_row { 0 };
+ u16 m_saved_cursor_column { 0 };
+ bool m_swallow_current { false };
+ bool m_stomp { false };
+
+ Attribute m_current_attribute;
+
+ u32 m_next_href_id { 0 };
+
+ void execute_escape_sequence(u8 final);
+ void execute_xterm_command();
+ void execute_hashtag(u8);
+
+ enum ParserState {
+ Normal,
+ GotEscape,
+ ExpectParameter,
+ ExpectIntermediate,
+ ExpectFinal,
+ ExpectHashtagDigit,
+ ExpectXtermParameter,
+ ExpectStringTerminator,
+ UTF8Needs3Bytes,
+ UTF8Needs2Bytes,
+ UTF8Needs1Byte,
+ };
+
+ ParserState m_parser_state { Normal };
+ u32 m_parser_code_point { 0 };
+ Vector<u8> m_parameters;
+ Vector<u8> m_intermediates;
+ Vector<u8> m_xterm_parameters;
+ Vector<bool> m_horizontal_tabs;
+ u8 m_final { 0 };
+ u32 m_last_code_point { 0 };
+ size_t m_max_history_lines { 1024 };
+};
+
+}
diff --git a/Userland/Libraries/LibVT/TerminalWidget.cpp b/Userland/Libraries/LibVT/TerminalWidget.cpp
new file mode 100644
index 0000000000..5fe579a2b4
--- /dev/null
+++ b/Userland/Libraries/LibVT/TerminalWidget.cpp
@@ -0,0 +1,1138 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "TerminalWidget.h"
+#include "XtermColors.h"
+#include <AK/LexicalPath.h>
+#include <AK/StdLibExtras.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/Utf32View.h>
+#include <AK/Utf8View.h>
+#include <LibCore/ConfigFile.h>
+#include <LibCore/MimeData.h>
+#include <LibDesktop/AppFile.h>
+#include <LibDesktop/Launcher.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/Clipboard.h>
+#include <LibGUI/DragOperation.h>
+#include <LibGUI/FileIconProvider.h>
+#include <LibGUI/Icon.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+#include <ctype.h>
+#include <errno.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+//#define TERMINAL_DEBUG
+
+void TerminalWidget::set_pty_master_fd(int fd)
+{
+ m_ptm_fd = fd;
+ if (m_ptm_fd == -1) {
+ m_notifier = nullptr;
+ return;
+ }
+ m_notifier = Core::Notifier::construct(m_ptm_fd, Core::Notifier::Read);
+ m_notifier->on_ready_to_read = [this] {
+ u8 buffer[BUFSIZ];
+ ssize_t nread = read(m_ptm_fd, buffer, sizeof(buffer));
+ if (nread < 0) {
+ dbgln("Terminal read error: {}", strerror(errno));
+ perror("read(ptm)");
+ GUI::Application::the()->quit(1);
+ return;
+ }
+ if (nread == 0) {
+ dbgln("TerminalWidget: EOF on master pty, firing on_command_exit hook.");
+ if (on_command_exit)
+ on_command_exit();
+ int rc = close(m_ptm_fd);
+ if (rc < 0) {
+ perror("close");
+ }
+ set_pty_master_fd(-1);
+ return;
+ }
+ for (ssize_t i = 0; i < nread; ++i)
+ m_terminal.on_input(buffer[i]);
+ flush_dirty_lines();
+ };
+}
+
+TerminalWidget::TerminalWidget(int ptm_fd, bool automatic_size_policy, RefPtr<Core::ConfigFile> config)
+ : m_terminal(*this)
+ , m_automatic_size_policy(automatic_size_policy)
+ , m_config(move(config))
+{
+ set_override_cursor(Gfx::StandardCursor::IBeam);
+ set_focus_policy(GUI::FocusPolicy::StrongFocus);
+ set_accepts_emoji_input(true);
+ set_pty_master_fd(ptm_fd);
+ m_cursor_blink_timer = add<Core::Timer>();
+ m_visual_beep_timer = add<Core::Timer>();
+ m_auto_scroll_timer = add<Core::Timer>();
+
+ m_scrollbar = add<GUI::ScrollBar>(Orientation::Vertical);
+ m_scrollbar->set_relative_rect(0, 0, 16, 0);
+ m_scrollbar->on_change = [this](int) {
+ force_repaint();
+ };
+
+ dbgln("Load config file from {}", m_config->file_name());
+ m_cursor_blink_timer->set_interval(m_config->read_num_entry("Text",
+ "CursorBlinkInterval",
+ 500));
+ m_cursor_blink_timer->on_timeout = [this] {
+ m_cursor_blink_state = !m_cursor_blink_state;
+ update_cursor();
+ };
+
+ m_auto_scroll_timer->set_interval(50);
+ m_auto_scroll_timer->on_timeout = [this] {
+ if (m_auto_scroll_direction != AutoScrollDirection::None) {
+ int scroll_amount = m_auto_scroll_direction == AutoScrollDirection::Up ? -1 : 1;
+ m_scrollbar->set_value(m_scrollbar->value() + scroll_amount);
+ }
+ };
+ m_auto_scroll_timer->start();
+
+ auto font_entry = m_config->read_entry("Text", "Font", "default");
+ if (font_entry == "default")
+ set_font(Gfx::FontDatabase::default_fixed_width_font());
+ else
+ set_font(Gfx::FontDatabase::the().get_by_name(font_entry));
+
+ m_line_height = font().glyph_height() + m_line_spacing;
+
+ m_terminal.set_size(m_config->read_num_entry("Window", "Width", 80), m_config->read_num_entry("Window", "Height", 25));
+
+ m_copy_action = GUI::Action::create("Copy", { Mod_Ctrl | Mod_Shift, Key_C }, Gfx::Bitmap::load_from_file("/res/icons/16x16/edit-copy.png"), [this](auto&) {
+ copy();
+ });
+ m_copy_action->set_swallow_key_event_when_disabled(true);
+
+ m_paste_action = GUI::Action::create("Paste", { Mod_Ctrl | Mod_Shift, Key_V }, Gfx::Bitmap::load_from_file("/res/icons/16x16/paste.png"), [this](auto&) {
+ paste();
+ });
+ m_paste_action->set_swallow_key_event_when_disabled(true);
+
+ m_clear_including_history_action = GUI::Action::create("Clear including history", { Mod_Ctrl | Mod_Shift, Key_K }, [this](auto&) {
+ clear_including_history();
+ });
+
+ m_context_menu = GUI::Menu::construct();
+ m_context_menu->add_action(copy_action());
+ m_context_menu->add_action(paste_action());
+ m_context_menu->add_separator();
+ m_context_menu->add_action(clear_including_history_action());
+
+ GUI::Clipboard::the().on_change = [this](const String&) {
+ update_paste_action();
+ };
+
+ update_copy_action();
+ update_paste_action();
+}
+
+TerminalWidget::~TerminalWidget()
+{
+}
+
+static inline Color color_from_rgb(unsigned color)
+{
+ return Color::from_rgb(color);
+}
+
+Gfx::IntRect TerminalWidget::glyph_rect(u16 row, u16 column)
+{
+ int y = row * m_line_height;
+ int x = column * font().glyph_width('x');
+ return { x + frame_thickness() + m_inset, y + frame_thickness() + m_inset, font().glyph_width('x'), font().glyph_height() };
+}
+
+Gfx::IntRect TerminalWidget::row_rect(u16 row)
+{
+ int y = row * m_line_height;
+ Gfx::IntRect rect = { frame_thickness() + m_inset, y + frame_thickness() + m_inset, font().glyph_width('x') * m_terminal.columns(), font().glyph_height() };
+ rect.inflate(0, m_line_spacing);
+ return rect;
+}
+
+void TerminalWidget::set_logical_focus(bool focus)
+{
+ m_has_logical_focus = focus;
+ if (!m_has_logical_focus) {
+ m_cursor_blink_timer->stop();
+ } else {
+ m_cursor_blink_state = true;
+ m_cursor_blink_timer->start();
+ }
+ m_auto_scroll_direction = AutoScrollDirection::None;
+ invalidate_cursor();
+ update();
+}
+
+void TerminalWidget::focusin_event(GUI::FocusEvent& event)
+{
+ set_logical_focus(true);
+ return GUI::Frame::focusin_event(event);
+}
+
+void TerminalWidget::focusout_event(GUI::FocusEvent& event)
+{
+ set_logical_focus(false);
+ return GUI::Frame::focusout_event(event);
+}
+
+void TerminalWidget::event(Core::Event& event)
+{
+ if (event.type() == GUI::Event::WindowBecameActive)
+ set_logical_focus(true);
+ else if (event.type() == GUI::Event::WindowBecameInactive)
+ set_logical_focus(false);
+ return GUI::Frame::event(event);
+}
+
+void TerminalWidget::keydown_event(GUI::KeyEvent& event)
+{
+ if (m_ptm_fd == -1) {
+ event.ignore();
+ return GUI::Frame::keydown_event(event);
+ }
+
+ // Reset timer so cursor doesn't blink while typing.
+ m_cursor_blink_timer->stop();
+ m_cursor_blink_state = true;
+ m_cursor_blink_timer->start();
+
+ if (event.key() == KeyCode::Key_PageUp && event.modifiers() == Mod_Shift) {
+ m_scrollbar->set_value(m_scrollbar->value() - m_terminal.rows());
+ return;
+ }
+ if (event.key() == KeyCode::Key_PageDown && event.modifiers() == Mod_Shift) {
+ m_scrollbar->set_value(m_scrollbar->value() + m_terminal.rows());
+ return;
+ }
+ if (event.key() == KeyCode::Key_Alt) {
+ m_alt_key_held = true;
+ return;
+ }
+
+ // Clear the selection if we type in/behind it.
+ auto future_cursor_column = (event.key() == KeyCode::Key_Backspace) ? m_terminal.cursor_column() - 1 : m_terminal.cursor_column();
+ auto min_selection_row = min(m_selection.start().row(), m_selection.end().row());
+ auto max_selection_row = max(m_selection.start().row(), m_selection.end().row());
+
+ if (future_cursor_column <= last_selection_column_on_row(m_terminal.cursor_row()) && m_terminal.cursor_row() >= min_selection_row && m_terminal.cursor_row() <= max_selection_row) {
+ m_selection.set_end({});
+ update_copy_action();
+ update();
+ }
+
+ m_terminal.handle_key_press(event.key(), event.code_point(), event.modifiers());
+
+ if (event.key() != Key_Control && event.key() != Key_Alt && event.key() != Key_LeftShift && event.key() != Key_RightShift && event.key() != Key_Logo)
+ scroll_to_bottom();
+}
+
+void TerminalWidget::keyup_event(GUI::KeyEvent& event)
+{
+ switch (event.key()) {
+ case KeyCode::Key_Alt:
+ m_alt_key_held = false;
+ return;
+ default:
+ break;
+ }
+}
+
+void TerminalWidget::paint_event(GUI::PaintEvent& event)
+{
+ GUI::Frame::paint_event(event);
+
+ GUI::Painter painter(*this);
+
+ auto visual_beep_active = m_visual_beep_timer->is_active();
+
+ painter.add_clip_rect(event.rect());
+
+ Gfx::IntRect terminal_buffer_rect(frame_inner_rect().top_left(), { frame_inner_rect().width() - m_scrollbar->width(), frame_inner_rect().height() });
+ painter.add_clip_rect(terminal_buffer_rect);
+
+ if (visual_beep_active)
+ painter.clear_rect(frame_inner_rect(), Color::Red);
+ else
+ painter.clear_rect(frame_inner_rect(), Color(Color::Black).with_alpha(m_opacity));
+ invalidate_cursor();
+
+ int rows_from_history = 0;
+ int first_row_from_history = m_terminal.history_size();
+ int row_with_cursor = m_terminal.cursor_row();
+ if (m_scrollbar->value() != m_scrollbar->max()) {
+ rows_from_history = min((int)m_terminal.rows(), m_scrollbar->max() - m_scrollbar->value());
+ first_row_from_history = m_terminal.history_size() - (m_scrollbar->max() - m_scrollbar->value());
+ row_with_cursor = m_terminal.cursor_row() + rows_from_history;
+ }
+
+ // Pass: Compute the rect(s) of the currently hovered link, if any.
+ Vector<Gfx::IntRect> hovered_href_rects;
+ if (!m_hovered_href_id.is_null()) {
+ for (u16 visual_row = 0; visual_row < m_terminal.rows(); ++visual_row) {
+ auto& line = m_terminal.line(first_row_from_history + visual_row);
+ for (size_t column = 0; column < line.length(); ++column) {
+ if (m_hovered_href_id == line.attributes()[column].href_id) {
+ bool merged_with_existing_rect = false;
+ auto glyph_rect = this->glyph_rect(visual_row, column);
+ for (auto& rect : hovered_href_rects) {
+ if (rect.inflated(1, 1).intersects(glyph_rect)) {
+ rect = rect.united(glyph_rect);
+ merged_with_existing_rect = true;
+ break;
+ }
+ }
+ if (!merged_with_existing_rect)
+ hovered_href_rects.append(glyph_rect);
+ }
+ }
+ }
+ }
+
+ // Pass: Paint background & text decorations.
+ for (u16 visual_row = 0; visual_row < m_terminal.rows(); ++visual_row) {
+ auto row_rect = this->row_rect(visual_row);
+ if (!event.rect().contains(row_rect))
+ continue;
+ auto& line = m_terminal.line(first_row_from_history + visual_row);
+ bool has_only_one_background_color = line.has_only_one_background_color();
+ if (visual_beep_active)
+ painter.clear_rect(row_rect, Color::Red);
+ else if (has_only_one_background_color)
+ painter.clear_rect(row_rect, color_from_rgb(line.attributes()[0].effective_background_color()).with_alpha(m_opacity));
+
+ for (size_t column = 0; column < line.length(); ++column) {
+ bool should_reverse_fill_for_cursor_or_selection = m_cursor_blink_state
+ && m_has_logical_focus
+ && visual_row == row_with_cursor
+ && column == m_terminal.cursor_column();
+ should_reverse_fill_for_cursor_or_selection |= selection_contains({ first_row_from_history + visual_row, (int)column });
+ auto attribute = line.attributes()[column];
+ auto character_rect = glyph_rect(visual_row, column);
+ auto cell_rect = character_rect.inflated(0, m_line_spacing);
+ auto text_color = color_from_rgb(should_reverse_fill_for_cursor_or_selection ? attribute.effective_background_color() : attribute.effective_foreground_color());
+ if ((!visual_beep_active && !has_only_one_background_color) || should_reverse_fill_for_cursor_or_selection) {
+ painter.clear_rect(cell_rect, color_from_rgb(should_reverse_fill_for_cursor_or_selection ? attribute.effective_foreground_color() : attribute.effective_background_color()).with_alpha(m_opacity));
+ }
+
+ enum class UnderlineStyle {
+ None,
+ Dotted,
+ Solid,
+ };
+
+ auto underline_style = UnderlineStyle::None;
+
+ if (attribute.flags & VT::Attribute::Underline) {
+ // Content has specified underline
+ underline_style = UnderlineStyle::Solid;
+ } else if (!attribute.href.is_empty()) {
+ // We're hovering a hyperlink
+ if (m_hovered_href_id == attribute.href_id || m_active_href_id == attribute.href_id)
+ underline_style = UnderlineStyle::Solid;
+ else
+ underline_style = UnderlineStyle::Dotted;
+ }
+
+ if (underline_style == UnderlineStyle::Solid) {
+ if (attribute.href_id == m_active_href_id && m_hovered_href_id == m_active_href_id)
+ text_color = palette().active_link();
+ painter.draw_line(cell_rect.bottom_left(), cell_rect.bottom_right(), text_color);
+ } else if (underline_style == UnderlineStyle::Dotted) {
+ auto dotted_line_color = text_color.darkened(0.6f);
+ int x1 = cell_rect.bottom_left().x();
+ int x2 = cell_rect.bottom_right().x();
+ int y = cell_rect.bottom_left().y();
+ for (int x = x1; x <= x2; ++x) {
+ if ((x % 3) == 0)
+ painter.set_pixel({ x, y }, dotted_line_color);
+ }
+ }
+ }
+ }
+
+ // Paint the hovered link rects, if any.
+ for (auto rect : hovered_href_rects) {
+ rect.inflate(6, 6);
+ painter.fill_rect(rect, palette().base());
+ painter.draw_rect(rect.inflated(2, 2).intersected(frame_inner_rect()), palette().base_text());
+ }
+
+ // Pass: Paint foreground (text).
+ for (u16 visual_row = 0; visual_row < m_terminal.rows(); ++visual_row) {
+ auto row_rect = this->row_rect(visual_row);
+ if (!event.rect().contains(row_rect))
+ continue;
+ auto& line = m_terminal.line(first_row_from_history + visual_row);
+ for (size_t column = 0; column < line.length(); ++column) {
+ auto attribute = line.attributes()[column];
+ bool should_reverse_fill_for_cursor_or_selection = m_cursor_blink_state
+ && m_has_logical_focus
+ && visual_row == row_with_cursor
+ && column == m_terminal.cursor_column();
+ should_reverse_fill_for_cursor_or_selection |= selection_contains({ first_row_from_history + visual_row, (int)column });
+ auto text_color = color_from_rgb(should_reverse_fill_for_cursor_or_selection ? attribute.effective_background_color() : attribute.effective_foreground_color());
+ u32 code_point = line.code_point(column);
+
+ if (code_point == ' ')
+ continue;
+
+ auto character_rect = glyph_rect(visual_row, column);
+
+ if (!m_hovered_href_id.is_null() && attribute.href_id == m_hovered_href_id) {
+ text_color = palette().base_text();
+ }
+
+ painter.draw_glyph_or_emoji(
+ character_rect.location(),
+ code_point,
+ attribute.flags & VT::Attribute::Bold ? bold_font() : font(),
+ text_color);
+ }
+ }
+
+ // Draw cursor.
+ if (!m_has_logical_focus && row_with_cursor < m_terminal.rows()) {
+ auto& cursor_line = m_terminal.line(first_row_from_history + row_with_cursor);
+ if (m_terminal.cursor_row() < (m_terminal.rows() - rows_from_history)) {
+ auto cell_rect = glyph_rect(row_with_cursor, m_terminal.cursor_column()).inflated(0, m_line_spacing);
+ painter.draw_rect(cell_rect, color_from_rgb(cursor_line.attributes()[m_terminal.cursor_column()].effective_foreground_color()));
+ }
+ }
+}
+
+void TerminalWidget::set_window_progress(int value, int max)
+{
+ float float_value = value;
+ float float_max = max;
+ float progress = (float_value / float_max) * 100.0f;
+ window()->set_progress((int)roundf(progress));
+}
+
+void TerminalWidget::set_window_title(const StringView& title)
+{
+ if (!Utf8View(title).validate()) {
+ dbgln("TerminalWidget: Attempted to set window title to invalid UTF-8 string");
+ return;
+ }
+
+ if (on_title_change)
+ on_title_change(title);
+}
+
+void TerminalWidget::invalidate_cursor()
+{
+ m_terminal.invalidate_cursor();
+}
+
+void TerminalWidget::flush_dirty_lines()
+{
+ // FIXME: Update smarter when scrolled
+ if (m_terminal.m_need_full_flush || m_scrollbar->value() != m_scrollbar->max()) {
+ update();
+ m_terminal.m_need_full_flush = false;
+ return;
+ }
+ Gfx::IntRect rect;
+ for (int i = 0; i < m_terminal.rows(); ++i) {
+ if (m_terminal.visible_line(i).is_dirty()) {
+ rect = rect.united(row_rect(i));
+ m_terminal.visible_line(i).set_dirty(false);
+ }
+ }
+ update(rect);
+}
+
+void TerminalWidget::force_repaint()
+{
+ m_needs_background_fill = true;
+ update();
+}
+
+void TerminalWidget::resize_event(GUI::ResizeEvent& event)
+{
+ relayout(event.size());
+}
+
+void TerminalWidget::relayout(const Gfx::IntSize& size)
+{
+ if (!m_scrollbar)
+ return;
+
+ auto base_size = compute_base_size();
+ int new_columns = (size.width() - base_size.width()) / font().glyph_width('x');
+ int new_rows = (size.height() - base_size.height()) / m_line_height;
+ m_terminal.set_size(new_columns, new_rows);
+
+ Gfx::IntRect scrollbar_rect = {
+ size.width() - m_scrollbar->width() - frame_thickness(),
+ frame_thickness(),
+ m_scrollbar->width(),
+ size.height() - frame_thickness() * 2,
+ };
+ m_scrollbar->set_relative_rect(scrollbar_rect);
+ m_scrollbar->set_page_step(new_rows);
+}
+
+Gfx::IntSize TerminalWidget::compute_base_size() const
+{
+ int base_width = frame_thickness() * 2 + m_inset * 2 + m_scrollbar->width();
+ int base_height = frame_thickness() * 2 + m_inset * 2;
+ return { base_width, base_height };
+}
+
+void TerminalWidget::apply_size_increments_to_window(GUI::Window& window)
+{
+ window.set_size_increment({ font().glyph_width('x'), m_line_height });
+ window.set_base_size(compute_base_size());
+}
+
+void TerminalWidget::update_cursor()
+{
+ invalidate_cursor();
+ flush_dirty_lines();
+}
+
+void TerminalWidget::set_opacity(u8 new_opacity)
+{
+ if (m_opacity == new_opacity)
+ return;
+
+ window()->set_has_alpha_channel(new_opacity < 255);
+ m_opacity = new_opacity;
+ force_repaint();
+}
+
+bool TerminalWidget::has_selection() const
+{
+ return m_selection.is_valid();
+}
+
+void TerminalWidget::set_selection(const VT::Range& selection)
+{
+ m_selection = selection;
+ update_copy_action();
+ update();
+}
+
+bool TerminalWidget::selection_contains(const VT::Position& position) const
+{
+ if (!has_selection())
+ return false;
+
+ if (m_rectangle_selection) {
+ auto m_selection_start = m_selection.start();
+ auto m_selection_end = m_selection.end();
+ auto min_selection_column = min(m_selection_start.column(), m_selection_end.column());
+ auto max_selection_column = max(m_selection_start.column(), m_selection_end.column());
+ auto min_selection_row = min(m_selection_start.row(), m_selection_end.row());
+ auto max_selection_row = max(m_selection_start.row(), m_selection_end.row());
+
+ return position.column() >= min_selection_column && position.column() <= max_selection_column && position.row() >= min_selection_row && position.row() <= max_selection_row;
+ }
+
+ auto normalized_selection = m_selection.normalized();
+ return position >= normalized_selection.start() && position <= normalized_selection.end();
+}
+
+VT::Position TerminalWidget::buffer_position_at(const Gfx::IntPoint& position) const
+{
+ auto adjusted_position = position.translated(-(frame_thickness() + m_inset), -(frame_thickness() + m_inset));
+ int row = adjusted_position.y() / m_line_height;
+ int column = adjusted_position.x() / font().glyph_width('x');
+ if (row < 0)
+ row = 0;
+ if (column < 0)
+ column = 0;
+ if (row >= m_terminal.rows())
+ row = m_terminal.rows() - 1;
+ if (column >= m_terminal.columns())
+ column = m_terminal.columns() - 1;
+ row += m_scrollbar->value();
+ return { row, column };
+}
+
+u32 TerminalWidget::code_point_at(const VT::Position& position) const
+{
+ ASSERT(position.row() >= 0 && static_cast<size_t>(position.row()) < m_terminal.line_count());
+ auto& line = m_terminal.line(position.row());
+ if (position.column() == line.length())
+ return '\n';
+ return line.code_point(position.column());
+}
+
+VT::Position TerminalWidget::next_position_after(const VT::Position& position, bool should_wrap) const
+{
+ ASSERT(position.row() >= 0 && static_cast<size_t>(position.row()) < m_terminal.line_count());
+ auto& line = m_terminal.line(position.row());
+ if (position.column() == line.length()) {
+ if (static_cast<size_t>(position.row()) == m_terminal.line_count() - 1) {
+ if (should_wrap)
+ return { 0, 0 };
+ return {};
+ }
+ return { position.row() + 1, 0 };
+ }
+ return { position.row(), position.column() + 1 };
+}
+
+VT::Position TerminalWidget::previous_position_before(const VT::Position& position, bool should_wrap) const
+{
+ ASSERT(position.row() >= 0 && static_cast<size_t>(position.row()) < m_terminal.line_count());
+ if (position.column() == 0) {
+ if (position.row() == 0) {
+ if (should_wrap) {
+ auto& last_line = m_terminal.line(m_terminal.line_count() - 1);
+ return { static_cast<int>(m_terminal.line_count() - 1), last_line.length() };
+ }
+ return {};
+ }
+ auto& prev_line = m_terminal.line(position.row() - 1);
+ return { position.row() - 1, prev_line.length() };
+ }
+ return { position.row(), position.column() - 1 };
+}
+
+static u32 to_lowercase_code_point(u32 code_point)
+{
+ // FIXME: this only handles ascii characters, but handling unicode lowercasing seems like a mess
+ if (code_point < 128)
+ return tolower(code_point);
+ return code_point;
+}
+
+VT::Range TerminalWidget::find_next(const StringView& needle, const VT::Position& start, bool case_sensitivity, bool should_wrap)
+{
+ if (needle.is_empty())
+ return {};
+
+ VT::Position position = start.is_valid() ? start : VT::Position(0, 0);
+ VT::Position original_position = position;
+
+ VT::Position start_of_potential_match;
+ size_t needle_index = 0;
+
+ do {
+ auto ch = code_point_at(position);
+ // FIXME: This is not the right way to use a Unicode needle!
+ auto needle_ch = (u32)needle[needle_index];
+ if (case_sensitivity ? ch == needle_ch : to_lowercase_code_point(ch) == to_lowercase_code_point(needle_ch)) {
+ if (needle_index == 0)
+ start_of_potential_match = position;
+ ++needle_index;
+ if (needle_index >= needle.length())
+ return { start_of_potential_match, position };
+ } else {
+ if (needle_index > 0)
+ position = start_of_potential_match;
+ needle_index = 0;
+ }
+ position = next_position_after(position, should_wrap);
+ } while (position.is_valid() && position != original_position);
+
+ return {};
+}
+
+VT::Range TerminalWidget::find_previous(const StringView& needle, const VT::Position& start, bool case_sensitivity, bool should_wrap)
+{
+ if (needle.is_empty())
+ return {};
+
+ VT::Position position = start.is_valid() ? start : VT::Position(m_terminal.line_count() - 1, m_terminal.line(m_terminal.line_count() - 1).length() - 1);
+ VT::Position original_position = position;
+
+ VT::Position end_of_potential_match;
+ size_t needle_index = needle.length() - 1;
+
+ do {
+ auto ch = code_point_at(position);
+ // FIXME: This is not the right way to use a Unicode needle!
+ auto needle_ch = (u32)needle[needle_index];
+ if (case_sensitivity ? ch == needle_ch : to_lowercase_code_point(ch) == to_lowercase_code_point(needle_ch)) {
+ if (needle_index == needle.length() - 1)
+ end_of_potential_match = position;
+ if (needle_index == 0)
+ return { position, end_of_potential_match };
+ --needle_index;
+ } else {
+ if (needle_index < needle.length() - 1)
+ position = end_of_potential_match;
+ needle_index = needle.length() - 1;
+ }
+ position = previous_position_before(position, should_wrap);
+ } while (position.is_valid() && position != original_position);
+
+ return {};
+}
+
+void TerminalWidget::doubleclick_event(GUI::MouseEvent& event)
+{
+ if (event.button() == GUI::MouseButton::Left) {
+ auto attribute = m_terminal.attribute_at(buffer_position_at(event.position()));
+ if (!attribute.href_id.is_null()) {
+ dbgln("Open hyperlinked URL: '{}'", attribute.href);
+ Desktop::Launcher::open(attribute.href);
+ return;
+ }
+
+ m_triple_click_timer.start();
+
+ auto position = buffer_position_at(event.position());
+ auto& line = m_terminal.line(position.row());
+ bool want_whitespace = line.code_point(position.column()) == ' ';
+
+ int start_column = 0;
+ int end_column = 0;
+
+ for (int column = position.column(); column >= 0 && (line.code_point(column) == ' ') == want_whitespace; --column) {
+ start_column = column;
+ }
+
+ for (int column = position.column(); column < m_terminal.columns() && (line.code_point(column) == ' ') == want_whitespace; ++column) {
+ end_column = column;
+ }
+
+ m_selection.set({ position.row(), start_column }, { position.row(), end_column });
+ update_copy_action();
+ }
+ GUI::Frame::doubleclick_event(event);
+}
+
+void TerminalWidget::paste()
+{
+ if (m_ptm_fd == -1)
+ return;
+ auto mime_type = GUI::Clipboard::the().mime_type();
+ if (!mime_type.starts_with("text/"))
+ return;
+ auto text = GUI::Clipboard::the().data();
+ if (text.is_empty())
+ return;
+ int nwritten = write(m_ptm_fd, text.data(), text.size());
+ if (nwritten < 0) {
+ perror("write");
+ ASSERT_NOT_REACHED();
+ }
+}
+
+void TerminalWidget::copy()
+{
+ if (has_selection())
+ GUI::Clipboard::the().set_plain_text(selected_text());
+}
+
+void TerminalWidget::mouseup_event(GUI::MouseEvent& event)
+{
+ if (event.button() == GUI::MouseButton::Left) {
+ if (!m_active_href_id.is_null()) {
+ m_active_href = {};
+ m_active_href_id = {};
+ update();
+ }
+ m_auto_scroll_direction = AutoScrollDirection::None;
+ }
+}
+
+void TerminalWidget::mousedown_event(GUI::MouseEvent& event)
+{
+ if (event.button() == GUI::MouseButton::Left) {
+ m_left_mousedown_position = event.position();
+
+ auto attribute = m_terminal.attribute_at(buffer_position_at(event.position()));
+ if (!(event.modifiers() & Mod_Shift) && !attribute.href.is_empty()) {
+ m_active_href = attribute.href;
+ m_active_href_id = attribute.href_id;
+ update();
+ return;
+ }
+ m_active_href = {};
+ m_active_href_id = {};
+
+ if (m_triple_click_timer.is_valid() && m_triple_click_timer.elapsed() < 250) {
+ int start_column = 0;
+ int end_column = m_terminal.columns() - 1;
+
+ auto position = buffer_position_at(event.position());
+ m_selection.set({ position.row(), start_column }, { position.row(), end_column });
+ } else {
+ m_selection.set(buffer_position_at(event.position()), {});
+ }
+ if (m_alt_key_held)
+ m_rectangle_selection = true;
+ else if (m_rectangle_selection)
+ m_rectangle_selection = false;
+
+ update_copy_action();
+ update();
+ }
+}
+
+void TerminalWidget::mousemove_event(GUI::MouseEvent& event)
+{
+ auto position = buffer_position_at(event.position());
+
+ auto attribute = m_terminal.attribute_at(position);
+
+ if (attribute.href_id != m_hovered_href_id) {
+ if (m_active_href_id.is_null() || m_active_href_id == attribute.href_id) {
+ m_hovered_href_id = attribute.href_id;
+ m_hovered_href = attribute.href;
+ } else {
+ m_hovered_href_id = {};
+ m_hovered_href = {};
+ }
+ set_tooltip(m_hovered_href);
+ show_or_hide_tooltip();
+ if (!m_hovered_href.is_empty())
+ set_override_cursor(Gfx::StandardCursor::Arrow);
+ else
+ set_override_cursor(Gfx::StandardCursor::IBeam);
+ update();
+ }
+
+ if (!(event.buttons() & GUI::MouseButton::Left))
+ return;
+
+ if (!m_active_href_id.is_null()) {
+ auto diff = event.position() - m_left_mousedown_position;
+ auto distance_travelled_squared = diff.x() * diff.x() + diff.y() * diff.y();
+ constexpr int drag_distance_threshold = 5;
+
+ if (distance_travelled_squared <= drag_distance_threshold)
+ return;
+
+ auto drag_operation = GUI::DragOperation::construct();
+ drag_operation->set_text(m_active_href);
+ drag_operation->set_data("text/uri-list", m_active_href);
+ drag_operation->exec();
+
+ m_active_href = {};
+ m_active_href_id = {};
+ m_hovered_href = {};
+ m_hovered_href_id = {};
+ update();
+ return;
+ }
+
+ auto adjusted_position = event.position().translated(-(frame_thickness() + m_inset), -(frame_thickness() + m_inset));
+ if (adjusted_position.y() < 0)
+ m_auto_scroll_direction = AutoScrollDirection::Up;
+ else if (adjusted_position.y() > m_terminal.rows() * m_line_height)
+ m_auto_scroll_direction = AutoScrollDirection::Down;
+ else
+ m_auto_scroll_direction = AutoScrollDirection::None;
+
+ VT::Position old_selection_end = m_selection.end();
+ m_selection.set_end(position);
+ if (old_selection_end != m_selection.end()) {
+ update_copy_action();
+ update();
+ }
+}
+
+void TerminalWidget::leave_event(Core::Event&)
+{
+ bool should_update = !m_hovered_href.is_empty();
+ m_hovered_href = {};
+ m_hovered_href_id = {};
+ set_tooltip(m_hovered_href);
+ set_override_cursor(Gfx::StandardCursor::IBeam);
+ if (should_update)
+ update();
+}
+
+void TerminalWidget::mousewheel_event(GUI::MouseEvent& event)
+{
+ if (!is_scrollable())
+ return;
+ m_auto_scroll_direction = AutoScrollDirection::None;
+ m_scrollbar->set_value(m_scrollbar->value() + event.wheel_delta() * scroll_length());
+ GUI::Frame::mousewheel_event(event);
+}
+
+bool TerminalWidget::is_scrollable() const
+{
+ return m_scrollbar->is_scrollable();
+}
+
+int TerminalWidget::scroll_length() const
+{
+ return m_scrollbar->step();
+}
+
+String TerminalWidget::selected_text() const
+{
+ StringBuilder builder;
+
+ auto normalized_selection = m_selection.normalized();
+ auto start = normalized_selection.start();
+ auto end = normalized_selection.end();
+
+ for (int row = start.row(); row <= end.row(); ++row) {
+ int first_column = first_selection_column_on_row(row);
+ int last_column = last_selection_column_on_row(row);
+ for (int column = first_column; column <= last_column; ++column) {
+ auto& line = m_terminal.line(row);
+ if (line.attributes()[column].is_untouched()) {
+ builder.append('\n');
+ break;
+ }
+ // FIXME: This is a bit hackish.
+ if (line.is_utf32()) {
+ u32 code_point = line.code_point(column);
+ builder.append(Utf32View(&code_point, 1));
+ } else {
+ builder.append(line.code_point(column));
+ }
+ if (column == line.length() - 1 || (m_rectangle_selection && column == last_column)) {
+ builder.append('\n');
+ }
+ }
+ }
+
+ return builder.to_string();
+}
+
+int TerminalWidget::first_selection_column_on_row(int row) const
+{
+ auto normalized_selection_start = m_selection.normalized().start();
+ return row == normalized_selection_start.row() || m_rectangle_selection ? normalized_selection_start.column() : 0;
+}
+
+int TerminalWidget::last_selection_column_on_row(int row) const
+{
+ auto normalized_selection_end = m_selection.normalized().end();
+ return row == normalized_selection_end.row() || m_rectangle_selection ? normalized_selection_end.column() : m_terminal.columns() - 1;
+}
+
+void TerminalWidget::terminal_history_changed()
+{
+ bool was_max = m_scrollbar->value() == m_scrollbar->max();
+ m_scrollbar->set_max(m_terminal.history_size());
+ if (was_max)
+ m_scrollbar->set_value(m_scrollbar->max());
+ m_scrollbar->update();
+}
+
+void TerminalWidget::terminal_did_resize(u16 columns, u16 rows)
+{
+ auto pixel_size = widget_size_for_font(font());
+ m_pixel_width = pixel_size.width();
+ m_pixel_height = pixel_size.height();
+
+ if (m_automatic_size_policy) {
+ set_fixed_size(m_pixel_width, m_pixel_height);
+ }
+
+ m_needs_background_fill = true;
+ force_repaint();
+
+ winsize ws;
+ ws.ws_row = rows;
+ ws.ws_col = columns;
+ if (m_ptm_fd != -1) {
+ if (ioctl(m_ptm_fd, TIOCSWINSZ, &ws) < 0) {
+ // This can happen if we resize just as the shell exits.
+ dbgln("Notifying the pseudo-terminal about a size change failed.");
+ }
+ }
+}
+
+void TerminalWidget::beep()
+{
+ if (m_bell_mode == BellMode::Disabled) {
+ return;
+ }
+ if (m_bell_mode == BellMode::AudibleBeep) {
+ sysbeep();
+ return;
+ }
+ m_visual_beep_timer->restart(200);
+ m_visual_beep_timer->set_single_shot(true);
+ m_visual_beep_timer->on_timeout = [this] {
+ force_repaint();
+ };
+ force_repaint();
+}
+
+void TerminalWidget::emit(const u8* data, size_t size)
+{
+ if (write(m_ptm_fd, data, size) < 0) {
+ perror("TerminalWidget::emit: write");
+ }
+}
+
+void TerminalWidget::context_menu_event(GUI::ContextMenuEvent& event)
+{
+ if (m_hovered_href_id.is_null()) {
+ m_context_menu->popup(event.screen_position());
+ } else {
+ m_context_menu_href = m_hovered_href;
+
+ // Ask LaunchServer for a list of programs that can handle the right-clicked URL.
+ auto handlers = Desktop::Launcher::get_handlers_for_url(m_hovered_href);
+ if (handlers.is_empty()) {
+ m_context_menu->popup(event.screen_position());
+ return;
+ }
+
+ m_context_menu_for_hyperlink = GUI::Menu::construct();
+ RefPtr<GUI::Action> context_menu_default_action;
+
+ // Go through the list of handlers and see if we can find a nice display name + icon for them.
+ // Then add them to the context menu.
+ // FIXME: Adapt this code when we actually support calling LaunchServer with a specific handler in mind.
+ for (auto& handler : handlers) {
+ auto af = Desktop::AppFile::get_for_app(LexicalPath(handler).basename());
+ if (!af->is_valid())
+ continue;
+ auto action = GUI::Action::create(String::formatted("Open in {}", af->name()), af->icon().bitmap_for_size(16), [this, handler](auto&) {
+ Desktop::Launcher::open(m_context_menu_href, handler);
+ });
+
+ if (context_menu_default_action.is_null()) {
+ context_menu_default_action = action;
+ }
+
+ m_context_menu_for_hyperlink->add_action(action);
+ }
+ m_context_menu_for_hyperlink->add_action(GUI::Action::create("Copy URL", [this](auto&) {
+ GUI::Clipboard::the().set_plain_text(m_context_menu_href);
+ }));
+ m_context_menu_for_hyperlink->add_action(GUI::Action::create("Copy name", [&](auto&) {
+ // file://courage/home/anon/something -> /home/anon/something
+ auto path = URL(m_context_menu_href).path();
+ // /home/anon/something -> something
+ auto name = LexicalPath(path).basename();
+ GUI::Clipboard::the().set_plain_text(name);
+ }));
+ m_context_menu_for_hyperlink->add_separator();
+ m_context_menu_for_hyperlink->add_action(copy_action());
+ m_context_menu_for_hyperlink->add_action(paste_action());
+
+ m_context_menu_for_hyperlink->popup(event.screen_position(), context_menu_default_action);
+ }
+}
+
+void TerminalWidget::drop_event(GUI::DropEvent& event)
+{
+ if (event.mime_data().has_text()) {
+ event.accept();
+ auto text = event.mime_data().text();
+ write(m_ptm_fd, text.characters(), text.length());
+ } else if (event.mime_data().has_urls()) {
+ event.accept();
+ auto urls = event.mime_data().urls();
+ bool first = true;
+ for (auto& url : event.mime_data().urls()) {
+ if (!first) {
+ write(m_ptm_fd, " ", 1);
+ first = false;
+ }
+ if (url.protocol() == "file")
+ write(m_ptm_fd, url.path().characters(), url.path().length());
+ else
+ write(m_ptm_fd, url.to_string().characters(), url.to_string().length());
+ }
+ }
+}
+
+void TerminalWidget::did_change_font()
+{
+ GUI::Frame::did_change_font();
+ m_line_height = font().glyph_height() + m_line_spacing;
+
+ // TODO: try to find a bold version of the new font (e.g. CsillaThin7x10 -> CsillaBold7x10)
+ const Gfx::Font& bold_font = Gfx::FontDatabase::default_bold_fixed_width_font();
+
+ if (bold_font.glyph_height() == font().glyph_height() && bold_font.glyph_width(' ') == font().glyph_width(' '))
+ m_bold_font = &bold_font;
+ else
+ m_bold_font = font();
+
+ if (!size().is_empty())
+ relayout(size());
+}
+
+void TerminalWidget::clear_including_history()
+{
+ m_terminal.clear_including_history();
+}
+
+void TerminalWidget::scroll_to_bottom()
+{
+ m_scrollbar->set_value(m_scrollbar->max());
+}
+
+void TerminalWidget::scroll_to_row(int row)
+{
+ m_scrollbar->set_value(row);
+}
+
+void TerminalWidget::update_copy_action()
+{
+ m_copy_action->set_enabled(has_selection());
+}
+
+void TerminalWidget::update_paste_action()
+{
+ m_paste_action->set_enabled(GUI::Clipboard::the().mime_type().starts_with("text/") && !GUI::Clipboard::the().data().is_empty());
+}
+
+Gfx::IntSize TerminalWidget::widget_size_for_font(const Gfx::Font& font) const
+{
+ return {
+ (frame_thickness() * 2) + (m_inset * 2) + (m_terminal.columns() * font.glyph_width('x')) + m_scrollbar->width(),
+ (frame_thickness() * 2) + (m_inset * 2) + (m_terminal.rows() * (font.glyph_height() + m_line_spacing))
+ };
+}
+
+void TerminalWidget::set_font_and_resize_to_fit(const Gfx::Font& font)
+{
+ set_font(font);
+ resize(widget_size_for_font(font));
+}
diff --git a/Userland/Libraries/LibVT/TerminalWidget.h b/Userland/Libraries/LibVT/TerminalWidget.h
new file mode 100644
index 0000000000..a9a8370ecc
--- /dev/null
+++ b/Userland/Libraries/LibVT/TerminalWidget.h
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <LibCore/ConfigFile.h>
+#include <LibCore/ElapsedTimer.h>
+#include <LibCore/Notifier.h>
+#include <LibCore/Timer.h>
+#include <LibGUI/Frame.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Rect.h>
+#include <LibVT/Range.h>
+#include <LibVT/Terminal.h>
+
+class TerminalWidget final : public GUI::Frame
+ , public VT::TerminalClient {
+ C_OBJECT(TerminalWidget)
+public:
+ TerminalWidget(int ptm_fd, bool automatic_size_policy, RefPtr<Core::ConfigFile> config);
+ virtual ~TerminalWidget() override;
+
+ void set_pty_master_fd(int fd);
+ void inject_string(const StringView& string)
+ {
+ m_terminal.inject_string(string);
+ flush_dirty_lines();
+ }
+
+ void create_window();
+
+ void flush_dirty_lines();
+ void force_repaint();
+
+ void apply_size_increments_to_window(GUI::Window&);
+
+ const Gfx::Font& bold_font() const { return *m_bold_font; }
+
+ void set_opacity(u8);
+ float opacity() { return m_opacity; };
+
+ enum class BellMode {
+ Visible,
+ AudibleBeep,
+ Disabled
+ };
+
+ BellMode bell_mode() { return m_bell_mode; }
+ void set_bell_mode(BellMode bm) { m_bell_mode = bm; };
+
+ RefPtr<Core::ConfigFile> config() const { return m_config; }
+
+ bool has_selection() const;
+ bool selection_contains(const VT::Position&) const;
+ String selected_text() const;
+ VT::Range normalized_selection() const { return m_selection.normalized(); }
+ void set_selection(const VT::Range& selection);
+ VT::Position buffer_position_at(const Gfx::IntPoint&) const;
+
+ VT::Range find_next(const StringView&, const VT::Position& start = {}, bool case_sensitivity = false, bool should_wrap = false);
+ VT::Range find_previous(const StringView&, const VT::Position& start = {}, bool case_sensitivity = false, bool should_wrap = false);
+
+ void scroll_to_bottom();
+ void scroll_to_row(int);
+
+ bool is_scrollable() const;
+ int scroll_length() const;
+
+ size_t max_history_size() const { return m_terminal.max_history_size(); }
+ void set_max_history_size(size_t value) { m_terminal.set_max_history_size(value); }
+
+ GUI::Action& copy_action() { return *m_copy_action; }
+ GUI::Action& paste_action() { return *m_paste_action; }
+ GUI::Action& clear_including_history_action() { return *m_clear_including_history_action; }
+
+ void copy();
+ void paste();
+ void clear_including_history();
+
+ Function<void(const StringView&)> on_title_change;
+ Function<void()> on_command_exit;
+
+ GUI::Menu& context_menu() { return *m_context_menu; }
+
+ void set_font_and_resize_to_fit(const Gfx::Font&);
+
+private:
+ // ^GUI::Widget
+ virtual void event(Core::Event&) override;
+ virtual void paint_event(GUI::PaintEvent&) override;
+ virtual void resize_event(GUI::ResizeEvent&) override;
+ virtual void keydown_event(GUI::KeyEvent&) override;
+ virtual void keyup_event(GUI::KeyEvent&) override;
+ virtual void mousedown_event(GUI::MouseEvent&) override;
+ virtual void mouseup_event(GUI::MouseEvent&) override;
+ virtual void mousemove_event(GUI::MouseEvent&) override;
+ virtual void mousewheel_event(GUI::MouseEvent&) override;
+ virtual void doubleclick_event(GUI::MouseEvent&) override;
+ virtual void focusin_event(GUI::FocusEvent&) override;
+ virtual void focusout_event(GUI::FocusEvent&) override;
+ virtual void context_menu_event(GUI::ContextMenuEvent&) override;
+ virtual void drop_event(GUI::DropEvent&) override;
+ virtual void leave_event(Core::Event&) override;
+ virtual void did_change_font() override;
+
+ // ^TerminalClient
+ virtual void beep() override;
+ virtual void set_window_title(const StringView&) override;
+ virtual void set_window_progress(int value, int max) override;
+ virtual void terminal_did_resize(u16 columns, u16 rows) override;
+ virtual void terminal_history_changed() override;
+ virtual void emit(const u8*, size_t) override;
+
+ void set_logical_focus(bool);
+
+ Gfx::IntRect glyph_rect(u16 row, u16 column);
+ Gfx::IntRect row_rect(u16 row);
+
+ Gfx::IntSize widget_size_for_font(const Gfx::Font&) const;
+
+ void update_cursor();
+ void invalidate_cursor();
+
+ void relayout(const Gfx::IntSize&);
+
+ void update_copy_action();
+ void update_paste_action();
+
+ Gfx::IntSize compute_base_size() const;
+ int first_selection_column_on_row(int row) const;
+ int last_selection_column_on_row(int row) const;
+
+ u32 code_point_at(const VT::Position&) const;
+ VT::Position next_position_after(const VT::Position&, bool should_wrap) const;
+ VT::Position previous_position_before(const VT::Position&, bool should_wrap) const;
+
+ VT::Terminal m_terminal;
+
+ VT::Range m_selection;
+
+ String m_hovered_href;
+ String m_hovered_href_id;
+
+ String m_active_href;
+ String m_active_href_id;
+
+ // Snapshot of m_hovered_href when opening a context menu for a hyperlink.
+ String m_context_menu_href;
+
+ BellMode m_bell_mode { BellMode::Visible };
+ bool m_belling { false };
+ bool m_alt_key_held { false };
+ bool m_rectangle_selection { false };
+
+ int m_pixel_width { 0 };
+ int m_pixel_height { 0 };
+
+ int m_inset { 2 };
+ int m_line_spacing { 4 };
+ int m_line_height { 0 };
+
+ int m_ptm_fd { -1 };
+
+ bool m_has_logical_focus { false };
+
+ RefPtr<Core::Notifier> m_notifier;
+
+ u8 m_opacity { 255 };
+ bool m_needs_background_fill { true };
+ bool m_cursor_blink_state { true };
+ bool m_automatic_size_policy { false };
+
+ RefPtr<Gfx::Font> m_bold_font;
+
+ int m_glyph_width { 0 };
+
+ enum class AutoScrollDirection {
+ None,
+ Up,
+ Down
+ };
+
+ AutoScrollDirection m_auto_scroll_direction { AutoScrollDirection::None };
+
+ RefPtr<Core::Timer> m_cursor_blink_timer;
+ RefPtr<Core::Timer> m_visual_beep_timer;
+ RefPtr<Core::Timer> m_auto_scroll_timer;
+ RefPtr<Core::ConfigFile> m_config;
+
+ RefPtr<GUI::ScrollBar> m_scrollbar;
+
+ RefPtr<GUI::Action> m_copy_action;
+ RefPtr<GUI::Action> m_paste_action;
+ RefPtr<GUI::Action> m_clear_including_history_action;
+
+ RefPtr<GUI::Menu> m_context_menu;
+ RefPtr<GUI::Menu> m_context_menu_for_hyperlink;
+
+ Core::ElapsedTimer m_triple_click_timer;
+
+ Gfx::IntPoint m_left_mousedown_position;
+};
diff --git a/Userland/Libraries/LibVT/XtermColors.h b/Userland/Libraries/LibVT/XtermColors.h
new file mode 100644
index 0000000000..513fb75292
--- /dev/null
+++ b/Userland/Libraries/LibVT/XtermColors.h
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+static constexpr unsigned xterm_colors[256] = {
+ 0x000000,
+ 0xcc0000,
+ 0x3e9a06,
+ 0xc4a000,
+ 0x3465a4,
+ 0x75507b,
+ 0x06989a,
+ 0xeeeeec,
+ 0x555753,
+ 0xef2929,
+ 0x8ae234,
+ 0xfce94f,
+ 0x729fcf,
+ 0xad7fa8,
+ 0x34e2e2,
+ 0xFFFFFF,
+ 0x000000,
+ 0x00005f,
+ 0x000087,
+ 0x0000af,
+ 0x0000d7,
+ 0x0000ff,
+ 0x005f00,
+ 0x005f5f,
+ 0x005f87,
+ 0x005faf,
+ 0x005fd7,
+ 0x005fff,
+ 0x008700,
+ 0x00875f,
+ 0x008787,
+ 0x0087af,
+ 0x0087d7,
+ 0x0087ff,
+ 0x00af00,
+ 0x00af5f,
+ 0x00af87,
+ 0x00afaf,
+ 0x00afd7,
+ 0x00afff,
+ 0x00d700,
+ 0x00d75f,
+ 0x00d787,
+ 0x00d7af,
+ 0x00d7d7,
+ 0x00d7ff,
+ 0x00ff00,
+ 0x00ff5f,
+ 0x00ff87,
+ 0x00ffaf,
+ 0x00ffd7,
+ 0x00ffff,
+ 0x5f0000,
+ 0x5f005f,
+ 0x5f0087,
+ 0x5f00af,
+ 0x5f00d7,
+ 0x5f00ff,
+ 0x5f5f00,
+ 0x5f5f5f,
+ 0x5f5f87,
+ 0x5f5faf,
+ 0x5f5fd7,
+ 0x5f5fff,
+ 0x5f8700,
+ 0x5f875f,
+ 0x5f8787,
+ 0x5f87af,
+ 0x5f87d7,
+ 0x5f87ff,
+ 0x5faf00,
+ 0x5faf5f,
+ 0x5faf87,
+ 0x5fafaf,
+ 0x5fafd7,
+ 0x5fafff,
+ 0x5fd700,
+ 0x5fd75f,
+ 0x5fd787,
+ 0x5fd7af,
+ 0x5fd7d7,
+ 0x5fd7ff,
+ 0x5fff00,
+ 0x5fff5f,
+ 0x5fff87,
+ 0x5fffaf,
+ 0x5fffd7,
+ 0x5fffff,
+ 0x870000,
+ 0x87005f,
+ 0x870087,
+ 0x8700af,
+ 0x8700d7,
+ 0x8700ff,
+ 0x875f00,
+ 0x875f5f,
+ 0x875f87,
+ 0x875faf,
+ 0x875fd7,
+ 0x875fff,
+ 0x878700,
+ 0x87875f,
+ 0x878787,
+ 0x8787af,
+ 0x8787d7,
+ 0x8787ff,
+ 0x87af00,
+ 0x87af5f,
+ 0x87af87,
+ 0x87afaf,
+ 0x87afd7,
+ 0x87afff,
+ 0x87d700,
+ 0x87d75f,
+ 0x87d787,
+ 0x87d7af,
+ 0x87d7d7,
+ 0x87d7ff,
+ 0x87ff00,
+ 0x87ff5f,
+ 0x87ff87,
+ 0x87ffaf,
+ 0x87ffd7,
+ 0x87ffff,
+ 0xaf0000,
+ 0xaf005f,
+ 0xaf0087,
+ 0xaf00af,
+ 0xaf00d7,
+ 0xaf00ff,
+ 0xaf5f00,
+ 0xaf5f5f,
+ 0xaf5f87,
+ 0xaf5faf,
+ 0xaf5fd7,
+ 0xaf5fff,
+ 0xaf8700,
+ 0xaf875f,
+ 0xaf8787,
+ 0xaf87af,
+ 0xaf87d7,
+ 0xaf87ff,
+ 0xafaf00,
+ 0xafaf5f,
+ 0xafaf87,
+ 0xafafaf,
+ 0xafafd7,
+ 0xafafff,
+ 0xafd700,
+ 0xafd75f,
+ 0xafd787,
+ 0xafd7af,
+ 0xafd7d7,
+ 0xafd7ff,
+ 0xafff00,
+ 0xafff5f,
+ 0xafff87,
+ 0xafffaf,
+ 0xafffd7,
+ 0xafffff,
+ 0xd70000,
+ 0xd7005f,
+ 0xd70087,
+ 0xd700af,
+ 0xd700d7,
+ 0xd700ff,
+ 0xd75f00,
+ 0xd75f5f,
+ 0xd75f87,
+ 0xd75faf,
+ 0xd75fd7,
+ 0xd75fff,
+ 0xd78700,
+ 0xd7875f,
+ 0xd78787,
+ 0xd787af,
+ 0xd787d7,
+ 0xd787ff,
+ 0xd7af00,
+ 0xd7af5f,
+ 0xd7af87,
+ 0xd7afaf,
+ 0xd7afd7,
+ 0xd7afff,
+ 0xd7d700,
+ 0xd7d75f,
+ 0xd7d787,
+ 0xd7d7af,
+ 0xd7d7d7,
+ 0xd7d7ff,
+ 0xd7ff00,
+ 0xd7ff5f,
+ 0xd7ff87,
+ 0xd7ffaf,
+ 0xd7ffd7,
+ 0xd7ffff,
+ 0xff0000,
+ 0xff005f,
+ 0xff0087,
+ 0xff00af,
+ 0xff00d7,
+ 0xff00ff,
+ 0xff5f00,
+ 0xff5f5f,
+ 0xff5f87,
+ 0xff5faf,
+ 0xff5fd7,
+ 0xff5fff,
+ 0xff8700,
+ 0xff875f,
+ 0xff8787,
+ 0xff87af,
+ 0xff87d7,
+ 0xff87ff,
+ 0xffaf00,
+ 0xffaf5f,
+ 0xffaf87,
+ 0xffafaf,
+ 0xffafd7,
+ 0xffafff,
+ 0xffd700,
+ 0xffd75f,
+ 0xffd787,
+ 0xffd7af,
+ 0xffd7d7,
+ 0xffd7ff,
+ 0xffff00,
+ 0xffff5f,
+ 0xffff87,
+ 0xffffaf,
+ 0xffffd7,
+ 0xffffff,
+ 0x080808,
+ 0x121212,
+ 0x1c1c1c,
+ 0x262626,
+ 0x303030,
+ 0x3a3a3a,
+ 0x444444,
+ 0x4e4e4e,
+ 0x585858,
+ 0x626262,
+ 0x6c6c6c,
+ 0x767676,
+ 0x808080,
+ 0x8a8a8a,
+ 0x949494,
+ 0x9e9e9e,
+ 0xa8a8a8,
+ 0xb2b2b2,
+ 0xbcbcbc,
+ 0xc6c6c6,
+ 0xd0d0d0,
+ 0xdadada,
+ 0xe4e4e4,
+ 0xeeeeee,
+};
diff --git a/Userland/Libraries/LibWeb/Bindings/EventListenerWrapper.cpp b/Userland/Libraries/LibWeb/Bindings/EventListenerWrapper.cpp
new file mode 100644
index 0000000000..2f9cbac2c2
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/EventListenerWrapper.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/Function.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibWeb/Bindings/EventListenerWrapper.h>
+#include <LibWeb/DOM/EventListener.h>
+
+namespace Web {
+namespace Bindings {
+
+EventListenerWrapper::EventListenerWrapper(JS::GlobalObject& global_object, DOM::EventListener& impl)
+ : Wrapper(*global_object.object_prototype())
+ , m_impl(impl)
+{
+}
+
+EventListenerWrapper::~EventListenerWrapper()
+{
+}
+
+}
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/EventListenerWrapper.h b/Userland/Libraries/LibWeb/Bindings/EventListenerWrapper.h
new file mode 100644
index 0000000000..fdd74108ea
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/EventListenerWrapper.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Bindings/Wrapper.h>
+
+namespace Web {
+namespace Bindings {
+
+class EventListenerWrapper final : public Wrapper {
+ JS_OBJECT(EventListenerWrapper, Wrapper);
+
+public:
+ EventListenerWrapper(JS::GlobalObject&, DOM::EventListener&);
+ virtual ~EventListenerWrapper() override;
+
+ DOM::EventListener& impl() { return *m_impl; }
+ const DOM::EventListener& impl() const { return *m_impl; }
+
+private:
+ NonnullRefPtr<DOM::EventListener> m_impl;
+};
+
+}
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/EventTargetWrapperFactory.cpp b/Userland/Libraries/LibWeb/Bindings/EventTargetWrapperFactory.cpp
new file mode 100644
index 0000000000..4a6e6344e9
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/EventTargetWrapperFactory.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Bindings/EventTargetWrapperFactory.h>
+#include <LibWeb/DOM/EventTarget.h>
+
+namespace Web::Bindings {
+
+EventTargetWrapper* wrap(JS::GlobalObject& global_object, DOM::EventTarget& target)
+{
+ return target.create_wrapper(global_object);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/EventTargetWrapperFactory.h b/Userland/Libraries/LibWeb/Bindings/EventTargetWrapperFactory.h
new file mode 100644
index 0000000000..c28c7724ca
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/EventTargetWrapperFactory.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Forward.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::Bindings {
+
+EventTargetWrapper* wrap(JS::GlobalObject&, DOM::EventTarget&);
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/EventWrapperFactory.cpp b/Userland/Libraries/LibWeb/Bindings/EventWrapperFactory.cpp
new file mode 100644
index 0000000000..3064799cf0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/EventWrapperFactory.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Bindings/EventWrapper.h>
+#include <LibWeb/Bindings/EventWrapperFactory.h>
+#include <LibWeb/Bindings/MouseEventWrapper.h>
+
+namespace Web {
+namespace Bindings {
+
+EventWrapper* wrap(JS::GlobalObject& global_object, DOM::Event& event)
+{
+ if (is<UIEvents::MouseEvent>(event))
+ return static_cast<MouseEventWrapper*>(wrap_impl(global_object, static_cast<UIEvents::MouseEvent&>(event)));
+ return static_cast<EventWrapper*>(wrap_impl(global_object, event));
+}
+
+}
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/EventWrapperFactory.h b/Userland/Libraries/LibWeb/Bindings/EventWrapperFactory.h
new file mode 100644
index 0000000000..ebe82c5e88
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/EventWrapperFactory.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Forward.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::Bindings {
+
+EventWrapper* wrap(JS::GlobalObject&, DOM::Event&);
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/LocationObject.cpp b/Userland/Libraries/LibWeb/Bindings/LocationObject.cpp
new file mode 100644
index 0000000000..93d00d8af7
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/LocationObject.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/FlyString.h>
+#include <AK/StringBuilder.h>
+#include <LibWeb/Bindings/LocationObject.h>
+#include <LibWeb/Bindings/WindowObject.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Window.h>
+
+namespace Web {
+namespace Bindings {
+
+LocationObject::LocationObject(JS::GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void LocationObject::initialize(JS::GlobalObject& global_object)
+{
+ Object::initialize(global_object);
+ u8 attr = JS::Attribute::Writable | JS::Attribute::Enumerable;
+ define_native_property("href", href_getter, href_setter, attr);
+ define_native_property("host", host_getter, nullptr, attr);
+ define_native_property("hostname", hostname_getter, nullptr, attr);
+ define_native_property("pathname", pathname_getter, nullptr, attr);
+ define_native_property("hash", hash_getter, nullptr, attr);
+ define_native_property("search", search_getter, nullptr, attr);
+ define_native_property("protocol", protocol_getter, nullptr, attr);
+
+ define_native_function("reload", reload, 0, JS::Attribute::Enumerable);
+}
+
+LocationObject::~LocationObject()
+{
+}
+
+JS_DEFINE_NATIVE_GETTER(LocationObject::href_getter)
+{
+ auto& window = static_cast<WindowObject&>(global_object);
+ return JS::js_string(vm, window.impl().document().url().to_string());
+}
+
+JS_DEFINE_NATIVE_SETTER(LocationObject::href_setter)
+{
+ auto& window = static_cast<WindowObject&>(global_object);
+ auto new_href = value.to_string(global_object);
+ if (vm.exception())
+ return;
+ auto href_url = window.impl().document().complete_url(new_href);
+ if (!href_url.is_valid()) {
+ vm.throw_exception<JS::URIError>(global_object, String::formatted("Invalid URL '{}'", new_href));
+ return;
+ }
+ window.impl().did_set_location_href({}, href_url);
+}
+
+JS_DEFINE_NATIVE_GETTER(LocationObject::pathname_getter)
+{
+ auto& window = static_cast<WindowObject&>(global_object);
+ return JS::js_string(vm, window.impl().document().url().path());
+}
+
+JS_DEFINE_NATIVE_GETTER(LocationObject::hostname_getter)
+{
+ auto& window = static_cast<WindowObject&>(global_object);
+ return JS::js_string(vm, window.impl().document().url().host());
+}
+
+JS_DEFINE_NATIVE_GETTER(LocationObject::host_getter)
+{
+ auto& window = static_cast<WindowObject&>(global_object);
+ auto url = window.impl().document().url();
+ StringBuilder builder;
+ builder.append(url.host());
+ builder.append(':');
+ builder.appendf("%u", url.port());
+ return JS::js_string(vm, builder.to_string());
+}
+
+JS_DEFINE_NATIVE_GETTER(LocationObject::hash_getter)
+{
+ auto& window = static_cast<WindowObject&>(global_object);
+ auto fragment = window.impl().document().url().fragment();
+ if (!fragment.length())
+ return JS::js_string(vm, "");
+ StringBuilder builder;
+ builder.append('#');
+ builder.append(fragment);
+ return JS::js_string(vm, builder.to_string());
+}
+
+JS_DEFINE_NATIVE_GETTER(LocationObject::search_getter)
+{
+ auto& window = static_cast<WindowObject&>(global_object);
+ auto query = window.impl().document().url().query();
+ if (!query.length())
+ return JS::js_string(vm, "");
+ StringBuilder builder;
+ builder.append('?');
+ builder.append(query);
+ return JS::js_string(vm, builder.to_string());
+}
+
+JS_DEFINE_NATIVE_GETTER(LocationObject::protocol_getter)
+{
+ auto& window = static_cast<WindowObject&>(global_object);
+ StringBuilder builder;
+ builder.append(window.impl().document().url().protocol());
+ builder.append(':');
+ return JS::js_string(vm, builder.to_string());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(LocationObject::reload)
+{
+ auto& window = static_cast<WindowObject&>(global_object);
+ window.impl().did_call_location_reload({});
+ return JS::js_undefined();
+}
+
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/LocationObject.h b/Userland/Libraries/LibWeb/Bindings/LocationObject.h
new file mode 100644
index 0000000000..76a38946c1
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/LocationObject.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+#include <LibWeb/Forward.h>
+
+namespace Web {
+namespace Bindings {
+
+class LocationObject final : public JS::Object {
+ JS_OBJECT(LocationObject, JS::Object);
+
+public:
+ explicit LocationObject(JS::GlobalObject&);
+ virtual void initialize(JS::GlobalObject&) override;
+ virtual ~LocationObject() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(reload);
+
+ JS_DECLARE_NATIVE_GETTER(href_getter);
+ JS_DECLARE_NATIVE_SETTER(href_setter);
+
+ JS_DECLARE_NATIVE_GETTER(host_getter);
+ JS_DECLARE_NATIVE_GETTER(hostname_getter);
+ JS_DECLARE_NATIVE_GETTER(pathname_getter);
+ JS_DECLARE_NATIVE_GETTER(hash_getter);
+ JS_DECLARE_NATIVE_GETTER(search_getter);
+ JS_DECLARE_NATIVE_GETTER(protocol_getter);
+};
+
+}
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/NavigatorObject.cpp b/Userland/Libraries/LibWeb/Bindings/NavigatorObject.cpp
new file mode 100644
index 0000000000..248097d8e3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/NavigatorObject.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/FlyString.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibWeb/Bindings/NavigatorObject.h>
+#include <LibWeb/Loader/ResourceLoader.h>
+
+namespace Web {
+namespace Bindings {
+
+NavigatorObject::NavigatorObject(JS::GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void NavigatorObject::initialize(JS::GlobalObject& global_object)
+{
+ auto& heap = this->heap();
+ auto* languages = JS::Array::create(global_object);
+ languages->indexed_properties().append(js_string(heap, "en-US"));
+
+ define_property("appCodeName", js_string(heap, "Mozilla"));
+ define_property("appName", js_string(heap, "Netscape"));
+ define_property("appVersion", js_string(heap, "4.0"));
+ define_property("language", languages->get(0));
+ define_property("languages", languages);
+ define_property("platform", js_string(heap, "SerenityOS"));
+ define_property("product", js_string(heap, "Gecko"));
+
+ define_native_property("userAgent", user_agent_getter, nullptr);
+}
+
+NavigatorObject::~NavigatorObject()
+{
+}
+
+JS_DEFINE_NATIVE_GETTER(NavigatorObject::user_agent_getter)
+{
+ return JS::js_string(vm, ResourceLoader::the().user_agent());
+}
+
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/NavigatorObject.h b/Userland/Libraries/LibWeb/Bindings/NavigatorObject.h
new file mode 100644
index 0000000000..fe10271e96
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/NavigatorObject.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+#include <LibWeb/Forward.h>
+
+namespace Web {
+namespace Bindings {
+
+class NavigatorObject final : public JS::Object {
+ JS_OBJECT(NavigatorObject, JS::Object);
+
+public:
+ NavigatorObject(JS::GlobalObject&);
+ virtual void initialize(JS::GlobalObject&) override;
+ virtual ~NavigatorObject() override;
+
+private:
+ JS_DECLARE_NATIVE_GETTER(user_agent_getter);
+};
+
+}
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/NodeWrapperFactory.cpp b/Userland/Libraries/LibWeb/Bindings/NodeWrapperFactory.cpp
new file mode 100644
index 0000000000..f78491d33a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/NodeWrapperFactory.cpp
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Bindings/CharacterDataWrapper.h>
+#include <LibWeb/Bindings/CommentWrapper.h>
+#include <LibWeb/Bindings/DocumentFragmentWrapper.h>
+#include <LibWeb/Bindings/DocumentTypeWrapper.h>
+#include <LibWeb/Bindings/DocumentWrapper.h>
+#include <LibWeb/Bindings/HTMLAnchorElementWrapper.h>
+#include <LibWeb/Bindings/HTMLAreaElementWrapper.h>
+#include <LibWeb/Bindings/HTMLAudioElementWrapper.h>
+#include <LibWeb/Bindings/HTMLBRElementWrapper.h>
+#include <LibWeb/Bindings/HTMLBaseElementWrapper.h>
+#include <LibWeb/Bindings/HTMLBodyElementWrapper.h>
+#include <LibWeb/Bindings/HTMLButtonElementWrapper.h>
+#include <LibWeb/Bindings/HTMLCanvasElementWrapper.h>
+#include <LibWeb/Bindings/HTMLDListElementWrapper.h>
+#include <LibWeb/Bindings/HTMLDataElementWrapper.h>
+#include <LibWeb/Bindings/HTMLDataListElementWrapper.h>
+#include <LibWeb/Bindings/HTMLDetailsElementWrapper.h>
+#include <LibWeb/Bindings/HTMLDialogElementWrapper.h>
+#include <LibWeb/Bindings/HTMLDirectoryElementWrapper.h>
+#include <LibWeb/Bindings/HTMLDivElementWrapper.h>
+#include <LibWeb/Bindings/HTMLElementWrapper.h>
+#include <LibWeb/Bindings/HTMLEmbedElementWrapper.h>
+#include <LibWeb/Bindings/HTMLFieldSetElementWrapper.h>
+#include <LibWeb/Bindings/HTMLFontElementWrapper.h>
+#include <LibWeb/Bindings/HTMLFormElementWrapper.h>
+#include <LibWeb/Bindings/HTMLFrameElementWrapper.h>
+#include <LibWeb/Bindings/HTMLFrameSetElementWrapper.h>
+#include <LibWeb/Bindings/HTMLHRElementWrapper.h>
+#include <LibWeb/Bindings/HTMLHeadElementWrapper.h>
+#include <LibWeb/Bindings/HTMLHeadingElementWrapper.h>
+#include <LibWeb/Bindings/HTMLHtmlElementWrapper.h>
+#include <LibWeb/Bindings/HTMLIFrameElementWrapper.h>
+#include <LibWeb/Bindings/HTMLImageElementWrapper.h>
+#include <LibWeb/Bindings/HTMLInputElementWrapper.h>
+#include <LibWeb/Bindings/HTMLLIElementWrapper.h>
+#include <LibWeb/Bindings/HTMLLabelElementWrapper.h>
+#include <LibWeb/Bindings/HTMLLegendElementWrapper.h>
+#include <LibWeb/Bindings/HTMLLinkElementWrapper.h>
+#include <LibWeb/Bindings/HTMLMapElementWrapper.h>
+#include <LibWeb/Bindings/HTMLMarqueeElementWrapper.h>
+#include <LibWeb/Bindings/HTMLMenuElementWrapper.h>
+#include <LibWeb/Bindings/HTMLMetaElementWrapper.h>
+#include <LibWeb/Bindings/HTMLMeterElementWrapper.h>
+#include <LibWeb/Bindings/HTMLModElementWrapper.h>
+#include <LibWeb/Bindings/HTMLOListElementWrapper.h>
+#include <LibWeb/Bindings/HTMLObjectElementWrapper.h>
+#include <LibWeb/Bindings/HTMLOptGroupElementWrapper.h>
+#include <LibWeb/Bindings/HTMLOptionElementWrapper.h>
+#include <LibWeb/Bindings/HTMLOutputElementWrapper.h>
+#include <LibWeb/Bindings/HTMLParagraphElementWrapper.h>
+#include <LibWeb/Bindings/HTMLParamElementWrapper.h>
+#include <LibWeb/Bindings/HTMLPictureElementWrapper.h>
+#include <LibWeb/Bindings/HTMLPreElementWrapper.h>
+#include <LibWeb/Bindings/HTMLProgressElementWrapper.h>
+#include <LibWeb/Bindings/HTMLQuoteElementWrapper.h>
+#include <LibWeb/Bindings/HTMLScriptElementWrapper.h>
+#include <LibWeb/Bindings/HTMLSelectElementWrapper.h>
+#include <LibWeb/Bindings/HTMLSlotElementWrapper.h>
+#include <LibWeb/Bindings/HTMLSourceElementWrapper.h>
+#include <LibWeb/Bindings/HTMLSpanElementWrapper.h>
+#include <LibWeb/Bindings/HTMLStyleElementWrapper.h>
+#include <LibWeb/Bindings/HTMLTableCaptionElementWrapper.h>
+#include <LibWeb/Bindings/HTMLTableCellElementWrapper.h>
+#include <LibWeb/Bindings/HTMLTableColElementWrapper.h>
+#include <LibWeb/Bindings/HTMLTableElementWrapper.h>
+#include <LibWeb/Bindings/HTMLTableRowElementWrapper.h>
+#include <LibWeb/Bindings/HTMLTableSectionElementWrapper.h>
+#include <LibWeb/Bindings/HTMLTemplateElementWrapper.h>
+#include <LibWeb/Bindings/HTMLTextAreaElementWrapper.h>
+#include <LibWeb/Bindings/HTMLTimeElementWrapper.h>
+#include <LibWeb/Bindings/HTMLTitleElementWrapper.h>
+#include <LibWeb/Bindings/HTMLTrackElementWrapper.h>
+#include <LibWeb/Bindings/HTMLUListElementWrapper.h>
+#include <LibWeb/Bindings/HTMLUnknownElementWrapper.h>
+#include <LibWeb/Bindings/HTMLVideoElementWrapper.h>
+#include <LibWeb/Bindings/NodeWrapper.h>
+#include <LibWeb/Bindings/NodeWrapperFactory.h>
+#include <LibWeb/Bindings/SVGPathElementWrapper.h>
+#include <LibWeb/Bindings/SVGSVGElementWrapper.h>
+#include <LibWeb/Bindings/TextWrapper.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Node.h>
+#include <LibWeb/HTML/HTMLAnchorElement.h>
+#include <LibWeb/HTML/HTMLAreaElement.h>
+#include <LibWeb/HTML/HTMLAudioElement.h>
+#include <LibWeb/HTML/HTMLBRElement.h>
+#include <LibWeb/HTML/HTMLBaseElement.h>
+#include <LibWeb/HTML/HTMLBodyElement.h>
+#include <LibWeb/HTML/HTMLButtonElement.h>
+#include <LibWeb/HTML/HTMLCanvasElement.h>
+#include <LibWeb/HTML/HTMLDListElement.h>
+#include <LibWeb/HTML/HTMLDataElement.h>
+#include <LibWeb/HTML/HTMLDataListElement.h>
+#include <LibWeb/HTML/HTMLDetailsElement.h>
+#include <LibWeb/HTML/HTMLDialogElement.h>
+#include <LibWeb/HTML/HTMLDirectoryElement.h>
+#include <LibWeb/HTML/HTMLDivElement.h>
+#include <LibWeb/HTML/HTMLEmbedElement.h>
+#include <LibWeb/HTML/HTMLFieldSetElement.h>
+#include <LibWeb/HTML/HTMLFontElement.h>
+#include <LibWeb/HTML/HTMLFormElement.h>
+#include <LibWeb/HTML/HTMLFrameElement.h>
+#include <LibWeb/HTML/HTMLFrameSetElement.h>
+#include <LibWeb/HTML/HTMLHRElement.h>
+#include <LibWeb/HTML/HTMLHeadElement.h>
+#include <LibWeb/HTML/HTMLHeadingElement.h>
+#include <LibWeb/HTML/HTMLHtmlElement.h>
+#include <LibWeb/HTML/HTMLIFrameElement.h>
+#include <LibWeb/HTML/HTMLImageElement.h>
+#include <LibWeb/HTML/HTMLInputElement.h>
+#include <LibWeb/HTML/HTMLLIElement.h>
+#include <LibWeb/HTML/HTMLLabelElement.h>
+#include <LibWeb/HTML/HTMLLegendElement.h>
+#include <LibWeb/HTML/HTMLLinkElement.h>
+#include <LibWeb/HTML/HTMLMapElement.h>
+#include <LibWeb/HTML/HTMLMarqueeElement.h>
+#include <LibWeb/HTML/HTMLMenuElement.h>
+#include <LibWeb/HTML/HTMLMetaElement.h>
+#include <LibWeb/HTML/HTMLMeterElement.h>
+#include <LibWeb/HTML/HTMLModElement.h>
+#include <LibWeb/HTML/HTMLOListElement.h>
+#include <LibWeb/HTML/HTMLObjectElement.h>
+#include <LibWeb/HTML/HTMLOptGroupElement.h>
+#include <LibWeb/HTML/HTMLOptionElement.h>
+#include <LibWeb/HTML/HTMLOutputElement.h>
+#include <LibWeb/HTML/HTMLParagraphElement.h>
+#include <LibWeb/HTML/HTMLParamElement.h>
+#include <LibWeb/HTML/HTMLPictureElement.h>
+#include <LibWeb/HTML/HTMLPreElement.h>
+#include <LibWeb/HTML/HTMLProgressElement.h>
+#include <LibWeb/HTML/HTMLQuoteElement.h>
+#include <LibWeb/HTML/HTMLScriptElement.h>
+#include <LibWeb/HTML/HTMLSelectElement.h>
+#include <LibWeb/HTML/HTMLSlotElement.h>
+#include <LibWeb/HTML/HTMLSourceElement.h>
+#include <LibWeb/HTML/HTMLSpanElement.h>
+#include <LibWeb/HTML/HTMLStyleElement.h>
+#include <LibWeb/HTML/HTMLTableCaptionElement.h>
+#include <LibWeb/HTML/HTMLTableCellElement.h>
+#include <LibWeb/HTML/HTMLTableColElement.h>
+#include <LibWeb/HTML/HTMLTableElement.h>
+#include <LibWeb/HTML/HTMLTableRowElement.h>
+#include <LibWeb/HTML/HTMLTableSectionElement.h>
+#include <LibWeb/HTML/HTMLTemplateElement.h>
+#include <LibWeb/HTML/HTMLTextAreaElement.h>
+#include <LibWeb/HTML/HTMLTimeElement.h>
+#include <LibWeb/HTML/HTMLTitleElement.h>
+#include <LibWeb/HTML/HTMLTrackElement.h>
+#include <LibWeb/HTML/HTMLUListElement.h>
+#include <LibWeb/HTML/HTMLUnknownElement.h>
+#include <LibWeb/HTML/HTMLVideoElement.h>
+#include <LibWeb/SVG/SVGPathElement.h>
+#include <LibWeb/SVG/SVGSVGElement.h>
+
+namespace Web::Bindings {
+
+NodeWrapper* wrap(JS::GlobalObject& global_object, DOM::Node& node)
+{
+ if (is<DOM::Document>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<DOM::Document>(node)));
+ if (is<DOM::DocumentType>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<DOM::DocumentType>(node)));
+ if (is<HTML::HTMLAnchorElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLAnchorElement>(node)));
+ if (is<HTML::HTMLAreaElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLAreaElement>(node)));
+ if (is<HTML::HTMLAudioElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLAudioElement>(node)));
+ if (is<HTML::HTMLBaseElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLBaseElement>(node)));
+ if (is<HTML::HTMLBodyElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLBodyElement>(node)));
+ if (is<HTML::HTMLBRElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLBRElement>(node)));
+ if (is<HTML::HTMLButtonElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLButtonElement>(node)));
+ if (is<HTML::HTMLCanvasElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLCanvasElement>(node)));
+ if (is<HTML::HTMLDataElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLDataElement>(node)));
+ if (is<HTML::HTMLDataListElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLDataListElement>(node)));
+ if (is<HTML::HTMLDetailsElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLDetailsElement>(node)));
+ if (is<HTML::HTMLDialogElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLDialogElement>(node)));
+ if (is<HTML::HTMLDirectoryElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLDirectoryElement>(node)));
+ if (is<HTML::HTMLDivElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLDivElement>(node)));
+ if (is<HTML::HTMLDListElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLDListElement>(node)));
+ if (is<HTML::HTMLEmbedElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLEmbedElement>(node)));
+ if (is<HTML::HTMLFieldSetElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLFieldSetElement>(node)));
+ if (is<HTML::HTMLFontElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLFontElement>(node)));
+ if (is<HTML::HTMLFormElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLFormElement>(node)));
+ if (is<HTML::HTMLFrameElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLFrameElement>(node)));
+ if (is<HTML::HTMLFrameSetElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLFrameSetElement>(node)));
+ if (is<HTML::HTMLHeadElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLHeadElement>(node)));
+ if (is<HTML::HTMLHeadingElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLHeadingElement>(node)));
+ if (is<HTML::HTMLHRElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLHRElement>(node)));
+ if (is<HTML::HTMLHtmlElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLHtmlElement>(node)));
+ if (is<HTML::HTMLIFrameElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLIFrameElement>(node)));
+ if (is<HTML::HTMLImageElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLImageElement>(node)));
+ if (is<HTML::HTMLInputElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLInputElement>(node)));
+ if (is<HTML::HTMLLabelElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLLabelElement>(node)));
+ if (is<HTML::HTMLLegendElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLLegendElement>(node)));
+ if (is<HTML::HTMLLIElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLLIElement>(node)));
+ if (is<HTML::HTMLLinkElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLLinkElement>(node)));
+ if (is<HTML::HTMLMapElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLMapElement>(node)));
+ if (is<HTML::HTMLMarqueeElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLMarqueeElement>(node)));
+ if (is<HTML::HTMLMenuElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLMenuElement>(node)));
+ if (is<HTML::HTMLMetaElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLMetaElement>(node)));
+ if (is<HTML::HTMLMeterElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLMeterElement>(node)));
+ if (is<HTML::HTMLModElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLModElement>(node)));
+ if (is<HTML::HTMLObjectElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLObjectElement>(node)));
+ if (is<HTML::HTMLOListElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLOListElement>(node)));
+ if (is<HTML::HTMLOptGroupElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLOptGroupElement>(node)));
+ if (is<HTML::HTMLOptionElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLOptionElement>(node)));
+ if (is<HTML::HTMLOutputElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLOutputElement>(node)));
+ if (is<HTML::HTMLParagraphElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLParagraphElement>(node)));
+ if (is<HTML::HTMLParamElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLParamElement>(node)));
+ if (is<HTML::HTMLPictureElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLPictureElement>(node)));
+ if (is<HTML::HTMLPreElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLPreElement>(node)));
+ if (is<HTML::HTMLProgressElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLProgressElement>(node)));
+ if (is<HTML::HTMLQuoteElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLQuoteElement>(node)));
+ if (is<HTML::HTMLScriptElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLScriptElement>(node)));
+ if (is<HTML::HTMLSelectElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLSelectElement>(node)));
+ if (is<HTML::HTMLSlotElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLSlotElement>(node)));
+ if (is<HTML::HTMLSourceElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLSourceElement>(node)));
+ if (is<HTML::HTMLSpanElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLSpanElement>(node)));
+ if (is<HTML::HTMLStyleElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLStyleElement>(node)));
+ if (is<HTML::HTMLTableCaptionElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLTableCaptionElement>(node)));
+ if (is<HTML::HTMLTableCellElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLTableCellElement>(node)));
+ if (is<HTML::HTMLTableColElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLTableColElement>(node)));
+ if (is<HTML::HTMLTableElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLTableElement>(node)));
+ if (is<HTML::HTMLTableRowElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLTableRowElement>(node)));
+ if (is<HTML::HTMLTableSectionElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLTableSectionElement>(node)));
+ if (is<HTML::HTMLTemplateElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLTemplateElement>(node)));
+ if (is<HTML::HTMLTextAreaElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLTextAreaElement>(node)));
+ if (is<HTML::HTMLTimeElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLTimeElement>(node)));
+ if (is<HTML::HTMLTitleElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLTitleElement>(node)));
+ if (is<HTML::HTMLTrackElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLTrackElement>(node)));
+ if (is<HTML::HTMLUListElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLUListElement>(node)));
+ if (is<HTML::HTMLUnknownElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLUnknownElement>(node)));
+ if (is<HTML::HTMLVideoElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLVideoElement>(node)));
+ if (is<HTML::HTMLElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<HTML::HTMLElement>(node)));
+ if (is<SVG::SVGSVGElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<SVG::SVGSVGElement>(node)));
+ if (is<SVG::SVGPathElement>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<SVG::SVGPathElement>(node)));
+ if (is<DOM::Element>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<DOM::Element>(node)));
+ if (is<DOM::DocumentFragment>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<DOM::DocumentFragment>(node)));
+ if (is<DOM::Comment>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<DOM::Comment>(node)));
+ if (is<DOM::Text>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<DOM::Text>(node)));
+ if (is<DOM::CharacterData>(node))
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, downcast<DOM::CharacterData>(node)));
+ return static_cast<NodeWrapper*>(wrap_impl(global_object, node));
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/NodeWrapperFactory.h b/Userland/Libraries/LibWeb/Bindings/NodeWrapperFactory.h
new file mode 100644
index 0000000000..153e78cb32
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/NodeWrapperFactory.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Forward.h>
+#include <LibWeb/Forward.h>
+
+namespace Web {
+namespace Bindings {
+
+NodeWrapper* wrap(JS::GlobalObject&, DOM::Node&);
+
+}
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/RangeConstructor.cpp b/Userland/Libraries/LibWeb/Bindings/RangeConstructor.cpp
new file mode 100644
index 0000000000..27311c4420
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/RangeConstructor.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Heap/Heap.h>
+#include <LibWeb/Bindings/RangeConstructor.h>
+#include <LibWeb/Bindings/RangePrototype.h>
+#include <LibWeb/Bindings/RangeWrapper.h>
+#include <LibWeb/Bindings/WindowObject.h>
+#include <LibWeb/DOM/Range.h>
+
+namespace Web::Bindings {
+
+RangeConstructor::RangeConstructor(JS::GlobalObject& global_object)
+ : NativeFunction(*global_object.function_prototype())
+{
+}
+
+void RangeConstructor::initialize(JS::GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+ auto& window = static_cast<WindowObject&>(global_object);
+ define_property(vm.names.prototype, window.range_prototype(), 0);
+ define_property(vm.names.length, JS::Value(0), JS::Attribute::Configurable);
+}
+
+JS::Value RangeConstructor::call()
+{
+ vm().throw_exception<JS::TypeError>(global_object(), JS::ErrorType::ConstructorWithoutNew, "Range");
+ return {};
+}
+
+JS::Value RangeConstructor::construct(Function&)
+{
+ auto& window = static_cast<WindowObject&>(global_object());
+ return heap().allocate<RangeWrapper>(window, window, DOM::Range::create(window.impl()));
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/RangeConstructor.h b/Userland/Libraries/LibWeb/Bindings/RangeConstructor.h
new file mode 100644
index 0000000000..3189c67bc6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/RangeConstructor.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace Web::Bindings {
+
+class RangeConstructor final : public JS::NativeFunction {
+public:
+ explicit RangeConstructor(JS::GlobalObject&);
+
+ void initialize(JS::GlobalObject&) override;
+
+ JS::Value call() override;
+ JS::Value construct(JS::Function& new_target) override;
+
+private:
+ bool has_constructor() const override { return true; }
+ const char* class_name() const override { return "RangeConstructor"; }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/RangePrototype.cpp b/Userland/Libraries/LibWeb/Bindings/RangePrototype.cpp
new file mode 100644
index 0000000000..ed317cb1c6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/RangePrototype.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibWeb/Bindings/NodeWrapper.h>
+#include <LibWeb/Bindings/NodeWrapperFactory.h>
+#include <LibWeb/Bindings/RangePrototype.h>
+#include <LibWeb/Bindings/RangeWrapper.h>
+#include <LibWeb/DOM/Range.h>
+
+namespace Web::Bindings {
+
+RangePrototype::RangePrototype(JS::GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void RangePrototype::initialize(JS::GlobalObject& global_object)
+{
+ auto default_attributes = JS::Attribute::Enumerable | JS::Attribute::Configurable;
+
+ Object::initialize(global_object);
+
+ define_native_function("setStart", set_start, 2);
+ define_native_function("setEnd", set_end, 2);
+ define_native_function("cloneRange", clone_range, 0);
+
+ define_native_property("startContainer", start_container_getter, nullptr, default_attributes);
+ define_native_property("endContainer", end_container_getter, nullptr, default_attributes);
+ define_native_property("startOffset", start_offset_getter, nullptr, default_attributes);
+ define_native_property("endOffset", end_offset_getter, nullptr, default_attributes);
+}
+
+static DOM::Range* impl_from(JS::VM& vm, JS::GlobalObject& global_object)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return nullptr;
+ if (StringView("RangeWrapper") != this_object->class_name()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "Range");
+ return nullptr;
+ }
+ return &static_cast<RangeWrapper*>(this_object)->impl();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(RangePrototype::set_start)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+
+ auto arg0 = vm.argument(0).to_object(global_object);
+ if (vm.exception())
+ return {};
+ auto arg1 = vm.argument(1).to_number(global_object);
+ if (vm.exception())
+ return {};
+
+ if (!is<NodeWrapper>(arg0)) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "Node");
+ return {};
+ }
+
+ impl->set_start(static_cast<NodeWrapper*>(arg0)->impl(), arg1.as_i32());
+
+ return JS::js_undefined();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(RangePrototype::set_end)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+
+ auto arg0 = vm.argument(0).to_object(global_object);
+ if (vm.exception())
+ return {};
+ auto arg1 = vm.argument(1).to_number(global_object);
+ if (vm.exception())
+ return {};
+
+ if (!is<NodeWrapper>(arg0)) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "Node");
+ return {};
+ }
+
+ impl->set_end(static_cast<NodeWrapper*>(arg0)->impl(), arg1.as_i32());
+
+ return JS::js_undefined();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(RangePrototype::clone_range)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+
+ return wrap(global_object, *impl->clone_range());
+}
+
+JS_DEFINE_NATIVE_GETTER(RangePrototype::start_container_getter)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+
+ return wrap(global_object, *impl->start_container());
+}
+
+JS_DEFINE_NATIVE_GETTER(RangePrototype::end_container_getter)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+
+ return wrap(global_object, *impl->end_container());
+}
+
+JS_DEFINE_NATIVE_GETTER(RangePrototype::start_offset_getter)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+
+ return JS::Value(impl->start_offset());
+}
+
+JS_DEFINE_NATIVE_GETTER(RangePrototype::end_offset_getter)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+
+ return JS::Value(impl->end_offset());
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/RangePrototype.h b/Userland/Libraries/LibWeb/Bindings/RangePrototype.h
new file mode 100644
index 0000000000..560eaa4a7c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/RangePrototype.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace Web::Bindings {
+
+class RangePrototype final : public JS::Object {
+ JS_OBJECT(RangePrototype, JS::Object);
+
+public:
+ explicit RangePrototype(JS::GlobalObject&);
+
+ void initialize(JS::GlobalObject&) override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(set_start);
+ JS_DECLARE_NATIVE_FUNCTION(set_end);
+ JS_DECLARE_NATIVE_FUNCTION(clone_range);
+
+ JS_DECLARE_NATIVE_GETTER(start_container_getter);
+ JS_DECLARE_NATIVE_GETTER(end_container_getter);
+ JS_DECLARE_NATIVE_GETTER(start_offset_getter);
+ JS_DECLARE_NATIVE_GETTER(end_offset_getter);
+ JS_DECLARE_NATIVE_GETTER(collapsed_getter);
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/RangeWrapper.cpp b/Userland/Libraries/LibWeb/Bindings/RangeWrapper.cpp
new file mode 100644
index 0000000000..dc58a4358d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/RangeWrapper.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Bindings/RangePrototype.h>
+#include <LibWeb/Bindings/RangeWrapper.h>
+#include <LibWeb/Bindings/WindowObject.h>
+#include <LibWeb/Bindings/Wrappable.h>
+#include <LibWeb/DOM/Range.h>
+
+namespace Web::Bindings {
+
+RangeWrapper::RangeWrapper(JS::GlobalObject& global_object, DOM::Range& impl)
+ : Wrapper(global_object)
+ , m_impl(impl)
+{
+ set_prototype(static_cast<WindowObject&>(global_object).range_prototype());
+}
+
+RangeWrapper* wrap(JS::GlobalObject& global_object, DOM::Range& impl)
+{
+ return static_cast<RangeWrapper*>(wrap_impl(global_object, impl));
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/RangeWrapper.h b/Userland/Libraries/LibWeb/Bindings/RangeWrapper.h
new file mode 100644
index 0000000000..5528d81911
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/RangeWrapper.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Bindings/Wrapper.h>
+
+namespace Web::Bindings {
+
+class RangeWrapper final : public Wrapper {
+public:
+ RangeWrapper(JS::GlobalObject&, DOM::Range&);
+
+ DOM::Range& impl() { return m_impl; }
+ const DOM::Range& impl() const { return m_impl; }
+
+private:
+ virtual const char* class_name() const override { return "RangeWrapper"; }
+
+ NonnullRefPtr<DOM::Range> m_impl;
+};
+
+RangeWrapper* wrap(JS::GlobalObject&, DOM::Range&);
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/ScriptExecutionContext.cpp b/Userland/Libraries/LibWeb/Bindings/ScriptExecutionContext.cpp
new file mode 100644
index 0000000000..ea7e58864e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/ScriptExecutionContext.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Bindings/ScriptExecutionContext.h>
+
+namespace Web::Bindings {
+
+ScriptExecutionContext::~ScriptExecutionContext()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/ScriptExecutionContext.h b/Userland/Libraries/LibWeb/Bindings/ScriptExecutionContext.h
new file mode 100644
index 0000000000..1c21910741
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/ScriptExecutionContext.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Weakable.h>
+#include <LibJS/Forward.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::Bindings {
+
+class ScriptExecutionContext {
+public:
+ virtual ~ScriptExecutionContext();
+
+ // FIXME: This should not work this way long-term, interpreters should be on the stack.
+ virtual JS::Interpreter& interpreter() = 0;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp b/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp
new file mode 100644
index 0000000000..f147a86bf0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp
@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Base64.h>
+#include <AK/ByteBuffer.h>
+#include <AK/FlyString.h>
+#include <AK/Function.h>
+#include <AK/String.h>
+#include <AK/Utf8View.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/Function.h>
+#include <LibJS/Runtime/Shape.h>
+#include <LibTextCodec/Decoder.h>
+#include <LibWeb/Bindings/DocumentWrapper.h>
+#include <LibWeb/Bindings/EventWrapper.h>
+#include <LibWeb/Bindings/EventWrapperFactory.h>
+#include <LibWeb/Bindings/LocationObject.h>
+#include <LibWeb/Bindings/NavigatorObject.h>
+#include <LibWeb/Bindings/NodeWrapperFactory.h>
+#include <LibWeb/Bindings/PerformanceWrapper.h>
+#include <LibWeb/Bindings/RangeConstructor.h>
+#include <LibWeb/Bindings/RangePrototype.h>
+#include <LibWeb/Bindings/WindowObject.h>
+#include <LibWeb/Bindings/XMLHttpRequestConstructor.h>
+#include <LibWeb/Bindings/XMLHttpRequestPrototype.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/DOM/Window.h>
+#include <LibWeb/Origin.h>
+
+namespace Web {
+namespace Bindings {
+
+WindowObject::WindowObject(DOM::Window& impl)
+ : m_impl(impl)
+{
+ impl.set_wrapper({}, *this);
+}
+
+void WindowObject::initialize()
+{
+ GlobalObject::initialize();
+
+ define_property("window", this, JS::Attribute::Enumerable);
+ define_property("frames", this, JS::Attribute::Enumerable);
+ define_property("self", this, JS::Attribute::Enumerable);
+ define_native_property("document", document_getter, document_setter, JS::Attribute::Enumerable);
+ define_native_property("performance", performance_getter, nullptr, JS::Attribute::Enumerable);
+ define_native_function("alert", alert);
+ define_native_function("confirm", confirm);
+ define_native_function("setInterval", set_interval, 1);
+ define_native_function("setTimeout", set_timeout, 1);
+ define_native_function("clearInterval", clear_interval, 1);
+ define_native_function("clearTimeout", clear_timeout, 1);
+ define_native_function("requestAnimationFrame", request_animation_frame, 1);
+ define_native_function("cancelAnimationFrame", cancel_animation_frame, 1);
+ define_native_function("atob", atob, 1);
+ define_native_function("btoa", btoa, 1);
+
+ // Legacy
+ define_native_property("event", event_getter, nullptr, JS::Attribute::Enumerable);
+
+ define_property("navigator", heap().allocate<NavigatorObject>(*this, *this), JS::Attribute::Enumerable | JS::Attribute::Configurable);
+ define_property("location", heap().allocate<LocationObject>(*this, *this), JS::Attribute::Enumerable | JS::Attribute::Configurable);
+
+ m_xhr_prototype = heap().allocate<XMLHttpRequestPrototype>(*this, *this);
+ add_constructor("XMLHttpRequest", m_xhr_constructor, m_xhr_prototype);
+
+ m_range_prototype = heap().allocate<RangePrototype>(*this, *this);
+ add_constructor("Range", m_range_constructor, m_range_prototype);
+}
+
+WindowObject::~WindowObject()
+{
+}
+
+void WindowObject::visit_edges(Visitor& visitor)
+{
+ GlobalObject::visit_edges(visitor);
+ visitor.visit(m_xhr_constructor);
+ visitor.visit(m_xhr_prototype);
+ visitor.visit(m_range_constructor);
+ visitor.visit(m_range_prototype);
+}
+
+Origin WindowObject::origin() const
+{
+ return impl().document().origin();
+}
+
+static DOM::Window* impl_from(JS::VM& vm, JS::GlobalObject& global_object)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object) {
+ ASSERT_NOT_REACHED();
+ return nullptr;
+ }
+ if (StringView("WindowObject") != this_object->class_name()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "WindowObject");
+ return nullptr;
+ }
+ return &static_cast<WindowObject*>(this_object)->impl();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(WindowObject::alert)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ String message = "";
+ if (vm.argument_count()) {
+ message = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+ }
+ impl->alert(message);
+ return JS::js_undefined();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(WindowObject::confirm)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ String message = "";
+ if (vm.argument_count()) {
+ message = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+ }
+ return JS::Value(impl->confirm(message));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(WindowObject::set_interval)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ if (!vm.argument_count()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::BadArgCountAtLeastOne, "setInterval");
+ return {};
+ }
+ auto* callback_object = vm.argument(0).to_object(global_object);
+ if (!callback_object)
+ return {};
+ if (!callback_object->is_function()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotAFunctionNoParam);
+ return {};
+ }
+ i32 interval = 0;
+ if (vm.argument_count() >= 2) {
+ interval = vm.argument(1).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ if (interval < 0)
+ interval = 0;
+ }
+
+ auto timer_id = impl->set_interval(*static_cast<JS::Function*>(callback_object), interval);
+ return JS::Value(timer_id);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(WindowObject::set_timeout)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ if (!vm.argument_count()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::BadArgCountAtLeastOne, "setTimeout");
+ return {};
+ }
+ auto* callback_object = vm.argument(0).to_object(global_object);
+ if (!callback_object)
+ return {};
+ if (!callback_object->is_function()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotAFunctionNoParam);
+ return {};
+ }
+ i32 interval = 0;
+ if (vm.argument_count() >= 2) {
+ interval = vm.argument(1).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ if (interval < 0)
+ interval = 0;
+ }
+
+ auto timer_id = impl->set_timeout(*static_cast<JS::Function*>(callback_object), interval);
+ return JS::Value(timer_id);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(WindowObject::clear_timeout)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ if (!vm.argument_count()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::BadArgCountAtLeastOne, "clearTimeout");
+ return {};
+ }
+ i32 timer_id = vm.argument(0).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ impl->clear_timeout(timer_id);
+ return JS::js_undefined();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(WindowObject::clear_interval)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ if (!vm.argument_count()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::BadArgCountAtLeastOne, "clearInterval");
+ return {};
+ }
+ i32 timer_id = vm.argument(0).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ impl->clear_timeout(timer_id);
+ return JS::js_undefined();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(WindowObject::request_animation_frame)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ if (!vm.argument_count()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::BadArgCountOne, "requestAnimationFrame");
+ return {};
+ }
+ auto* callback_object = vm.argument(0).to_object(global_object);
+ if (!callback_object)
+ return {};
+ if (!callback_object->is_function()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotAFunctionNoParam);
+ return {};
+ }
+ return JS::Value(impl->request_animation_frame(*static_cast<JS::Function*>(callback_object)));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(WindowObject::cancel_animation_frame)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ if (!vm.argument_count()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::BadArgCountOne, "cancelAnimationFrame");
+ return {};
+ }
+ auto id = vm.argument(0).to_i32(global_object);
+ if (vm.exception())
+ return {};
+ impl->cancel_animation_frame(id);
+ return JS::js_undefined();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(WindowObject::atob)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ if (!vm.argument_count()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::BadArgCountOne, "atob");
+ return {};
+ }
+ auto string = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+ auto decoded = decode_base64(StringView(string));
+
+ // decode_base64() returns a byte string. LibJS uses UTF-8 for strings. Use Latin1Decoder to convert bytes 128-255 to UTF-8.
+ auto decoder = TextCodec::decoder_for("windows-1252");
+ ASSERT(decoder);
+ return JS::js_string(vm, decoder->to_utf8(decoded));
+}
+
+JS_DEFINE_NATIVE_FUNCTION(WindowObject::btoa)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ if (!vm.argument_count()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::BadArgCountOne, "btoa");
+ return {};
+ }
+ auto string = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+
+ Vector<u8> byte_string;
+ byte_string.ensure_capacity(string.length());
+ for (u32 code_point : Utf8View(string)) {
+ if (code_point > 0xff) {
+ vm.throw_exception<JS::InvalidCharacterError>(global_object, JS::ErrorType::NotAByteString, "btoa");
+ return {};
+ }
+ byte_string.append(code_point);
+ }
+
+ auto encoded = encode_base64(byte_string.span());
+ return JS::js_string(vm, move(encoded));
+}
+
+JS_DEFINE_NATIVE_GETTER(WindowObject::document_getter)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ return wrap(global_object, impl->document());
+}
+
+JS_DEFINE_NATIVE_SETTER(WindowObject::document_setter)
+{
+ // FIXME: Figure out what we should do here. Just ignore attempts to set window.document for now.
+}
+
+JS_DEFINE_NATIVE_GETTER(WindowObject::performance_getter)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ return wrap(global_object, impl->performance());
+}
+
+JS_DEFINE_NATIVE_GETTER(WindowObject::event_getter)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ if (!impl->current_event())
+ return JS::js_undefined();
+ return wrap(global_object, const_cast<DOM::Event&>(*impl->current_event()));
+}
+
+}
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/WindowObject.h b/Userland/Libraries/LibWeb/Bindings/WindowObject.h
new file mode 100644
index 0000000000..d68a3364c9
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/WindowObject.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/TypeCasts.h>
+#include <AK/Weakable.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibWeb/Forward.h>
+
+namespace Web {
+namespace Bindings {
+
+class WindowObject final
+ : public JS::GlobalObject
+ , public Weakable<WindowObject> {
+public:
+ explicit WindowObject(DOM::Window&);
+ virtual void initialize() override;
+ virtual ~WindowObject() override;
+
+ DOM::Window& impl() { return *m_impl; }
+ const DOM::Window& impl() const { return *m_impl; }
+
+ Origin origin() const;
+
+ XMLHttpRequestPrototype* xhr_prototype() { return m_xhr_prototype; }
+ XMLHttpRequestConstructor* xhr_constructor() { return m_xhr_constructor; }
+
+ RangePrototype* range_prototype() { return m_range_prototype; }
+ RangeConstructor* range_constructor() { return m_range_constructor; }
+
+private:
+ virtual const char* class_name() const override { return "WindowObject"; }
+ virtual void visit_edges(Visitor&) override;
+
+ JS_DECLARE_NATIVE_GETTER(document_getter);
+ JS_DECLARE_NATIVE_SETTER(document_setter);
+
+ JS_DECLARE_NATIVE_GETTER(performance_getter);
+
+ JS_DECLARE_NATIVE_GETTER(event_getter);
+
+ JS_DECLARE_NATIVE_FUNCTION(alert);
+ JS_DECLARE_NATIVE_FUNCTION(confirm);
+ JS_DECLARE_NATIVE_FUNCTION(set_interval);
+ JS_DECLARE_NATIVE_FUNCTION(set_timeout);
+ JS_DECLARE_NATIVE_FUNCTION(clear_interval);
+ JS_DECLARE_NATIVE_FUNCTION(clear_timeout);
+ JS_DECLARE_NATIVE_FUNCTION(request_animation_frame);
+ JS_DECLARE_NATIVE_FUNCTION(cancel_animation_frame);
+ JS_DECLARE_NATIVE_FUNCTION(atob);
+ JS_DECLARE_NATIVE_FUNCTION(btoa);
+
+ NonnullRefPtr<DOM::Window> m_impl;
+
+ XMLHttpRequestConstructor* m_xhr_constructor { nullptr };
+ XMLHttpRequestPrototype* m_xhr_prototype { nullptr };
+
+ RangePrototype* m_range_prototype { nullptr };
+ RangeConstructor* m_range_constructor { nullptr };
+};
+
+}
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/Wrappable.cpp b/Userland/Libraries/LibWeb/Bindings/Wrappable.cpp
new file mode 100644
index 0000000000..b257a378a6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/Wrappable.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Bindings/Wrappable.h>
+#include <LibWeb/Bindings/Wrapper.h>
+
+namespace Web {
+namespace Bindings {
+
+Wrappable::~Wrappable()
+{
+}
+
+void Wrappable::set_wrapper(Wrapper& wrapper)
+{
+ ASSERT(!m_wrapper);
+ m_wrapper = wrapper.make_weak_ptr();
+}
+
+}
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/Wrappable.h b/Userland/Libraries/LibWeb/Bindings/Wrappable.h
new file mode 100644
index 0000000000..211fc4755a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/Wrappable.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/WeakPtr.h>
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::Bindings {
+
+class Wrappable {
+public:
+ virtual ~Wrappable();
+
+ void set_wrapper(Wrapper&);
+ Wrapper* wrapper() { return m_wrapper; }
+ const Wrapper* wrapper() const { return m_wrapper; }
+
+private:
+ WeakPtr<Wrapper> m_wrapper;
+};
+
+template<class NativeObject>
+inline Wrapper* wrap_impl(JS::GlobalObject& global_object, NativeObject& native_object)
+{
+ if (!native_object.wrapper()) {
+ native_object.set_wrapper(*global_object.heap().allocate<typename NativeObject::WrapperType>(global_object, global_object, native_object));
+ }
+ return native_object.wrapper();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/Wrapper.h b/Userland/Libraries/LibWeb/Bindings/Wrapper.h
new file mode 100644
index 0000000000..c83556605a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/Wrapper.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtr.h>
+#include <AK/Weakable.h>
+#include <LibJS/Runtime/Object.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::Bindings {
+
+class Wrapper
+ : public JS::Object
+ , public Weakable<Wrapper> {
+ JS_OBJECT(Wrapper, JS::Object);
+
+public:
+protected:
+ explicit Wrapper(Object& prototype)
+ : Object(prototype)
+ {
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestConstructor.cpp b/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestConstructor.cpp
new file mode 100644
index 0000000000..16bb2a4b8b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestConstructor.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibWeb/Bindings/WindowObject.h>
+#include <LibWeb/Bindings/XMLHttpRequestConstructor.h>
+#include <LibWeb/Bindings/XMLHttpRequestPrototype.h>
+#include <LibWeb/Bindings/XMLHttpRequestWrapper.h>
+#include <LibWeb/DOM/XMLHttpRequest.h>
+
+namespace Web::Bindings {
+
+XMLHttpRequestConstructor::XMLHttpRequestConstructor(JS::GlobalObject& global_object)
+ : NativeFunction(*global_object.function_prototype())
+{
+}
+
+void XMLHttpRequestConstructor::initialize(JS::GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+ auto& window = static_cast<WindowObject&>(global_object);
+ define_property(vm.names.prototype, window.xhr_prototype(), 0);
+ define_property(vm.names.length, JS::Value(1), JS::Attribute::Configurable);
+
+ define_property("UNSENT", JS::Value((i32)XMLHttpRequest::ReadyState::Unsent), JS::Attribute::Enumerable);
+ define_property("OPENED", JS::Value((i32)XMLHttpRequest::ReadyState::Opened), JS::Attribute::Enumerable);
+ define_property("HEADERS_RECEIVED", JS::Value((i32)XMLHttpRequest::ReadyState::HeadersReceived), JS::Attribute::Enumerable);
+ define_property("LOADING", JS::Value((i32)XMLHttpRequest::ReadyState::Loading), JS::Attribute::Enumerable);
+ define_property("DONE", JS::Value((i32)XMLHttpRequest::ReadyState::Done), JS::Attribute::Enumerable);
+}
+
+XMLHttpRequestConstructor::~XMLHttpRequestConstructor()
+{
+}
+
+JS::Value XMLHttpRequestConstructor::call()
+{
+ vm().throw_exception<JS::TypeError>(global_object(), JS::ErrorType::ConstructorWithoutNew, "XMLHttpRequest");
+ return {};
+}
+
+JS::Value XMLHttpRequestConstructor::construct(Function&)
+{
+ auto& window = static_cast<WindowObject&>(global_object());
+ return heap().allocate<XMLHttpRequestWrapper>(window, window, XMLHttpRequest::create(window.impl()));
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestConstructor.h b/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestConstructor.h
new file mode 100644
index 0000000000..0ca6f5063f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestConstructor.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace Web::Bindings {
+
+class XMLHttpRequestConstructor final : public JS::NativeFunction {
+public:
+ explicit XMLHttpRequestConstructor(JS::GlobalObject&);
+ virtual void initialize(JS::GlobalObject&) override;
+ virtual ~XMLHttpRequestConstructor() override;
+
+ virtual JS::Value call() override;
+ virtual JS::Value construct(JS::Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+ virtual const char* class_name() const override { return "XMLHttpRequestConstructor"; }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestPrototype.cpp b/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestPrototype.cpp
new file mode 100644
index 0000000000..2db588bc2c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestPrototype.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibWeb/Bindings/XMLHttpRequestPrototype.h>
+#include <LibWeb/Bindings/XMLHttpRequestWrapper.h>
+#include <LibWeb/DOM/XMLHttpRequest.h>
+
+namespace Web::Bindings {
+
+XMLHttpRequestPrototype::XMLHttpRequestPrototype(JS::GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+void XMLHttpRequestPrototype::initialize(JS::GlobalObject& global_object)
+{
+ Object::initialize(global_object);
+ define_native_function("open", open, 2);
+ define_native_function("send", send, 0);
+ define_native_property("readyState", ready_state_getter, nullptr, JS::Attribute::Enumerable | JS::Attribute::Configurable);
+ define_native_property("responseText", response_text_getter, nullptr, JS::Attribute::Enumerable | JS::Attribute::Configurable);
+
+ define_property("UNSENT", JS::Value((i32)XMLHttpRequest::ReadyState::Unsent), JS::Attribute::Enumerable);
+ define_property("OPENED", JS::Value((i32)XMLHttpRequest::ReadyState::Opened), JS::Attribute::Enumerable);
+ define_property("HEADERS_RECEIVED", JS::Value((i32)XMLHttpRequest::ReadyState::HeadersReceived), JS::Attribute::Enumerable);
+ define_property("LOADING", JS::Value((i32)XMLHttpRequest::ReadyState::Loading), JS::Attribute::Enumerable);
+ define_property("DONE", JS::Value((i32)XMLHttpRequest::ReadyState::Done), JS::Attribute::Enumerable);
+}
+
+XMLHttpRequestPrototype::~XMLHttpRequestPrototype()
+{
+}
+
+static XMLHttpRequest* impl_from(JS::VM& vm, JS::GlobalObject& global_object)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return nullptr;
+ if (!is<XMLHttpRequestWrapper>(this_object)) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "XMLHttpRequest");
+ return nullptr;
+ }
+ return &static_cast<XMLHttpRequestWrapper*>(this_object)->impl();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(XMLHttpRequestPrototype::open)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ auto arg0 = vm.argument(0).to_string(global_object);
+ if (vm.exception())
+ return {};
+ auto arg1 = vm.argument(1).to_string(global_object);
+ if (vm.exception())
+ return {};
+ impl->open(arg0, arg1);
+ return JS::js_undefined();
+}
+
+JS_DEFINE_NATIVE_FUNCTION(XMLHttpRequestPrototype::send)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ impl->send();
+ return JS::js_undefined();
+}
+
+JS_DEFINE_NATIVE_GETTER(XMLHttpRequestPrototype::ready_state_getter)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ return JS::Value((i32)impl->ready_state());
+}
+
+JS_DEFINE_NATIVE_GETTER(XMLHttpRequestPrototype::response_text_getter)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+ return JS::js_string(vm, impl->response_text());
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestPrototype.h b/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestPrototype.h
new file mode 100644
index 0000000000..7b534b3b00
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestPrototype.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace Web::Bindings {
+
+class XMLHttpRequestPrototype final : public JS::Object {
+ JS_OBJECT(XMLHttpRequestPrototype, JS::Object);
+
+public:
+ explicit XMLHttpRequestPrototype(JS::GlobalObject&);
+ virtual void initialize(JS::GlobalObject&) override;
+ virtual ~XMLHttpRequestPrototype() override;
+
+private:
+ JS_DECLARE_NATIVE_FUNCTION(open);
+ JS_DECLARE_NATIVE_FUNCTION(send);
+
+ JS_DECLARE_NATIVE_GETTER(ready_state_getter);
+ JS_DECLARE_NATIVE_GETTER(response_text_getter);
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestWrapper.cpp b/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestWrapper.cpp
new file mode 100644
index 0000000000..dedd97313a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestWrapper.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/FlyString.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Value.h>
+#include <LibWeb/Bindings/WindowObject.h>
+#include <LibWeb/Bindings/XMLHttpRequestPrototype.h>
+#include <LibWeb/Bindings/XMLHttpRequestWrapper.h>
+#include <LibWeb/DOM/XMLHttpRequest.h>
+
+namespace Web::Bindings {
+
+XMLHttpRequestWrapper* wrap(JS::GlobalObject& global_object, XMLHttpRequest& impl)
+{
+ return static_cast<XMLHttpRequestWrapper*>(wrap_impl(global_object, impl));
+}
+
+XMLHttpRequestWrapper::XMLHttpRequestWrapper(JS::GlobalObject& global_object, XMLHttpRequest& impl)
+ : EventTargetWrapper(global_object, impl)
+{
+ set_prototype(static_cast<WindowObject&>(global_object).xhr_prototype());
+}
+
+XMLHttpRequestWrapper::~XMLHttpRequestWrapper()
+{
+}
+
+XMLHttpRequest& XMLHttpRequestWrapper::impl()
+{
+ return static_cast<XMLHttpRequest&>(EventTargetWrapper::impl());
+}
+
+const XMLHttpRequest& XMLHttpRequestWrapper::impl() const
+{
+ return static_cast<const XMLHttpRequest&>(EventTargetWrapper::impl());
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestWrapper.h b/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestWrapper.h
new file mode 100644
index 0000000000..a961564b60
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/XMLHttpRequestWrapper.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Bindings/EventTargetWrapper.h>
+
+namespace Web::Bindings {
+
+class XMLHttpRequestWrapper final : public EventTargetWrapper {
+public:
+ XMLHttpRequestWrapper(JS::GlobalObject&, XMLHttpRequest&);
+ virtual ~XMLHttpRequestWrapper() override;
+
+ XMLHttpRequest& impl();
+ const XMLHttpRequest& impl() const;
+
+private:
+ virtual const char* class_name() const override { return "XMLHttpRequestWrapper"; }
+};
+
+XMLHttpRequestWrapper* wrap(JS::GlobalObject&, XMLHttpRequest&);
+
+}
diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt
new file mode 100644
index 0000000000..4e4abc1551
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CMakeLists.txt
@@ -0,0 +1,399 @@
+set(SOURCES
+ Bindings/EventListenerWrapper.cpp
+ Bindings/EventWrapperFactory.cpp
+ Bindings/EventTargetWrapperFactory.cpp
+ Bindings/LocationObject.cpp
+ Bindings/NavigatorObject.cpp
+ Bindings/NodeWrapperFactory.cpp
+ Bindings/ScriptExecutionContext.cpp
+ Bindings/WindowObject.cpp
+ Bindings/Wrappable.cpp
+ Bindings/XMLHttpRequestConstructor.cpp
+ Bindings/XMLHttpRequestPrototype.cpp
+ Bindings/XMLHttpRequestWrapper.cpp
+ Bindings/RangeConstructor.cpp
+ Bindings/RangePrototype.cpp
+ Bindings/RangeWrapper.cpp
+ CSS/DefaultStyleSheetSource.cpp
+ CSS/Length.cpp
+ CSS/Parser/CSSParser.cpp
+ CSS/PropertyID.cpp
+ CSS/PropertyID.h
+ CSS/QuirksModeStyleSheetSource.cpp
+ CSS/Selector.cpp
+ CSS/SelectorEngine.cpp
+ CSS/StyleDeclaration.cpp
+ CSS/StyleInvalidator.cpp
+ CSS/StyleProperties.cpp
+ CSS/StyleResolver.cpp
+ CSS/StyleRule.cpp
+ CSS/StyleSheet.cpp
+ CSS/StyleSheetList.cpp
+ CSS/StyleValue.cpp
+ CSS/ValueID.cpp
+ CSS/ValueID.h
+ DOM/CharacterData.cpp
+ DOM/CharacterData.idl
+ DOM/Comment.cpp
+ DOM/Document.cpp
+ DOM/DocumentFragment.cpp
+ DOM/DocumentType.cpp
+ DOM/DOMImplementation.cpp
+ DOM/Element.cpp
+ DOM/ElementFactory.cpp
+ DOM/Event.cpp
+ DOM/Range.cpp
+ DOM/EventDispatcher.cpp
+ DOM/EventListener.cpp
+ DOM/EventTarget.cpp
+ DOM/Node.cpp
+ DOM/ParentNode.cpp
+ DOM/Position.cpp
+ DOM/ShadowRoot.cpp
+ DOM/Text.cpp
+ DOM/Text.idl
+ DOM/Timer.cpp
+ DOM/Window.cpp
+ DOM/XMLHttpRequest.cpp
+ DOMTreeModel.cpp
+ Dump.cpp
+ FontCache.cpp
+ HTML/AttributeNames.cpp
+ HTML/CanvasRenderingContext2D.cpp
+ HTML/EventNames.cpp
+ HTML/HTMLAnchorElement.cpp
+ HTML/HTMLAreaElement.cpp
+ HTML/HTMLAudioElement.cpp
+ HTML/HTMLBRElement.cpp
+ HTML/HTMLBaseElement.cpp
+ HTML/HTMLBlinkElement.cpp
+ HTML/HTMLBodyElement.cpp
+ HTML/HTMLButtonElement.cpp
+ HTML/HTMLCanvasElement.cpp
+ HTML/HTMLDListElement.cpp
+ HTML/HTMLDataElement.cpp
+ HTML/HTMLDataListElement.cpp
+ HTML/HTMLDetailsElement.cpp
+ HTML/HTMLDialogElement.cpp
+ HTML/HTMLDirectoryElement.cpp
+ HTML/HTMLDivElement.cpp
+ HTML/HTMLElement.cpp
+ HTML/HTMLEmbedElement.cpp
+ HTML/HTMLFieldSetElement.cpp
+ HTML/HTMLFontElement.cpp
+ HTML/HTMLFormElement.cpp
+ HTML/HTMLFrameElement.cpp
+ HTML/HTMLFrameSetElement.cpp
+ HTML/HTMLHRElement.cpp
+ HTML/HTMLHeadElement.cpp
+ HTML/HTMLHeadingElement.cpp
+ HTML/HTMLHtmlElement.cpp
+ HTML/HTMLIFrameElement.cpp
+ HTML/HTMLImageElement.cpp
+ HTML/HTMLInputElement.cpp
+ HTML/HTMLLIElement.cpp
+ HTML/HTMLLabelElement.cpp
+ HTML/HTMLLegendElement.cpp
+ HTML/HTMLLinkElement.cpp
+ HTML/HTMLMapElement.cpp
+ HTML/HTMLMarqueeElement.cpp
+ HTML/HTMLMediaElement.cpp
+ HTML/HTMLMenuElement.cpp
+ HTML/HTMLMetaElement.cpp
+ HTML/HTMLMeterElement.cpp
+ HTML/HTMLModElement.cpp
+ HTML/HTMLOListElement.cpp
+ HTML/HTMLObjectElement.cpp
+ HTML/HTMLOptGroupElement.cpp
+ HTML/HTMLOptionElement.cpp
+ HTML/HTMLOutputElement.cpp
+ HTML/HTMLParagraphElement.cpp
+ HTML/HTMLParamElement.cpp
+ HTML/HTMLPictureElement.cpp
+ HTML/HTMLPreElement.cpp
+ HTML/HTMLProgressElement.cpp
+ HTML/HTMLQuoteElement.cpp
+ HTML/HTMLScriptElement.cpp
+ HTML/HTMLSelectElement.cpp
+ HTML/HTMLSlotElement.cpp
+ HTML/HTMLSourceElement.cpp
+ HTML/HTMLSpanElement.cpp
+ HTML/HTMLStyleElement.cpp
+ HTML/HTMLTableCaptionElement.cpp
+ HTML/HTMLTableCellElement.cpp
+ HTML/HTMLTableColElement.cpp
+ HTML/HTMLTableElement.cpp
+ HTML/HTMLTableRowElement.cpp
+ HTML/HTMLTableSectionElement.cpp
+ HTML/HTMLTemplateElement.cpp
+ HTML/HTMLTextAreaElement.cpp
+ HTML/HTMLTimeElement.cpp
+ HTML/HTMLTitleElement.cpp
+ HTML/HTMLTrackElement.cpp
+ HTML/HTMLUListElement.cpp
+ HTML/HTMLUnknownElement.cpp
+ HTML/HTMLVideoElement.cpp
+ HTML/ImageData.cpp
+ HTML/Parser/Entities.cpp
+ HTML/Parser/HTMLDocumentParser.cpp
+ HTML/Parser/HTMLToken.cpp
+ HTML/Parser/HTMLTokenizer.cpp
+ HTML/Parser/ListOfActiveFormattingElements.cpp
+ HTML/Parser/StackOfOpenElements.cpp
+ HTML/TagNames.cpp
+ HighResolutionTime/Performance.cpp
+ InProcessWebView.cpp
+ Layout/BlockBox.cpp
+ Layout/BlockFormattingContext.cpp
+ Layout/Box.cpp
+ Layout/BoxModelMetrics.cpp
+ Layout/BreakNode.cpp
+ Layout/ButtonBox.cpp
+ Layout/CanvasBox.cpp
+ Layout/CheckBox.cpp
+ Layout/FormattingContext.cpp
+ Layout/FrameBox.cpp
+ Layout/ImageBox.cpp
+ Layout/InitialContainingBlockBox.cpp
+ Layout/InlineFormattingContext.cpp
+ Layout/InlineNode.cpp
+ Layout/LayoutPosition.cpp
+ Layout/LineBox.cpp
+ Layout/LineBoxFragment.cpp
+ Layout/ListItemBox.cpp
+ Layout/ListItemMarkerBox.cpp
+ Layout/Node.cpp
+ Layout/ReplacedBox.cpp
+ Layout/SVGBox.cpp
+ Layout/SVGGraphicsBox.cpp
+ Layout/SVGPathBox.cpp
+ Layout/SVGSVGBox.cpp
+ Layout/TableBox.cpp
+ Layout/TableCellBox.cpp
+ Layout/TableFormattingContext.cpp
+ Layout/TableRowBox.cpp
+ Layout/TableRowGroupBox.cpp
+ Layout/TextNode.cpp
+ Layout/TreeBuilder.cpp
+ Layout/WidgetBox.cpp
+ LayoutTreeModel.cpp
+ Loader/ContentFilter.cpp
+ Loader/FrameLoader.cpp
+ Loader/ImageLoader.cpp
+ Loader/ImageResource.cpp
+ Loader/Resource.cpp
+ Loader/ResourceLoader.cpp
+ Namespace.cpp
+ OutOfProcessWebView.cpp
+ Page/EventHandler.cpp
+ Page/EditEventHandler.cpp
+ Page/Frame.cpp
+ Page/Page.cpp
+ Painting/BorderPainting.cpp
+ Painting/StackingContext.cpp
+ SVG/SVGElement.cpp
+ SVG/SVGGeometryElement.cpp
+ SVG/SVGGraphicsElement.cpp
+ SVG/SVGPathElement.cpp
+ SVG/SVGSVGElement.cpp
+ SVG/TagNames.cpp
+ StylePropertiesModel.cpp
+ UIEvents/EventNames.cpp
+ UIEvents/MouseEvent.cpp
+ URLEncoder.cpp
+ WebContentClient.cpp
+)
+
+set(GENERATED_SOURCES
+ ../../Services/ProtocolServer/ProtocolClientEndpoint.h
+ ../../Services/ProtocolServer/ProtocolServerEndpoint.h
+ ../../Services/WebContent/WebContentClientEndpoint.h
+ ../../Services/WebContent/WebContentServerEndpoint.h
+)
+
+set_property(GLOBAL PROPERTY wrapper_sources)
+function(add_wrapper_sources)
+ get_property(tmp GLOBAL PROPERTY wrapper_sources)
+ foreach(arg ${ARGV})
+ set(tmp ${tmp}
+ ${arg}
+ )
+ endforeach()
+ set_property(GLOBAL PROPERTY wrapper_sources "${tmp}")
+endfunction(add_wrapper_sources)
+
+function(libweb_js_wrapper class)
+ get_filename_component(basename ${class} NAME)
+ add_wrapper_sources(Bindings/${basename}Wrapper.cpp Bindings/${basename}Wrapper.h)
+ add_custom_command(
+ OUTPUT Bindings/${basename}Wrapper.h
+ COMMAND ${write_if_different} Bindings/${basename}Wrapper.h CodeGenerators/WrapperGenerator --header ${CMAKE_CURRENT_SOURCE_DIR}/${class}.idl
+ VERBATIM
+ DEPENDS WrapperGenerator
+ MAIN_DEPENDENCY ${class}.idl
+ )
+ add_custom_command(
+ OUTPUT Bindings/${basename}Wrapper.cpp
+ COMMAND ${write_if_different} Bindings/${basename}Wrapper.cpp CodeGenerators/WrapperGenerator --implementation ${CMAKE_CURRENT_SOURCE_DIR}/${class}.idl
+ VERBATIM
+ DEPENDS WrapperGenerator
+ MAIN_DEPENDENCY ${class}.idl
+ )
+ add_custom_target(generate_${basename}Wrapper.h DEPENDS Bindings/${class}Wrapper.h)
+ add_custom_target(generate_${basename}Wrapper.cpp DEPENDS Bindings/${class}Wrapper.cpp)
+endfunction()
+
+libweb_js_wrapper(DOM/CharacterData)
+libweb_js_wrapper(DOM/Comment)
+libweb_js_wrapper(DOM/Document)
+libweb_js_wrapper(DOM/DocumentFragment)
+libweb_js_wrapper(DOM/DocumentType)
+libweb_js_wrapper(DOM/DOMImplementation)
+libweb_js_wrapper(DOM/Element)
+libweb_js_wrapper(DOM/Event)
+libweb_js_wrapper(DOM/EventTarget)
+libweb_js_wrapper(DOM/ShadowRoot)
+libweb_js_wrapper(DOM/Node)
+libweb_js_wrapper(DOM/Text)
+libweb_js_wrapper(HTML/CanvasRenderingContext2D)
+libweb_js_wrapper(HTML/HTMLAnchorElement)
+libweb_js_wrapper(HTML/HTMLAreaElement)
+libweb_js_wrapper(HTML/HTMLAudioElement)
+libweb_js_wrapper(HTML/HTMLBaseElement)
+libweb_js_wrapper(HTML/HTMLBodyElement)
+libweb_js_wrapper(HTML/HTMLBRElement)
+libweb_js_wrapper(HTML/HTMLButtonElement)
+libweb_js_wrapper(HTML/HTMLCanvasElement)
+libweb_js_wrapper(HTML/HTMLDataElement)
+libweb_js_wrapper(HTML/HTMLDataListElement)
+libweb_js_wrapper(HTML/HTMLDetailsElement)
+libweb_js_wrapper(HTML/HTMLDialogElement)
+libweb_js_wrapper(HTML/HTMLDirectoryElement)
+libweb_js_wrapper(HTML/HTMLDivElement)
+libweb_js_wrapper(HTML/HTMLDListElement)
+libweb_js_wrapper(HTML/HTMLElement)
+libweb_js_wrapper(HTML/HTMLEmbedElement)
+libweb_js_wrapper(HTML/HTMLFieldSetElement)
+libweb_js_wrapper(HTML/HTMLFontElement)
+libweb_js_wrapper(HTML/HTMLFormElement)
+libweb_js_wrapper(HTML/HTMLFrameElement)
+libweb_js_wrapper(HTML/HTMLFrameSetElement)
+libweb_js_wrapper(HTML/HTMLHeadElement)
+libweb_js_wrapper(HTML/HTMLHeadingElement)
+libweb_js_wrapper(HTML/HTMLHRElement)
+libweb_js_wrapper(HTML/HTMLHtmlElement)
+libweb_js_wrapper(HTML/HTMLIFrameElement)
+libweb_js_wrapper(HTML/HTMLImageElement)
+libweb_js_wrapper(HTML/HTMLInputElement)
+libweb_js_wrapper(HTML/HTMLLabelElement)
+libweb_js_wrapper(HTML/HTMLLegendElement)
+libweb_js_wrapper(HTML/HTMLLIElement)
+libweb_js_wrapper(HTML/HTMLLinkElement)
+libweb_js_wrapper(HTML/HTMLMapElement)
+libweb_js_wrapper(HTML/HTMLMarqueeElement)
+libweb_js_wrapper(HTML/HTMLMediaElement)
+libweb_js_wrapper(HTML/HTMLMenuElement)
+libweb_js_wrapper(HTML/HTMLMetaElement)
+libweb_js_wrapper(HTML/HTMLMeterElement)
+libweb_js_wrapper(HTML/HTMLModElement)
+libweb_js_wrapper(HTML/HTMLObjectElement)
+libweb_js_wrapper(HTML/HTMLOListElement)
+libweb_js_wrapper(HTML/HTMLOptGroupElement)
+libweb_js_wrapper(HTML/HTMLOptionElement)
+libweb_js_wrapper(HTML/HTMLOutputElement)
+libweb_js_wrapper(HTML/HTMLParagraphElement)
+libweb_js_wrapper(HTML/HTMLParamElement)
+libweb_js_wrapper(HTML/HTMLPictureElement)
+libweb_js_wrapper(HTML/HTMLPreElement)
+libweb_js_wrapper(HTML/HTMLProgressElement)
+libweb_js_wrapper(HTML/HTMLQuoteElement)
+libweb_js_wrapper(HTML/HTMLScriptElement)
+libweb_js_wrapper(HTML/HTMLSelectElement)
+libweb_js_wrapper(HTML/HTMLSlotElement)
+libweb_js_wrapper(HTML/HTMLSourceElement)
+libweb_js_wrapper(HTML/HTMLSpanElement)
+libweb_js_wrapper(HTML/HTMLStyleElement)
+libweb_js_wrapper(HTML/HTMLTableCaptionElement)
+libweb_js_wrapper(HTML/HTMLTableCellElement)
+libweb_js_wrapper(HTML/HTMLTableColElement)
+libweb_js_wrapper(HTML/HTMLTableElement)
+libweb_js_wrapper(HTML/HTMLTableRowElement)
+libweb_js_wrapper(HTML/HTMLTableSectionElement)
+libweb_js_wrapper(HTML/HTMLTemplateElement)
+libweb_js_wrapper(HTML/HTMLTextAreaElement)
+libweb_js_wrapper(HTML/HTMLTimeElement)
+libweb_js_wrapper(HTML/HTMLTitleElement)
+libweb_js_wrapper(HTML/HTMLTrackElement)
+libweb_js_wrapper(HTML/HTMLUListElement)
+libweb_js_wrapper(HTML/HTMLUnknownElement)
+libweb_js_wrapper(HTML/HTMLVideoElement)
+libweb_js_wrapper(HTML/ImageData)
+libweb_js_wrapper(HTML/SubmitEvent)
+libweb_js_wrapper(HighResolutionTime/Performance)
+libweb_js_wrapper(SVG/SVGElement)
+libweb_js_wrapper(SVG/SVGGeometryElement)
+libweb_js_wrapper(SVG/SVGGraphicsElement)
+libweb_js_wrapper(SVG/SVGPathElement)
+libweb_js_wrapper(SVG/SVGSVGElement)
+libweb_js_wrapper(UIEvents/MouseEvent)
+libweb_js_wrapper(UIEvents/UIEvent)
+
+get_property(WRAPPER_SOURCES GLOBAL PROPERTY wrapper_sources)
+set(SOURCES ${SOURCES} ${WRAPPER_SOURCES})
+
+add_custom_command(
+ OUTPUT CSS/PropertyID.h
+ COMMAND ${write_if_different} CSS/PropertyID.h CodeGenerators/Generate_CSS_PropertyID_h ${CMAKE_CURRENT_SOURCE_DIR}/CSS/Properties.json
+ VERBATIM
+ DEPENDS Generate_CSS_PropertyID_h
+ MAIN_DEPENDENCY CSS/Properties.json
+)
+add_custom_target(generate_PropertyID.h DEPENDS CSS/PropertyID.h)
+
+add_custom_command(
+ OUTPUT CSS/PropertyID.cpp
+ COMMAND /bin/mkdir -p CSS
+ COMMAND ${write_if_different} CSS/PropertyID.cpp CodeGenerators/Generate_CSS_PropertyID_cpp ${CMAKE_CURRENT_SOURCE_DIR}/CSS/Properties.json
+ VERBATIM
+ DEPENDS Generate_CSS_PropertyID_cpp
+ MAIN_DEPENDENCY CSS/Properties.json
+)
+
+add_custom_command(
+ OUTPUT CSS/ValueID.h
+ COMMAND ${write_if_different} CSS/ValueID.h CodeGenerators/Generate_CSS_ValueID_h ${CMAKE_CURRENT_SOURCE_DIR}/CSS/Identifiers.json
+ VERBATIM
+ DEPENDS Generate_CSS_ValueID_h
+ MAIN_DEPENDENCY CSS/Identifiers.json
+)
+add_custom_target(generate_ValueID.h DEPENDS CSS/ValueID.h)
+
+add_custom_command(
+ OUTPUT CSS/ValueID.cpp
+ COMMAND /bin/mkdir -p CSS
+ COMMAND ${write_if_different} CSS/ValueID.cpp CodeGenerators/Generate_CSS_ValueID_cpp ${CMAKE_CURRENT_SOURCE_DIR}/CSS/Identifiers.json
+ VERBATIM
+ DEPENDS Generate_CSS_ValueID_cpp
+ MAIN_DEPENDENCY CSS/Identifiers.json
+)
+
+add_custom_command(
+ OUTPUT CSS/DefaultStyleSheetSource.cpp
+ COMMAND ${write_if_different} CSS/DefaultStyleSheetSource.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Scripts/GenerateStyleSheetSource.sh default_stylesheet_source ${CMAKE_CURRENT_SOURCE_DIR}/CSS/Default.css
+ VERBATIM
+ DEPENDS Scripts/GenerateStyleSheetSource.sh
+ MAIN_DEPENDENCY CSS/Default.css
+)
+
+add_custom_command(
+ OUTPUT CSS/QuirksModeStyleSheetSource.cpp
+ COMMAND ${write_if_different} CSS/QuirksModeStyleSheetSource.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Scripts/GenerateStyleSheetSource.sh quirks_mode_stylesheet_source ${CMAKE_CURRENT_SOURCE_DIR}/CSS/QuirksMode.css
+ VERBATIM
+ DEPENDS Scripts/GenerateStyleSheetSource.sh
+ MAIN_DEPENDENCY CSS/Default.css
+)
+
+serenity_lib(LibWeb web)
+target_link_libraries(LibWeb LibCore LibJS LibMarkdown LibGemini LibGUI LibGfx LibTextCodec LibProtocol LibImageDecoderClient)
+
+add_subdirectory(DumpLayoutTree)
diff --git a/Userland/Libraries/LibWeb/CSS/.gitignore b/Userland/Libraries/LibWeb/CSS/.gitignore
new file mode 100644
index 0000000000..ae00c71e95
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/.gitignore
@@ -0,0 +1,3 @@
+DefaultStyleSheetSource.cpp
+PropertyID.cpp
+PropertyID.h
diff --git a/Userland/Libraries/LibWeb/CSS/ComputedValues.h b/Userland/Libraries/LibWeb/CSS/ComputedValues.h
new file mode 100644
index 0000000000..57866b07f4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/ComputedValues.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Optional.h>
+#include <LibWeb/CSS/LengthBox.h>
+#include <LibWeb/CSS/StyleValue.h>
+
+namespace Web::CSS {
+
+class InitialValues {
+public:
+ static CSS::Float float_() { return CSS::Float::None; }
+ static CSS::Clear clear() { return CSS::Clear::None; }
+ static CSS::WhiteSpace white_space() { return CSS::WhiteSpace::Normal; }
+ static CSS::TextAlign text_align() { return CSS::TextAlign::Left; }
+ static CSS::Position position() { return CSS::Position::Static; }
+ static CSS::TextDecorationLine text_decoration_line() { return CSS::TextDecorationLine::None; }
+ static CSS::TextTransform text_transform() { return CSS::TextTransform::None; }
+ static CSS::Display display() { return CSS::Display::Inline; }
+ static Color color() { return Color::Black; }
+ static Color background_color() { return Color::Transparent; }
+ static CSS::ListStyleType list_style_type() { return CSS::ListStyleType::Disc; }
+};
+
+struct BorderData {
+public:
+ Color color { Color::Transparent };
+ CSS::LineStyle line_style { CSS::LineStyle::None };
+ float width { 0 };
+};
+
+class ComputedValues {
+public:
+ CSS::Float float_() const { return m_noninherited.float_; }
+ CSS::Clear clear() const { return m_noninherited.clear; }
+ CSS::Display display() const { return m_noninherited.display; }
+ Optional<int> z_index() const { return m_noninherited.z_index; }
+ CSS::TextAlign text_align() const { return m_inherited.text_align; }
+ CSS::TextDecorationLine text_decoration_line() const { return m_noninherited.text_decoration_line; }
+ CSS::TextTransform text_transform() const { return m_inherited.text_transform; }
+ CSS::Position position() const { return m_noninherited.position; }
+ CSS::WhiteSpace white_space() const { return m_inherited.white_space; }
+ const CSS::Length& width() const { return m_noninherited.width; }
+ const CSS::Length& min_width() const { return m_noninherited.min_width; }
+ const CSS::Length& max_width() const { return m_noninherited.max_width; }
+ const CSS::Length& height() const { return m_noninherited.height; }
+ const CSS::Length& min_height() const { return m_noninherited.min_height; }
+ const CSS::Length& max_height() const { return m_noninherited.max_height; }
+
+ const CSS::LengthBox& offset() const { return m_noninherited.offset; }
+ const CSS::LengthBox& margin() const { return m_noninherited.margin; }
+ const CSS::LengthBox& padding() const { return m_noninherited.padding; }
+
+ const BorderData& border_left() const { return m_noninherited.border_left; }
+ const BorderData& border_top() const { return m_noninherited.border_top; }
+ const BorderData& border_right() const { return m_noninherited.border_right; }
+ const BorderData& border_bottom() const { return m_noninherited.border_bottom; }
+
+ Color color() const { return m_inherited.color; }
+ Color background_color() const { return m_noninherited.background_color; }
+
+ CSS::ListStyleType list_style_type() const { return m_inherited.list_style_type; }
+
+ ComputedValues clone_inherited_values() const
+ {
+ ComputedValues clone;
+ clone.m_inherited = m_inherited;
+ return clone;
+ }
+
+protected:
+ struct {
+ Color color { InitialValues::color() };
+ CSS::TextAlign text_align { InitialValues::text_align() };
+ CSS::TextTransform text_transform { InitialValues::text_transform() };
+ CSS::WhiteSpace white_space { InitialValues::white_space() };
+ CSS::ListStyleType list_style_type { InitialValues::list_style_type() };
+ } m_inherited;
+
+ struct {
+ CSS::Float float_ { InitialValues::float_() };
+ CSS::Clear clear { InitialValues::clear() };
+ CSS::Display display { InitialValues::display() };
+ Optional<int> z_index;
+ CSS::TextDecorationLine text_decoration_line { InitialValues::text_decoration_line() };
+ CSS::Position position { InitialValues::position() };
+ CSS::Length width;
+ CSS::Length min_width;
+ CSS::Length max_width;
+ CSS::Length height;
+ CSS::Length min_height;
+ CSS::Length max_height;
+ CSS::LengthBox offset;
+ CSS::LengthBox margin;
+ CSS::LengthBox padding;
+ BorderData border_left;
+ BorderData border_top;
+ BorderData border_right;
+ BorderData border_bottom;
+ Color background_color { InitialValues::background_color() };
+ } m_noninherited;
+};
+
+class ImmutableComputedValues final : public ComputedValues {
+};
+
+class MutableComputedValues final : public ComputedValues {
+public:
+ void set_color(const Color& color) { m_inherited.color = color; }
+ void set_background_color(const Color& color) { m_noninherited.background_color = color; }
+ void set_float(CSS::Float value) { m_noninherited.float_ = value; }
+ void set_clear(CSS::Clear value) { m_noninherited.clear = value; }
+ void set_z_index(Optional<int> value) { m_noninherited.z_index = value; }
+ void set_text_align(CSS::TextAlign text_align) { m_inherited.text_align = text_align; }
+ void set_text_decoration_line(CSS::TextDecorationLine value) { m_noninherited.text_decoration_line = value; }
+ void set_text_transform(CSS::TextTransform value) { m_inherited.text_transform = value; }
+ void set_position(CSS::Position position) { m_noninherited.position = position; }
+ void set_white_space(CSS::WhiteSpace value) { m_inherited.white_space = value; }
+ void set_width(const CSS::Length& width) { m_noninherited.width = width; }
+ void set_min_width(const CSS::Length& width) { m_noninherited.min_width = width; }
+ void set_max_width(const CSS::Length& width) { m_noninherited.max_width = width; }
+ void set_height(const CSS::Length& height) { m_noninherited.height = height; }
+ void set_min_height(const CSS::Length& height) { m_noninherited.min_height = height; }
+ void set_max_height(const CSS::Length& height) { m_noninherited.max_height = height; }
+ void set_offset(const CSS::LengthBox& offset) { m_noninherited.offset = offset; }
+ void set_margin(const CSS::LengthBox& margin) { m_noninherited.margin = margin; }
+ void set_padding(const CSS::LengthBox& padding) { m_noninherited.padding = padding; }
+ void set_list_style_type(CSS::ListStyleType value) { m_inherited.list_style_type = value; }
+ void set_display(CSS::Display value) { m_noninherited.display = value; }
+ BorderData& border_left() { return m_noninherited.border_left; }
+ BorderData& border_top() { return m_noninherited.border_top; }
+ BorderData& border_right() { return m_noninherited.border_right; }
+ BorderData& border_bottom() { return m_noninherited.border_bottom; }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/Default.css b/Userland/Libraries/LibWeb/CSS/Default.css
new file mode 100644
index 0000000000..a63c7873ef
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/Default.css
@@ -0,0 +1,196 @@
+html {
+ font-family: sans-serif;
+}
+
+head,
+link,
+meta,
+script,
+style,
+title {
+ display: none;
+}
+
+body {
+ margin: 8px;
+}
+
+h1,
+h2 {
+ font-family: Pebbleton;
+ font-size: 14px;
+ font-weight: bold;
+}
+
+h3,
+h4,
+h5,
+h6 {
+ font-weight: bold;
+}
+
+pre {
+ font-family: monospace;
+ margin-bottom: 8px;
+ margin-top: 8px;
+ white-space: pre;
+}
+
+code {
+ font-family: monospace;
+}
+
+u,
+ins {
+ text-decoration: underline;
+}
+
+strong,
+b {
+ font-weight: bold;
+}
+
+html,
+address,
+blockquote,
+body,
+dd,
+div,
+dl,
+dt,
+fieldset,
+form,
+frame,
+frameset,
+hgroup,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+noframes,
+ol,
+p,
+ul,
+center,
+dir,
+hr,
+menu,
+pre,
+header,
+footer,
+nav,
+main,
+article,
+aside,
+section {
+ display: block;
+}
+
+center {
+ text-align: -libweb-center;
+}
+
+h1,
+h2,
+h3 {
+ margin: 8px 0 8px 0;
+}
+
+h4,
+p,
+blockquote,
+ul,
+fieldset,
+form,
+ol,
+dl,
+dir,
+menu {
+ margin: 4px 0 4px 0;
+}
+
+h5,
+h6 {
+ margin: 2px 0 2px 0;
+}
+
+li {
+ display: list-item;
+ margin-left: 8px;
+ margin-top: 2px;
+ margin-bottom: 2px;
+}
+
+a:link {
+ color: -libweb-link;
+ text-decoration: underline;
+}
+
+a:hover {
+ color: red;
+}
+
+hr {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+ border: 1px inset #888888;
+}
+
+blink {
+ display: inline;
+}
+
+table {
+ display: table;
+}
+
+thead {
+ display: table-header-group;
+ vertical-align: middle;
+ border-color: inherit;
+}
+
+tbody {
+ display: table-row-group;
+ vertical-align: middle;
+ border-color: inherit;
+}
+
+tfoot {
+ display: table-footer-group;
+ vertical-align: middle;
+ border-color: inherit;
+}
+
+tr {
+ display: table-row;
+}
+
+td,
+th {
+ display: table-cell;
+}
+
+col {
+ display: table-column;
+}
+
+colgroup {
+ display: table-column-group;
+}
+
+basefont {
+ display: block;
+}
+
+blockquote {
+ margin-left: 25px;
+ margin-right: 25px;
+}
+
+ul,
+ol {
+ padding-left: 20px;
+}
diff --git a/Userland/Libraries/LibWeb/CSS/Identifiers.json b/Userland/Libraries/LibWeb/CSS/Identifiers.json
new file mode 100644
index 0000000000..5e412147fe
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/Identifiers.json
@@ -0,0 +1,122 @@
+[
+ "-libweb-center",
+ "-libweb-link",
+ "-libweb-palette-active-link",
+ "-libweb-palette-active-window-border1",
+ "-libweb-palette-active-window-border2",
+ "-libweb-palette-active-window-title",
+ "-libweb-palette-base",
+ "-libweb-palette-base-text",
+ "-libweb-palette-button",
+ "-libweb-palette-button-text",
+ "-libweb-palette-desktop-background",
+ "-libweb-palette-focus-outline",
+ "-libweb-palette-highlight-window-border1",
+ "-libweb-palette-highlight-window-border2",
+ "-libweb-palette-highlight-window-title",
+ "-libweb-palette-hover-highlight",
+ "-libweb-palette-inactive-selection",
+ "-libweb-palette-inactive-selection-text",
+ "-libweb-palette-inactive-window-border1",
+ "-libweb-palette-inactive-window-border2",
+ "-libweb-palette-inactive-window-title",
+ "-libweb-palette-link",
+ "-libweb-palette-menu-base",
+ "-libweb-palette-menu-base-text",
+ "-libweb-palette-menu-selection",
+ "-libweb-palette-menu-selection-text",
+ "-libweb-palette-menu-stripe",
+ "-libweb-palette-moving-window-border1",
+ "-libweb-palette-moving-window-border2",
+ "-libweb-palette-moving-window-title",
+ "-libweb-palette-rubber-band-border",
+ "-libweb-palette-rubber-band-fill",
+ "-libweb-palette-ruler",
+ "-libweb-palette-ruler-active-text",
+ "-libweb-palette-ruler-border",
+ "-libweb-palette-ruler-inactive-text",
+ "-libweb-palette-selection",
+ "-libweb-palette-selection-text",
+ "-libweb-palette-syntax-comment",
+ "-libweb-palette-syntax-control-keyword",
+ "-libweb-palette-syntax-identifier",
+ "-libweb-palette-syntax-keyword",
+ "-libweb-palette-syntax-number",
+ "-libweb-palette-syntax-operator",
+ "-libweb-palette-syntax-preprocessor-statement",
+ "-libweb-palette-syntax-preprocessor-value",
+ "-libweb-palette-syntax-punctuation",
+ "-libweb-palette-syntax-string",
+ "-libweb-palette-syntax-type",
+ "-libweb-palette-text-cursor",
+ "-libweb-palette-threed-highlight",
+ "-libweb-palette-threed-shadow1",
+ "-libweb-palette-threed-shadow2",
+ "-libweb-palette-visited-link",
+ "-libweb-palette-window",
+ "-libweb-palette-window-text",
+ "absolute",
+ "blink",
+ "block",
+ "bold",
+ "bolder",
+ "both",
+ "capitalize",
+ "center",
+ "circle",
+ "dashed",
+ "decimal",
+ "disc",
+ "dotted",
+ "double",
+ "fixed",
+ "full-size-kana",
+ "full-width",
+ "groove",
+ "hidden",
+ "inline",
+ "inline-block",
+ "inset",
+ "justify",
+ "large",
+ "larger",
+ "left",
+ "lighter",
+ "line-through",
+ "list-item",
+ "lowercase",
+ "medium",
+ "none",
+ "normal",
+ "nowrap",
+ "outset",
+ "overline",
+ "pre",
+ "pre-line",
+ "pre-wrap",
+ "relative",
+ "ridge",
+ "right",
+ "small",
+ "smaller",
+ "solid",
+ "square",
+ "static",
+ "sticky",
+ "table",
+ "table-caption",
+ "table-cell",
+ "table-column",
+ "table-column-group",
+ "table-footer-group",
+ "table-header-group",
+ "table-row",
+ "table-row-group",
+ "underline",
+ "uppercase",
+ "x-large",
+ "x-small",
+ "xx-large",
+ "xx-small",
+ "xxx-large"
+]
diff --git a/Userland/Libraries/LibWeb/CSS/Length.cpp b/Userland/Libraries/LibWeb/CSS/Length.cpp
new file mode 100644
index 0000000000..08b2ca500f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/Length.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/Length.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/HTML/HTMLHtmlElement.h>
+#include <LibWeb/Page/Frame.h>
+
+namespace Web::CSS {
+
+float Length::relative_length_to_px(const Layout::Node& layout_node) const
+{
+ switch (m_type) {
+ case Type::Ex:
+ return m_value * layout_node.font().x_height();
+ case Type::Em:
+ return m_value * layout_node.font_size();
+ case Type::Rem:
+ return m_value * layout_node.document().document_element()->layout_node()->font_size();
+ case Type::Vw:
+ return layout_node.document().frame()->viewport_rect().width() * (m_value / 100);
+ case Type::Vh:
+ return layout_node.document().frame()->viewport_rect().height() * (m_value / 100);
+ case Type::Vmin: {
+ auto viewport = layout_node.document().frame()->viewport_rect();
+
+ return min(viewport.width(), viewport.height()) * (m_value / 100);
+ }
+ case Type::Vmax: {
+ auto viewport = layout_node.document().frame()->viewport_rect();
+
+ return max(viewport.width(), viewport.height()) * (m_value / 100);
+ }
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+const char* Length::unit_name() const
+{
+ switch (m_type) {
+ case Type::Cm:
+ return "cm";
+ case Type::In:
+ return "in";
+ case Type::Px:
+ return "px";
+ case Type::Pt:
+ return "pt";
+ case Type::Mm:
+ return "mm";
+ case Type::Q:
+ return "Q";
+ case Type::Pc:
+ return "pc";
+ case Type::Ex:
+ return "ex";
+ case Type::Em:
+ return "em";
+ case Type::Rem:
+ return "rem";
+ case Type::Auto:
+ return "auto";
+ case Type::Percentage:
+ return "%";
+ case Type::Undefined:
+ return "undefined";
+ case Type::Vh:
+ return "vh";
+ case Type::Vw:
+ return "vw";
+ case Type::Vmax:
+ return "vmax";
+ case Type::Vmin:
+ return "vmin";
+ }
+ ASSERT_NOT_REACHED();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/Length.h b/Userland/Libraries/LibWeb/CSS/Length.h
new file mode 100644
index 0000000000..bce76a059c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/Length.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::CSS {
+
+class Length {
+public:
+ enum class Type {
+ Undefined,
+ Percentage,
+ Auto,
+ Cm,
+ In,
+ Mm,
+ Q,
+ Px,
+ Pt,
+ Pc,
+ Ex,
+ Em,
+ Rem,
+ Vh,
+ Vw,
+ Vmax,
+ Vmin,
+ };
+
+ Length() { }
+ Length(int value, Type type)
+ : m_type(type)
+ , m_value(value)
+ {
+ }
+ Length(float value, Type type)
+ : m_type(type)
+ , m_value(value)
+ {
+ }
+
+ static Length make_auto() { return Length(0, Type::Auto); }
+ static Length make_px(float value) { return Length(value, Type::Px); }
+
+ Length resolved(const Length& fallback_for_undefined, const Layout::Node& layout_node, float reference_for_percent) const
+ {
+ if (is_undefined())
+ return fallback_for_undefined;
+ if (is_percentage())
+ return make_px(raw_value() / 100.0 * reference_for_percent);
+ if (is_relative())
+ return make_px(to_px(layout_node));
+ return *this;
+ }
+
+ Length resolved_or_auto(const Layout::Node& layout_node, float reference_for_percent) const
+ {
+ return resolved(make_auto(), layout_node, reference_for_percent);
+ }
+
+ Length resolved_or_zero(const Layout::Node& layout_node, float reference_for_percent) const
+ {
+ return resolved(make_px(0), layout_node, reference_for_percent);
+ }
+
+ bool is_undefined_or_auto() const { return m_type == Type::Undefined || m_type == Type::Auto; }
+ bool is_undefined() const { return m_type == Type::Undefined; }
+ bool is_percentage() const { return m_type == Type::Percentage; }
+ bool is_auto() const { return m_type == Type::Auto; }
+
+ bool is_absolute() const
+ {
+ return m_type == Type::Cm
+ || m_type == Type::In
+ || m_type == Type::Mm
+ || m_type == Type::Px
+ || m_type == Type::Pt
+ || m_type == Type::Pc
+ || m_type == Type::Q;
+ }
+
+ bool is_relative() const
+ {
+ return m_type == Type::Ex
+ || m_type == Type::Em
+ || m_type == Type::Rem
+ || m_type == Type::Vh
+ || m_type == Type::Vw
+ || m_type == Type::Vmax
+ || m_type == Type::Vmin;
+ }
+
+ float raw_value() const { return m_value; }
+ ALWAYS_INLINE float to_px(const Layout::Node& layout_node) const
+ {
+ if (is_relative())
+ return relative_length_to_px(layout_node);
+ constexpr float inch_pixels = 96.0f;
+ constexpr float centimeter_pixels = (inch_pixels / 2.54f);
+ switch (m_type) {
+ case Type::Auto:
+ return 0;
+ case Type::Cm:
+ return m_value * centimeter_pixels; // 1cm = 96px/2.54
+ case Type::In:
+ return m_value * inch_pixels; // 1in = 2.54 cm = 96px
+ case Type::Px:
+ return m_value; // 1px = 1/96th of 1in
+ case Type::Pt:
+ return m_value * ((1.0f / 72.0f) * inch_pixels); // 1pt = 1/72th of 1in
+ case Type::Pc:
+ return m_value * ((1.0f / 6.0f) * inch_pixels); // 1pc = 1/6th of 1in
+ case Type::Mm:
+ return m_value * ((1.0f / 10.0f) * centimeter_pixels); // 1mm = 1/10th of 1cm
+ case Type::Q:
+ return m_value * ((1.0f / 40.0f) * centimeter_pixels); // 1Q = 1/40th of 1cm
+ case Type::Undefined:
+ case Type::Percentage:
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ String to_string() const
+ {
+ if (is_auto())
+ return "[auto]";
+ return String::formatted("[{} {}]", m_value, unit_name());
+ }
+
+ bool operator==(const Length& other) const
+ {
+ return m_type == other.m_type && m_value == other.m_value;
+ }
+
+ bool operator!=(const Length& other) const
+ {
+ return !(*this == other);
+ }
+
+private:
+ float relative_length_to_px(const Layout::Node&) const;
+
+ const char* unit_name() const;
+
+ Type m_type { Type::Undefined };
+ float m_value { 0 };
+};
+
+inline const LogStream& operator<<(const LogStream& stream, const Length& value)
+{
+ return stream << value.to_string();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/LengthBox.h b/Userland/Libraries/LibWeb/CSS/LengthBox.h
new file mode 100644
index 0000000000..931d6c6da2
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/LengthBox.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/CSS/Length.h>
+
+namespace Web::CSS {
+
+struct LengthBox {
+ Length top { Length::make_auto() };
+ Length right { Length::make_auto() };
+ Length bottom { Length::make_auto() };
+ Length left { Length::make_auto() };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/CSSParser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/CSSParser.cpp
new file mode 100644
index 0000000000..62bee3b733
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/Parser/CSSParser.cpp
@@ -0,0 +1,902 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/HashMap.h>
+#include <LibWeb/CSS/Parser/CSSParser.h>
+#include <LibWeb/CSS/PropertyID.h>
+#include <LibWeb/CSS/StyleSheet.h>
+#include <LibWeb/DOM/Document.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define PARSE_ASSERT(x) \
+ if (!(x)) { \
+ dbg() << "CSS PARSER ASSERTION FAILED: " << #x; \
+ dbg() << "At character# " << index << " in CSS: _" << css << "_"; \
+ ASSERT_NOT_REACHED(); \
+ }
+
+#define PARSE_ERROR() \
+ do { \
+ dbgln("CSS parse error"); \
+ } while (0)
+
+namespace Web {
+
+namespace CSS {
+
+ParsingContext::ParsingContext()
+{
+}
+
+ParsingContext::ParsingContext(const DOM::Document& document)
+ : m_document(&document)
+{
+}
+
+ParsingContext::ParsingContext(const DOM::ParentNode& parent_node)
+ : m_document(&parent_node.document())
+{
+}
+
+bool ParsingContext::in_quirks_mode() const
+{
+ return m_document ? m_document->in_quirks_mode() : false;
+}
+
+}
+
+static Optional<Color> parse_css_color(const CSS::ParsingContext&, const StringView& view)
+{
+ if (view.equals_ignoring_case("transparent"))
+ return Color::from_rgba(0x00000000);
+
+ auto color = Color::from_string(view.to_string().to_lowercase());
+ if (color.has_value())
+ return color;
+
+ return {};
+}
+
+static Optional<float> try_parse_float(const StringView& string)
+{
+ const char* str = string.characters_without_null_termination();
+ size_t len = string.length();
+ size_t weight = 1;
+ int exp_val = 0;
+ float value = 0.0f;
+ float fraction = 0.0f;
+ bool has_sign = false;
+ bool is_negative = false;
+ bool is_fractional = false;
+ bool is_scientific = false;
+
+ if (str[0] == '-') {
+ is_negative = true;
+ has_sign = true;
+ }
+ if (str[0] == '+') {
+ has_sign = true;
+ }
+
+ for (size_t i = has_sign; i < len; i++) {
+
+ // Looks like we're about to start working on the fractional part
+ if (str[i] == '.') {
+ is_fractional = true;
+ continue;
+ }
+
+ if (str[i] == 'e' || str[i] == 'E') {
+ if (str[i + 1] == '-' || str[i + 1] == '+')
+ exp_val = atoi(str + i + 2);
+ else
+ exp_val = atoi(str + i + 1);
+
+ is_scientific = true;
+ continue;
+ }
+
+ if (str[i] < '0' || str[i] > '9' || exp_val != 0) {
+ return {};
+ continue;
+ }
+
+ if (is_fractional) {
+ fraction *= 10;
+ fraction += str[i] - '0';
+ weight *= 10;
+ } else {
+ value = value * 10;
+ value += str[i] - '0';
+ }
+ }
+
+ fraction /= weight;
+ value += fraction;
+
+ if (is_scientific) {
+ bool divide = exp_val < 0;
+ if (divide)
+ exp_val *= -1;
+
+ for (int i = 0; i < exp_val; i++) {
+ if (divide)
+ value /= 10;
+ else
+ value *= 10;
+ }
+ }
+
+ return is_negative ? -value : value;
+}
+
+static CSS::Length parse_length(const CSS::ParsingContext& context, const StringView& view, bool& is_bad_length)
+{
+ CSS::Length::Type type = CSS::Length::Type::Undefined;
+ Optional<float> value;
+
+ if (view.ends_with('%')) {
+ type = CSS::Length::Type::Percentage;
+ value = try_parse_float(view.substring_view(0, view.length() - 1));
+ } else if (view.ends_with("px", CaseSensitivity::CaseInsensitive)) {
+ type = CSS::Length::Type::Px;
+ value = try_parse_float(view.substring_view(0, view.length() - 2));
+ } else if (view.ends_with("pt", CaseSensitivity::CaseInsensitive)) {
+ type = CSS::Length::Type::Pt;
+ value = try_parse_float(view.substring_view(0, view.length() - 2));
+ } else if (view.ends_with("pc", CaseSensitivity::CaseInsensitive)) {
+ type = CSS::Length::Type::Pc;
+ value = try_parse_float(view.substring_view(0, view.length() - 2));
+ } else if (view.ends_with("mm", CaseSensitivity::CaseInsensitive)) {
+ type = CSS::Length::Type::Mm;
+ value = try_parse_float(view.substring_view(0, view.length() - 2));
+ } else if (view.ends_with("rem", CaseSensitivity::CaseInsensitive)) {
+ type = CSS::Length::Type::Rem;
+ value = try_parse_float(view.substring_view(0, view.length() - 3));
+ } else if (view.ends_with("em", CaseSensitivity::CaseInsensitive)) {
+ type = CSS::Length::Type::Em;
+ value = try_parse_float(view.substring_view(0, view.length() - 2));
+ } else if (view.ends_with("ex", CaseSensitivity::CaseInsensitive)) {
+ type = CSS::Length::Type::Ex;
+ value = try_parse_float(view.substring_view(0, view.length() - 2));
+ } else if (view.ends_with("vw", CaseSensitivity::CaseInsensitive)) {
+ type = CSS::Length::Type::Vw;
+ value = try_parse_float(view.substring_view(0, view.length() - 2));
+ } else if (view.ends_with("vh", CaseSensitivity::CaseInsensitive)) {
+ type = CSS::Length::Type::Vh;
+ value = try_parse_float(view.substring_view(0, view.length() - 2));
+ } else if (view.ends_with("vmax", CaseSensitivity::CaseInsensitive)) {
+ type = CSS::Length::Type::Vmax;
+ value = try_parse_float(view.substring_view(0, view.length() - 4));
+ } else if (view.ends_with("vmin", CaseSensitivity::CaseInsensitive)) {
+ type = CSS::Length::Type::Vmin;
+ value = try_parse_float(view.substring_view(0, view.length() - 4));
+ } else if (view.ends_with("cm", CaseSensitivity::CaseInsensitive)) {
+ type = CSS::Length::Type::Cm;
+ value = try_parse_float(view.substring_view(0, view.length() - 2));
+ } else if (view.ends_with("in", CaseSensitivity::CaseInsensitive)) {
+ type = CSS::Length::Type::In;
+ value = try_parse_float(view.substring_view(0, view.length() - 2));
+ } else if (view.ends_with("Q", CaseSensitivity::CaseInsensitive)) {
+ type = CSS::Length::Type::Q;
+ value = try_parse_float(view.substring_view(0, view.length() - 1));
+ } else if (view == "0") {
+ type = CSS::Length::Type::Px;
+ value = 0;
+ } else if (context.in_quirks_mode()) {
+ type = CSS::Length::Type::Px;
+ value = try_parse_float(view);
+ } else {
+ value = try_parse_float(view);
+ if (value.has_value())
+ is_bad_length = true;
+ }
+
+ if (!value.has_value())
+ return {};
+
+ return CSS::Length(value.value(), type);
+}
+
+static bool takes_integer_value(CSS::PropertyID property_id)
+{
+ return property_id == CSS::PropertyID::ZIndex || property_id == CSS::PropertyID::FontWeight;
+}
+
+RefPtr<CSS::StyleValue> parse_css_value(const CSS::ParsingContext& context, const StringView& string, CSS::PropertyID property_id)
+{
+ bool is_bad_length = false;
+
+ if (takes_integer_value(property_id)) {
+ auto integer = string.to_int();
+ if (integer.has_value())
+ return CSS::LengthStyleValue::create(CSS::Length::make_px(integer.value()));
+ }
+
+ auto length = parse_length(context, string, is_bad_length);
+ if (is_bad_length)
+ return nullptr;
+ if (!length.is_undefined())
+ return CSS::LengthStyleValue::create(length);
+
+ if (string.equals_ignoring_case("inherit"))
+ return CSS::InheritStyleValue::create();
+ if (string.equals_ignoring_case("initial"))
+ return CSS::InitialStyleValue::create();
+ if (string.equals_ignoring_case("auto"))
+ return CSS::LengthStyleValue::create(CSS::Length::make_auto());
+
+ auto value_id = CSS::value_id_from_string(string);
+ if (value_id != CSS::ValueID::Invalid)
+ return CSS::IdentifierStyleValue::create(value_id);
+
+ auto color = parse_css_color(context, string);
+ if (color.has_value())
+ return CSS::ColorStyleValue::create(color.value());
+
+ return CSS::StringStyleValue::create(string);
+}
+
+RefPtr<CSS::LengthStyleValue> parse_line_width(const CSS::ParsingContext& context, const StringView& part)
+{
+ auto value = parse_css_value(context, part);
+ if (value && value->is_length())
+ return static_ptr_cast<CSS::LengthStyleValue>(value);
+ return nullptr;
+}
+
+RefPtr<CSS::ColorStyleValue> parse_color(const CSS::ParsingContext& context, const StringView& part)
+{
+ auto value = parse_css_value(context, part);
+ if (value && value->is_color())
+ return static_ptr_cast<CSS::ColorStyleValue>(value);
+ return nullptr;
+}
+
+RefPtr<CSS::StringStyleValue> parse_line_style(const CSS::ParsingContext& context, const StringView& part)
+{
+ auto parsed_value = parse_css_value(context, part);
+ if (!parsed_value || !parsed_value->is_string())
+ return nullptr;
+ auto value = static_ptr_cast<CSS::StringStyleValue>(parsed_value);
+ if (value->to_string() == "dotted")
+ return value;
+ if (value->to_string() == "dashed")
+ return value;
+ if (value->to_string() == "solid")
+ return value;
+ if (value->to_string() == "double")
+ return value;
+ if (value->to_string() == "groove")
+ return value;
+ if (value->to_string() == "ridge")
+ return value;
+ return nullptr;
+}
+
+class CSSParser {
+public:
+ CSSParser(const CSS::ParsingContext& context, const StringView& input)
+ : m_context(context)
+ , css(input)
+ {
+ }
+
+ bool next_is(const char* str) const
+ {
+ size_t len = strlen(str);
+ for (size_t i = 0; i < len; ++i) {
+ if (peek(i) != str[i])
+ return false;
+ }
+ return true;
+ }
+
+ char peek(size_t offset = 0) const
+ {
+ if ((index + offset) < css.length())
+ return css[index + offset];
+ return 0;
+ }
+
+ bool consume_specific(char ch)
+ {
+ if (peek() != ch) {
+ dbgln("CSSParser: Peeked '{:c}' wanted specific '{:c}'", peek(), ch);
+ }
+ if (!peek()) {
+ PARSE_ERROR();
+ return false;
+ }
+ if (peek() != ch) {
+ PARSE_ERROR();
+ ++index;
+ return false;
+ }
+ ++index;
+ return true;
+ }
+
+ char consume_one()
+ {
+ PARSE_ASSERT(index < css.length());
+ return css[index++];
+ };
+
+ bool consume_whitespace_or_comments()
+ {
+ size_t original_index = index;
+ bool in_comment = false;
+ for (; index < css.length(); ++index) {
+ char ch = peek();
+ if (isspace(ch))
+ continue;
+ if (!in_comment && ch == '/' && peek(1) == '*') {
+ in_comment = true;
+ ++index;
+ continue;
+ }
+ if (in_comment && ch == '*' && peek(1) == '/') {
+ in_comment = false;
+ ++index;
+ continue;
+ }
+ if (in_comment)
+ continue;
+ break;
+ }
+ return original_index != index;
+ }
+
+ bool is_valid_selector_char(char ch) const
+ {
+ return isalnum(ch) || ch == '-' || ch == '_' || ch == '(' || ch == ')' || ch == '@';
+ }
+
+ bool is_combinator(char ch) const
+ {
+ return ch == '~' || ch == '>' || ch == '+';
+ }
+
+ Optional<CSS::Selector::SimpleSelector> parse_simple_selector()
+ {
+ auto index_at_start = index;
+
+ if (consume_whitespace_or_comments())
+ return {};
+
+ if (!peek() || peek() == '{' || peek() == ',' || is_combinator(peek()))
+ return {};
+
+ CSS::Selector::SimpleSelector::Type type;
+
+ if (peek() == '*') {
+ type = CSS::Selector::SimpleSelector::Type::Universal;
+ consume_one();
+ return CSS::Selector::SimpleSelector {
+ type,
+ CSS::Selector::SimpleSelector::PseudoClass::None,
+ CSS::Selector::SimpleSelector::PseudoElement::None,
+ String(),
+ CSS::Selector::SimpleSelector::AttributeMatchType::None,
+ String(),
+ String()
+ };
+ }
+
+ if (peek() == '.') {
+ type = CSS::Selector::SimpleSelector::Type::Class;
+ consume_one();
+ } else if (peek() == '#') {
+ type = CSS::Selector::SimpleSelector::Type::Id;
+ consume_one();
+ } else if (isalpha(peek())) {
+ type = CSS::Selector::SimpleSelector::Type::TagName;
+ } else {
+ type = CSS::Selector::SimpleSelector::Type::Universal;
+ }
+
+ if (type != CSS::Selector::SimpleSelector::Type::Universal) {
+ while (is_valid_selector_char(peek()))
+ buffer.append(consume_one());
+ PARSE_ASSERT(!buffer.is_null());
+ }
+
+ auto value = String::copy(buffer);
+
+ if (type == CSS::Selector::SimpleSelector::Type::TagName) {
+ // Some stylesheets use uppercase tag names, so here's a hack to just lowercase them internally.
+ value = value.to_lowercase();
+ }
+
+ CSS::Selector::SimpleSelector simple_selector {
+ type,
+ CSS::Selector::SimpleSelector::PseudoClass::None,
+ CSS::Selector::SimpleSelector::PseudoElement::None,
+ value,
+ CSS::Selector::SimpleSelector::AttributeMatchType::None,
+ String(),
+ String()
+ };
+ buffer.clear();
+
+ if (peek() == '[') {
+ CSS::Selector::SimpleSelector::AttributeMatchType attribute_match_type = CSS::Selector::SimpleSelector::AttributeMatchType::HasAttribute;
+ String attribute_name;
+ String attribute_value;
+ bool in_value = false;
+ consume_specific('[');
+ char expected_end_of_attribute_selector = ']';
+ while (peek() != expected_end_of_attribute_selector) {
+ char ch = consume_one();
+ if (ch == '=' || (ch == '~' && peek() == '=')) {
+ if (ch == '=') {
+ attribute_match_type = CSS::Selector::SimpleSelector::AttributeMatchType::ExactValueMatch;
+ } else if (ch == '~') {
+ consume_one();
+ attribute_match_type = CSS::Selector::SimpleSelector::AttributeMatchType::Contains;
+ }
+ attribute_name = String::copy(buffer);
+ buffer.clear();
+ in_value = true;
+ consume_whitespace_or_comments();
+ if (peek() == '\'') {
+ expected_end_of_attribute_selector = '\'';
+ consume_one();
+ } else if (peek() == '"') {
+ expected_end_of_attribute_selector = '"';
+ consume_one();
+ }
+ continue;
+ }
+ // FIXME: This is a hack that will go away when we replace this with a big boy CSS parser.
+ if (ch == '\\')
+ ch = consume_one();
+ buffer.append(ch);
+ }
+ if (in_value)
+ attribute_value = String::copy(buffer);
+ else
+ attribute_name = String::copy(buffer);
+ buffer.clear();
+ simple_selector.attribute_match_type = attribute_match_type;
+ simple_selector.attribute_name = attribute_name;
+ simple_selector.attribute_value = attribute_value;
+ if (expected_end_of_attribute_selector != ']') {
+ if (!consume_specific(expected_end_of_attribute_selector))
+ return {};
+ }
+ consume_whitespace_or_comments();
+ if (!consume_specific(']'))
+ return {};
+ }
+
+ if (peek() == ':') {
+ // FIXME: Implement pseudo elements.
+ [[maybe_unused]] bool is_pseudo_element = false;
+ consume_one();
+ if (peek() == ':') {
+ is_pseudo_element = true;
+ consume_one();
+ }
+ if (next_is("not")) {
+ buffer.append(consume_one());
+ buffer.append(consume_one());
+ buffer.append(consume_one());
+ if (!consume_specific('('))
+ return {};
+ buffer.append('(');
+ while (peek() != ')')
+ buffer.append(consume_one());
+ if (!consume_specific(')'))
+ return {};
+ buffer.append(')');
+ } else {
+ while (is_valid_selector_char(peek()))
+ buffer.append(consume_one());
+ }
+
+ auto pseudo_name = String::copy(buffer);
+ buffer.clear();
+
+ // Ignore for now, otherwise we produce a "false positive" selector
+ // and apply styles to the element itself, not its pseudo element
+ if (is_pseudo_element)
+ return {};
+
+ if (pseudo_name.equals_ignoring_case("link"))
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Link;
+ else if (pseudo_name.equals_ignoring_case("visited"))
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Visited;
+ else if (pseudo_name.equals_ignoring_case("hover"))
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Hover;
+ else if (pseudo_name.equals_ignoring_case("focus"))
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Focus;
+ else if (pseudo_name.equals_ignoring_case("first-child"))
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::FirstChild;
+ else if (pseudo_name.equals_ignoring_case("last-child"))
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::LastChild;
+ else if (pseudo_name.equals_ignoring_case("only-child"))
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::OnlyChild;
+ else if (pseudo_name.equals_ignoring_case("empty"))
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Empty;
+ else if (pseudo_name.equals_ignoring_case("root"))
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Root;
+ else if (pseudo_name.equals_ignoring_case("before"))
+ simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::Before;
+ else if (pseudo_name.equals_ignoring_case("after"))
+ simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::After;
+ }
+
+ if (index == index_at_start) {
+ // We consumed nothing.
+ return {};
+ }
+
+ return simple_selector;
+ }
+
+ Optional<CSS::Selector::ComplexSelector> parse_complex_selector()
+ {
+ auto relation = CSS::Selector::ComplexSelector::Relation::Descendant;
+
+ if (peek() == '{' || peek() == ',')
+ return {};
+
+ if (is_combinator(peek())) {
+ switch (peek()) {
+ case '>':
+ relation = CSS::Selector::ComplexSelector::Relation::ImmediateChild;
+ break;
+ case '+':
+ relation = CSS::Selector::ComplexSelector::Relation::AdjacentSibling;
+ break;
+ case '~':
+ relation = CSS::Selector::ComplexSelector::Relation::GeneralSibling;
+ break;
+ }
+ consume_one();
+ consume_whitespace_or_comments();
+ }
+
+ consume_whitespace_or_comments();
+
+ Vector<CSS::Selector::SimpleSelector> simple_selectors;
+ for (;;) {
+ auto component = parse_simple_selector();
+ if (!component.has_value())
+ break;
+ simple_selectors.append(component.value());
+ // If this assert triggers, we're most likely up to no good.
+ PARSE_ASSERT(simple_selectors.size() < 100);
+ }
+
+ if (simple_selectors.is_empty())
+ return {};
+
+ return CSS::Selector::ComplexSelector { relation, move(simple_selectors) };
+ }
+
+ void parse_selector()
+ {
+ Vector<CSS::Selector::ComplexSelector> complex_selectors;
+
+ for (;;) {
+ auto index_before = index;
+ auto complex_selector = parse_complex_selector();
+ if (complex_selector.has_value())
+ complex_selectors.append(complex_selector.value());
+ consume_whitespace_or_comments();
+ if (!peek() || peek() == ',' || peek() == '{')
+ break;
+ // HACK: If we didn't move forward, just let go.
+ if (index == index_before)
+ break;
+ }
+
+ if (complex_selectors.is_empty())
+ return;
+ complex_selectors.first().relation = CSS::Selector::ComplexSelector::Relation::None;
+
+ current_rule.selectors.append(CSS::Selector(move(complex_selectors)));
+ }
+
+ Optional<CSS::Selector> parse_individual_selector()
+ {
+ parse_selector();
+ if (current_rule.selectors.is_empty())
+ return {};
+ return current_rule.selectors.last();
+ }
+
+ void parse_selector_list()
+ {
+ for (;;) {
+ auto index_before = index;
+ parse_selector();
+ consume_whitespace_or_comments();
+ if (peek() == ',') {
+ consume_one();
+ continue;
+ }
+ if (peek() == '{')
+ break;
+ // HACK: If we didn't move forward, just let go.
+ if (index_before == index)
+ break;
+ }
+ }
+
+ bool is_valid_property_name_char(char ch) const
+ {
+ return ch && !isspace(ch) && ch != ':';
+ }
+
+ bool is_valid_property_value_char(char ch) const
+ {
+ return ch && ch != '!' && ch != ';' && ch != '}';
+ }
+
+ struct ValueAndImportant {
+ String value;
+ bool important { false };
+ };
+
+ ValueAndImportant consume_css_value()
+ {
+ buffer.clear();
+
+ int paren_nesting_level = 0;
+ bool important = false;
+
+ for (;;) {
+ char ch = peek();
+ if (ch == '(') {
+ ++paren_nesting_level;
+ buffer.append(consume_one());
+ continue;
+ }
+ if (ch == ')') {
+ PARSE_ASSERT(paren_nesting_level > 0);
+ --paren_nesting_level;
+ buffer.append(consume_one());
+ continue;
+ }
+ if (paren_nesting_level > 0) {
+ buffer.append(consume_one());
+ continue;
+ }
+ if (next_is("!important")) {
+ consume_specific('!');
+ consume_specific('i');
+ consume_specific('m');
+ consume_specific('p');
+ consume_specific('o');
+ consume_specific('r');
+ consume_specific('t');
+ consume_specific('a');
+ consume_specific('n');
+ consume_specific('t');
+ important = true;
+ continue;
+ }
+ if (next_is("/*")) {
+ consume_whitespace_or_comments();
+ continue;
+ }
+ if (!ch)
+ break;
+ if (ch == '\\') {
+ consume_one();
+ buffer.append(consume_one());
+ continue;
+ }
+ if (ch == '}')
+ break;
+ if (ch == ';')
+ break;
+ buffer.append(consume_one());
+ }
+
+ // Remove trailing whitespace.
+ while (!buffer.is_empty() && isspace(buffer.last()))
+ buffer.take_last();
+
+ auto string = String::copy(buffer);
+ buffer.clear();
+
+ return { string, important };
+ }
+
+ Optional<CSS::StyleProperty> parse_property()
+ {
+ consume_whitespace_or_comments();
+ if (peek() == ';') {
+ consume_one();
+ return {};
+ }
+ if (peek() == '}')
+ return {};
+ buffer.clear();
+ while (is_valid_property_name_char(peek()))
+ buffer.append(consume_one());
+ auto property_name = String::copy(buffer);
+ buffer.clear();
+ consume_whitespace_or_comments();
+ if (!consume_specific(':'))
+ return {};
+ consume_whitespace_or_comments();
+
+ auto [property_value, important] = consume_css_value();
+
+ consume_whitespace_or_comments();
+
+ if (peek() && peek() != '}') {
+ if (!consume_specific(';'))
+ return {};
+ }
+
+ auto property_id = CSS::property_id_from_string(property_name);
+ if (property_id == CSS::PropertyID::Invalid) {
+ dbg() << "CSSParser: Unrecognized property '" << property_name << "'";
+ }
+ auto value = parse_css_value(m_context, property_value, property_id);
+ if (!value)
+ return {};
+ return CSS::StyleProperty { property_id, value.release_nonnull(), important };
+ }
+
+ void parse_declaration()
+ {
+ for (;;) {
+ auto property = parse_property();
+ if (property.has_value())
+ current_rule.properties.append(property.value());
+ consume_whitespace_or_comments();
+ if (!peek() || peek() == '}')
+ break;
+ }
+ }
+
+ void parse_rule()
+ {
+ consume_whitespace_or_comments();
+ if (!peek())
+ return;
+
+ // FIXME: We ignore @-rules for now.
+ if (peek() == '@') {
+ while (peek() != '{')
+ consume_one();
+ int level = 0;
+ for (;;) {
+ auto ch = consume_one();
+ if (ch == '{') {
+ ++level;
+ } else if (ch == '}') {
+ --level;
+ if (level == 0)
+ break;
+ }
+ }
+ consume_whitespace_or_comments();
+ return;
+ }
+
+ parse_selector_list();
+ if (!consume_specific('{')) {
+ PARSE_ERROR();
+ return;
+ }
+ parse_declaration();
+ if (!consume_specific('}')) {
+ PARSE_ERROR();
+ return;
+ }
+ rules.append(CSS::StyleRule::create(move(current_rule.selectors), CSS::StyleDeclaration::create(move(current_rule.properties))));
+ consume_whitespace_or_comments();
+ }
+
+ RefPtr<CSS::StyleSheet> parse_sheet()
+ {
+ if (peek(0) == (char)0xef && peek(1) == (char)0xbb && peek(2) == (char)0xbf) {
+ // HACK: Skip UTF-8 BOM.
+ index += 3;
+ }
+
+ while (peek()) {
+ parse_rule();
+ }
+
+ return CSS::StyleSheet::create(move(rules));
+ }
+
+ RefPtr<CSS::StyleDeclaration> parse_standalone_declaration()
+ {
+ consume_whitespace_or_comments();
+ for (;;) {
+ auto property = parse_property();
+ if (property.has_value())
+ current_rule.properties.append(property.value());
+ consume_whitespace_or_comments();
+ if (!peek())
+ break;
+ }
+ return CSS::StyleDeclaration::create(move(current_rule.properties));
+ }
+
+private:
+ CSS::ParsingContext m_context;
+
+ NonnullRefPtrVector<CSS::StyleRule> rules;
+
+ struct CurrentRule {
+ Vector<CSS::Selector> selectors;
+ Vector<CSS::StyleProperty> properties;
+ };
+
+ CurrentRule current_rule;
+ Vector<char> buffer;
+
+ size_t index = 0;
+
+ StringView css;
+};
+
+Optional<CSS::Selector> parse_selector(const CSS::ParsingContext& context, const StringView& selector_text)
+{
+ CSSParser parser(context, selector_text);
+ return parser.parse_individual_selector();
+}
+
+RefPtr<CSS::StyleSheet> parse_css(const CSS::ParsingContext& context, const StringView& css)
+{
+ if (css.is_empty())
+ return CSS::StyleSheet::create({});
+ CSSParser parser(context, css);
+ return parser.parse_sheet();
+}
+
+RefPtr<CSS::StyleDeclaration> parse_css_declaration(const CSS::ParsingContext& context, const StringView& css)
+{
+ if (css.is_empty())
+ return CSS::StyleDeclaration::create({});
+ CSSParser parser(context, css);
+ return parser.parse_standalone_declaration();
+}
+
+RefPtr<CSS::StyleValue> parse_html_length(const DOM::Document& document, const StringView& string)
+{
+ auto integer = string.to_int();
+ if (integer.has_value())
+ return CSS::LengthStyleValue::create(CSS::Length::make_px(integer.value()));
+ return parse_css_value(CSS::ParsingContext(document), string);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/CSSParser.h b/Userland/Libraries/LibWeb/CSS/Parser/CSSParser.h
new file mode 100644
index 0000000000..ca9a4688e2
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/Parser/CSSParser.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtr.h>
+#include <LibWeb/CSS/StyleSheet.h>
+
+namespace Web::CSS {
+class ParsingContext {
+public:
+ ParsingContext();
+ explicit ParsingContext(const DOM::Document&);
+ explicit ParsingContext(const DOM::ParentNode&);
+
+ bool in_quirks_mode() const;
+
+private:
+ const DOM::Document* m_document { nullptr };
+};
+}
+
+namespace Web {
+
+RefPtr<CSS::StyleSheet> parse_css(const CSS::ParsingContext&, const StringView&);
+RefPtr<CSS::StyleDeclaration> parse_css_declaration(const CSS::ParsingContext&, const StringView&);
+RefPtr<CSS::StyleValue> parse_css_value(const CSS::ParsingContext&, const StringView&, CSS::PropertyID property_id = CSS::PropertyID::Invalid);
+Optional<CSS::Selector> parse_selector(const CSS::ParsingContext&, const StringView&);
+
+RefPtr<CSS::LengthStyleValue> parse_line_width(const CSS::ParsingContext&, const StringView&);
+RefPtr<CSS::ColorStyleValue> parse_color(const CSS::ParsingContext&, const StringView&);
+RefPtr<CSS::StringStyleValue> parse_line_style(const CSS::ParsingContext&, const StringView&);
+
+RefPtr<CSS::StyleValue> parse_html_length(const DOM::Document&, const StringView&);
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/Properties.json b/Userland/Libraries/LibWeb/CSS/Properties.json
new file mode 100644
index 0000000000..86c4009834
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/Properties.json
@@ -0,0 +1,368 @@
+{
+ "background": {
+ },
+ "background-attachment": {
+ "inherited": false,
+ "initial": "scroll"
+ },
+ "background-color": {
+ "inherited": false,
+ "initial": "transparent"
+ },
+ "background-image": {
+ "inherited": false,
+ "initial": "none"
+ },
+ "background-position": {
+ "inherited": false,
+ "initial": "0% 0%"
+ },
+ "background-repeat": {
+ "inherited": false,
+ "initial": "repeat"
+ },
+ "border": {
+ "longhands": [
+ "border-width",
+ "border-style",
+ "border-color"
+ ]
+ },
+ "border-top": {
+ "longhands": [
+ "border-top-width",
+ "border-top-style",
+ "border-top-color"
+ ]
+ },
+ "border-right": {
+ "longhands": [
+ "border-right-width",
+ "border-right-style",
+ "border-right-color"
+ ]
+ },
+ "border-bottom": {
+ "longhands": [
+ "border-bottom-width",
+ "border-bottom-style",
+ "border-bottom-color"
+ ]
+ },
+ "border-left": {
+ "longhands": [
+ "border-left-width",
+ "border-left-style",
+ "border-left-color"
+ ]
+ },
+ "border-bottom-color": {
+ "initial": "currentColor",
+ "inherited": false
+ },
+ "border-bottom-style": {
+ "initial": "none",
+ "inherited": false
+ },
+ "border-bottom-width": {
+ "initial": "medium",
+ "inherited": false
+ },
+ "border-color": {
+ "longhands": [
+ "border-top-color",
+ "border-right-color",
+ "border-bottom-color",
+ "border-left-color"
+ ]
+ },
+ "border-collapse": {
+ "inherited": true,
+ "initial": "separate"
+ },
+ "border-left-color": {
+ "initial": "currentColor",
+ "inherited": false
+ },
+ "border-left-style": {
+ "initial": "none",
+ "inherited": false
+ },
+ "border-left-width": {
+ "initial": "medium",
+ "inherited": false
+ },
+ "border-right-color": {
+ "initial": "currentColor",
+ "inherited": false
+ },
+ "border-right-style": {
+ "initial": "none",
+ "inherited": false
+ },
+ "border-right-width": {
+ "initial": "medium",
+ "inherited": false
+ },
+ "border-spacing": {
+ "inherited": true,
+ "initial": "0"
+ },
+ "border-style": {
+ "longhands": [
+ "border-top-style",
+ "border-right-style",
+ "border-bottom-style",
+ "border-left-style"
+ ]
+ },
+ "border-top-color": {
+ "initial": "currentColor",
+ "inherited": false
+ },
+ "border-top-style": {
+ "initial": "none",
+ "inherited": false
+ },
+ "border-top-width": {
+ "initial": "medium",
+ "inherited": false
+ },
+ "border-width": {
+ "longhands": [
+ "border-top-width",
+ "border-right-width",
+ "border-bottom-width",
+ "border-left-width"
+ ]
+ },
+ "bottom": {
+ "inherited": false,
+ "initial": "auto"
+ },
+ "caption-side": {
+ "inherited": true,
+ "initial": "top"
+ },
+ "clear": {
+ "inherited": false,
+ "initial": "none"
+ },
+ "clip": {
+ "inherited": true,
+ "initial": "auto"
+ },
+ "color": {
+ "inherited": true,
+ "initial": ""
+ },
+ "cursor": {
+ "inherited": true,
+ "initial": "auto"
+ },
+ "direction": {
+ "inherited": true,
+ "initial": "ltr"
+ },
+ "display": {
+ "inherited": false,
+ "initial": "inline"
+ },
+ "float": {
+ "inherited": false,
+ "initial": "none"
+ },
+ "font-family": {
+ "inherited": true,
+ "initial": "sans-serif"
+ },
+ "font-size": {
+ "inherited": true,
+ "initial": "medium"
+ },
+ "font-style": {
+ "inherited": true,
+ "initial": "normal"
+ },
+ "font-variant": {
+ "inherited": true,
+ "initial": "normal"
+ },
+ "font-weight": {
+ "inherited": true,
+ "initial": "normal"
+ },
+ "height": {
+ "inherited": false,
+ "initial": "auto"
+ },
+ "left": {
+ "inherited": false,
+ "initial": "auto"
+ },
+ "letter-spacing": {
+ "inherited": true,
+ "initial": "normal"
+ },
+ "line-height": {
+ "inherited": true,
+ "initial": "normal"
+ },
+ "list-style": {
+ "longhands": [
+ "list-style-type",
+ "list-style-position",
+ "list-style-image"
+ ]
+ },
+ "list-style-image": {
+ "inherited": true,
+ "initial": "none"
+ },
+ "list-style-position": {
+ "inherited": true,
+ "initial": "outside"
+ },
+ "list-style-type": {
+ "inherited": true,
+ "initial": "disc"
+ },
+ "margin": {
+ "longhands": [
+ "margin-top",
+ "margin-right",
+ "margin-bottom",
+ "margin-left"
+ ]
+ },
+ "margin-bottom": {
+ "inherited": false,
+ "initial": "0"
+ },
+ "margin-left": {
+ "inherited": false,
+ "initial": "0"
+ },
+ "margin-right": {
+ "inherited": false,
+ "initial": "0"
+ },
+ "margin-top": {
+ "inherited": false,
+ "initial": "0"
+ },
+ "max-height": {
+ "inherited": false,
+ "initial": "none"
+ },
+ "max-width": {
+ "inherited": false,
+ "initial": "none"
+ },
+ "min-height": {
+ "inherited": false,
+ "initial": "0"
+ },
+ "min-width": {
+ "inherited": false,
+ "initial": "0"
+ },
+ "padding": {
+ "longhands": [
+ "padding-top",
+ "padding-right",
+ "padding-bottom",
+ "padding-left"
+ ]
+ },
+ "padding-bottom": {
+ "inherited": false,
+ "initial": "0"
+ },
+ "padding-left": {
+ "inherited": false,
+ "initial": "0"
+ },
+ "padding-right": {
+ "inherited": false,
+ "initial": "0"
+ },
+ "padding-top": {
+ "inherited": false,
+ "initial": "0"
+ },
+ "position": {
+ "inherited": false,
+ "initial": "static"
+ },
+ "right": {
+ "inherited": false,
+ "initial": "auto"
+ },
+ "text-align": {
+ "inherited": true,
+ "initial": "left"
+ },
+ "text-decoration": {
+ "inherited": false,
+ "initial": "none",
+ "longhands": [
+ "text-decoration-color",
+ "text-decoration-line",
+ "text-decoration-style",
+ "text-decoration-thickness"
+ ]
+ },
+ "text-decoration-color": {
+ "inherited": false,
+ "initial": "none"
+ },
+ "text-decoration-line": {
+ "inherited": false,
+ "initial": "none"
+ },
+ "text-decoration-style": {
+ "inherited": false,
+ "initial": "none"
+ },
+ "text-decoration-thickness": {
+ "inherited": false,
+ "initial": "none"
+ },
+ "text-indent": {
+ "inherited": true,
+ "initial": "0"
+ },
+ "text-transform": {
+ "inherited": true,
+ "initial": "none"
+ },
+ "top": {
+ "inherited": false,
+ "initial": "auto"
+ },
+ "vertical-align": {
+ "inherited": false,
+ "initial": "baseline"
+ },
+ "visibility": {
+ "inherited": true,
+ "initial": "visible"
+ },
+ "width": {
+ "inherited": false,
+ "initial": "auto"
+ },
+ "white-space": {
+ "inherited": true,
+ "initial": "normal"
+ },
+ "word-spacing": {
+ "inherited": true,
+ "initial": "normal"
+ },
+ "z-index": {
+ "inherited": false,
+ "initial": "auto"
+ }
+}
diff --git a/Userland/Libraries/LibWeb/CSS/QuirksMode.css b/Userland/Libraries/LibWeb/CSS/QuirksMode.css
new file mode 100644
index 0000000000..8e1aeb392b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/QuirksMode.css
@@ -0,0 +1,3 @@
+table {
+ text-align: left;
+}
diff --git a/Userland/Libraries/LibWeb/CSS/Selector.cpp b/Userland/Libraries/LibWeb/CSS/Selector.cpp
new file mode 100644
index 0000000000..bfd44f204a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/Selector.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/Selector.h>
+
+namespace Web::CSS {
+
+Selector::Selector(Vector<ComplexSelector>&& component_lists)
+ : m_complex_selectors(move(component_lists))
+{
+}
+
+Selector::~Selector()
+{
+}
+
+u32 Selector::specificity() const
+{
+ unsigned ids = 0;
+ unsigned tag_names = 0;
+ unsigned classes = 0;
+
+ for (auto& list : m_complex_selectors) {
+ for (auto& simple_selector : list.compound_selector) {
+ switch (simple_selector.type) {
+ case SimpleSelector::Type::Id:
+ ++ids;
+ break;
+ case SimpleSelector::Type::Class:
+ ++classes;
+ break;
+ case SimpleSelector::Type::TagName:
+ ++tag_names;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return ids * 0x10000 + classes * 0x100 + tag_names;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h
new file mode 100644
index 0000000000..d5dae94f98
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/Selector.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <AK/Vector.h>
+
+namespace Web::CSS {
+
+class Selector {
+public:
+ struct SimpleSelector {
+ enum class Type {
+ Invalid,
+ Universal,
+ TagName,
+ Id,
+ Class,
+ };
+ Type type { Type::Invalid };
+
+ enum class PseudoClass {
+ None,
+ Link,
+ Visited,
+ Hover,
+ Focus,
+ FirstChild,
+ LastChild,
+ OnlyChild,
+ Empty,
+ Root,
+ };
+ PseudoClass pseudo_class { PseudoClass::None };
+
+ enum class PseudoElement {
+ None,
+ Before,
+ After,
+ };
+ PseudoElement pseudo_element { PseudoElement::None };
+
+ FlyString value;
+
+ enum class AttributeMatchType {
+ None,
+ HasAttribute,
+ ExactValueMatch,
+ Contains,
+ };
+
+ AttributeMatchType attribute_match_type { AttributeMatchType::None };
+ FlyString attribute_name;
+ String attribute_value;
+ };
+
+ struct ComplexSelector {
+ enum class Relation {
+ None,
+ ImmediateChild,
+ Descendant,
+ AdjacentSibling,
+ GeneralSibling,
+ };
+ Relation relation { Relation::None };
+
+ using CompoundSelector = Vector<SimpleSelector>;
+ CompoundSelector compound_selector;
+ };
+
+ explicit Selector(Vector<ComplexSelector>&&);
+ ~Selector();
+
+ const Vector<ComplexSelector>& complex_selectors() const { return m_complex_selectors; }
+
+ u32 specificity() const;
+
+private:
+ Vector<ComplexSelector> m_complex_selectors;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
new file mode 100644
index 0000000000..5f553e2be0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/SelectorEngine.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/HTML/AttributeNames.h>
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::SelectorEngine {
+
+static bool matches_hover_pseudo_class(const DOM::Element& element)
+{
+ auto* hovered_node = element.document().hovered_node();
+ if (!hovered_node)
+ return false;
+ if (&element == hovered_node)
+ return true;
+ return element.is_ancestor_of(*hovered_node);
+}
+
+static bool matches(const CSS::Selector::SimpleSelector& component, const DOM::Element& element)
+{
+ switch (component.pseudo_element) {
+ case CSS::Selector::SimpleSelector::PseudoElement::None:
+ break;
+ default:
+ // FIXME: Implement pseudo-elements.
+ return false;
+ }
+
+ switch (component.pseudo_class) {
+ case CSS::Selector::SimpleSelector::PseudoClass::None:
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::Link:
+ if (!element.is_link())
+ return false;
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::Visited:
+ // FIXME: Maybe match this selector sometimes?
+ return false;
+ case CSS::Selector::SimpleSelector::PseudoClass::Hover:
+ if (!matches_hover_pseudo_class(element))
+ return false;
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::Focus:
+ // FIXME: Implement matches_focus_pseudo_class(element)
+ return false;
+ case CSS::Selector::SimpleSelector::PseudoClass::FirstChild:
+ if (element.previous_element_sibling())
+ return false;
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::LastChild:
+ if (element.next_element_sibling())
+ return false;
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::OnlyChild:
+ if (element.previous_element_sibling() || element.next_element_sibling())
+ return false;
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::Empty:
+ if (element.first_child_of_type<DOM::Element>() || element.first_child_of_type<DOM::Text>())
+ return false;
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::Root:
+ if (!is<HTML::HTMLElement>(element))
+ return false;
+ break;
+ }
+
+ switch (component.attribute_match_type) {
+ case CSS::Selector::SimpleSelector::AttributeMatchType::HasAttribute:
+ if (!element.has_attribute(component.attribute_name))
+ return false;
+ break;
+ case CSS::Selector::SimpleSelector::AttributeMatchType::ExactValueMatch:
+ if (element.attribute(component.attribute_name) != component.attribute_value)
+ return false;
+ break;
+ case CSS::Selector::SimpleSelector::AttributeMatchType::Contains:
+ if (!element.attribute(component.attribute_name).split(' ').contains_slow(component.attribute_value))
+ return false;
+ break;
+ default:
+ break;
+ }
+
+ switch (component.type) {
+ case CSS::Selector::SimpleSelector::Type::Universal:
+ return true;
+ case CSS::Selector::SimpleSelector::Type::Id:
+ return component.value == element.attribute(HTML::AttributeNames::id);
+ case CSS::Selector::SimpleSelector::Type::Class:
+ return element.has_class(component.value);
+ case CSS::Selector::SimpleSelector::Type::TagName:
+ return component.value == element.local_name();
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+static bool matches(const CSS::Selector& selector, int component_list_index, const DOM::Element& element)
+{
+ auto& component_list = selector.complex_selectors()[component_list_index];
+ for (auto& component : component_list.compound_selector) {
+ if (!matches(component, element))
+ return false;
+ }
+ switch (component_list.relation) {
+ case CSS::Selector::ComplexSelector::Relation::None:
+ return true;
+ case CSS::Selector::ComplexSelector::Relation::Descendant:
+ ASSERT(component_list_index != 0);
+ for (auto* ancestor = element.parent(); ancestor; ancestor = ancestor->parent()) {
+ if (!is<DOM::Element>(*ancestor))
+ continue;
+ if (matches(selector, component_list_index - 1, downcast<DOM::Element>(*ancestor)))
+ return true;
+ }
+ return false;
+ case CSS::Selector::ComplexSelector::Relation::ImmediateChild:
+ ASSERT(component_list_index != 0);
+ if (!element.parent() || !is<DOM::Element>(*element.parent()))
+ return false;
+ return matches(selector, component_list_index - 1, downcast<DOM::Element>(*element.parent()));
+ case CSS::Selector::ComplexSelector::Relation::AdjacentSibling:
+ ASSERT(component_list_index != 0);
+ if (auto* sibling = element.previous_element_sibling())
+ return matches(selector, component_list_index - 1, *sibling);
+ return false;
+ case CSS::Selector::ComplexSelector::Relation::GeneralSibling:
+ ASSERT(component_list_index != 0);
+ for (auto* sibling = element.previous_element_sibling(); sibling; sibling = sibling->previous_element_sibling()) {
+ if (matches(selector, component_list_index - 1, *sibling))
+ return true;
+ }
+ return false;
+ }
+ ASSERT_NOT_REACHED();
+}
+
+bool matches(const CSS::Selector& selector, const DOM::Element& element)
+{
+ ASSERT(!selector.complex_selectors().is_empty());
+ return matches(selector, selector.complex_selectors().size() - 1, element);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.h b/Userland/Libraries/LibWeb/CSS/SelectorEngine.h
new file mode 100644
index 0000000000..96bd76de36
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/CSS/Selector.h>
+#include <LibWeb/DOM/Element.h>
+
+namespace Web::SelectorEngine {
+
+bool matches(const CSS::Selector&, const DOM::Element&);
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleDeclaration.cpp b/Userland/Libraries/LibWeb/CSS/StyleDeclaration.cpp
new file mode 100644
index 0000000000..eb56b94625
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleDeclaration.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/StyleDeclaration.h>
+
+namespace Web::CSS {
+
+StyleDeclaration::StyleDeclaration(Vector<StyleProperty>&& properties)
+ : m_properties(move(properties))
+{
+}
+
+StyleDeclaration::~StyleDeclaration()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleDeclaration.h b/Userland/Libraries/LibWeb/CSS/StyleDeclaration.h
new file mode 100644
index 0000000000..787429eb15
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleDeclaration.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <LibWeb/CSS/StyleValue.h>
+
+namespace Web::CSS {
+
+struct StyleProperty {
+ CSS::PropertyID property_id;
+ NonnullRefPtr<StyleValue> value;
+ bool important { false };
+};
+
+class StyleDeclaration : public RefCounted<StyleDeclaration> {
+public:
+ static NonnullRefPtr<StyleDeclaration> create(Vector<StyleProperty>&& properties)
+ {
+ return adopt(*new StyleDeclaration(move(properties)));
+ }
+
+ ~StyleDeclaration();
+
+ const Vector<StyleProperty>& properties() const { return m_properties; }
+
+private:
+ explicit StyleDeclaration(Vector<StyleProperty>&&);
+
+ Vector<StyleProperty> m_properties;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleInvalidator.cpp b/Userland/Libraries/LibWeb/CSS/StyleInvalidator.cpp
new file mode 100644
index 0000000000..bf5f15c332
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleInvalidator.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/StyleInvalidator.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Element.h>
+
+namespace Web::CSS {
+
+StyleInvalidator::StyleInvalidator(DOM::Document& document)
+ : m_document(document)
+{
+ if (!m_document.should_invalidate_styles_on_attribute_changes())
+ return;
+ auto& style_resolver = m_document.style_resolver();
+ m_document.for_each_in_subtree_of_type<DOM::Element>([&](auto& element) {
+ m_elements_and_matching_rules_before.set(&element, style_resolver.collect_matching_rules(element));
+ return IterationDecision::Continue;
+ });
+}
+
+StyleInvalidator::~StyleInvalidator()
+{
+ if (!m_document.should_invalidate_styles_on_attribute_changes())
+ return;
+ auto& style_resolver = m_document.style_resolver();
+ m_document.for_each_in_subtree_of_type<DOM::Element>([&](auto& element) {
+ auto maybe_matching_rules_before = m_elements_and_matching_rules_before.get(&element);
+ if (!maybe_matching_rules_before.has_value()) {
+ element.set_needs_style_update(true);
+ return IterationDecision::Continue;
+ }
+ auto& matching_rules_before = maybe_matching_rules_before.value();
+ auto matching_rules_after = style_resolver.collect_matching_rules(element);
+ if (matching_rules_before.size() != matching_rules_after.size()) {
+ element.set_needs_style_update(true);
+ return IterationDecision::Continue;
+ }
+ style_resolver.sort_matching_rules(matching_rules_before);
+ style_resolver.sort_matching_rules(matching_rules_after);
+ for (size_t i = 0; i < matching_rules_before.size(); ++i) {
+ if (matching_rules_before[i].rule != matching_rules_after[i].rule) {
+ element.set_needs_style_update(true);
+ break;
+ }
+ }
+ return IterationDecision::Continue;
+ });
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleInvalidator.h b/Userland/Libraries/LibWeb/CSS/StyleInvalidator.h
new file mode 100644
index 0000000000..aea59c8e65
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleInvalidator.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <LibWeb/CSS/StyleResolver.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Element.h>
+
+namespace Web::CSS {
+
+class StyleInvalidator {
+public:
+ explicit StyleInvalidator(DOM::Document&);
+ ~StyleInvalidator();
+
+private:
+ DOM::Document& m_document;
+ HashMap<DOM::Element*, Vector<MatchingRule>> m_elements_and_matching_rules_before;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp
new file mode 100644
index 0000000000..76b2834771
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp
@@ -0,0 +1,484 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/DirIterator.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibWeb/CSS/StyleProperties.h>
+#include <LibWeb/FontCache.h>
+#include <ctype.h>
+
+namespace Web::CSS {
+
+StyleProperties::StyleProperties()
+{
+}
+
+StyleProperties::StyleProperties(const StyleProperties& other)
+ : m_property_values(other.m_property_values)
+{
+ if (other.m_font) {
+ m_font = other.m_font->clone();
+ } else {
+ m_font = nullptr;
+ }
+}
+
+NonnullRefPtr<StyleProperties> StyleProperties::clone() const
+{
+ return adopt(*new StyleProperties(*this));
+}
+
+void StyleProperties::set_property(CSS::PropertyID id, NonnullRefPtr<StyleValue> value)
+{
+ m_property_values.set((unsigned)id, move(value));
+}
+
+void StyleProperties::set_property(CSS::PropertyID id, const StringView& value)
+{
+ m_property_values.set((unsigned)id, StringStyleValue::create(value));
+}
+
+Optional<NonnullRefPtr<StyleValue>> StyleProperties::property(CSS::PropertyID id) const
+{
+ auto it = m_property_values.find((unsigned)id);
+ if (it == m_property_values.end())
+ return {};
+ return it->value;
+}
+
+Length StyleProperties::length_or_fallback(CSS::PropertyID id, const Length& fallback) const
+{
+ auto value = property(id);
+ if (!value.has_value())
+ return fallback;
+ return value.value()->to_length();
+}
+
+LengthBox StyleProperties::length_box(CSS::PropertyID left_id, CSS::PropertyID top_id, CSS::PropertyID right_id, CSS::PropertyID bottom_id, const CSS::Length& default_value) const
+{
+ LengthBox box;
+ box.left = length_or_fallback(left_id, default_value);
+ box.top = length_or_fallback(top_id, default_value);
+ box.right = length_or_fallback(right_id, default_value);
+ box.bottom = length_or_fallback(bottom_id, default_value);
+ return box;
+}
+
+String StyleProperties::string_or_fallback(CSS::PropertyID id, const StringView& fallback) const
+{
+ auto value = property(id);
+ if (!value.has_value())
+ return fallback;
+ return value.value()->to_string();
+}
+
+Color StyleProperties::color_or_fallback(CSS::PropertyID id, const DOM::Document& document, Color fallback) const
+{
+ auto value = property(id);
+ if (!value.has_value())
+ return fallback;
+ return value.value()->to_color(document);
+}
+
+void StyleProperties::load_font() const
+{
+ auto family_value = string_or_fallback(CSS::PropertyID::FontFamily, "Katica");
+ auto font_size = property(CSS::PropertyID::FontSize).value_or(IdentifierStyleValue::create(CSS::ValueID::Medium));
+ auto font_weight = property(CSS::PropertyID::FontWeight).value_or(IdentifierStyleValue::create(CSS::ValueID::Normal));
+
+ auto family_parts = family_value.split(',');
+ auto family = family_parts[0];
+
+ if (family.is_one_of("monospace", "ui-monospace"))
+ family = "Csilla";
+ else if (family.is_one_of("serif", "sans-serif", "cursive", "fantasy", "ui-serif", "ui-sans-serif", "ui-rounded"))
+ family = "Katica";
+
+ int weight = 400;
+ if (font_weight->is_identifier()) {
+ switch (static_cast<const IdentifierStyleValue&>(*font_weight).id()) {
+ case CSS::ValueID::Normal:
+ weight = 400;
+ break;
+ case CSS::ValueID::Bold:
+ weight = 700;
+ break;
+ case CSS::ValueID::Lighter:
+ // FIXME: This should be relative to the parent.
+ weight = 400;
+ break;
+ case CSS::ValueID::Bolder:
+ // FIXME: This should be relative to the parent.
+ weight = 700;
+ break;
+ default:
+ break;
+ }
+ } else if (font_weight->is_length()) {
+ // FIXME: This isn't really a length, it's a numeric value..
+ int font_weight_integer = font_weight->to_length().raw_value();
+ if (font_weight_integer <= 400)
+ weight = 400;
+ if (font_weight_integer <= 700)
+ weight = 700;
+ weight = 900;
+ }
+
+ int size = 10;
+ if (font_size->is_identifier()) {
+ switch (static_cast<const IdentifierStyleValue&>(*font_size).id()) {
+ case CSS::ValueID::XxSmall:
+ case CSS::ValueID::XSmall:
+ case CSS::ValueID::Small:
+ case CSS::ValueID::Medium:
+ // FIXME: Should be based on "user's default font size"
+ size = 10;
+ break;
+ case CSS::ValueID::Large:
+ case CSS::ValueID::XLarge:
+ case CSS::ValueID::XxLarge:
+ case CSS::ValueID::XxxLarge:
+ // FIXME: Should be based on "user's default font size"
+ size = 12;
+ break;
+ case CSS::ValueID::Smaller:
+ // FIXME: This should be relative to the parent.
+ size = 10;
+ break;
+ case CSS::ValueID::Larger:
+ // FIXME: This should be relative to the parent.
+ size = 12;
+ break;
+
+ default:
+ break;
+ }
+ } else if (font_size->is_length()) {
+ // FIXME: This isn't really a length, it's a numeric value..
+ int font_size_integer = font_size->to_length().raw_value();
+ if (font_size_integer <= 10)
+ size = 10;
+ else if (font_size_integer <= 12)
+ size = 12;
+ else
+ size = 14;
+ }
+
+ FontSelector font_selector { family, size, weight };
+
+ auto found_font = FontCache::the().get(font_selector);
+ if (found_font) {
+ m_font = found_font;
+ return;
+ }
+
+ Gfx::FontDatabase::the().for_each_font([&](auto& font) {
+ if (font.family() == family && font.weight() == weight && font.presentation_size() == size)
+ found_font = font;
+ });
+
+ if (!found_font) {
+ dbgln("Font not found: '{}' {} {}", family, size, weight);
+ found_font = Gfx::FontDatabase::default_font();
+ }
+
+ m_font = found_font;
+ FontCache::the().set(font_selector, *m_font);
+}
+
+float StyleProperties::line_height(const Layout::Node& layout_node) const
+{
+ auto line_height_length = length_or_fallback(CSS::PropertyID::LineHeight, Length::make_auto());
+ if (line_height_length.is_absolute())
+ return (float)line_height_length.to_px(layout_node);
+ return (float)font().glyph_height() * 1.4f;
+}
+
+Optional<int> StyleProperties::z_index() const
+{
+ auto value = property(CSS::PropertyID::ZIndex);
+ if (!value.has_value())
+ return {};
+ return static_cast<int>(value.value()->to_length().raw_value());
+}
+
+Optional<CSS::Position> StyleProperties::position() const
+{
+ auto value = property(CSS::PropertyID::Position);
+ if (!value.has_value() || !value.value()->is_identifier())
+ return {};
+ switch (static_cast<const IdentifierStyleValue&>(*value.value()).id()) {
+ case CSS::ValueID::Static:
+ return CSS::Position::Static;
+ case CSS::ValueID::Relative:
+ return CSS::Position::Relative;
+ case CSS::ValueID::Absolute:
+ return CSS::Position::Absolute;
+ case CSS::ValueID::Fixed:
+ return CSS::Position::Fixed;
+ case CSS::ValueID::Sticky:
+ return CSS::Position::Sticky;
+ default:
+ return {};
+ }
+}
+
+bool StyleProperties::operator==(const StyleProperties& other) const
+{
+ if (m_property_values.size() != other.m_property_values.size())
+ return false;
+
+ for (auto& it : m_property_values) {
+ auto jt = other.m_property_values.find(it.key);
+ if (jt == other.m_property_values.end())
+ return false;
+ auto& my_value = *it.value;
+ auto& other_value = *jt->value;
+ if (my_value.type() != other_value.type())
+ return false;
+ if (my_value != other_value)
+ return false;
+ }
+
+ return true;
+}
+
+Optional<CSS::TextAlign> StyleProperties::text_align() const
+{
+ auto value = property(CSS::PropertyID::TextAlign);
+ if (!value.has_value() || !value.value()->is_identifier())
+ return {};
+
+ switch (static_cast<const IdentifierStyleValue&>(*value.value()).id()) {
+ case CSS::ValueID::Left:
+ return CSS::TextAlign::Left;
+ case CSS::ValueID::Center:
+ return CSS::TextAlign::Center;
+ case CSS::ValueID::Right:
+ return CSS::TextAlign::Right;
+ case CSS::ValueID::Justify:
+ return CSS::TextAlign::Justify;
+ case CSS::ValueID::LibwebCenter:
+ return CSS::TextAlign::LibwebCenter;
+ default:
+ return {};
+ }
+}
+
+Optional<CSS::WhiteSpace> StyleProperties::white_space() const
+{
+ auto value = property(CSS::PropertyID::WhiteSpace);
+ if (!value.has_value() || !value.value()->is_identifier())
+ return {};
+ switch (static_cast<const IdentifierStyleValue&>(*value.value()).id()) {
+ case CSS::ValueID::Normal:
+ return CSS::WhiteSpace::Normal;
+ case CSS::ValueID::Nowrap:
+ return CSS::WhiteSpace::Nowrap;
+ case CSS::ValueID::Pre:
+ return CSS::WhiteSpace::Pre;
+ case CSS::ValueID::PreLine:
+ return CSS::WhiteSpace::PreLine;
+ case CSS::ValueID::PreWrap:
+ return CSS::WhiteSpace::PreWrap;
+ default:
+ return {};
+ }
+}
+
+Optional<CSS::LineStyle> StyleProperties::line_style(CSS::PropertyID property_id) const
+{
+ auto value = property(property_id);
+ if (!value.has_value() || !value.value()->is_identifier())
+ return {};
+ switch (static_cast<const IdentifierStyleValue&>(*value.value()).id()) {
+ case CSS::ValueID::None:
+ return CSS::LineStyle::None;
+ case CSS::ValueID::Hidden:
+ return CSS::LineStyle::Hidden;
+ case CSS::ValueID::Dotted:
+ return CSS::LineStyle::Dotted;
+ case CSS::ValueID::Dashed:
+ return CSS::LineStyle::Dashed;
+ case CSS::ValueID::Solid:
+ return CSS::LineStyle::Solid;
+ case CSS::ValueID::Double:
+ return CSS::LineStyle::Double;
+ case CSS::ValueID::Groove:
+ return CSS::LineStyle::Groove;
+ case CSS::ValueID::Ridge:
+ return CSS::LineStyle::Ridge;
+ case CSS::ValueID::Inset:
+ return CSS::LineStyle::Inset;
+ case CSS::ValueID::Outset:
+ return CSS::LineStyle::Outset;
+ default:
+ return {};
+ }
+}
+
+Optional<CSS::Float> StyleProperties::float_() const
+{
+ auto value = property(CSS::PropertyID::Float);
+ if (!value.has_value() || !value.value()->is_identifier())
+ return {};
+ switch (static_cast<const IdentifierStyleValue&>(*value.value()).id()) {
+ case CSS::ValueID::None:
+ return CSS::Float::None;
+ case CSS::ValueID::Left:
+ return CSS::Float::Left;
+ case CSS::ValueID::Right:
+ return CSS::Float::Right;
+ default:
+ return {};
+ }
+}
+
+Optional<CSS::Clear> StyleProperties::clear() const
+{
+ auto value = property(CSS::PropertyID::Clear);
+ if (!value.has_value() || !value.value()->is_identifier())
+ return {};
+ switch (static_cast<const IdentifierStyleValue&>(*value.value()).id()) {
+ case CSS::ValueID::None:
+ return CSS::Clear::None;
+ case CSS::ValueID::Left:
+ return CSS::Clear::Left;
+ case CSS::ValueID::Right:
+ return CSS::Clear::Right;
+ case CSS::ValueID::Both:
+ return CSS::Clear::Both;
+ default:
+ return {};
+ }
+}
+
+CSS::Display StyleProperties::display() const
+{
+ auto value = property(CSS::PropertyID::Display);
+ if (!value.has_value() || !value.value()->is_identifier())
+ return CSS::Display::Inline;
+ switch (static_cast<const IdentifierStyleValue&>(*value.value()).id()) {
+ case CSS::ValueID::None:
+ return CSS::Display::None;
+ case CSS::ValueID::Block:
+ return CSS::Display::Block;
+ case CSS::ValueID::Inline:
+ return CSS::Display::Inline;
+ case CSS::ValueID::InlineBlock:
+ return CSS::Display::InlineBlock;
+ case CSS::ValueID::ListItem:
+ return CSS::Display::ListItem;
+ case CSS::ValueID::Table:
+ return CSS::Display::Table;
+ case CSS::ValueID::TableRow:
+ return CSS::Display::TableRow;
+ case CSS::ValueID::TableCell:
+ return CSS::Display::TableCell;
+ case CSS::ValueID::TableColumn:
+ return CSS::Display::TableColumn;
+ case CSS::ValueID::TableColumnGroup:
+ return CSS::Display::TableColumnGroup;
+ case CSS::ValueID::TableCaption:
+ return CSS::Display::TableCaption;
+ case CSS::ValueID::TableRowGroup:
+ return CSS::Display::TableRowGroup;
+ case CSS::ValueID::TableHeaderGroup:
+ return CSS::Display::TableHeaderGroup;
+ case CSS::ValueID::TableFooterGroup:
+ return CSS::Display::TableFooterGroup;
+ default:
+ return CSS::Display::Block;
+ }
+}
+
+Optional<CSS::TextDecorationLine> StyleProperties::text_decoration_line() const
+{
+ auto value = property(CSS::PropertyID::TextDecorationLine);
+ if (!value.has_value() || !value.value()->is_identifier())
+ return {};
+ switch (static_cast<const IdentifierStyleValue&>(*value.value()).id()) {
+ case CSS::ValueID::None:
+ return CSS::TextDecorationLine::None;
+ case CSS::ValueID::Underline:
+ return CSS::TextDecorationLine::Underline;
+ case CSS::ValueID::Overline:
+ return CSS::TextDecorationLine::Overline;
+ case CSS::ValueID::LineThrough:
+ return CSS::TextDecorationLine::LineThrough;
+ case CSS::ValueID::Blink:
+ return CSS::TextDecorationLine::Blink;
+ default:
+ return {};
+ }
+}
+
+Optional<CSS::TextTransform> StyleProperties::text_transform() const
+{
+ auto value = property(CSS::PropertyID::TextTransform);
+ if (!value.has_value())
+ return {};
+ switch (value.value()->to_identifier()) {
+ case CSS::ValueID::None:
+ return CSS::TextTransform::None;
+ case CSS::ValueID::Lowercase:
+ return CSS::TextTransform::Lowercase;
+ case CSS::ValueID::Uppercase:
+ return CSS::TextTransform::Uppercase;
+ case CSS::ValueID::Capitalize:
+ return CSS::TextTransform::Capitalize;
+ case CSS::ValueID::FullWidth:
+ return CSS::TextTransform::FullWidth;
+ case CSS::ValueID::FullSizeKana:
+ return CSS::TextTransform::FullSizeKana;
+ default:
+ return {};
+ }
+}
+
+Optional<CSS::ListStyleType> StyleProperties::list_style_type() const
+{
+ auto value = property(CSS::PropertyID::ListStyleType);
+ if (!value.has_value())
+ return {};
+
+ switch (value.value()->to_identifier()) {
+ case CSS::ValueID::None:
+ return CSS::ListStyleType::None;
+ case CSS::ValueID::Disc:
+ return CSS::ListStyleType::Disc;
+ case CSS::ValueID::Circle:
+ return CSS::ListStyleType::Circle;
+ case CSS::ValueID::Square:
+ return CSS::ListStyleType::Square;
+ case CSS::ValueID::Decimal:
+ return CSS::ListStyleType::Decimal;
+ default:
+ return {};
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.h b/Userland/Libraries/LibWeb/CSS/StyleProperties.h
new file mode 100644
index 0000000000..29da1ab073
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/NonnullRefPtr.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Forward.h>
+#include <LibWeb/CSS/LengthBox.h>
+#include <LibWeb/CSS/StyleValue.h>
+
+namespace Web::CSS {
+
+class StyleProperties : public RefCounted<StyleProperties> {
+public:
+ StyleProperties();
+
+ explicit StyleProperties(const StyleProperties&);
+
+ static NonnullRefPtr<StyleProperties> create() { return adopt(*new StyleProperties); }
+
+ NonnullRefPtr<StyleProperties> clone() const;
+
+ template<typename Callback>
+ inline void for_each_property(Callback callback) const
+ {
+ for (auto& it : m_property_values)
+ callback((CSS::PropertyID)it.key, *it.value);
+ }
+
+ void set_property(CSS::PropertyID, NonnullRefPtr<StyleValue> value);
+ void set_property(CSS::PropertyID, const StringView&);
+ Optional<NonnullRefPtr<StyleValue>> property(CSS::PropertyID) const;
+
+ Length length_or_fallback(CSS::PropertyID, const Length& fallback) const;
+ LengthBox length_box(CSS::PropertyID left_id, CSS::PropertyID top_id, CSS::PropertyID right_id, CSS::PropertyID bottom_id, const CSS::Length& default_value) const;
+ String string_or_fallback(CSS::PropertyID, const StringView& fallback) const;
+ Color color_or_fallback(CSS::PropertyID, const DOM::Document&, Color fallback) const;
+ Optional<CSS::TextAlign> text_align() const;
+ CSS::Display display() const;
+ Optional<CSS::Float> float_() const;
+ Optional<CSS::Clear> clear() const;
+ Optional<CSS::WhiteSpace> white_space() const;
+ Optional<CSS::LineStyle> line_style(CSS::PropertyID) const;
+ Optional<CSS::TextDecorationLine> text_decoration_line() const;
+ Optional<CSS::TextTransform> text_transform() const;
+ Optional<CSS::ListStyleType> list_style_type() const;
+
+ const Gfx::Font& font() const
+ {
+ if (!m_font)
+ load_font();
+ return *m_font;
+ }
+
+ float line_height(const Layout::Node&) const;
+
+ bool operator==(const StyleProperties&) const;
+ bool operator!=(const StyleProperties& other) const { return !(*this == other); }
+
+ Optional<CSS::Position> position() const;
+ Optional<int> z_index() const;
+
+private:
+ HashMap<unsigned, NonnullRefPtr<StyleValue>> m_property_values;
+
+ void load_font() const;
+
+ mutable RefPtr<Gfx::Font> m_font;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp b/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp
new file mode 100644
index 0000000000..21715ee92b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp
@@ -0,0 +1,598 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/QuickSort.h>
+#include <LibWeb/CSS/Parser/CSSParser.h>
+#include <LibWeb/CSS/SelectorEngine.h>
+#include <LibWeb/CSS/StyleResolver.h>
+#include <LibWeb/CSS/StyleSheet.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/Dump.h>
+#include <ctype.h>
+#include <stdio.h>
+
+namespace Web::CSS {
+
+StyleResolver::StyleResolver(DOM::Document& document)
+ : m_document(document)
+{
+}
+
+StyleResolver::~StyleResolver()
+{
+}
+
+static StyleSheet& default_stylesheet()
+{
+ static StyleSheet* sheet;
+ if (!sheet) {
+ extern const char default_stylesheet_source[];
+ String css = default_stylesheet_source;
+ sheet = parse_css(CSS::ParsingContext(), css).leak_ref();
+ }
+ return *sheet;
+}
+
+static StyleSheet& quirks_mode_stylesheet()
+{
+ static StyleSheet* sheet;
+ if (!sheet) {
+ extern const char quirks_mode_stylesheet_source[];
+ String css = quirks_mode_stylesheet_source;
+ sheet = parse_css(CSS::ParsingContext(), css).leak_ref();
+ }
+ return *sheet;
+}
+
+template<typename Callback>
+void StyleResolver::for_each_stylesheet(Callback callback) const
+{
+ callback(default_stylesheet());
+ if (document().in_quirks_mode())
+ callback(quirks_mode_stylesheet());
+ for (auto& sheet : document().style_sheets().sheets()) {
+ callback(sheet);
+ }
+}
+
+Vector<MatchingRule> StyleResolver::collect_matching_rules(const DOM::Element& element) const
+{
+ Vector<MatchingRule> matching_rules;
+
+ size_t style_sheet_index = 0;
+ for_each_stylesheet([&](auto& sheet) {
+ size_t rule_index = 0;
+ for (auto& rule : sheet.rules()) {
+ size_t selector_index = 0;
+ for (auto& selector : rule.selectors()) {
+ if (SelectorEngine::matches(selector, element)) {
+ matching_rules.append({ rule, style_sheet_index, rule_index, selector_index });
+ break;
+ }
+ ++selector_index;
+ }
+ ++rule_index;
+ }
+ ++style_sheet_index;
+ });
+
+ return matching_rules;
+}
+
+void StyleResolver::sort_matching_rules(Vector<MatchingRule>& matching_rules) const
+{
+ quick_sort(matching_rules, [&](MatchingRule& a, MatchingRule& b) {
+ auto& a_selector = a.rule->selectors()[a.selector_index];
+ auto& b_selector = b.rule->selectors()[b.selector_index];
+ auto a_specificity = a_selector.specificity();
+ auto b_specificity = b_selector.specificity();
+ if (a_selector.specificity() == b_selector.specificity()) {
+ if (a.style_sheet_index == b.style_sheet_index)
+ return a.rule_index < b.rule_index;
+ return a.style_sheet_index < b.style_sheet_index;
+ }
+ return a_specificity < b_specificity;
+ });
+}
+
+bool StyleResolver::is_inherited_property(CSS::PropertyID property_id)
+{
+ static HashTable<CSS::PropertyID> inherited_properties;
+ if (inherited_properties.is_empty()) {
+ inherited_properties.set(CSS::PropertyID::BorderCollapse);
+ inherited_properties.set(CSS::PropertyID::BorderSpacing);
+ inherited_properties.set(CSS::PropertyID::Color);
+ inherited_properties.set(CSS::PropertyID::FontFamily);
+ inherited_properties.set(CSS::PropertyID::FontSize);
+ inherited_properties.set(CSS::PropertyID::FontStyle);
+ inherited_properties.set(CSS::PropertyID::FontVariant);
+ inherited_properties.set(CSS::PropertyID::FontWeight);
+ inherited_properties.set(CSS::PropertyID::LetterSpacing);
+ inherited_properties.set(CSS::PropertyID::LineHeight);
+ inherited_properties.set(CSS::PropertyID::ListStyle);
+ inherited_properties.set(CSS::PropertyID::ListStyleImage);
+ inherited_properties.set(CSS::PropertyID::ListStylePosition);
+ inherited_properties.set(CSS::PropertyID::ListStyleType);
+ inherited_properties.set(CSS::PropertyID::TextAlign);
+ inherited_properties.set(CSS::PropertyID::TextIndent);
+ inherited_properties.set(CSS::PropertyID::TextTransform);
+ inherited_properties.set(CSS::PropertyID::Visibility);
+ inherited_properties.set(CSS::PropertyID::WhiteSpace);
+ inherited_properties.set(CSS::PropertyID::WordSpacing);
+
+ // FIXME: This property is not supposed to be inherited, but we currently
+ // rely on inheritance to propagate decorations into line boxes.
+ inherited_properties.set(CSS::PropertyID::TextDecorationLine);
+ }
+ return inherited_properties.contains(property_id);
+}
+
+static Vector<String> split_on_whitespace(const StringView& string)
+{
+ if (string.is_empty())
+ return {};
+
+ Vector<String> v;
+ size_t substart = 0;
+ for (size_t i = 0; i < string.length(); ++i) {
+ char ch = string.characters_without_null_termination()[i];
+ if (isspace(ch)) {
+ size_t sublen = i - substart;
+ if (sublen != 0)
+ v.append(string.substring_view(substart, sublen));
+ substart = i + 1;
+ }
+ }
+ size_t taillen = string.length() - substart;
+ if (taillen != 0)
+ v.append(string.substring_view(substart, taillen));
+ return v;
+}
+
+enum class Edge {
+ Top,
+ Right,
+ Bottom,
+ Left,
+ All,
+};
+
+static bool contains(Edge a, Edge b)
+{
+ return a == b || b == Edge::All;
+}
+
+static inline void set_property_border_width(StyleProperties& style, const StyleValue& value, Edge edge)
+{
+ ASSERT(value.is_length());
+ if (contains(Edge::Top, edge))
+ style.set_property(CSS::PropertyID::BorderTopWidth, value);
+ if (contains(Edge::Right, edge))
+ style.set_property(CSS::PropertyID::BorderRightWidth, value);
+ if (contains(Edge::Bottom, edge))
+ style.set_property(CSS::PropertyID::BorderBottomWidth, value);
+ if (contains(Edge::Left, edge))
+ style.set_property(CSS::PropertyID::BorderLeftWidth, value);
+}
+
+static inline void set_property_border_color(StyleProperties& style, const StyleValue& value, Edge edge)
+{
+ ASSERT(value.is_color());
+ if (contains(Edge::Top, edge))
+ style.set_property(CSS::PropertyID::BorderTopColor, value);
+ if (contains(Edge::Right, edge))
+ style.set_property(CSS::PropertyID::BorderRightColor, value);
+ if (contains(Edge::Bottom, edge))
+ style.set_property(CSS::PropertyID::BorderBottomColor, value);
+ if (contains(Edge::Left, edge))
+ style.set_property(CSS::PropertyID::BorderLeftColor, value);
+}
+
+static inline void set_property_border_style(StyleProperties& style, const StyleValue& value, Edge edge)
+{
+ ASSERT(value.is_string());
+ if (contains(Edge::Top, edge))
+ style.set_property(CSS::PropertyID::BorderTopStyle, value);
+ if (contains(Edge::Right, edge))
+ style.set_property(CSS::PropertyID::BorderRightStyle, value);
+ if (contains(Edge::Bottom, edge))
+ style.set_property(CSS::PropertyID::BorderBottomStyle, value);
+ if (contains(Edge::Left, edge))
+ style.set_property(CSS::PropertyID::BorderLeftStyle, value);
+}
+
+static void set_property_expanding_shorthands(StyleProperties& style, CSS::PropertyID property_id, const StyleValue& value, DOM::Document& document)
+{
+ CSS::ParsingContext context(document);
+
+ if (property_id == CSS::PropertyID::TextDecoration) {
+ switch (value.to_identifier()) {
+ case CSS::ValueID::None:
+ case CSS::ValueID::Underline:
+ case CSS::ValueID::Overline:
+ case CSS::ValueID::LineThrough:
+ case CSS::ValueID::Blink:
+ set_property_expanding_shorthands(style, CSS::PropertyID::TextDecorationLine, value, document);
+ default:
+ break;
+ }
+ return;
+ }
+
+ if (property_id == CSS::PropertyID::Border) {
+ set_property_expanding_shorthands(style, CSS::PropertyID::BorderTop, value, document);
+ set_property_expanding_shorthands(style, CSS::PropertyID::BorderRight, value, document);
+ set_property_expanding_shorthands(style, CSS::PropertyID::BorderBottom, value, document);
+ set_property_expanding_shorthands(style, CSS::PropertyID::BorderLeft, value, document);
+ return;
+ }
+
+ if (property_id == CSS::PropertyID::BorderTop
+ || property_id == CSS::PropertyID::BorderRight
+ || property_id == CSS::PropertyID::BorderBottom
+ || property_id == CSS::PropertyID::BorderLeft) {
+
+ Edge edge = Edge::All;
+ switch (property_id) {
+ case CSS::PropertyID::BorderTop:
+ edge = Edge::Top;
+ break;
+ case CSS::PropertyID::BorderRight:
+ edge = Edge::Right;
+ break;
+ case CSS::PropertyID::BorderBottom:
+ edge = Edge::Bottom;
+ break;
+ case CSS::PropertyID::BorderLeft:
+ edge = Edge::Left;
+ break;
+ default:
+ break;
+ }
+
+ auto parts = split_on_whitespace(value.to_string());
+ if (value.is_length()) {
+ set_property_border_width(style, value, edge);
+ return;
+ }
+ if (value.is_color()) {
+ set_property_border_color(style, value, edge);
+ return;
+ }
+ if (value.is_string()) {
+ auto parts = split_on_whitespace(value.to_string());
+
+ if (parts.size() == 1) {
+ if (auto value = parse_line_style(context, parts[0])) {
+ set_property_border_style(style, value.release_nonnull(), edge);
+ set_property_border_color(style, ColorStyleValue::create(Gfx::Color::Black), edge);
+ set_property_border_width(style, LengthStyleValue::create(Length(3, Length::Type::Px)), edge);
+ return;
+ }
+ }
+
+ RefPtr<LengthStyleValue> line_width_value;
+ RefPtr<ColorStyleValue> color_value;
+ RefPtr<StringStyleValue> line_style_value;
+
+ for (auto& part : parts) {
+ if (auto value = parse_line_width(context, part)) {
+ if (line_width_value)
+ return;
+ line_width_value = move(value);
+ continue;
+ }
+ if (auto value = parse_color(context, part)) {
+ if (color_value)
+ return;
+ color_value = move(value);
+ continue;
+ }
+ if (auto value = parse_line_style(context, part)) {
+ if (line_style_value)
+ return;
+ line_style_value = move(value);
+ continue;
+ }
+ }
+
+ if (line_width_value)
+ set_property_border_width(style, line_width_value.release_nonnull(), edge);
+ if (color_value)
+ set_property_border_color(style, color_value.release_nonnull(), edge);
+ if (line_style_value)
+ set_property_border_style(style, line_style_value.release_nonnull(), edge);
+
+ return;
+ }
+ return;
+ }
+
+ if (property_id == CSS::PropertyID::BorderStyle) {
+ auto parts = split_on_whitespace(value.to_string());
+ if (value.is_string() && parts.size() == 3) {
+ auto top = parse_css_value(context, parts[0]);
+ auto right = parse_css_value(context, parts[1]);
+ auto bottom = parse_css_value(context, parts[2]);
+ auto left = parse_css_value(context, parts[1]);
+ if (top && right && bottom && left) {
+ style.set_property(CSS::PropertyID::BorderTopStyle, *top);
+ style.set_property(CSS::PropertyID::BorderRightStyle, *right);
+ style.set_property(CSS::PropertyID::BorderBottomStyle, *bottom);
+ style.set_property(CSS::PropertyID::BorderLeftStyle, *left);
+ }
+ } else {
+ style.set_property(CSS::PropertyID::BorderTopStyle, value);
+ style.set_property(CSS::PropertyID::BorderRightStyle, value);
+ style.set_property(CSS::PropertyID::BorderBottomStyle, value);
+ style.set_property(CSS::PropertyID::BorderLeftStyle, value);
+ }
+ return;
+ }
+
+ if (property_id == CSS::PropertyID::BorderWidth) {
+ auto parts = split_on_whitespace(value.to_string());
+ if (value.is_string() && parts.size() == 2) {
+ auto vertical_border_width = parse_css_value(context, parts[0]);
+ auto horizontal_border_width = parse_css_value(context, parts[1]);
+ if (vertical_border_width && horizontal_border_width) {
+ style.set_property(CSS::PropertyID::BorderTopWidth, *vertical_border_width);
+ style.set_property(CSS::PropertyID::BorderRightWidth, *horizontal_border_width);
+ style.set_property(CSS::PropertyID::BorderBottomWidth, *vertical_border_width);
+ style.set_property(CSS::PropertyID::BorderLeftWidth, *horizontal_border_width);
+ }
+ } else {
+ style.set_property(CSS::PropertyID::BorderTopWidth, value);
+ style.set_property(CSS::PropertyID::BorderRightWidth, value);
+ style.set_property(CSS::PropertyID::BorderBottomWidth, value);
+ style.set_property(CSS::PropertyID::BorderLeftWidth, value);
+ }
+ return;
+ }
+
+ if (property_id == CSS::PropertyID::BorderColor) {
+ auto parts = split_on_whitespace(value.to_string());
+ if (value.is_string() && parts.size() == 4) {
+ auto top = parse_css_value(context, parts[0]);
+ auto right = parse_css_value(context, parts[1]);
+ auto bottom = parse_css_value(context, parts[2]);
+ auto left = parse_css_value(context, parts[3]);
+ if (top && right && bottom && left) {
+ style.set_property(CSS::PropertyID::BorderTopColor, *top);
+ style.set_property(CSS::PropertyID::BorderRightColor, *right);
+ style.set_property(CSS::PropertyID::BorderBottomColor, *bottom);
+ style.set_property(CSS::PropertyID::BorderLeftColor, *left);
+ }
+ } else {
+ style.set_property(CSS::PropertyID::BorderTopColor, value);
+ style.set_property(CSS::PropertyID::BorderRightColor, value);
+ style.set_property(CSS::PropertyID::BorderBottomColor, value);
+ style.set_property(CSS::PropertyID::BorderLeftColor, value);
+ }
+ return;
+ }
+
+ if (property_id == CSS::PropertyID::Background) {
+ if (value.is_identifier() && static_cast<const IdentifierStyleValue&>(value).id() == CSS::ValueID::None) {
+ style.set_property(CSS::PropertyID::BackgroundColor, ColorStyleValue::create(Color::Transparent));
+ return;
+ }
+ auto parts = split_on_whitespace(value.to_string());
+ NonnullRefPtrVector<StyleValue> values;
+ for (auto& part : parts) {
+ auto value = parse_css_value(context, part);
+ if (!value)
+ return;
+ values.append(value.release_nonnull());
+ }
+
+ // HACK: Disallow more than one color value in a 'background' shorthand
+ size_t color_value_count = 0;
+ for (auto& value : values)
+ color_value_count += value.is_color();
+
+ if (values[0].is_color() && color_value_count == 1)
+ style.set_property(CSS::PropertyID::BackgroundColor, values[0]);
+
+ for (auto& value : values) {
+ if (!value.is_string())
+ continue;
+ auto string = value.to_string();
+ set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundImage, value, document);
+ }
+ return;
+ }
+
+ if (property_id == CSS::PropertyID::BackgroundImage) {
+ if (!value.is_string())
+ return;
+ auto string = value.to_string();
+ if (!string.starts_with("url("))
+ return;
+ if (!string.ends_with(')'))
+ return;
+ auto url = string.substring_view(4, string.length() - 5);
+ if (url.length() >= 2 && url.starts_with('"') && url.ends_with('"'))
+ url = url.substring_view(1, url.length() - 2);
+ else if (url.length() >= 2 && url.starts_with('\'') && url.ends_with('\''))
+ url = url.substring_view(1, url.length() - 2);
+
+ auto background_image_value = ImageStyleValue::create(document.complete_url(url), document);
+ style.set_property(CSS::PropertyID::BackgroundImage, move(background_image_value));
+ return;
+ }
+
+ if (property_id == CSS::PropertyID::Margin) {
+ if (value.is_length()) {
+ style.set_property(CSS::PropertyID::MarginTop, value);
+ style.set_property(CSS::PropertyID::MarginRight, value);
+ style.set_property(CSS::PropertyID::MarginBottom, value);
+ style.set_property(CSS::PropertyID::MarginLeft, value);
+ return;
+ }
+ if (value.is_string()) {
+ auto parts = split_on_whitespace(value.to_string());
+ if (value.is_string() && parts.size() == 2) {
+ auto vertical = parse_css_value(context, parts[0]);
+ auto horizontal = parse_css_value(context, parts[1]);
+ if (vertical && horizontal) {
+ style.set_property(CSS::PropertyID::MarginTop, *vertical);
+ style.set_property(CSS::PropertyID::MarginBottom, *vertical);
+ style.set_property(CSS::PropertyID::MarginLeft, *horizontal);
+ style.set_property(CSS::PropertyID::MarginRight, *horizontal);
+ }
+ return;
+ }
+ if (value.is_string() && parts.size() == 3) {
+ auto top = parse_css_value(context, parts[0]);
+ auto horizontal = parse_css_value(context, parts[1]);
+ auto bottom = parse_css_value(context, parts[2]);
+ if (top && horizontal && bottom) {
+ style.set_property(CSS::PropertyID::MarginTop, *top);
+ style.set_property(CSS::PropertyID::MarginBottom, *bottom);
+ style.set_property(CSS::PropertyID::MarginLeft, *horizontal);
+ style.set_property(CSS::PropertyID::MarginRight, *horizontal);
+ }
+ return;
+ }
+ if (value.is_string() && parts.size() == 4) {
+ auto top = parse_css_value(context, parts[0]);
+ auto right = parse_css_value(context, parts[1]);
+ auto bottom = parse_css_value(context, parts[2]);
+ auto left = parse_css_value(context, parts[3]);
+ if (top && right && bottom && left) {
+ style.set_property(CSS::PropertyID::MarginTop, *top);
+ style.set_property(CSS::PropertyID::MarginBottom, *bottom);
+ style.set_property(CSS::PropertyID::MarginLeft, *left);
+ style.set_property(CSS::PropertyID::MarginRight, *right);
+ }
+ return;
+ }
+ dbg() << "Unsure what to do with CSS margin value '" << value.to_string() << "'";
+ return;
+ }
+ return;
+ }
+
+ if (property_id == CSS::PropertyID::Padding) {
+ if (value.is_length()) {
+ style.set_property(CSS::PropertyID::PaddingTop, value);
+ style.set_property(CSS::PropertyID::PaddingRight, value);
+ style.set_property(CSS::PropertyID::PaddingBottom, value);
+ style.set_property(CSS::PropertyID::PaddingLeft, value);
+ return;
+ }
+ if (value.is_string()) {
+ auto parts = split_on_whitespace(value.to_string());
+ if (value.is_string() && parts.size() == 2) {
+ auto vertical = parse_css_value(context, parts[0]);
+ auto horizontal = parse_css_value(context, parts[1]);
+ if (vertical && horizontal) {
+ style.set_property(CSS::PropertyID::PaddingTop, *vertical);
+ style.set_property(CSS::PropertyID::PaddingBottom, *vertical);
+ style.set_property(CSS::PropertyID::PaddingLeft, *horizontal);
+ style.set_property(CSS::PropertyID::PaddingRight, *horizontal);
+ }
+ return;
+ }
+ if (value.is_string() && parts.size() == 3) {
+ auto top = parse_css_value(context, parts[0]);
+ auto horizontal = parse_css_value(context, parts[1]);
+ auto bottom = parse_css_value(context, parts[2]);
+ if (top && bottom && horizontal) {
+ style.set_property(CSS::PropertyID::PaddingTop, *top);
+ style.set_property(CSS::PropertyID::PaddingBottom, *bottom);
+ style.set_property(CSS::PropertyID::PaddingLeft, *horizontal);
+ style.set_property(CSS::PropertyID::PaddingRight, *horizontal);
+ }
+ return;
+ }
+ if (value.is_string() && parts.size() == 4) {
+ auto top = parse_css_value(context, parts[0]);
+ auto right = parse_css_value(context, parts[1]);
+ auto bottom = parse_css_value(context, parts[2]);
+ auto left = parse_css_value(context, parts[3]);
+ if (top && bottom && left && right) {
+ style.set_property(CSS::PropertyID::PaddingTop, *top);
+ style.set_property(CSS::PropertyID::PaddingBottom, *bottom);
+ style.set_property(CSS::PropertyID::PaddingLeft, *left);
+ style.set_property(CSS::PropertyID::PaddingRight, *right);
+ }
+ return;
+ }
+ dbg() << "Unsure what to do with CSS padding value '" << value.to_string() << "'";
+ return;
+ }
+ return;
+ }
+
+ if (property_id == CSS::PropertyID::ListStyle) {
+ auto parts = split_on_whitespace(value.to_string());
+ if (!parts.is_empty()) {
+ auto value = parse_css_value(context, parts[0]);
+ if (!value)
+ return;
+ style.set_property(CSS::PropertyID::ListStyleType, value.release_nonnull());
+ }
+ return;
+ }
+
+ style.set_property(property_id, value);
+}
+
+NonnullRefPtr<StyleProperties> StyleResolver::resolve_style(const DOM::Element& element) const
+{
+ auto style = StyleProperties::create();
+
+ if (auto* parent_style = element.parent_element() ? element.parent_element()->specified_css_values() : nullptr) {
+ parent_style->for_each_property([&](auto property_id, auto& value) {
+ if (is_inherited_property(property_id))
+ set_property_expanding_shorthands(style, property_id, value, m_document);
+ });
+ }
+
+ element.apply_presentational_hints(*style);
+
+ auto matching_rules = collect_matching_rules(element);
+ sort_matching_rules(matching_rules);
+
+ for (auto& match : matching_rules) {
+ for (auto& property : match.rule->declaration().properties()) {
+ set_property_expanding_shorthands(style, property.property_id, property.value, m_document);
+ }
+ }
+
+ if (auto* inline_style = element.inline_style()) {
+ for (auto& property : inline_style->properties()) {
+ set_property_expanding_shorthands(style, property.property_id, property.value, m_document);
+ }
+ }
+
+ return style;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleResolver.h b/Userland/Libraries/LibWeb/CSS/StyleResolver.h
new file mode 100644
index 0000000000..0dde35a261
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleResolver.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtrVector.h>
+#include <AK/OwnPtr.h>
+#include <LibWeb/CSS/StyleProperties.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::CSS {
+
+struct MatchingRule {
+ RefPtr<StyleRule> rule;
+ size_t style_sheet_index { 0 };
+ size_t rule_index { 0 };
+ size_t selector_index { 0 };
+};
+
+class StyleResolver {
+public:
+ explicit StyleResolver(DOM::Document&);
+ ~StyleResolver();
+
+ DOM::Document& document() { return m_document; }
+ const DOM::Document& document() const { return m_document; }
+
+ NonnullRefPtr<StyleProperties> resolve_style(const DOM::Element&) const;
+
+ Vector<MatchingRule> collect_matching_rules(const DOM::Element&) const;
+ void sort_matching_rules(Vector<MatchingRule>&) const;
+
+ static bool is_inherited_property(CSS::PropertyID);
+
+private:
+ template<typename Callback>
+ void for_each_stylesheet(Callback) const;
+
+ DOM::Document& m_document;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleRule.cpp b/Userland/Libraries/LibWeb/CSS/StyleRule.cpp
new file mode 100644
index 0000000000..d92f362500
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleRule.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/StyleRule.h>
+
+namespace Web::CSS {
+
+StyleRule::StyleRule(Vector<Selector>&& selectors, NonnullRefPtr<StyleDeclaration>&& declaration)
+ : m_selectors(move(selectors))
+ , m_declaration(move(declaration))
+{
+}
+
+StyleRule::~StyleRule()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleRule.h b/Userland/Libraries/LibWeb/CSS/StyleRule.h
new file mode 100644
index 0000000000..d668c5b42b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleRule.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtrVector.h>
+#include <LibWeb/CSS/Selector.h>
+#include <LibWeb/CSS/StyleDeclaration.h>
+
+namespace Web::CSS {
+
+class StyleRule : public RefCounted<StyleRule> {
+ AK_MAKE_NONCOPYABLE(StyleRule);
+ AK_MAKE_NONMOVABLE(StyleRule);
+
+public:
+ static NonnullRefPtr<StyleRule> create(Vector<Selector>&& selectors, NonnullRefPtr<StyleDeclaration>&& declaration)
+ {
+ return adopt(*new StyleRule(move(selectors), move(declaration)));
+ }
+
+ ~StyleRule();
+
+ const Vector<Selector>& selectors() const { return m_selectors; }
+ const StyleDeclaration& declaration() const { return m_declaration; }
+
+private:
+ StyleRule(Vector<Selector>&&, NonnullRefPtr<StyleDeclaration>&&);
+
+ Vector<Selector> m_selectors;
+ NonnullRefPtr<StyleDeclaration> m_declaration;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleSheet.cpp b/Userland/Libraries/LibWeb/CSS/StyleSheet.cpp
new file mode 100644
index 0000000000..d4c37ba53e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleSheet.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/StyleSheet.h>
+
+namespace Web::CSS {
+
+StyleSheet::StyleSheet(NonnullRefPtrVector<StyleRule>&& rules)
+ : m_rules(move(rules))
+{
+}
+
+StyleSheet::~StyleSheet()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleSheet.h b/Userland/Libraries/LibWeb/CSS/StyleSheet.h
new file mode 100644
index 0000000000..9e3695e03d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleSheet.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtrVector.h>
+#include <LibWeb/CSS/StyleRule.h>
+
+namespace Web::CSS {
+
+class StyleSheet : public RefCounted<StyleSheet> {
+public:
+ static NonnullRefPtr<StyleSheet> create(NonnullRefPtrVector<StyleRule>&& rules)
+ {
+ return adopt(*new StyleSheet(move(rules)));
+ }
+
+ ~StyleSheet();
+
+ const NonnullRefPtrVector<StyleRule>& rules() const { return m_rules; }
+ NonnullRefPtrVector<StyleRule>& rules() { return m_rules; }
+
+private:
+ explicit StyleSheet(NonnullRefPtrVector<StyleRule>&&);
+
+ NonnullRefPtrVector<StyleRule> m_rules;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleSheetList.cpp b/Userland/Libraries/LibWeb/CSS/StyleSheetList.cpp
new file mode 100644
index 0000000000..addca5598d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleSheetList.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/StyleSheetList.h>
+
+namespace Web::CSS {
+
+void StyleSheetList::add_sheet(NonnullRefPtr<StyleSheet> sheet)
+{
+ m_sheets.append(move(sheet));
+}
+
+StyleSheetList::StyleSheetList(DOM::Document& document)
+ : m_document(document)
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleSheetList.h b/Userland/Libraries/LibWeb/CSS/StyleSheetList.h
new file mode 100644
index 0000000000..636e030f39
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleSheetList.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/RefCounted.h>
+#include <LibWeb/CSS/StyleSheet.h>
+
+namespace Web::CSS {
+
+class StyleSheetList : public RefCounted<StyleSheetList> {
+public:
+ static NonnullRefPtr<StyleSheetList> create(DOM::Document& document)
+ {
+ return adopt(*new StyleSheetList(document));
+ }
+
+ void add_sheet(NonnullRefPtr<StyleSheet>);
+
+ const NonnullRefPtrVector<StyleSheet>& sheets() const { return m_sheets; }
+
+private:
+ explicit StyleSheetList(DOM::Document&);
+
+ DOM::Document& m_document;
+ NonnullRefPtrVector<StyleSheet> m_sheets;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp
new file mode 100644
index 0000000000..f961649bdd
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ByteBuffer.h>
+#include <LibGfx/PNGLoader.h>
+#include <LibGfx/Palette.h>
+#include <LibWeb/CSS/StyleValue.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/InProcessWebView.h>
+#include <LibWeb/Loader/LoadRequest.h>
+#include <LibWeb/Loader/ResourceLoader.h>
+#include <LibWeb/Page/Frame.h>
+
+namespace Web::CSS {
+
+StyleValue::StyleValue(Type type)
+ : m_type(type)
+{
+}
+
+StyleValue::~StyleValue()
+{
+}
+
+String IdentifierStyleValue::to_string() const
+{
+ return CSS::string_from_value_id(m_id);
+}
+
+Color IdentifierStyleValue::to_color(const DOM::Document& document) const
+{
+ if (id() == CSS::ValueID::LibwebLink)
+ return document.link_color();
+
+ ASSERT(document.page());
+ auto palette = document.page()->palette();
+ switch (id()) {
+ case CSS::ValueID::LibwebPaletteDesktopBackground:
+ return palette.color(ColorRole::DesktopBackground);
+ case CSS::ValueID::LibwebPaletteActiveWindowBorder1:
+ return palette.color(ColorRole::ActiveWindowBorder1);
+ case CSS::ValueID::LibwebPaletteActiveWindowBorder2:
+ return palette.color(ColorRole::ActiveWindowBorder2);
+ case CSS::ValueID::LibwebPaletteActiveWindowTitle:
+ return palette.color(ColorRole::ActiveWindowTitle);
+ case CSS::ValueID::LibwebPaletteInactiveWindowBorder1:
+ return palette.color(ColorRole::InactiveWindowBorder1);
+ case CSS::ValueID::LibwebPaletteInactiveWindowBorder2:
+ return palette.color(ColorRole::InactiveWindowBorder2);
+ case CSS::ValueID::LibwebPaletteInactiveWindowTitle:
+ return palette.color(ColorRole::InactiveWindowTitle);
+ case CSS::ValueID::LibwebPaletteMovingWindowBorder1:
+ return palette.color(ColorRole::MovingWindowBorder1);
+ case CSS::ValueID::LibwebPaletteMovingWindowBorder2:
+ return palette.color(ColorRole::MovingWindowBorder2);
+ case CSS::ValueID::LibwebPaletteMovingWindowTitle:
+ return palette.color(ColorRole::MovingWindowTitle);
+ case CSS::ValueID::LibwebPaletteHighlightWindowBorder1:
+ return palette.color(ColorRole::HighlightWindowBorder1);
+ case CSS::ValueID::LibwebPaletteHighlightWindowBorder2:
+ return palette.color(ColorRole::HighlightWindowBorder2);
+ case CSS::ValueID::LibwebPaletteHighlightWindowTitle:
+ return palette.color(ColorRole::HighlightWindowTitle);
+ case CSS::ValueID::LibwebPaletteMenuStripe:
+ return palette.color(ColorRole::MenuStripe);
+ case CSS::ValueID::LibwebPaletteMenuBase:
+ return palette.color(ColorRole::MenuBase);
+ case CSS::ValueID::LibwebPaletteMenuBaseText:
+ return palette.color(ColorRole::MenuBaseText);
+ case CSS::ValueID::LibwebPaletteMenuSelection:
+ return palette.color(ColorRole::MenuSelection);
+ case CSS::ValueID::LibwebPaletteMenuSelectionText:
+ return palette.color(ColorRole::MenuSelectionText);
+ case CSS::ValueID::LibwebPaletteWindow:
+ return palette.color(ColorRole::Window);
+ case CSS::ValueID::LibwebPaletteWindowText:
+ return palette.color(ColorRole::WindowText);
+ case CSS::ValueID::LibwebPaletteButton:
+ return palette.color(ColorRole::Button);
+ case CSS::ValueID::LibwebPaletteButtonText:
+ return palette.color(ColorRole::ButtonText);
+ case CSS::ValueID::LibwebPaletteBase:
+ return palette.color(ColorRole::Base);
+ case CSS::ValueID::LibwebPaletteBaseText:
+ return palette.color(ColorRole::BaseText);
+ case CSS::ValueID::LibwebPaletteThreedHighlight:
+ return palette.color(ColorRole::ThreedHighlight);
+ case CSS::ValueID::LibwebPaletteThreedShadow1:
+ return palette.color(ColorRole::ThreedShadow1);
+ case CSS::ValueID::LibwebPaletteThreedShadow2:
+ return palette.color(ColorRole::ThreedShadow2);
+ case CSS::ValueID::LibwebPaletteHoverHighlight:
+ return palette.color(ColorRole::HoverHighlight);
+ case CSS::ValueID::LibwebPaletteSelection:
+ return palette.color(ColorRole::Selection);
+ case CSS::ValueID::LibwebPaletteSelectionText:
+ return palette.color(ColorRole::SelectionText);
+ case CSS::ValueID::LibwebPaletteInactiveSelection:
+ return palette.color(ColorRole::InactiveSelection);
+ case CSS::ValueID::LibwebPaletteInactiveSelectionText:
+ return palette.color(ColorRole::InactiveSelectionText);
+ case CSS::ValueID::LibwebPaletteRubberBandFill:
+ return palette.color(ColorRole::RubberBandFill);
+ case CSS::ValueID::LibwebPaletteRubberBandBorder:
+ return palette.color(ColorRole::RubberBandBorder);
+ case CSS::ValueID::LibwebPaletteLink:
+ return palette.color(ColorRole::Link);
+ case CSS::ValueID::LibwebPaletteActiveLink:
+ return palette.color(ColorRole::ActiveLink);
+ case CSS::ValueID::LibwebPaletteVisitedLink:
+ return palette.color(ColorRole::VisitedLink);
+ case CSS::ValueID::LibwebPaletteRuler:
+ return palette.color(ColorRole::Ruler);
+ case CSS::ValueID::LibwebPaletteRulerBorder:
+ return palette.color(ColorRole::RulerBorder);
+ case CSS::ValueID::LibwebPaletteRulerActiveText:
+ return palette.color(ColorRole::RulerActiveText);
+ case CSS::ValueID::LibwebPaletteRulerInactiveText:
+ return palette.color(ColorRole::RulerInactiveText);
+ case CSS::ValueID::LibwebPaletteTextCursor:
+ return palette.color(ColorRole::TextCursor);
+ case CSS::ValueID::LibwebPaletteFocusOutline:
+ return palette.color(ColorRole::FocusOutline);
+ case CSS::ValueID::LibwebPaletteSyntaxComment:
+ return palette.color(ColorRole::SyntaxComment);
+ case CSS::ValueID::LibwebPaletteSyntaxNumber:
+ return palette.color(ColorRole::SyntaxNumber);
+ case CSS::ValueID::LibwebPaletteSyntaxString:
+ return palette.color(ColorRole::SyntaxString);
+ case CSS::ValueID::LibwebPaletteSyntaxType:
+ return palette.color(ColorRole::SyntaxType);
+ case CSS::ValueID::LibwebPaletteSyntaxPunctuation:
+ return palette.color(ColorRole::SyntaxPunctuation);
+ case CSS::ValueID::LibwebPaletteSyntaxOperator:
+ return palette.color(ColorRole::SyntaxOperator);
+ case CSS::ValueID::LibwebPaletteSyntaxKeyword:
+ return palette.color(ColorRole::SyntaxKeyword);
+ case CSS::ValueID::LibwebPaletteSyntaxControlKeyword:
+ return palette.color(ColorRole::SyntaxControlKeyword);
+ case CSS::ValueID::LibwebPaletteSyntaxIdentifier:
+ return palette.color(ColorRole::SyntaxIdentifier);
+ case CSS::ValueID::LibwebPaletteSyntaxPreprocessorStatement:
+ return palette.color(ColorRole::SyntaxPreprocessorStatement);
+ case CSS::ValueID::LibwebPaletteSyntaxPreprocessorValue:
+ return palette.color(ColorRole::SyntaxPreprocessorValue);
+ default:
+ return {};
+ }
+}
+
+ImageStyleValue::ImageStyleValue(const URL& url, DOM::Document& document)
+ : StyleValue(Type::Image)
+ , m_url(url)
+ , m_document(document)
+{
+ LoadRequest request;
+ request.set_url(url);
+ set_resource(ResourceLoader::the().load_resource(Resource::Type::Image, request));
+}
+
+void ImageStyleValue::resource_did_load()
+{
+ if (!m_document)
+ return;
+ m_bitmap = resource()->bitmap();
+ // FIXME: Do less than a full repaint if possible?
+ if (m_document->frame())
+ m_document->frame()->set_needs_display({});
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValue.h
new file mode 100644
index 0000000000..4b3d1d3ecc
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CSS/StyleValue.h
@@ -0,0 +1,357 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <AK/String.h>
+#include <AK/StringView.h>
+#include <AK/URL.h>
+#include <AK/WeakPtr.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Color.h>
+#include <LibWeb/CSS/Length.h>
+#include <LibWeb/CSS/PropertyID.h>
+#include <LibWeb/CSS/ValueID.h>
+#include <LibWeb/Forward.h>
+#include <LibWeb/Loader/ImageResource.h>
+
+namespace Web::CSS {
+
+enum class Position {
+ Static,
+ Relative,
+ Absolute,
+ Fixed,
+ Sticky,
+};
+
+enum class TextAlign {
+ Left,
+ Center,
+ Right,
+ Justify,
+ LibwebCenter,
+};
+
+enum class TextDecorationLine {
+ None,
+ Underline,
+ Overline,
+ LineThrough,
+ Blink,
+};
+
+enum class TextTransform {
+ None,
+ Capitalize,
+ Uppercase,
+ Lowercase,
+ FullWidth,
+ FullSizeKana,
+};
+
+enum class Display {
+ None,
+ Block,
+ Inline,
+ InlineBlock,
+ ListItem,
+ Table,
+ TableRow,
+ TableCell,
+ TableHeaderGroup,
+ TableRowGroup,
+ TableFooterGroup,
+ TableColumn,
+ TableColumnGroup,
+ TableCaption,
+};
+
+enum class WhiteSpace {
+ Normal,
+ Pre,
+ Nowrap,
+ PreLine,
+ PreWrap,
+};
+
+enum class Float {
+ None,
+ Left,
+ Right,
+};
+
+enum class Clear {
+ None,
+ Left,
+ Right,
+ Both,
+};
+
+enum class LineStyle {
+ None,
+ Hidden,
+ Dotted,
+ Dashed,
+ Solid,
+ Double,
+ Groove,
+ Ridge,
+ Inset,
+ Outset,
+};
+
+enum class ListStyleType {
+ None,
+ Disc,
+ Circle,
+ Square,
+ Decimal,
+};
+
+class StyleValue : public RefCounted<StyleValue> {
+public:
+ virtual ~StyleValue();
+
+ enum class Type {
+ Invalid,
+ Inherit,
+ Initial,
+ String,
+ Length,
+ Color,
+ Identifier,
+ Image,
+ Position,
+ };
+
+ Type type() const { return m_type; }
+
+ bool is_inherit() const { return type() == Type::Inherit; }
+ bool is_initial() const { return type() == Type::Initial; }
+ bool is_color() const { return type() == Type::Color; }
+ bool is_identifier() const { return type() == Type::Identifier; }
+ bool is_image() const { return type() == Type::Image; }
+ bool is_string() const { return type() == Type::String; }
+ bool is_length() const { return type() == Type::Length; }
+ bool is_position() const { return type() == Type::Position; }
+
+ virtual String to_string() const = 0;
+ virtual Length to_length() const { return Length::make_auto(); }
+ virtual Color to_color(const DOM::Document&) const { return {}; }
+
+ CSS::ValueID to_identifier() const;
+
+ virtual bool is_auto() const { return false; }
+
+ bool operator==(const StyleValue& other) const { return equals(other); }
+ bool operator!=(const StyleValue& other) const { return !(*this == other); }
+
+ virtual bool equals(const StyleValue& other) const
+ {
+ if (type() != other.type())
+ return false;
+ return to_string() == other.to_string();
+ }
+
+protected:
+ explicit StyleValue(Type);
+
+private:
+ Type m_type { Type::Invalid };
+};
+
+class StringStyleValue : public StyleValue {
+public:
+ static NonnullRefPtr<StringStyleValue> create(const String& string)
+ {
+ return adopt(*new StringStyleValue(string));
+ }
+ virtual ~StringStyleValue() override { }
+
+ String to_string() const override { return m_string; }
+
+private:
+ explicit StringStyleValue(const String& string)
+ : StyleValue(Type::String)
+ , m_string(string)
+ {
+ }
+
+ String m_string;
+};
+
+class LengthStyleValue : public StyleValue {
+public:
+ static NonnullRefPtr<LengthStyleValue> create(const Length& length)
+ {
+ return adopt(*new LengthStyleValue(length));
+ }
+ virtual ~LengthStyleValue() override { }
+
+ virtual String to_string() const override { return m_length.to_string(); }
+ virtual Length to_length() const override { return m_length; }
+
+ const Length& length() const { return m_length; }
+
+ virtual bool is_auto() const override { return m_length.is_auto(); }
+
+ virtual bool equals(const StyleValue& other) const override
+ {
+ if (type() != other.type())
+ return false;
+ return m_length == static_cast<const LengthStyleValue&>(other).m_length;
+ }
+
+private:
+ explicit LengthStyleValue(const Length& length)
+ : StyleValue(Type::Length)
+ , m_length(length)
+ {
+ }
+
+ Length m_length;
+};
+
+class InitialStyleValue final : public StyleValue {
+public:
+ static NonnullRefPtr<InitialStyleValue> create() { return adopt(*new InitialStyleValue); }
+ virtual ~InitialStyleValue() override { }
+
+ String to_string() const override { return "initial"; }
+
+private:
+ InitialStyleValue()
+ : StyleValue(Type::Initial)
+ {
+ }
+};
+
+class InheritStyleValue final : public StyleValue {
+public:
+ static NonnullRefPtr<InheritStyleValue> create() { return adopt(*new InheritStyleValue); }
+ virtual ~InheritStyleValue() override { }
+
+ String to_string() const override { return "inherit"; }
+
+private:
+ InheritStyleValue()
+ : StyleValue(Type::Inherit)
+ {
+ }
+};
+
+class ColorStyleValue : public StyleValue {
+public:
+ static NonnullRefPtr<ColorStyleValue> create(Color color)
+ {
+ return adopt(*new ColorStyleValue(color));
+ }
+ virtual ~ColorStyleValue() override { }
+
+ Color color() const { return m_color; }
+ String to_string() const override { return m_color.to_string(); }
+ Color to_color(const DOM::Document&) const override { return m_color; }
+
+ virtual bool equals(const StyleValue& other) const override
+ {
+ if (type() != other.type())
+ return false;
+ return m_color == static_cast<const ColorStyleValue&>(other).m_color;
+ }
+
+private:
+ explicit ColorStyleValue(Color color)
+ : StyleValue(Type::Color)
+ , m_color(color)
+ {
+ }
+
+ Color m_color;
+};
+
+class IdentifierStyleValue final : public StyleValue {
+public:
+ static NonnullRefPtr<IdentifierStyleValue> create(CSS::ValueID id)
+ {
+ return adopt(*new IdentifierStyleValue(id));
+ }
+ virtual ~IdentifierStyleValue() override { }
+
+ CSS::ValueID id() const { return m_id; }
+
+ virtual String to_string() const override;
+ virtual Color to_color(const DOM::Document&) const override;
+
+ virtual bool equals(const StyleValue& other) const override
+ {
+ if (type() != other.type())
+ return false;
+ return m_id == static_cast<const IdentifierStyleValue&>(other).m_id;
+ }
+
+private:
+ explicit IdentifierStyleValue(CSS::ValueID id)
+ : StyleValue(Type::Identifier)
+ , m_id(id)
+ {
+ }
+
+ CSS::ValueID m_id { CSS::ValueID::Invalid };
+};
+
+class ImageStyleValue final
+ : public StyleValue
+ , public ImageResourceClient {
+public:
+ static NonnullRefPtr<ImageStyleValue> create(const URL& url, DOM::Document& document) { return adopt(*new ImageStyleValue(url, document)); }
+ virtual ~ImageStyleValue() override { }
+
+ String to_string() const override { return String::formatted("Image({})", m_url.to_string()); }
+
+ const Gfx::Bitmap* bitmap() const { return m_bitmap; }
+
+private:
+ ImageStyleValue(const URL&, DOM::Document&);
+
+ // ^ResourceClient
+ virtual void resource_did_load() override;
+
+ URL m_url;
+ WeakPtr<DOM::Document> m_document;
+ RefPtr<Gfx::Bitmap> m_bitmap;
+};
+
+inline CSS::ValueID StyleValue::to_identifier() const
+{
+ if (is_identifier())
+ return static_cast<const IdentifierStyleValue&>(*this).id();
+ return CSS::ValueID::Invalid;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/CodeGenerators/CMakeLists.txt b/Userland/Libraries/LibWeb/CodeGenerators/CMakeLists.txt
new file mode 100644
index 0000000000..b3c4073df9
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CodeGenerators/CMakeLists.txt
@@ -0,0 +1,11 @@
+add_executable(Generate_CSS_PropertyID_h Generate_CSS_PropertyID_h.cpp)
+add_executable(Generate_CSS_PropertyID_cpp Generate_CSS_PropertyID_cpp.cpp)
+add_executable(Generate_CSS_ValueID_h Generate_CSS_ValueID_h.cpp)
+add_executable(Generate_CSS_ValueID_cpp Generate_CSS_ValueID_cpp.cpp)
+add_executable(WrapperGenerator WrapperGenerator.cpp)
+target_compile_options(WrapperGenerator PUBLIC -g)
+target_link_libraries(Generate_CSS_PropertyID_h LagomCore)
+target_link_libraries(Generate_CSS_PropertyID_cpp LagomCore)
+target_link_libraries(Generate_CSS_ValueID_h LagomCore)
+target_link_libraries(Generate_CSS_ValueID_cpp LagomCore)
+target_link_libraries(WrapperGenerator LagomCore)
diff --git a/Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_cpp.cpp b/Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_cpp.cpp
new file mode 100644
index 0000000000..2b7b2218c7
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_cpp.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ByteBuffer.h>
+#include <AK/JsonObject.h>
+#include <AK/SourceGenerator.h>
+#include <AK/StringBuilder.h>
+#include <LibCore/File.h>
+#include <ctype.h>
+#include <stdio.h>
+
+static String title_casify(const String& dashy_name)
+{
+ auto parts = dashy_name.split('-');
+ StringBuilder builder;
+ for (auto& part : parts) {
+ if (part.is_empty())
+ continue;
+ builder.append(toupper(part[0]));
+ if (part.length() == 1)
+ continue;
+ builder.append(part.substring_view(1, part.length() - 1));
+ }
+ return builder.to_string();
+}
+
+int main(int argc, char** argv)
+{
+ if (argc != 2) {
+ warnln("usage: {} <path/to/CSS/Properties.json>", argv[0]);
+ return 1;
+ }
+ auto file = Core::File::construct(argv[1]);
+ if (!file->open(Core::IODevice::ReadOnly))
+ return 1;
+
+ auto json = JsonValue::from_string(file->read_all());
+ ASSERT(json.has_value());
+ ASSERT(json.value().is_object());
+
+ StringBuilder builder;
+ SourceGenerator generator { builder };
+
+ generator.append(R"~~~(
+#include <AK/Assertions.h>
+#include <LibWeb/CSS/PropertyID.h>
+
+namespace Web::CSS {
+
+PropertyID property_id_from_string(const StringView& string)
+{
+)~~~");
+
+ json.value().as_object().for_each_member([&](auto& name, auto& value) {
+ ASSERT(value.is_object());
+
+ auto member_generator = generator.fork();
+ member_generator.set("name", name);
+ member_generator.set("name:titlecase", title_casify(name));
+ member_generator.append(R"~~~(
+ if (string.equals_ignoring_case("@name@"))
+ return PropertyID::@name:titlecase@;
+)~~~");
+ });
+
+ generator.append(R"~~~(
+ return PropertyID::Invalid;
+}
+
+const char* string_from_property_id(PropertyID property_id) {
+ switch (property_id) {
+)~~~");
+
+ json.value().as_object().for_each_member([&](auto& name, auto& value) {
+ ASSERT(value.is_object());
+
+ auto member_generator = generator.fork();
+ member_generator.set("name", name);
+ member_generator.set("name:titlecase", title_casify(name));
+ member_generator.append(R"~~~(
+ case PropertyID::@name:titlecase@:
+ return "@name@";
+ )~~~");
+ });
+
+ generator.append(R"~~~(
+ default:
+ return "(invalid CSS::PropertyID)";
+ }
+}
+
+} // namespace Web::CSS
+)~~~");
+
+ outln("{}", generator.as_string_view());
+}
diff --git a/Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_h.cpp b/Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_h.cpp
new file mode 100644
index 0000000000..e2f235db62
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_h.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ByteBuffer.h>
+#include <AK/JsonObject.h>
+#include <AK/SourceGenerator.h>
+#include <AK/StringBuilder.h>
+#include <LibCore/File.h>
+#include <ctype.h>
+#include <stdio.h>
+
+static String title_casify(const String& dashy_name)
+{
+ auto parts = dashy_name.split('-');
+ StringBuilder builder;
+ for (auto& part : parts) {
+ if (part.is_empty())
+ continue;
+ builder.append(toupper(part[0]));
+ if (part.length() == 1)
+ continue;
+ builder.append(part.substring_view(1, part.length() - 1));
+ }
+ return builder.to_string();
+}
+
+int main(int argc, char** argv)
+{
+ if (argc != 2) {
+ warnln("usage: {} <path/to/CSS/Properties.json>", argv[0]);
+ return 1;
+ }
+ auto file = Core::File::construct(argv[1]);
+ if (!file->open(Core::IODevice::ReadOnly))
+ return 1;
+
+ auto json = JsonValue::from_string(file->read_all());
+ ASSERT(json.has_value());
+ ASSERT(json.value().is_object());
+
+ StringBuilder builder;
+ SourceGenerator generator { builder };
+ generator.append(R"~~~(
+#pragma once
+
+#include <AK/StringView.h>
+#include <AK/Traits.h>
+
+namespace Web::CSS {
+
+enum class PropertyID {
+ Invalid,
+)~~~");
+
+ json.value().as_object().for_each_member([&](auto& name, auto& value) {
+ ASSERT(value.is_object());
+
+ auto member_generator = generator.fork();
+ member_generator.set("name:titlecase", title_casify(name));
+
+ member_generator.append(R"~~~(
+ @name:titlecase@,
+)~~~");
+ });
+
+ generator.append(R"~~~(
+};
+
+PropertyID property_id_from_string(const StringView&);
+const char* string_from_property_id(PropertyID);
+
+} // namespace Web::CSS
+
+namespace AK {
+template<>
+struct Traits<Web::CSS::PropertyID> : public GenericTraits<Web::CSS::PropertyID> {
+ static unsigned hash(Web::CSS::PropertyID property_id) { return int_hash((unsigned)property_id); }
+};
+} // namespace AK
+)~~~");
+
+ outln("{}", generator.as_string_view());
+}
diff --git a/Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_ValueID_cpp.cpp b/Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_ValueID_cpp.cpp
new file mode 100644
index 0000000000..4991a4040a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_ValueID_cpp.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ByteBuffer.h>
+#include <AK/JsonObject.h>
+#include <AK/SourceGenerator.h>
+#include <AK/StringBuilder.h>
+#include <LibCore/File.h>
+#include <ctype.h>
+#include <stdio.h>
+
+static String title_casify(const String& dashy_name)
+{
+ auto parts = dashy_name.split('-');
+ StringBuilder builder;
+ for (auto& part : parts) {
+ if (part.is_empty())
+ continue;
+ builder.append(toupper(part[0]));
+ if (part.length() == 1)
+ continue;
+ builder.append(part.substring_view(1, part.length() - 1));
+ }
+ return builder.to_string();
+}
+
+int main(int argc, char** argv)
+{
+ if (argc != 2) {
+ warnln("usage: {} <path/to/CSS/Identifiers.json>", argv[0]);
+ return 1;
+ }
+ auto file = Core::File::construct(argv[1]);
+ if (!file->open(Core::IODevice::ReadOnly))
+ return 1;
+
+ auto json = JsonValue::from_string(file->read_all());
+ ASSERT(json.has_value());
+ ASSERT(json.value().is_array());
+
+ StringBuilder builder;
+ SourceGenerator generator { builder };
+
+ generator.append(R"~~~(
+#include <AK/Assertions.h>
+#include <LibWeb/CSS/ValueID.h>
+
+namespace Web::CSS {
+
+ValueID value_id_from_string(const StringView& string)
+{
+)~~~");
+
+ json.value().as_array().for_each([&](auto& name) {
+ auto member_generator = generator.fork();
+ member_generator.set("name", name.to_string());
+ member_generator.set("name:titlecase", title_casify(name.to_string()));
+ member_generator.append(R"~~~(
+ if (string.equals_ignoring_case("@name@"))
+ return ValueID::@name:titlecase@;
+)~~~");
+ });
+
+ generator.append(R"~~~(
+ return ValueID::Invalid;
+}
+
+const char* string_from_value_id(ValueID value_id) {
+ switch (value_id) {
+)~~~");
+
+ json.value().as_array().for_each([&](auto& name) {
+ auto member_generator = generator.fork();
+ member_generator.set("name", name.to_string());
+ member_generator.set("name:titlecase", title_casify(name.to_string()));
+ member_generator.append(R"~~~(
+ case ValueID::@name:titlecase@:
+ return "@name@";
+ )~~~");
+ });
+
+ generator.append(R"~~~(
+ default:
+ return "(invalid CSS::ValueID)";
+ }
+}
+
+} // namespace Web::CSS
+)~~~");
+
+ outln("{}", generator.as_string_view());
+}
diff --git a/Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_ValueID_h.cpp b/Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_ValueID_h.cpp
new file mode 100644
index 0000000000..7c85ade83f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CodeGenerators/Generate_CSS_ValueID_h.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ByteBuffer.h>
+#include <AK/JsonObject.h>
+#include <AK/SourceGenerator.h>
+#include <AK/StringBuilder.h>
+#include <LibCore/File.h>
+#include <ctype.h>
+#include <stdio.h>
+
+static String title_casify(const String& dashy_name)
+{
+ auto parts = dashy_name.split('-');
+ StringBuilder builder;
+ for (auto& part : parts) {
+ if (part.is_empty())
+ continue;
+ builder.append(toupper(part[0]));
+ if (part.length() == 1)
+ continue;
+ builder.append(part.substring_view(1, part.length() - 1));
+ }
+ return builder.to_string();
+}
+
+int main(int argc, char** argv)
+{
+ if (argc != 2) {
+ warnln("usage: {} <path/to/CSS/Identifiers.json>", argv[0]);
+ return 1;
+ }
+ auto file = Core::File::construct(argv[1]);
+ if (!file->open(Core::IODevice::ReadOnly))
+ return 1;
+
+ auto json = JsonValue::from_string(file->read_all());
+ ASSERT(json.has_value());
+ ASSERT(json.value().is_array());
+
+ StringBuilder builder;
+ SourceGenerator generator { builder };
+ generator.append(R"~~~(
+#pragma once
+
+#include <AK/StringView.h>
+#include <AK/Traits.h>
+
+namespace Web::CSS {
+
+enum class ValueID {
+ Invalid,
+)~~~");
+
+ json.value().as_array().for_each([&](auto& name) {
+ auto member_generator = generator.fork();
+ member_generator.set("name:titlecase", title_casify(name.to_string()));
+
+ member_generator.append(R"~~~(
+ @name:titlecase@,
+)~~~");
+ });
+
+ generator.append(R"~~~(
+};
+
+ValueID value_id_from_string(const StringView&);
+const char* string_from_value_id(ValueID);
+
+}
+
+)~~~");
+
+ outln("{}", generator.as_string_view());
+}
diff --git a/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp b/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp
new file mode 100644
index 0000000000..dbedd118b5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp
@@ -0,0 +1,951 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ByteBuffer.h>
+#include <AK/GenericLexer.h>
+#include <AK/HashMap.h>
+#include <AK/LexicalPath.h>
+#include <AK/SourceGenerator.h>
+#include <AK/StringBuilder.h>
+#include <LibCore/ArgsParser.h>
+#include <LibCore/File.h>
+#include <ctype.h>
+
+static String snake_name(const StringView& title_name)
+{
+ StringBuilder builder;
+ bool first = true;
+ bool last_was_uppercase = false;
+ for (auto ch : title_name) {
+ if (isupper(ch)) {
+ if (!first && !last_was_uppercase)
+ builder.append('_');
+ builder.append(tolower(ch));
+ } else {
+ builder.append(ch);
+ }
+ first = false;
+ last_was_uppercase = isupper(ch);
+ }
+ return builder.to_string();
+}
+
+static String make_input_acceptable_cpp(const String& input)
+{
+ if (input.is_one_of("class", "template", "for", "default", "char")) {
+ StringBuilder builder;
+ builder.append(input);
+ builder.append('_');
+ return builder.to_string();
+ }
+
+ String input_without_dashes = input;
+ input_without_dashes.replace("-", "_");
+
+ return input_without_dashes;
+}
+
+static void report_parsing_error(StringView message, StringView filename, StringView input, size_t offset)
+{
+ // FIXME: Spaghetti code ahead.
+
+ size_t lineno = 1;
+ size_t colno = 1;
+ size_t start_line = 0;
+ size_t line_length = 0;
+ for (size_t index = 0; index < input.length(); ++index) {
+ if (offset == index)
+ colno = index - start_line + 1;
+
+ if (input[index] == '\n') {
+ if (index >= offset)
+ break;
+
+ start_line = index + 1;
+ line_length = 0;
+ ++lineno;
+ } else {
+ ++line_length;
+ }
+ }
+
+ StringBuilder error_message;
+ error_message.appendff("{}\n", input.substring_view(start_line, line_length));
+ for (size_t i = 0; i < colno - 1; ++i)
+ error_message.append(' ');
+ error_message.append("\033[1;31m^\n");
+ error_message.appendff("{}:{}: error: {}\033[0m\n", filename, lineno, message);
+
+ warnln("{}", error_message.string_view());
+ exit(EXIT_FAILURE);
+}
+
+namespace IDL {
+
+struct Type {
+ String name;
+ bool nullable { false };
+};
+
+struct Parameter {
+ Type type;
+ String name;
+ bool optional { false };
+};
+
+struct Function {
+ Type return_type;
+ String name;
+ Vector<Parameter> parameters;
+ HashMap<String, String> extended_attributes;
+
+ size_t length() const
+ {
+ // FIXME: This seems to produce a length that is way over what it's supposed to be.
+ // For example, getElementsByTagName has its length set to 20 when it should be 1.
+ size_t length = 0;
+ for (auto& parameter : parameters) {
+ if (!parameter.optional)
+ length++;
+ }
+ return length;
+ }
+};
+
+struct Attribute {
+ bool readonly { false };
+ bool unsigned_ { false };
+ Type type;
+ String name;
+ HashMap<String, String> extended_attributes;
+
+ // Added for convenience after parsing
+ String getter_callback_name;
+ String setter_callback_name;
+};
+
+struct Interface {
+ String name;
+ String parent_name;
+
+ Vector<Attribute> attributes;
+ Vector<Function> functions;
+
+ // Added for convenience after parsing
+ String wrapper_class;
+ String wrapper_base_class;
+ String fully_qualified_name;
+};
+
+static OwnPtr<Interface> parse_interface(StringView filename, const StringView& input)
+{
+ auto interface = make<Interface>();
+
+ GenericLexer lexer(input);
+
+ auto assert_specific = [&](char ch) {
+ if (!lexer.consume_specific(ch))
+ report_parsing_error(String::formatted("expected '{}'", ch), filename, input, lexer.tell());
+ };
+
+ auto consume_whitespace = [&] {
+ bool consumed = true;
+ while (consumed) {
+ consumed = lexer.consume_while([](char ch) { return isspace(ch); }).length() > 0;
+
+ if (lexer.consume_specific("//")) {
+ lexer.consume_until('\n');
+ consumed = true;
+ }
+ }
+ };
+
+ auto assert_string = [&](const StringView& expected) {
+ if (!lexer.consume_specific(expected))
+ report_parsing_error(String::formatted("expected '{}'", expected), filename, input, lexer.tell());
+ };
+
+ assert_string("interface");
+ consume_whitespace();
+ interface->name = lexer.consume_until([](auto ch) { return isspace(ch); });
+ consume_whitespace();
+ if (lexer.consume_specific(':')) {
+ consume_whitespace();
+ interface->parent_name = lexer.consume_until([](auto ch) { return isspace(ch); });
+ consume_whitespace();
+ }
+ assert_specific('{');
+
+ auto parse_type = [&] {
+ auto name = lexer.consume_until([](auto ch) { return isspace(ch) || ch == '?'; });
+ auto nullable = lexer.consume_specific('?');
+ return Type { name, nullable };
+ };
+
+ auto parse_attribute = [&](HashMap<String, String>& extended_attributes) {
+ bool readonly = lexer.consume_specific("readonly");
+ if (readonly)
+ consume_whitespace();
+
+ if (lexer.consume_specific("attribute"))
+ consume_whitespace();
+
+ bool unsigned_ = lexer.consume_specific("unsigned");
+ if (unsigned_)
+ consume_whitespace();
+
+ auto type = parse_type();
+ consume_whitespace();
+ auto name = lexer.consume_until([](auto ch) { return isspace(ch) || ch == ';'; });
+ consume_whitespace();
+ assert_specific(';');
+ Attribute attribute;
+ attribute.readonly = readonly;
+ attribute.unsigned_ = unsigned_;
+ attribute.type = type;
+ attribute.name = name;
+ attribute.getter_callback_name = String::formatted("{}_getter", snake_name(attribute.name));
+ attribute.setter_callback_name = String::formatted("{}_setter", snake_name(attribute.name));
+ attribute.extended_attributes = move(extended_attributes);
+ interface->attributes.append(move(attribute));
+ };
+
+ auto parse_function = [&](HashMap<String, String>& extended_attributes) {
+ auto return_type = parse_type();
+ consume_whitespace();
+ auto name = lexer.consume_until([](auto ch) { return isspace(ch) || ch == '('; });
+ consume_whitespace();
+ assert_specific('(');
+
+ Vector<Parameter> parameters;
+
+ for (;;) {
+ if (lexer.consume_specific(')'))
+ break;
+ bool optional = lexer.consume_specific("optional");
+ if (optional)
+ consume_whitespace();
+ auto type = parse_type();
+ consume_whitespace();
+ auto name = lexer.consume_until([](auto ch) { return isspace(ch) || ch == ',' || ch == ')'; });
+ parameters.append({ move(type), move(name), optional });
+ if (lexer.consume_specific(')'))
+ break;
+ assert_specific(',');
+ consume_whitespace();
+ }
+
+ consume_whitespace();
+ assert_specific(';');
+
+ interface->functions.append(Function { return_type, name, move(parameters), move(extended_attributes) });
+ };
+
+ auto parse_extended_attributes = [&] {
+ HashMap<String, String> extended_attributes;
+ for (;;) {
+ consume_whitespace();
+ if (lexer.consume_specific(']'))
+ break;
+ auto name = lexer.consume_until([](auto ch) { return ch == ']' || ch == '=' || ch == ','; });
+ if (lexer.consume_specific('=')) {
+ auto value = lexer.consume_until([](auto ch) { return ch == ']' || ch == ','; });
+ extended_attributes.set(name, value);
+ } else {
+ extended_attributes.set(name, {});
+ }
+ lexer.consume_specific(',');
+ }
+ consume_whitespace();
+ return extended_attributes;
+ };
+
+ for (;;) {
+ HashMap<String, String> extended_attributes;
+
+ consume_whitespace();
+
+ if (lexer.consume_specific('}')) {
+ consume_whitespace();
+ assert_specific(';');
+ break;
+ }
+
+ if (lexer.consume_specific('[')) {
+ extended_attributes = parse_extended_attributes();
+ }
+
+ if (lexer.next_is("readonly") || lexer.next_is("attribute")) {
+ parse_attribute(extended_attributes);
+ continue;
+ }
+
+ parse_function(extended_attributes);
+ }
+
+ interface->wrapper_class = String::formatted("{}Wrapper", interface->name);
+ interface->wrapper_base_class = String::formatted("{}Wrapper", interface->parent_name.is_empty() ? String::empty() : interface->parent_name);
+
+ return interface;
+}
+
+}
+
+static void generate_header(const IDL::Interface&);
+static void generate_implementation(const IDL::Interface&);
+
+int main(int argc, char** argv)
+{
+ Core::ArgsParser args_parser;
+ const char* path = nullptr;
+ bool header_mode = false;
+ bool implementation_mode = false;
+ args_parser.add_option(header_mode, "Generate the wrapper .h file", "header", 'H');
+ args_parser.add_option(implementation_mode, "Generate the wrapper .cpp file", "implementation", 'I');
+ args_parser.add_positional_argument(path, "IDL file", "idl-file");
+ args_parser.parse(argc, argv);
+
+ auto file_or_error = Core::File::open(path, Core::IODevice::ReadOnly);
+ if (file_or_error.is_error()) {
+ fprintf(stderr, "Cannot open %s\n", path);
+ return 1;
+ }
+
+ LexicalPath lexical_path(path);
+ auto namespace_ = lexical_path.parts().at(lexical_path.parts().size() - 2);
+
+ auto data = file_or_error.value()->read_all();
+ auto interface = IDL::parse_interface(path, data);
+
+ if (!interface) {
+ warnln("Cannot parse {}", path);
+ return 1;
+ }
+
+ if (namespace_.is_one_of("DOM", "HTML", "UIEvents", "HighResolutionTime", "SVG")) {
+ StringBuilder builder;
+ builder.append(namespace_);
+ builder.append("::");
+ builder.append(interface->name);
+ interface->fully_qualified_name = builder.to_string();
+ } else {
+ interface->fully_qualified_name = interface->name;
+ }
+
+#if 0
+ dbgln("Attributes:");
+ for (auto& attribute : interface->attributes) {
+ dbg() << " " << (attribute.readonly ? "Readonly " : "")
+ << attribute.type.name << (attribute.type.nullable ? "?" : "")
+ << " " << attribute.name;
+ }
+
+ dbgln("Functions:");
+ for (auto& function : interface->functions) {
+ dbg() << " " << function.return_type.name << (function.return_type.nullable ? "?" : "")
+ << " " << function.name;
+ for (auto& parameter : function.parameters) {
+ dbg() << " " << parameter.type.name << (parameter.type.nullable ? "?" : "") << " " << parameter.name;
+ }
+ }
+#endif
+
+ if (header_mode)
+ generate_header(*interface);
+
+ if (implementation_mode)
+ generate_implementation(*interface);
+
+ return 0;
+}
+
+static bool should_emit_wrapper_factory(const IDL::Interface& interface)
+{
+ // FIXME: This is very hackish.
+ if (interface.name == "Event")
+ return false;
+ if (interface.name == "EventTarget")
+ return false;
+ if (interface.name == "Node")
+ return false;
+ if (interface.name == "Text")
+ return false;
+ if (interface.name == "Document")
+ return false;
+ if (interface.name == "DocumentType")
+ return false;
+ if (interface.name.ends_with("Element"))
+ return false;
+ return true;
+}
+
+static bool is_wrappable_type(const IDL::Type& type)
+{
+ if (type.name == "Node")
+ return true;
+ if (type.name == "Document")
+ return true;
+ if (type.name == "Text")
+ return true;
+ if (type.name == "DocumentType")
+ return true;
+ if (type.name.ends_with("Element"))
+ return true;
+ if (type.name == "ImageData")
+ return true;
+ return false;
+}
+
+static void generate_header(const IDL::Interface& interface)
+{
+ StringBuilder builder;
+ SourceGenerator generator { builder };
+
+ generator.set("name", interface.name);
+ generator.set("fully_qualified_name", interface.fully_qualified_name);
+ generator.set("wrapper_base_class", interface.wrapper_base_class);
+ generator.set("wrapper_class", interface.wrapper_class);
+ generator.set("wrapper_class:snakecase", snake_name(interface.wrapper_class));
+
+ generator.append(R"~~~(
+#pragma once
+
+#include <LibWeb/Bindings/Wrapper.h>
+
+// FIXME: This is very strange.
+#if __has_include(<LibWeb/DOM/@name@.h>)
+# include <LibWeb/DOM/@name@.h>
+#elif __has_include(<LibWeb/HTML/@name@.h>)
+# include <LibWeb/HTML/@name@.h>
+#elif __has_include(<LibWeb/UIEvents/@name@.h>)
+# include <LibWeb/UIEvents/@name@.h>
+#elif __has_include(<LibWeb/HighResolutionTime/@name@.h>)
+# include <LibWeb/HighResolutionTime/@name@.h>
+#elif __has_include(<LibWeb/SVG/@name@.h>)
+# include <LibWeb/SVG/@name@.h>
+#endif
+)~~~");
+
+ if (interface.wrapper_base_class != "Wrapper") {
+ generator.append(R"~~~(
+#include <LibWeb/Bindings/@wrapper_base_class@.h>
+)~~~");
+ }
+
+ generator.append(R"~~~(
+namespace Web::Bindings {
+
+class @wrapper_class@ : public @wrapper_base_class@ {
+ JS_OBJECT(@wrapper_class@, @wrapper_base_class@);
+public:
+ @wrapper_class@(JS::GlobalObject&, @fully_qualified_name@&);
+ virtual void initialize(JS::GlobalObject&) override;
+ virtual ~@wrapper_class@() override;
+)~~~");
+
+ if (interface.wrapper_base_class == "Wrapper") {
+ generator.append(R"~~~(
+ @fully_qualified_name@& impl() { return *m_impl; }
+ const @fully_qualified_name@& impl() const { return *m_impl; }
+)~~~");
+ } else {
+ generator.append(R"~~~(
+ @fully_qualified_name@& impl() { return static_cast<@fully_qualified_name@&>(@wrapper_base_class@::impl()); }
+ const @fully_qualified_name@& impl() const { return static_cast<const @fully_qualified_name@&>(@wrapper_base_class@::impl()); }
+)~~~");
+ }
+
+ generator.append(R"~~~(
+private:
+)~~~");
+
+ for (auto& function : interface.functions) {
+ auto function_generator = generator.fork();
+ function_generator.set("function.name:snakecase", snake_name(function.name));
+ function_generator.append(R"~~~(
+ JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@);
+ )~~~");
+ }
+
+ for (auto& attribute : interface.attributes) {
+ auto attribute_generator = generator.fork();
+ attribute_generator.set("attribute.name:snakecase", snake_name(attribute.name));
+ attribute_generator.append(R"~~~(
+ JS_DECLARE_NATIVE_GETTER(@attribute.name:snakecase@_getter);
+)~~~");
+
+ if (!attribute.readonly) {
+ attribute_generator.append(R"~~~(
+ JS_DECLARE_NATIVE_SETTER(@attribute.name:snakecase@_setter);
+)~~~");
+ }
+ }
+
+ if (interface.wrapper_base_class == "Wrapper") {
+ generator.append(R"~~~(
+ NonnullRefPtr<@fully_qualified_name@> m_impl;
+ )~~~");
+ }
+
+ generator.append(R"~~~(
+};
+)~~~");
+
+ if (should_emit_wrapper_factory(interface)) {
+ generator.append(R"~~~(
+@wrapper_class@* wrap(JS::GlobalObject&, @fully_qualified_name@&);
+)~~~");
+ }
+
+ generator.append(R"~~~(
+} // namespace Web::Bindings
+)~~~");
+
+ outln("{}", generator.as_string_view());
+}
+
+void generate_implementation(const IDL::Interface& interface)
+{
+ StringBuilder builder;
+ SourceGenerator generator { builder };
+
+ generator.set("wrapper_class", interface.wrapper_class);
+ generator.set("wrapper_base_class", interface.wrapper_base_class);
+ generator.set("fully_qualified_name", interface.fully_qualified_name);
+
+ generator.append(R"~~~(
+#include <AK/FlyString.h>
+#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/Function.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Uint8ClampedArray.h>
+#include <LibJS/Runtime/Value.h>
+#include <LibWeb/Bindings/@wrapper_class@.h>
+#include <LibWeb/Bindings/CanvasRenderingContext2DWrapper.h>
+#include <LibWeb/Bindings/CommentWrapper.h>
+#include <LibWeb/Bindings/DOMImplementationWrapper.h>
+#include <LibWeb/Bindings/DocumentFragmentWrapper.h>
+#include <LibWeb/Bindings/DocumentTypeWrapper.h>
+#include <LibWeb/Bindings/DocumentWrapper.h>
+#include <LibWeb/Bindings/EventTargetWrapperFactory.h>
+#include <LibWeb/Bindings/HTMLCanvasElementWrapper.h>
+#include <LibWeb/Bindings/HTMLHeadElementWrapper.h>
+#include <LibWeb/Bindings/HTMLImageElementWrapper.h>
+#include <LibWeb/Bindings/ImageDataWrapper.h>
+#include <LibWeb/Bindings/NodeWrapperFactory.h>
+#include <LibWeb/Bindings/TextWrapper.h>
+#include <LibWeb/Bindings/WindowObject.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/DOM/EventListener.h>
+#include <LibWeb/HTML/HTMLElement.h>
+#include <LibWeb/Origin.h>
+
+// FIXME: This is a total hack until we can figure out the namespace for a given type somehow.
+using namespace Web::DOM;
+using namespace Web::HTML;
+
+namespace Web::Bindings {
+
+)~~~");
+
+ if (interface.wrapper_base_class == "Wrapper") {
+ generator.append(R"~~~(
+@wrapper_class@::@wrapper_class@(JS::GlobalObject& global_object, @fully_qualified_name@& impl)
+: Wrapper(*global_object.object_prototype())
+, m_impl(impl)
+{
+}
+)~~~");
+ } else {
+ generator.append(R"~~~(
+@wrapper_class@::@wrapper_class@(JS::GlobalObject& global_object, @fully_qualified_name@& impl)
+: @wrapper_base_class@(global_object, impl)
+{
+}
+)~~~");
+ }
+
+ generator.append(R"~~~(
+void @wrapper_class@::initialize(JS::GlobalObject& global_object)
+{
+ [[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable | JS::Attribute::Configurable;
+
+ @wrapper_base_class@::initialize(global_object);
+)~~~");
+
+ for (auto& attribute : interface.attributes) {
+ auto attribute_generator = generator.fork();
+ attribute_generator.set("attribute.name", attribute.name);
+ attribute_generator.set("attribute.getter_callback", attribute.getter_callback_name);
+
+ if (attribute.readonly)
+ attribute_generator.set("attribute.setter_callback", "nullptr");
+ else
+ attribute_generator.set("attribute.setter_callback", attribute.setter_callback_name);
+
+ attribute_generator.append(R"~~~(
+ define_native_property("@attribute.name@", @attribute.getter_callback@, @attribute.setter_callback@, default_attributes);
+)~~~");
+ }
+
+ for (auto& function : interface.functions) {
+ auto function_generator = generator.fork();
+ function_generator.set("function.name", function.name);
+ function_generator.set("function.name:snakecase", snake_name(function.name));
+ function_generator.set("function.name:length", String::number(function.name.length()));
+
+ function_generator.append(R"~~~(
+ define_native_function("@function.name@", @function.name:snakecase@, @function.name:length@, default_attributes);
+)~~~");
+ }
+
+ generator.append(R"~~~(
+}
+
+@wrapper_class@::~@wrapper_class@()
+{
+}
+)~~~");
+
+ if (!interface.attributes.is_empty() || !interface.functions.is_empty()) {
+ generator.append(R"~~~(
+static @fully_qualified_name@* impl_from(JS::VM& vm, JS::GlobalObject& global_object)
+{
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
+ return {};
+ if (!is<@wrapper_class@>(this_object)) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "@fully_qualified_name@");
+ return nullptr;
+ }
+
+ return &static_cast<@wrapper_class@*>(this_object)->impl();
+ }
+)~~~");
+ }
+
+ auto generate_to_cpp = [&](auto& parameter, auto& js_name, const auto& js_suffix, auto cpp_name, bool return_void = false, bool legacy_null_to_empty_string = false, bool optional = false) {
+ auto scoped_generator = generator.fork();
+ scoped_generator.set("cpp_name", cpp_name);
+ scoped_generator.set("js_name", js_name);
+ scoped_generator.set("js_suffix", js_suffix);
+ scoped_generator.set("legacy_null_to_empty_string", legacy_null_to_empty_string ? "true" : "false");
+ scoped_generator.set("parameter.type.name", parameter.type.name);
+
+ if (return_void)
+ scoped_generator.set("return_statement", "return;");
+ else
+ scoped_generator.set("return_statement", "return {};");
+
+ // FIXME: Add support for optional to all types
+ if (parameter.type.name == "DOMString") {
+ if (!optional) {
+ scoped_generator.append(R"~~~(
+ auto @cpp_name@ = @js_name@@js_suffix@.to_string(global_object, @legacy_null_to_empty_string@);
+ if (vm.exception())
+ @return_statement@
+)~~~");
+ } else {
+ scoped_generator.append(R"~~~(
+ String @cpp_name@;
+ if (!@js_name@@js_suffix@.is_undefined()) {
+ @cpp_name@ = @js_name@@js_suffix@.to_string(global_object, @legacy_null_to_empty_string@);
+ if (vm.exception())
+ @return_statement@
+ }
+)~~~");
+ }
+ } else if (parameter.type.name == "EventListener") {
+ scoped_generator.append(R"~~~(
+ if (!@js_name@@js_suffix@.is_function()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "Function");
+ @return_statement@
+ }
+ auto @cpp_name@ = adopt(*new EventListener(JS::make_handle(&@js_name@@js_suffix@.as_function())));
+)~~~");
+ } else if (is_wrappable_type(parameter.type)) {
+ scoped_generator.append(R"~~~(
+ auto @cpp_name@_object = @js_name@@js_suffix@.to_object(global_object);
+ if (vm.exception())
+ @return_statement@
+
+ if (!is<@parameter.type.name@Wrapper>(@cpp_name@_object)) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "@parameter.type.name@");
+ @return_statement@
+ }
+
+ auto& @cpp_name@ = static_cast<@parameter.type.name@Wrapper*>(@cpp_name@_object)->impl();
+)~~~");
+ } else if (parameter.type.name == "double") {
+ scoped_generator.append(R"~~~(
+ auto @cpp_name@ = @js_name@@js_suffix@.to_double(global_object);
+ if (vm.exception())
+ @return_statement@
+)~~~");
+ } else if (parameter.type.name == "boolean") {
+ scoped_generator.append(R"~~~(
+ auto @cpp_name@ = @js_name@@js_suffix@.to_boolean();
+)~~~");
+ } else {
+ dbgln("Unimplemented JS-to-C++ conversion: {}", parameter.type.name);
+ ASSERT_NOT_REACHED();
+ }
+ };
+
+ auto generate_arguments = [&](auto& parameters, auto& arguments_builder, bool return_void = false) {
+ auto arguments_generator = generator.fork();
+
+ Vector<String> parameter_names;
+ size_t argument_index = 0;
+ for (auto& parameter : parameters) {
+ parameter_names.append(snake_name(parameter.name));
+ arguments_generator.set("argument.index", String::number(argument_index));
+
+ arguments_generator.append(R"~~~(
+ auto arg@argument.index@ = vm.argument(@argument.index@);
+)~~~");
+ // FIXME: Parameters can have [LegacyNullToEmptyString] attached.
+ generate_to_cpp(parameter, "arg", String::number(argument_index), snake_name(parameter.name), return_void, false, parameter.optional);
+ ++argument_index;
+ }
+
+ arguments_builder.join(", ", parameter_names);
+ };
+
+ auto generate_return_statement = [&](auto& return_type) {
+ auto scoped_generator = generator.fork();
+ scoped_generator.set("return_type", return_type.name);
+
+ if (return_type.name == "undefined") {
+ scoped_generator.append(R"~~~(
+ return JS::js_undefined();
+)~~~");
+ return;
+ }
+
+ if (return_type.nullable) {
+ if (return_type.name == "DOMString") {
+ scoped_generator.append(R"~~~(
+ if (retval.is_null())
+ return JS::js_null();
+)~~~");
+ } else {
+ scoped_generator.append(R"~~~(
+ if (!retval)
+ return JS::js_null();
+)~~~");
+ }
+ }
+
+ if (return_type.name == "DOMString") {
+ scoped_generator.append(R"~~~(
+ return JS::js_string(vm, retval);
+)~~~");
+ } else if (return_type.name == "ArrayFromVector") {
+ // FIXME: Remove this fake type hack once it's no longer needed.
+ // Basically once we have NodeList we can throw this out.
+ scoped_generator.append(R"~~~(
+ auto* new_array = JS::Array::create(global_object);
+ for (auto& element : retval)
+ new_array->indexed_properties().append(wrap(global_object, element));
+
+ return new_array;
+)~~~");
+ } else if (return_type.name == "long" || return_type.name == "double" || return_type.name == "boolean" || return_type.name == "short") {
+ scoped_generator.append(R"~~~(
+ return JS::Value(retval);
+)~~~");
+ } else if (return_type.name == "Uint8ClampedArray") {
+ scoped_generator.append(R"~~~(
+ return retval;
+)~~~");
+ } else {
+ scoped_generator.append(R"~~~(
+ return wrap(global_object, const_cast<@return_type@&>(*retval));
+)~~~");
+ }
+ };
+
+ for (auto& attribute : interface.attributes) {
+ auto attribute_generator = generator.fork();
+ attribute_generator.set("attribute.getter_callback", attribute.getter_callback_name);
+ attribute_generator.set("attribute.setter_callback", attribute.setter_callback_name);
+ attribute_generator.set("attribute.name:snakecase", snake_name(attribute.name));
+
+ if (attribute.extended_attributes.contains("Reflect")) {
+ auto attribute_name = attribute.extended_attributes.get("Reflect").value();
+ if (attribute_name.is_null())
+ attribute_name = attribute.name;
+ attribute_name = make_input_acceptable_cpp(attribute_name);
+
+ attribute_generator.set("attribute.reflect_name", attribute_name);
+ } else {
+ attribute_generator.set("attribute.reflect_name", snake_name(attribute.name));
+ }
+
+ attribute_generator.append(R"~~~(
+JS_DEFINE_NATIVE_GETTER(@wrapper_class@::@attribute.getter_callback@)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+)~~~");
+
+ if (attribute.extended_attributes.contains("ReturnNullIfCrossOrigin")) {
+ attribute_generator.append(R"~~~(
+ if (!impl->may_access_from_origin(static_cast<WindowObject&>(global_object).origin()))
+ return JS::js_null();
+)~~~");
+ }
+
+ if (attribute.extended_attributes.contains("Reflect")) {
+ if (attribute.type.name != "boolean") {
+ attribute_generator.append(R"~~~(
+ auto retval = impl->attribute(HTML::AttributeNames::@attribute.reflect_name@);
+)~~~");
+ } else {
+ attribute_generator.append(R"~~~(
+ auto retval = impl->has_attribute(HTML::AttributeNames::@attribute.reflect_name@);
+)~~~");
+ }
+ } else {
+ attribute_generator.append(R"~~~(
+ auto retval = impl->@attribute.name:snakecase@();
+)~~~");
+ }
+
+ generate_return_statement(attribute.type);
+
+ attribute_generator.append(R"~~~(
+}
+)~~~");
+
+ if (!attribute.readonly) {
+ attribute_generator.append(R"~~~(
+JS_DEFINE_NATIVE_SETTER(@wrapper_class@::@attribute.setter_callback@)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return;
+)~~~");
+
+ generate_to_cpp(attribute, "value", "", "cpp_value", true, attribute.extended_attributes.contains("LegacyNullToEmptyString"));
+
+ if (attribute.extended_attributes.contains("Reflect")) {
+ if (attribute.type.name != "boolean") {
+ attribute_generator.append(R"~~~(
+ impl->set_attribute(HTML::AttributeNames::@attribute.reflect_name@, cpp_value);
+)~~~");
+ } else {
+ attribute_generator.append(R"~~~(
+ if (!cpp_value)
+ impl->remove_attribute(HTML::AttributeNames::@attribute.reflect_name@);
+ else
+ impl->set_attribute(HTML::AttributeNames::@attribute.reflect_name@, String::empty());
+)~~~");
+ }
+ } else {
+ attribute_generator.append(R"~~~(
+ impl->set_@attribute.name:snakecase@(cpp_value);
+)~~~");
+ }
+
+ attribute_generator.append(R"~~~(
+}
+)~~~");
+ }
+ }
+
+ // Implementation: Functions
+ for (auto& function : interface.functions) {
+ auto function_generator = generator.fork();
+ function_generator.set("function.name", function.name);
+ function_generator.set("function.name:snakecase", snake_name(function.name));
+ function_generator.set("function.nargs", String::number(function.length()));
+
+ function_generator.append(R"~~~(\
+JS_DEFINE_NATIVE_FUNCTION(@wrapper_class@::@function.name:snakecase@)
+{
+ auto* impl = impl_from(vm, global_object);
+ if (!impl)
+ return {};
+)~~~");
+
+ if (function.length() > 0) {
+ if (function.length() == 1) {
+ function_generator.set(".bad_arg_count", "JS::ErrorType::BadArgCountOne");
+ function_generator.set(".arg_count_suffix", "");
+ } else {
+ function_generator.set(".bad_arg_count", "JS::ErrorType::BadArgCountMany");
+ function_generator.set(".arg_count_suffix", String::formatted(", \"{}\"", function.length()));
+ }
+
+ function_generator.append(R"~~~(
+ if (vm.argument_count() < @function.nargs@) {
+ vm.throw_exception<JS::TypeError>(global_object, @.bad_arg_count@, "@function.name@"@.arg_count_suffix@);
+ return {};
+ }
+)~~~");
+ }
+
+ StringBuilder arguments_builder;
+ generate_arguments(function.parameters, arguments_builder);
+
+ function_generator.set(".arguments", arguments_builder.string_view());
+
+ if (function.return_type.name != "undefined") {
+ function_generator.append(R"~~~(
+ auto retval = impl->@function.name:snakecase@(@.arguments@);
+)~~~");
+ } else {
+ function_generator.append(R"~~~(
+ impl->@function.name:snakecase@(@.arguments@);
+)~~~");
+ }
+
+ generate_return_statement(function.return_type);
+
+ function_generator.append(R"~~~(
+}
+)~~~");
+ }
+
+ if (should_emit_wrapper_factory(interface)) {
+ generator.append(R"~~~(
+@wrapper_class@* wrap(JS::GlobalObject& global_object, @fully_qualified_name@& impl)
+{
+ return static_cast<@wrapper_class@*>(wrap_impl(global_object, impl));
+}
+)~~~");
+ }
+
+ generator.append(R"~~~(
+} // namespace Web::Bindings
+)~~~");
+
+ outln("{}", generator.as_string_view());
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Attribute.h b/Userland/Libraries/LibWeb/DOM/Attribute.h
new file mode 100644
index 0000000000..32ca3d11b4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Attribute.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+
+namespace Web {
+
+class Attribute {
+public:
+ Attribute(const FlyString& name, const String& value)
+ : m_name(name)
+ , m_value(value)
+ {
+ }
+
+ const FlyString& name() const { return m_name; }
+ const String& value() const { return m_value; }
+
+ void set_value(const String& value) { m_value = value; }
+
+private:
+ FlyString m_name;
+ String m_value;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/CharacterData.cpp b/Userland/Libraries/LibWeb/DOM/CharacterData.cpp
new file mode 100644
index 0000000000..60fb60ef7f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/CharacterData.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/CharacterData.h>
+
+namespace Web::DOM {
+
+CharacterData::CharacterData(Document& document, NodeType type, const String& data)
+ : Node(document, type)
+ , m_data(data)
+{
+}
+
+CharacterData::~CharacterData()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/CharacterData.h b/Userland/Libraries/LibWeb/DOM/CharacterData.h
new file mode 100644
index 0000000000..de09340e41
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/CharacterData.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <LibWeb/DOM/Node.h>
+#include <LibWeb/DOM/NonDocumentTypeChildNode.h>
+
+namespace Web::DOM {
+
+class CharacterData
+ : public Node
+ , public NonDocumentTypeChildNode<CharacterData> {
+public:
+ using WrapperType = Bindings::CharacterDataWrapper;
+
+ virtual ~CharacterData() override;
+
+ const String& data() const { return m_data; }
+ void set_data(const String& data) { m_data = data; }
+
+ unsigned length() const { return m_data.length(); }
+
+ virtual String text_content() const override { return m_data; }
+
+protected:
+ explicit CharacterData(Document&, NodeType, const String&);
+
+private:
+ String m_data;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/CharacterData.idl b/Userland/Libraries/LibWeb/DOM/CharacterData.idl
new file mode 100644
index 0000000000..e024979c05
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/CharacterData.idl
@@ -0,0 +1,9 @@
+interface CharacterData : Node {
+
+ attribute DOMString data;
+ readonly attribute unsigned long length;
+
+ readonly attribute Element? nextElementSibling;
+ readonly attribute Element? previousElementSibling;
+
+};
diff --git a/Userland/Libraries/LibWeb/DOM/Comment.cpp b/Userland/Libraries/LibWeb/DOM/Comment.cpp
new file mode 100644
index 0000000000..7e3f0ab8d2
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Comment.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Comment.h>
+#include <LibWeb/Layout/TextNode.h>
+
+namespace Web::DOM {
+
+Comment::Comment(Document& document, const String& data)
+ : CharacterData(document, NodeType::COMMENT_NODE, data)
+{
+}
+
+Comment::~Comment()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Comment.h b/Userland/Libraries/LibWeb/DOM/Comment.h
new file mode 100644
index 0000000000..4ab3bfbedb
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Comment.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <LibWeb/DOM/CharacterData.h>
+
+namespace Web::DOM {
+
+class Comment final : public CharacterData {
+public:
+ using WrapperType = Bindings::CommentWrapper;
+
+ explicit Comment(Document&, const String&);
+ virtual ~Comment() override;
+
+ virtual FlyString node_name() const override { return "#comment"; }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Comment.idl b/Userland/Libraries/LibWeb/DOM/Comment.idl
new file mode 100644
index 0000000000..7fb01d1d70
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Comment.idl
@@ -0,0 +1,3 @@
+interface Comment : CharacterData {
+
+};
diff --git a/Userland/Libraries/LibWeb/DOM/DOMImplementation.cpp b/Userland/Libraries/LibWeb/DOM/DOMImplementation.cpp
new file mode 100644
index 0000000000..0fdeb062b6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/DOMImplementation.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/DOMImplementation.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/DocumentType.h>
+#include <LibWeb/DOM/ElementFactory.h>
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/Namespace.h>
+#include <LibWeb/Origin.h>
+
+namespace Web::DOM {
+
+DOMImplementation::DOMImplementation(Document& document)
+ : m_document(document)
+{
+}
+
+const NonnullRefPtr<Document> DOMImplementation::create_htmldocument(const String& title) const
+{
+ auto html_document = Document::create();
+
+ html_document->set_content_type("text/html");
+ html_document->set_ready_for_post_load_tasks(true);
+
+ auto doctype = adopt(*new DocumentType(html_document));
+ doctype->set_name("html");
+ html_document->append_child(doctype);
+
+ auto html_element = create_element(html_document, HTML::TagNames::html, Namespace::HTML);
+ html_document->append_child(html_element);
+
+ auto head_element = create_element(html_document, HTML::TagNames::head, Namespace::HTML);
+ html_element->append_child(head_element);
+
+ if (!title.is_null()) {
+ auto title_element = create_element(html_document, HTML::TagNames::title, Namespace::HTML);
+ head_element->append_child(title_element);
+
+ auto text_node = adopt(*new Text(html_document, title));
+ title_element->append_child(text_node);
+ }
+
+ auto body_element = create_element(html_document, HTML::TagNames::body, Namespace::HTML);
+ html_element->append_child(body_element);
+
+ html_document->set_origin(m_document.origin());
+
+ return html_document;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/DOMImplementation.h b/Userland/Libraries/LibWeb/DOM/DOMImplementation.h
new file mode 100644
index 0000000000..ac59fab4d0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/DOMImplementation.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtr.h>
+#include <AK/RefCounted.h>
+#include <AK/Weakable.h>
+#include <LibWeb/Bindings/Wrappable.h>
+
+namespace Web::DOM {
+
+class DOMImplementation final
+ : public RefCounted<DOMImplementation>
+ , public Weakable<DOMImplementation>
+ , public Bindings::Wrappable {
+public:
+ using WrapperType = Bindings::DOMImplementationWrapper;
+
+ static NonnullRefPtr<DOMImplementation> create(Document& document)
+ {
+ return adopt(*new DOMImplementation(document));
+ }
+
+ // FIXME: snake_case in WrapperGenerator turns "createHTMLDocument" into "create_htmldocument"
+ const NonnullRefPtr<Document> create_htmldocument(const String& title) const;
+
+ // https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature
+ bool has_feature() const { return true; }
+
+private:
+ explicit DOMImplementation(Document&);
+
+ Document& m_document;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/DOMImplementation.idl b/Userland/Libraries/LibWeb/DOM/DOMImplementation.idl
new file mode 100644
index 0000000000..c979a587c3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/DOMImplementation.idl
@@ -0,0 +1,7 @@
+interface DOMImplementation {
+
+ Document createHTMLDocument(optional DOMString title);
+
+ boolean hasFeature();
+
+};
diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp
new file mode 100644
index 0000000000..f61e81fb70
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Document.cpp
@@ -0,0 +1,690 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <AK/Utf8View.h>
+#include <LibCore/Timer.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/DisplayLink.h>
+#include <LibGUI/MessageBox.h>
+#include <LibJS/Interpreter.h>
+#include <LibJS/Parser.h>
+#include <LibJS/Runtime/Function.h>
+#include <LibWeb/Bindings/DocumentWrapper.h>
+#include <LibWeb/Bindings/WindowObject.h>
+#include <LibWeb/CSS/StyleResolver.h>
+#include <LibWeb/DOM/Comment.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/DocumentFragment.h>
+#include <LibWeb/DOM/DocumentType.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/DOM/ElementFactory.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/DOM/Window.h>
+#include <LibWeb/Dump.h>
+#include <LibWeb/HTML/AttributeNames.h>
+#include <LibWeb/HTML/EventNames.h>
+#include <LibWeb/HTML/HTMLBodyElement.h>
+#include <LibWeb/HTML/HTMLFrameSetElement.h>
+#include <LibWeb/HTML/HTMLHeadElement.h>
+#include <LibWeb/HTML/HTMLHtmlElement.h>
+#include <LibWeb/HTML/HTMLScriptElement.h>
+#include <LibWeb/HTML/HTMLTitleElement.h>
+#include <LibWeb/InProcessWebView.h>
+#include <LibWeb/Layout/BlockFormattingContext.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+#include <LibWeb/Layout/TreeBuilder.h>
+#include <LibWeb/Namespace.h>
+#include <LibWeb/Origin.h>
+#include <LibWeb/Page/Frame.h>
+#include <LibWeb/SVG/TagNames.h>
+#include <ctype.h>
+#include <stdio.h>
+
+namespace Web::DOM {
+
+Document::Document(const URL& url)
+ : ParentNode(*this, NodeType::DOCUMENT_NODE)
+ , m_style_resolver(make<CSS::StyleResolver>(*this))
+ , m_style_sheets(CSS::StyleSheetList::create(*this))
+ , m_url(url)
+ , m_window(Window::create_with_document(*this))
+ , m_implementation(DOMImplementation::create(*this))
+{
+ m_style_update_timer = Core::Timer::create_single_shot(0, [this] {
+ update_style();
+ });
+
+ m_forced_layout_timer = Core::Timer::create_single_shot(0, [this] {
+ force_layout();
+ });
+}
+
+Document::~Document()
+{
+}
+
+void Document::removed_last_ref()
+{
+ ASSERT(!ref_count());
+ ASSERT(!m_deletion_has_begun);
+
+ if (m_referencing_node_count) {
+ // The document has reached ref_count==0 but still has nodes keeping it alive.
+ // At this point, sever all the node links we control.
+ // If nodes remain elsewhere (e.g JS wrappers), they will keep the document alive.
+
+ // NOTE: This makes sure we stay alive across for the duration of the cleanup below.
+ increment_referencing_node_count();
+
+ m_focused_element = nullptr;
+ m_hovered_node = nullptr;
+ m_pending_parsing_blocking_script = nullptr;
+ m_inspected_node = nullptr;
+ m_scripts_to_execute_when_parsing_has_finished.clear();
+ m_scripts_to_execute_as_soon_as_possible.clear();
+ m_associated_inert_template_document = nullptr;
+
+ m_interpreter = nullptr;
+
+ {
+ // Gather up all the descendants of this document and prune them from the tree.
+ // FIXME: This could definitely be more elegant.
+ NonnullRefPtrVector<Node> descendants;
+ for_each_in_subtree([&](auto& node) {
+ if (&node != this)
+ descendants.append(node);
+ return IterationDecision::Continue;
+ });
+
+ for (auto& node : descendants) {
+ ASSERT(&node.document() == this);
+ ASSERT(!node.is_document());
+ if (node.parent())
+ node.parent()->remove_child(node);
+ }
+ }
+
+ m_in_removed_last_ref = false;
+ decrement_referencing_node_count();
+ return;
+ }
+
+ m_in_removed_last_ref = false;
+ m_deletion_has_begun = true;
+ delete this;
+}
+
+Origin Document::origin() const
+{
+ if (!m_url.is_valid())
+ return {};
+ return { m_url.protocol(), m_url.host(), m_url.port() };
+}
+
+void Document::set_origin(const Origin& origin)
+{
+ m_url.set_protocol(origin.protocol());
+ m_url.set_host(origin.host());
+ m_url.set_port(origin.port());
+}
+
+void Document::schedule_style_update()
+{
+ if (m_style_update_timer->is_active())
+ return;
+ m_style_update_timer->start();
+}
+
+void Document::schedule_forced_layout()
+{
+ if (m_forced_layout_timer->is_active())
+ return;
+ m_forced_layout_timer->start();
+}
+
+bool Document::is_child_allowed(const Node& node) const
+{
+ switch (node.type()) {
+ case NodeType::DOCUMENT_NODE:
+ case NodeType::TEXT_NODE:
+ return false;
+ case NodeType::COMMENT_NODE:
+ return true;
+ case NodeType::DOCUMENT_TYPE_NODE:
+ return !first_child_of_type<DocumentType>();
+ case NodeType::ELEMENT_NODE:
+ return !first_child_of_type<Element>();
+ default:
+ return false;
+ }
+}
+
+const Element* Document::document_element() const
+{
+ return first_child_of_type<Element>();
+}
+
+const HTML::HTMLHtmlElement* Document::html_element() const
+{
+ auto* html = document_element();
+ if (is<HTML::HTMLHtmlElement>(html))
+ return downcast<HTML::HTMLHtmlElement>(html);
+ return nullptr;
+}
+
+const HTML::HTMLHeadElement* Document::head() const
+{
+ auto* html = html_element();
+ if (!html)
+ return nullptr;
+ return html->first_child_of_type<HTML::HTMLHeadElement>();
+}
+
+const HTML::HTMLElement* Document::body() const
+{
+ auto* html = html_element();
+ if (!html)
+ return nullptr;
+ auto* first_body = html->first_child_of_type<HTML::HTMLBodyElement>();
+ if (first_body)
+ return first_body;
+ auto* first_frameset = html->first_child_of_type<HTML::HTMLFrameSetElement>();
+ if (first_frameset)
+ return first_frameset;
+ return nullptr;
+}
+
+void Document::set_body(HTML::HTMLElement& new_body)
+{
+ if (!is<HTML::HTMLBodyElement>(new_body) && !is<HTML::HTMLFrameSetElement>(new_body)) {
+ // FIXME: throw a "HierarchyRequestError" DOMException.
+ return;
+ }
+
+ auto* existing_body = body();
+ if (existing_body) {
+ TODO();
+ return;
+ }
+
+ auto* html = document_element();
+ if (!html) {
+ // FIXME: throw a "HierarchyRequestError" DOMException.
+ return;
+ }
+
+ // FIXME: Implement this once there's a non-const first_child_of_type:
+ // "Otherwise, the body element is null, but there's a document element. Append the new value to the document element."
+ TODO();
+}
+
+String Document::title() const
+{
+ auto* head_element = head();
+ if (!head_element)
+ return {};
+
+ auto* title_element = head_element->first_child_of_type<HTML::HTMLTitleElement>();
+ if (!title_element)
+ return {};
+
+ auto raw_title = title_element->text_content();
+
+ StringBuilder builder;
+ bool last_was_space = false;
+ for (auto code_point : Utf8View(raw_title)) {
+ if (isspace(code_point)) {
+ last_was_space = true;
+ } else {
+ if (last_was_space && !builder.is_empty())
+ builder.append(' ');
+ builder.append_code_point(code_point);
+ last_was_space = false;
+ }
+ }
+ return builder.to_string();
+}
+
+void Document::set_title(const String& title)
+{
+ auto* head_element = const_cast<HTML::HTMLHeadElement*>(head());
+ if (!head_element)
+ return;
+
+ RefPtr<HTML::HTMLTitleElement> title_element = head_element->first_child_of_type<HTML::HTMLTitleElement>();
+ if (!title_element) {
+ title_element = static_ptr_cast<HTML::HTMLTitleElement>(create_element(HTML::TagNames::title));
+ head_element->append_child(*title_element);
+ }
+
+ while (RefPtr<Node> child = title_element->first_child())
+ title_element->remove_child(child.release_nonnull());
+
+ title_element->append_child(adopt(*new Text(*this, title)));
+
+ if (auto* page = this->page())
+ page->client().page_did_change_title(title);
+}
+
+void Document::attach_to_frame(Badge<Frame>, Frame& frame)
+{
+ m_frame = frame;
+ update_layout();
+}
+
+void Document::detach_from_frame(Badge<Frame>, Frame& frame)
+{
+ ASSERT(&frame == m_frame);
+ tear_down_layout_tree();
+ m_frame = nullptr;
+}
+
+void Document::tear_down_layout_tree()
+{
+ if (!m_layout_root)
+ return;
+
+ // Gather up all the layout nodes in a vector and detach them from parents
+ // while the vector keeps them alive.
+
+ NonnullRefPtrVector<Layout::Node> layout_nodes;
+
+ m_layout_root->for_each_in_subtree([&](auto& layout_node) {
+ layout_nodes.append(layout_node);
+ return IterationDecision::Continue;
+ });
+
+ for (auto& layout_node : layout_nodes) {
+ if (layout_node.parent())
+ layout_node.parent()->remove_child(layout_node);
+ }
+
+ m_layout_root = nullptr;
+}
+
+Color Document::background_color(const Palette& palette) const
+{
+ auto default_color = palette.base();
+ auto* body_element = body();
+ if (!body_element)
+ return default_color;
+
+ auto* body_layout_node = body_element->layout_node();
+ if (!body_layout_node)
+ return default_color;
+
+ auto color = body_layout_node->computed_values().background_color();
+ if (!color.alpha())
+ return default_color;
+ return color;
+}
+
+RefPtr<Gfx::Bitmap> Document::background_image() const
+{
+ auto* body_element = body();
+ if (!body_element)
+ return {};
+
+ auto* body_layout_node = body_element->layout_node();
+ if (!body_layout_node)
+ return {};
+
+ auto background_image = body_layout_node->background_image();
+ if (!background_image)
+ return {};
+ return background_image->bitmap();
+}
+
+URL Document::complete_url(const String& string) const
+{
+ return m_url.complete_url(string);
+}
+
+void Document::invalidate_layout()
+{
+ tear_down_layout_tree();
+}
+
+void Document::force_layout()
+{
+ invalidate_layout();
+ update_layout();
+}
+
+void Document::update_layout()
+{
+ if (!frame())
+ return;
+
+ if (!m_layout_root) {
+ Layout::TreeBuilder tree_builder;
+ m_layout_root = static_ptr_cast<Layout::InitialContainingBlockBox>(tree_builder.build(*this));
+ }
+
+ Layout::BlockFormattingContext root_formatting_context(*m_layout_root, nullptr);
+ root_formatting_context.run(*m_layout_root, Layout::LayoutMode::Default);
+
+ m_layout_root->set_needs_display();
+
+ if (frame()->is_main_frame()) {
+ if (auto* page = this->page())
+ page->client().page_did_layout();
+ }
+}
+
+static void update_style_recursively(DOM::Node& node)
+{
+ node.for_each_child([&](auto& child) {
+ if (child.needs_style_update()) {
+ if (is<Element>(child))
+ downcast<Element>(child).recompute_style();
+ child.set_needs_style_update(false);
+ }
+ if (child.child_needs_style_update()) {
+ update_style_recursively(child);
+ child.set_child_needs_style_update(false);
+ }
+ return IterationDecision::Continue;
+ });
+}
+
+void Document::update_style()
+{
+ update_style_recursively(*this);
+ update_layout();
+}
+
+RefPtr<Layout::Node> Document::create_layout_node()
+{
+ return adopt(*new Layout::InitialContainingBlockBox(*this, CSS::StyleProperties::create()));
+}
+
+void Document::set_link_color(Color color)
+{
+ m_link_color = color;
+}
+
+void Document::set_active_link_color(Color color)
+{
+ m_active_link_color = color;
+}
+
+void Document::set_visited_link_color(Color color)
+{
+ m_visited_link_color = color;
+}
+
+const Layout::InitialContainingBlockBox* Document::layout_node() const
+{
+ return static_cast<const Layout::InitialContainingBlockBox*>(Node::layout_node());
+}
+
+Layout::InitialContainingBlockBox* Document::layout_node()
+{
+ return static_cast<Layout::InitialContainingBlockBox*>(Node::layout_node());
+}
+
+void Document::set_inspected_node(Node* node)
+{
+ if (m_inspected_node == node)
+ return;
+
+ if (m_inspected_node && m_inspected_node->layout_node())
+ m_inspected_node->layout_node()->set_needs_display();
+
+ m_inspected_node = node;
+
+ if (m_inspected_node && m_inspected_node->layout_node())
+ m_inspected_node->layout_node()->set_needs_display();
+}
+
+void Document::set_hovered_node(Node* node)
+{
+ if (m_hovered_node == node)
+ return;
+
+ RefPtr<Node> old_hovered_node = move(m_hovered_node);
+ m_hovered_node = node;
+
+ invalidate_style();
+}
+
+NonnullRefPtrVector<Element> Document::get_elements_by_name(const String& name) const
+{
+ NonnullRefPtrVector<Element> elements;
+ for_each_in_subtree_of_type<Element>([&](auto& element) {
+ if (element.attribute(HTML::AttributeNames::name) == name)
+ elements.append(element);
+ return IterationDecision::Continue;
+ });
+ return elements;
+}
+
+NonnullRefPtrVector<Element> Document::get_elements_by_tag_name(const FlyString& tag_name) const
+{
+ NonnullRefPtrVector<Element> elements;
+ for_each_in_subtree_of_type<Element>([&](auto& element) {
+ if (element.local_name() == tag_name)
+ elements.append(element);
+ return IterationDecision::Continue;
+ });
+ return elements;
+}
+
+NonnullRefPtrVector<Element> Document::get_elements_by_class_name(const FlyString& class_name) const
+{
+ NonnullRefPtrVector<Element> elements;
+ for_each_in_subtree_of_type<Element>([&](auto& element) {
+ if (element.has_class(class_name))
+ elements.append(element);
+ return IterationDecision::Continue;
+ });
+ return elements;
+}
+
+Color Document::link_color() const
+{
+ if (m_link_color.has_value())
+ return m_link_color.value();
+ if (!page())
+ return Color::Blue;
+ return page()->palette().link();
+}
+
+Color Document::active_link_color() const
+{
+ if (m_active_link_color.has_value())
+ return m_active_link_color.value();
+ if (!page())
+ return Color::Red;
+ return page()->palette().active_link();
+}
+
+Color Document::visited_link_color() const
+{
+ if (m_visited_link_color.has_value())
+ return m_visited_link_color.value();
+ if (!page())
+ return Color::Magenta;
+ return page()->palette().visited_link();
+}
+
+static JS::VM& main_thread_vm()
+{
+ static RefPtr<JS::VM> vm;
+ if (!vm) {
+ vm = JS::VM::create();
+ vm->set_should_log_exceptions(true);
+ }
+ return *vm;
+}
+
+JS::Interpreter& Document::interpreter()
+{
+ if (!m_interpreter)
+ m_interpreter = JS::Interpreter::create<Bindings::WindowObject>(main_thread_vm(), *m_window);
+ return *m_interpreter;
+}
+
+JS::Value Document::run_javascript(const StringView& source)
+{
+ auto parser = JS::Parser(JS::Lexer(source));
+ auto program = parser.parse_program();
+ if (parser.has_errors()) {
+ parser.print_errors();
+ return JS::js_undefined();
+ }
+ auto& interpreter = document().interpreter();
+ auto result = interpreter.run(interpreter.global_object(), *program);
+ if (interpreter.exception())
+ interpreter.vm().clear_exception();
+ return result;
+}
+
+NonnullRefPtr<Element> Document::create_element(const String& tag_name)
+{
+ // FIXME: Let namespace be the HTML namespace, if this is an HTML document or this’s content type is "application/xhtml+xml", and null otherwise.
+ return DOM::create_element(*this, tag_name, Namespace::HTML);
+}
+
+NonnullRefPtr<DocumentFragment> Document::create_document_fragment()
+{
+ return adopt(*new DocumentFragment(*this));
+}
+
+NonnullRefPtr<Text> Document::create_text_node(const String& data)
+{
+ return adopt(*new Text(*this, data));
+}
+
+NonnullRefPtr<Comment> Document::create_comment(const String& data)
+{
+ return adopt(*new Comment(*this, data));
+}
+
+void Document::set_pending_parsing_blocking_script(Badge<HTML::HTMLScriptElement>, HTML::HTMLScriptElement* script)
+{
+ m_pending_parsing_blocking_script = script;
+}
+
+NonnullRefPtr<HTML::HTMLScriptElement> Document::take_pending_parsing_blocking_script(Badge<HTML::HTMLDocumentParser>)
+{
+ return m_pending_parsing_blocking_script.release_nonnull();
+}
+
+void Document::add_script_to_execute_when_parsing_has_finished(Badge<HTML::HTMLScriptElement>, HTML::HTMLScriptElement& script)
+{
+ m_scripts_to_execute_when_parsing_has_finished.append(script);
+}
+
+NonnullRefPtrVector<HTML::HTMLScriptElement> Document::take_scripts_to_execute_when_parsing_has_finished(Badge<HTML::HTMLDocumentParser>)
+{
+ return move(m_scripts_to_execute_when_parsing_has_finished);
+}
+
+void Document::add_script_to_execute_as_soon_as_possible(Badge<HTML::HTMLScriptElement>, HTML::HTMLScriptElement& script)
+{
+ m_scripts_to_execute_as_soon_as_possible.append(script);
+}
+
+NonnullRefPtrVector<HTML::HTMLScriptElement> Document::take_scripts_to_execute_as_soon_as_possible(Badge<HTML::HTMLDocumentParser>)
+{
+ return move(m_scripts_to_execute_as_soon_as_possible);
+}
+
+void Document::adopt_node(Node& subtree_root)
+{
+ subtree_root.for_each_in_subtree([&](auto& node) {
+ node.set_document({}, *this);
+ return IterationDecision::Continue;
+ });
+}
+
+const DocumentType* Document::doctype() const
+{
+ return first_child_of_type<DocumentType>();
+}
+
+const String& Document::compat_mode() const
+{
+ static String back_compat = "BackCompat";
+ static String css1_compat = "CSS1Compat";
+
+ if (m_quirks_mode == QuirksMode::Yes)
+ return back_compat;
+
+ return css1_compat;
+}
+
+bool Document::is_editable() const
+{
+ return m_editable;
+}
+
+void Document::set_focused_element(Element* element)
+{
+ if (m_focused_element == element)
+ return;
+
+ m_focused_element = element;
+
+ if (m_layout_root)
+ m_layout_root->set_needs_display();
+}
+
+void Document::set_ready_state(const String& ready_state)
+{
+ m_ready_state = ready_state;
+ dispatch_event(Event::create(HTML::EventNames::readystatechange));
+}
+
+Page* Document::page()
+{
+ return m_frame ? m_frame->page() : nullptr;
+}
+
+const Page* Document::page() const
+{
+ return m_frame ? m_frame->page() : nullptr;
+}
+
+EventTarget* Document::get_parent(const Event& event)
+{
+ if (event.type() == HTML::EventNames::load)
+ return nullptr;
+
+ return &window();
+}
+
+void Document::completely_finish_loading()
+{
+ // FIXME: This needs to handle iframes.
+ dispatch_event(DOM::Event::create(HTML::EventNames::load));
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h
new file mode 100644
index 0000000000..a46413758d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Document.h
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <AK/Function.h>
+#include <AK/NonnullRefPtrVector.h>
+#include <AK/OwnPtr.h>
+#include <AK/String.h>
+#include <AK/URL.h>
+#include <AK/WeakPtr.h>
+#include <LibCore/Forward.h>
+#include <LibJS/Forward.h>
+#include <LibWeb/Bindings/ScriptExecutionContext.h>
+#include <LibWeb/CSS/StyleResolver.h>
+#include <LibWeb/CSS/StyleSheet.h>
+#include <LibWeb/CSS/StyleSheetList.h>
+#include <LibWeb/DOM/DOMImplementation.h>
+#include <LibWeb/DOM/NonElementParentNode.h>
+#include <LibWeb/DOM/ParentNode.h>
+
+namespace Web::DOM {
+
+enum class QuirksMode {
+ No,
+ Limited,
+ Yes
+};
+
+class Document
+ : public ParentNode
+ , public NonElementParentNode<Document>
+ , public Bindings::ScriptExecutionContext {
+public:
+ using WrapperType = Bindings::DocumentWrapper;
+
+ static NonnullRefPtr<Document> create(const URL& url = "about:blank") { return adopt(*new Document(url)); }
+ virtual ~Document() override;
+
+ bool should_invalidate_styles_on_attribute_changes() const { return m_should_invalidate_styles_on_attribute_changes; }
+ void set_should_invalidate_styles_on_attribute_changes(bool b) { m_should_invalidate_styles_on_attribute_changes = b; }
+
+ void set_url(const URL& url) { m_url = url; }
+ URL url() const { return m_url; }
+
+ Origin origin() const;
+ void set_origin(const Origin& origin);
+
+ bool is_scripting_enabled() const { return true; }
+
+ URL complete_url(const String&) const;
+
+ CSS::StyleResolver& style_resolver() { return *m_style_resolver; }
+ const CSS::StyleResolver& style_resolver() const { return *m_style_resolver; }
+
+ CSS::StyleSheetList& style_sheets() { return *m_style_sheets; }
+ const CSS::StyleSheetList& style_sheets() const { return *m_style_sheets; }
+
+ virtual FlyString node_name() const override { return "#document"; }
+
+ void set_hovered_node(Node*);
+ Node* hovered_node() { return m_hovered_node; }
+ const Node* hovered_node() const { return m_hovered_node; }
+
+ void set_inspected_node(Node*);
+ Node* inspected_node() { return m_inspected_node; }
+ const Node* inspected_node() const { return m_inspected_node; }
+
+ const Element* document_element() const;
+ const HTML::HTMLHtmlElement* html_element() const;
+ const HTML::HTMLHeadElement* head() const;
+ const HTML::HTMLElement* body() const;
+ void set_body(HTML::HTMLElement& new_body);
+
+ String title() const;
+ void set_title(const String&);
+
+ void attach_to_frame(Badge<Frame>, Frame&);
+ void detach_from_frame(Badge<Frame>, Frame&);
+
+ Frame* frame() { return m_frame.ptr(); }
+ const Frame* frame() const { return m_frame.ptr(); }
+
+ Page* page();
+ const Page* page() const;
+
+ Color background_color(const Gfx::Palette&) const;
+ RefPtr<Gfx::Bitmap> background_image() const;
+
+ Color link_color() const;
+ void set_link_color(Color);
+
+ Color active_link_color() const;
+ void set_active_link_color(Color);
+
+ Color visited_link_color() const;
+ void set_visited_link_color(Color);
+
+ void force_layout();
+ void invalidate_layout();
+
+ void update_style();
+ void update_layout();
+
+ virtual bool is_child_allowed(const Node&) const override;
+
+ const Layout::InitialContainingBlockBox* layout_node() const;
+ Layout::InitialContainingBlockBox* layout_node();
+
+ void schedule_style_update();
+ void schedule_forced_layout();
+
+ NonnullRefPtrVector<Element> get_elements_by_name(const String&) const;
+ NonnullRefPtrVector<Element> get_elements_by_tag_name(const FlyString&) const;
+ NonnullRefPtrVector<Element> get_elements_by_class_name(const FlyString&) const;
+
+ const String& source() const { return m_source; }
+ void set_source(const String& source) { m_source = source; }
+
+ virtual JS::Interpreter& interpreter() override;
+
+ JS::Value run_javascript(const StringView&);
+
+ NonnullRefPtr<Element> create_element(const String& tag_name);
+ NonnullRefPtr<DocumentFragment> create_document_fragment();
+ NonnullRefPtr<Text> create_text_node(const String& data);
+ NonnullRefPtr<Comment> create_comment(const String& data);
+
+ void set_pending_parsing_blocking_script(Badge<HTML::HTMLScriptElement>, HTML::HTMLScriptElement*);
+ HTML::HTMLScriptElement* pending_parsing_blocking_script() { return m_pending_parsing_blocking_script; }
+ NonnullRefPtr<HTML::HTMLScriptElement> take_pending_parsing_blocking_script(Badge<HTML::HTMLDocumentParser>);
+
+ void add_script_to_execute_when_parsing_has_finished(Badge<HTML::HTMLScriptElement>, HTML::HTMLScriptElement&);
+ NonnullRefPtrVector<HTML::HTMLScriptElement> take_scripts_to_execute_when_parsing_has_finished(Badge<HTML::HTMLDocumentParser>);
+
+ void add_script_to_execute_as_soon_as_possible(Badge<HTML::HTMLScriptElement>, HTML::HTMLScriptElement&);
+ NonnullRefPtrVector<HTML::HTMLScriptElement> take_scripts_to_execute_as_soon_as_possible(Badge<HTML::HTMLDocumentParser>);
+
+ QuirksMode mode() const { return m_quirks_mode; }
+ bool in_quirks_mode() const { return m_quirks_mode == QuirksMode::Yes; }
+ void set_quirks_mode(QuirksMode mode) { m_quirks_mode = mode; }
+
+ void adopt_node(Node&);
+
+ const DocumentType* doctype() const;
+ const String& compat_mode() const;
+
+ void set_editable(bool editable) { m_editable = editable; }
+ virtual bool is_editable() const final;
+
+ Element* focused_element() { return m_focused_element; }
+ const Element* focused_element() const { return m_focused_element; }
+
+ void set_focused_element(Element*);
+
+ bool created_for_appropriate_template_contents() const { return m_created_for_appropriate_template_contents; }
+ void set_created_for_appropriate_template_contents(bool value) { m_created_for_appropriate_template_contents = value; }
+
+ Document* associated_inert_template_document() { return m_associated_inert_template_document; }
+ const Document* associated_inert_template_document() const { return m_associated_inert_template_document; }
+ void set_associated_inert_template_document(Document& document) { m_associated_inert_template_document = document; }
+
+ const String& ready_state() const { return m_ready_state; }
+ void set_ready_state(const String&);
+
+ void ref_from_node(Badge<Node>)
+ {
+ increment_referencing_node_count();
+ }
+
+ void unref_from_node(Badge<Node>)
+ {
+ decrement_referencing_node_count();
+ }
+
+ void removed_last_ref();
+
+ Window& window() { return *m_window; }
+
+ const String& content_type() const { return m_content_type; }
+ void set_content_type(const String& content_type) { m_content_type = content_type; }
+
+ const String& encoding() const { return m_encoding; }
+ void set_encoding(const String& encoding) { m_encoding = encoding; }
+
+ // NOTE: These are intended for the JS bindings
+ const String& character_set() const { return encoding(); }
+ const String& charset() const { return encoding(); }
+ const String& input_encoding() const { return encoding(); }
+
+ bool ready_for_post_load_tasks() const { return m_ready_for_post_load_tasks; }
+ void set_ready_for_post_load_tasks(bool ready) { m_ready_for_post_load_tasks = ready; }
+
+ void completely_finish_loading();
+
+ const NonnullRefPtr<DOMImplementation> implementation() const { return m_implementation; }
+
+ virtual EventTarget* get_parent(const Event&) override;
+
+private:
+ explicit Document(const URL&);
+
+ virtual RefPtr<Layout::Node> create_layout_node() override;
+
+ void tear_down_layout_tree();
+
+ void increment_referencing_node_count()
+ {
+ ASSERT(!m_deletion_has_begun);
+ ++m_referencing_node_count;
+ }
+
+ void decrement_referencing_node_count()
+ {
+ ASSERT(!m_deletion_has_begun);
+ ASSERT(m_referencing_node_count);
+ --m_referencing_node_count;
+ if (!m_referencing_node_count && !ref_count()) {
+ m_deletion_has_begun = true;
+ delete this;
+ }
+ }
+
+ unsigned m_referencing_node_count { 0 };
+
+ OwnPtr<CSS::StyleResolver> m_style_resolver;
+ RefPtr<CSS::StyleSheetList> m_style_sheets;
+ RefPtr<Node> m_hovered_node;
+ RefPtr<Node> m_inspected_node;
+ WeakPtr<Frame> m_frame;
+ URL m_url;
+
+ RefPtr<Window> m_window;
+
+ RefPtr<Layout::InitialContainingBlockBox> m_layout_root;
+
+ Optional<Color> m_link_color;
+ Optional<Color> m_active_link_color;
+ Optional<Color> m_visited_link_color;
+
+ RefPtr<Core::Timer> m_style_update_timer;
+ RefPtr<Core::Timer> m_forced_layout_timer;
+
+ String m_source;
+
+ OwnPtr<JS::Interpreter> m_interpreter;
+
+ RefPtr<HTML::HTMLScriptElement> m_pending_parsing_blocking_script;
+ NonnullRefPtrVector<HTML::HTMLScriptElement> m_scripts_to_execute_when_parsing_has_finished;
+ NonnullRefPtrVector<HTML::HTMLScriptElement> m_scripts_to_execute_as_soon_as_possible;
+
+ QuirksMode m_quirks_mode { QuirksMode::No };
+ bool m_editable { false };
+
+ WeakPtr<Element> m_focused_element;
+
+ bool m_created_for_appropriate_template_contents { false };
+ RefPtr<Document> m_associated_inert_template_document;
+
+ String m_ready_state { "loading" };
+ String m_content_type { "application/xml" };
+ String m_encoding { "UTF-8" };
+
+ bool m_ready_for_post_load_tasks { false };
+
+ NonnullRefPtr<DOMImplementation> m_implementation;
+
+ bool m_should_invalidate_styles_on_attribute_changes { true };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Document.idl b/Userland/Libraries/LibWeb/DOM/Document.idl
new file mode 100644
index 0000000000..3b34314e8d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Document.idl
@@ -0,0 +1,37 @@
+interface Document : Node {
+
+ readonly attribute DOMImplementation implementation;
+
+ readonly attribute DOMString characterSet;
+ readonly attribute DOMString charset;
+ readonly attribute DOMString inputEncoding;
+ readonly attribute DOMString contentType;
+
+ Element? getElementById(DOMString id);
+ ArrayFromVector getElementsByName(DOMString name);
+ ArrayFromVector getElementsByTagName(DOMString tagName);
+ ArrayFromVector getElementsByClassName(DOMString className);
+
+ readonly attribute Element? firstElementChild;
+ readonly attribute Element? lastElementChild;
+
+ Element? querySelector(DOMString selectors);
+ ArrayFromVector querySelectorAll(DOMString selectors);
+
+ Element createElement(DOMString tagName);
+ DocumentFragment createDocumentFragment();
+ Text createTextNode(DOMString data);
+ Comment createComment(DOMString data);
+
+ readonly attribute DOMString compatMode;
+ readonly attribute DocumentType? doctype;
+
+ readonly attribute Element? documentElement;
+ attribute HTMLElement? body;
+ readonly attribute HTMLHeadElement? head;
+
+ readonly attribute DOMString readyState;
+
+ attribute DOMString title;
+
+};
diff --git a/Userland/Libraries/LibWeb/DOM/DocumentFragment.cpp b/Userland/Libraries/LibWeb/DOM/DocumentFragment.cpp
new file mode 100644
index 0000000000..92400feb70
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/DocumentFragment.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/DocumentFragment.h>
+
+namespace Web::DOM {
+
+DocumentFragment::DocumentFragment(Document& document)
+ : ParentNode(document, NodeType::DOCUMENT_FRAGMENT_NODE)
+{
+}
+
+DocumentFragment::~DocumentFragment()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/DocumentFragment.h b/Userland/Libraries/LibWeb/DOM/DocumentFragment.h
new file mode 100644
index 0000000000..b5dbc81699
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/DocumentFragment.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/DOM/NonElementParentNode.h>
+#include <LibWeb/DOM/ParentNode.h>
+
+namespace Web::DOM {
+
+class DocumentFragment
+ : public ParentNode
+ , public NonElementParentNode<DocumentFragment> {
+public:
+ using WrapperType = Bindings::DocumentFragmentWrapper;
+
+ explicit DocumentFragment(Document& document);
+ virtual ~DocumentFragment() override;
+
+ virtual FlyString node_name() const override { return "#document-fragment"; }
+
+ RefPtr<Element> host() { return m_host; }
+ const RefPtr<Element> host() const { return m_host; }
+
+ void set_host(Element& host) { m_host = host; }
+
+private:
+ RefPtr<Element> m_host;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/DocumentFragment.idl b/Userland/Libraries/LibWeb/DOM/DocumentFragment.idl
new file mode 100644
index 0000000000..fc18e968be
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/DocumentFragment.idl
@@ -0,0 +1,11 @@
+interface DocumentFragment : Node {
+
+ Element? getElementById(DOMString id);
+
+ readonly attribute Element? firstElementChild;
+ readonly attribute Element? lastElementChild;
+
+ Element? querySelector(DOMString selectors);
+ ArrayFromVector querySelectorAll(DOMString selectors);
+
+};
diff --git a/Userland/Libraries/LibWeb/DOM/DocumentType.cpp b/Userland/Libraries/LibWeb/DOM/DocumentType.cpp
new file mode 100644
index 0000000000..da0f176efc
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/DocumentType.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/DocumentType.h>
+
+namespace Web::DOM {
+
+DocumentType::DocumentType(Document& document)
+ : Node(document, NodeType::DOCUMENT_TYPE_NODE)
+{
+}
+
+DocumentType::~DocumentType()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/DocumentType.h b/Userland/Libraries/LibWeb/DOM/DocumentType.h
new file mode 100644
index 0000000000..ffc4d82150
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/DocumentType.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <LibWeb/DOM/Node.h>
+
+namespace Web::DOM {
+
+class DocumentType final : public Node {
+public:
+ using WrapperType = Bindings::DocumentTypeWrapper;
+
+ explicit DocumentType(Document&);
+ virtual ~DocumentType() override;
+
+ virtual FlyString node_name() const override { return "#doctype"; }
+
+ const String& name() const { return m_name; }
+ void set_name(const String& name) { m_name = name; }
+
+ const String& public_id() const { return m_public_id; }
+ void set_public_id(const String& public_id) { m_public_id = public_id; }
+
+ const String& system_id() const { return m_system_id; }
+ void set_system_id(const String& system_id) { m_system_id = system_id; }
+
+private:
+ String m_name;
+ String m_public_id;
+ String m_system_id;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/DocumentType.idl b/Userland/Libraries/LibWeb/DOM/DocumentType.idl
new file mode 100644
index 0000000000..813b0e5b47
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/DocumentType.idl
@@ -0,0 +1,7 @@
+interface DocumentType : Node {
+
+ readonly attribute DOMString name;
+ readonly attribute DOMString publicId;
+ readonly attribute DOMString systemId;
+
+};
diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp
new file mode 100644
index 0000000000..094d877475
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Element.cpp
@@ -0,0 +1,335 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibWeb/CSS/Length.h>
+#include <LibWeb/CSS/Parser/CSSParser.h>
+#include <LibWeb/CSS/PropertyID.h>
+#include <LibWeb/CSS/StyleInvalidator.h>
+#include <LibWeb/CSS/StyleResolver.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/DocumentFragment.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/Dump.h>
+#include <LibWeb/HTML/Parser/HTMLDocumentParser.h>
+#include <LibWeb/Layout/BlockBox.h>
+#include <LibWeb/Layout/InlineNode.h>
+#include <LibWeb/Layout/ListItemBox.h>
+#include <LibWeb/Layout/TableBox.h>
+#include <LibWeb/Layout/TableCellBox.h>
+#include <LibWeb/Layout/TableRowBox.h>
+#include <LibWeb/Layout/TableRowGroupBox.h>
+#include <LibWeb/Layout/TreeBuilder.h>
+#include <LibWeb/Layout/WidgetBox.h>
+
+namespace Web::DOM {
+
+Element::Element(Document& document, const QualifiedName& qualified_name)
+ : ParentNode(document, NodeType::ELEMENT_NODE)
+ , m_qualified_name(qualified_name)
+{
+}
+
+Element::~Element()
+{
+}
+
+Attribute* Element::find_attribute(const FlyString& name)
+{
+ for (auto& attribute : m_attributes) {
+ if (attribute.name() == name)
+ return &attribute;
+ }
+ return nullptr;
+}
+
+const Attribute* Element::find_attribute(const FlyString& name) const
+{
+ for (auto& attribute : m_attributes) {
+ if (attribute.name() == name)
+ return &attribute;
+ }
+ return nullptr;
+}
+
+String Element::attribute(const FlyString& name) const
+{
+ if (auto* attribute = find_attribute(name))
+ return attribute->value();
+ return {};
+}
+
+void Element::set_attribute(const FlyString& name, const String& value)
+{
+ CSS::StyleInvalidator style_invalidator(document());
+
+ if (auto* attribute = find_attribute(name))
+ attribute->set_value(value);
+ else
+ m_attributes.empend(name, value);
+
+ parse_attribute(name, value);
+}
+
+void Element::remove_attribute(const FlyString& name)
+{
+ CSS::StyleInvalidator style_invalidator(document());
+
+ m_attributes.remove_first_matching([&](auto& attribute) { return attribute.name() == name; });
+}
+
+bool Element::has_class(const FlyString& class_name) const
+{
+ for (auto& class_ : m_classes) {
+ if (class_ == class_name)
+ return true;
+ }
+ return false;
+}
+
+RefPtr<Layout::Node> Element::create_layout_node()
+{
+ auto style = document().style_resolver().resolve_style(*this);
+ const_cast<Element&>(*this).m_specified_css_values = style;
+ auto display = style->display();
+
+ if (display == CSS::Display::None)
+ return nullptr;
+
+ if (local_name() == "noscript" && document().is_scripting_enabled())
+ return nullptr;
+
+ if (display == CSS::Display::Block)
+ return adopt(*new Layout::BlockBox(document(), this, move(style)));
+
+ if (display == CSS::Display::Inline) {
+ if (style->float_().value_or(CSS::Float::None) != CSS::Float::None)
+ return adopt(*new Layout::BlockBox(document(), this, move(style)));
+ return adopt(*new Layout::InlineNode(document(), *this, move(style)));
+ }
+
+ if (display == CSS::Display::ListItem)
+ return adopt(*new Layout::ListItemBox(document(), *this, move(style)));
+ if (display == CSS::Display::Table)
+ return adopt(*new Layout::TableBox(document(), this, move(style)));
+ if (display == CSS::Display::TableRow)
+ return adopt(*new Layout::TableRowBox(document(), this, move(style)));
+ if (display == CSS::Display::TableCell)
+ return adopt(*new Layout::TableCellBox(document(), this, move(style)));
+ if (display == CSS::Display::TableRowGroup || display == CSS::Display::TableHeaderGroup || display == CSS::Display::TableFooterGroup)
+ return adopt(*new Layout::TableRowGroupBox(document(), *this, move(style)));
+ if (display == CSS::Display::InlineBlock) {
+ auto inline_block = adopt(*new Layout::BlockBox(document(), this, move(style)));
+ inline_block->set_inline(true);
+ return inline_block;
+ }
+ ASSERT_NOT_REACHED();
+}
+
+void Element::parse_attribute(const FlyString& name, const String& value)
+{
+ if (name == HTML::AttributeNames::class_) {
+ auto new_classes = value.split_view(' ');
+ m_classes.clear();
+ m_classes.ensure_capacity(new_classes.size());
+ for (auto& new_class : new_classes) {
+ m_classes.unchecked_append(new_class);
+ }
+ } else if (name == HTML::AttributeNames::style) {
+ m_inline_style = parse_css_declaration(CSS::ParsingContext(document()), value);
+ set_needs_style_update(true);
+ }
+}
+
+enum class StyleDifference {
+ None,
+ NeedsRepaint,
+ NeedsRelayout,
+};
+
+static StyleDifference compute_style_difference(const CSS::StyleProperties& old_style, const CSS::StyleProperties& new_style, const Document& document)
+{
+ if (old_style == new_style)
+ return StyleDifference::None;
+
+ bool needs_repaint = false;
+ bool needs_relayout = false;
+
+ if (new_style.display() != old_style.display())
+ needs_relayout = true;
+
+ if (new_style.color_or_fallback(CSS::PropertyID::Color, document, Color::Black) != old_style.color_or_fallback(CSS::PropertyID::Color, document, Color::Black))
+ needs_repaint = true;
+ else if (new_style.color_or_fallback(CSS::PropertyID::BackgroundColor, document, Color::Black) != old_style.color_or_fallback(CSS::PropertyID::BackgroundColor, document, Color::Black))
+ needs_repaint = true;
+
+ if (needs_relayout)
+ return StyleDifference::NeedsRelayout;
+ if (needs_repaint)
+ return StyleDifference::NeedsRepaint;
+ return StyleDifference::None;
+}
+
+void Element::recompute_style()
+{
+ set_needs_style_update(false);
+ ASSERT(parent());
+ auto old_specified_css_values = m_specified_css_values;
+ auto new_specified_css_values = document().style_resolver().resolve_style(*this);
+ m_specified_css_values = new_specified_css_values;
+ if (!layout_node()) {
+ if (new_specified_css_values->display() == CSS::Display::None)
+ return;
+ // We need a new layout tree here!
+ Layout::TreeBuilder tree_builder;
+ tree_builder.build(*this);
+ return;
+ }
+
+ // Don't bother with style on widgets. NATIVE LOOK & FEEL BABY!
+ if (is<Layout::WidgetBox>(layout_node()))
+ return;
+
+ auto diff = StyleDifference::NeedsRelayout;
+ if (old_specified_css_values)
+ diff = compute_style_difference(*old_specified_css_values, *new_specified_css_values, document());
+ if (diff == StyleDifference::None)
+ return;
+ layout_node()->apply_style(*new_specified_css_values);
+ if (diff == StyleDifference::NeedsRelayout) {
+ document().schedule_forced_layout();
+ return;
+ }
+ if (diff == StyleDifference::NeedsRepaint) {
+ layout_node()->set_needs_display();
+ }
+}
+
+NonnullRefPtr<CSS::StyleProperties> Element::computed_style()
+{
+ // FIXME: This implementation is not doing anything it's supposed to.
+ auto properties = m_specified_css_values->clone();
+ if (layout_node() && layout_node()->has_style()) {
+ CSS::PropertyID box_model_metrics[] = {
+ CSS::PropertyID::MarginTop,
+ CSS::PropertyID::MarginBottom,
+ CSS::PropertyID::MarginLeft,
+ CSS::PropertyID::MarginRight,
+ CSS::PropertyID::PaddingTop,
+ CSS::PropertyID::PaddingBottom,
+ CSS::PropertyID::PaddingLeft,
+ CSS::PropertyID::PaddingRight,
+ CSS::PropertyID::BorderTopWidth,
+ CSS::PropertyID::BorderBottomWidth,
+ CSS::PropertyID::BorderLeftWidth,
+ CSS::PropertyID::BorderRightWidth,
+ };
+ for (CSS::PropertyID id : box_model_metrics) {
+ auto prop = m_specified_css_values->property(id);
+ if (prop.has_value())
+ properties->set_property(id, prop.value());
+ }
+ }
+ return properties;
+}
+
+void Element::set_inner_html(StringView markup)
+{
+ auto new_children = HTML::HTMLDocumentParser::parse_html_fragment(*this, markup);
+ remove_all_children();
+ while (!new_children.is_empty()) {
+ append_child(new_children.take_first());
+ }
+
+ set_needs_style_update(true);
+ document().invalidate_layout();
+}
+
+String Element::inner_html() const
+{
+ auto escape_string = [](const StringView& string, bool attribute_mode) -> String {
+ // https://html.spec.whatwg.org/multipage/parsing.html#escapingString
+ StringBuilder builder;
+ for (auto& ch : string) {
+ if (ch == '&')
+ builder.append("&amp;");
+ // FIXME: also replace U+00A0 NO-BREAK SPACE with &nbsp;
+ else if (ch == '"' && attribute_mode)
+ builder.append("&quot;");
+ else if (ch == '<' && !attribute_mode)
+ builder.append("&lt;");
+ else if (ch == '>' && !attribute_mode)
+ builder.append("&gt;");
+ else
+ builder.append(ch);
+ }
+ return builder.to_string();
+ };
+
+ StringBuilder builder;
+
+ Function<void(const Node&)> recurse = [&](auto& node) {
+ for (auto* child = node.first_child(); child; child = child->next_sibling()) {
+ if (child->is_element()) {
+ auto& element = downcast<Element>(*child);
+ builder.append('<');
+ builder.append(element.local_name());
+ element.for_each_attribute([&](auto& name, auto& value) {
+ builder.append(' ');
+ builder.append(name);
+ builder.append('=');
+ builder.append('"');
+ builder.append(escape_string(value, true));
+ builder.append('"');
+ });
+ builder.append('>');
+
+ recurse(*child);
+
+ // FIXME: This should be skipped for void elements
+ builder.append("</");
+ builder.append(element.local_name());
+ builder.append('>');
+ }
+ if (child->is_text()) {
+ auto& text = downcast<Text>(*child);
+ builder.append(escape_string(text.data(), false));
+ }
+ // FIXME: Also handle Comment, ProcessingInstruction, DocumentType
+ }
+ };
+ recurse(*this);
+
+ return builder.to_string();
+}
+
+bool Element::is_focused() const
+{
+ return document().focused_element() == this;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h
new file mode 100644
index 0000000000..ddefb2ab93
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Element.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <AK/String.h>
+#include <LibWeb/DOM/Attribute.h>
+#include <LibWeb/DOM/NonDocumentTypeChildNode.h>
+#include <LibWeb/DOM/ParentNode.h>
+#include <LibWeb/HTML/AttributeNames.h>
+#include <LibWeb/HTML/TagNames.h>
+#include <LibWeb/Layout/Node.h>
+#include <LibWeb/QualifiedName.h>
+
+namespace Web::DOM {
+
+class Element
+ : public ParentNode
+ , public NonDocumentTypeChildNode<Element> {
+
+public:
+ using WrapperType = Bindings::ElementWrapper;
+
+ Element(Document&, const QualifiedName& qualified_name);
+ virtual ~Element() override;
+
+ virtual FlyString node_name() const final { return m_qualified_name.local_name(); }
+ const FlyString& local_name() const { return m_qualified_name.local_name(); }
+
+ // NOTE: This is for the JS bindings
+ const FlyString& tag_name() const { return local_name(); }
+
+ const FlyString& namespace_() const { return m_qualified_name.namespace_(); }
+
+ // NOTE: This is for the JS bindings
+ const FlyString& namespace_uri() const { return namespace_(); }
+
+ bool has_attribute(const FlyString& name) const { return !attribute(name).is_null(); }
+ bool has_attributes() const { return !m_attributes.is_empty(); }
+ String attribute(const FlyString& name) const;
+ String get_attribute(const FlyString& name) const { return attribute(name); }
+ void set_attribute(const FlyString& name, const String& value);
+ void remove_attribute(const FlyString& name);
+
+ template<typename Callback>
+ void for_each_attribute(Callback callback) const
+ {
+ for (auto& attribute : m_attributes)
+ callback(attribute.name(), attribute.value());
+ }
+
+ bool has_class(const FlyString&) const;
+ const Vector<FlyString>& class_names() const { return m_classes; }
+
+ virtual void apply_presentational_hints(CSS::StyleProperties&) const { }
+ virtual void parse_attribute(const FlyString& name, const String& value);
+
+ void recompute_style();
+
+ Layout::NodeWithStyle* layout_node() { return static_cast<Layout::NodeWithStyle*>(Node::layout_node()); }
+ const Layout::NodeWithStyle* layout_node() const { return static_cast<const Layout::NodeWithStyle*>(Node::layout_node()); }
+
+ String name() const { return attribute(HTML::AttributeNames::name); }
+
+ const CSS::StyleProperties* specified_css_values() const { return m_specified_css_values.ptr(); }
+ NonnullRefPtr<CSS::StyleProperties> computed_style();
+
+ const CSS::StyleDeclaration* inline_style() const { return m_inline_style; }
+
+ // FIXME: innerHTML also appears on shadow roots. https://w3c.github.io/DOM-Parsing/#dom-innerhtml
+ String inner_html() const;
+ void set_inner_html(StringView);
+
+ bool is_focused() const;
+ virtual bool is_focusable() const { return false; }
+
+protected:
+ RefPtr<Layout::Node> create_layout_node() override;
+
+private:
+ Attribute* find_attribute(const FlyString& name);
+ const Attribute* find_attribute(const FlyString& name) const;
+
+ QualifiedName m_qualified_name;
+ Vector<Attribute> m_attributes;
+
+ RefPtr<CSS::StyleDeclaration> m_inline_style;
+
+ RefPtr<CSS::StyleProperties> m_specified_css_values;
+
+ Vector<FlyString> m_classes;
+};
+
+}
+
+namespace AK {
+template<>
+inline bool is<Web::DOM::Element>(const Web::DOM::Node& input)
+{
+ return input.is_element();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Element.idl b/Userland/Libraries/LibWeb/DOM/Element.idl
new file mode 100644
index 0000000000..e5de36e75b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Element.idl
@@ -0,0 +1,23 @@
+interface Element : Node {
+ readonly attribute DOMString? namespaceURI;
+ readonly attribute DOMString tagName;
+
+ DOMString? getAttribute(DOMString qualifiedName);
+ undefined setAttribute(DOMString qualifiedName, DOMString value);
+ undefined removeAttribute(DOMString qualifiedName);
+ boolean hasAttribute(DOMString qualifiedName);
+ boolean hasAttributes();
+
+ readonly attribute Element? firstElementChild;
+ readonly attribute Element? lastElementChild;
+
+ Element? querySelector(DOMString selectors);
+ ArrayFromVector querySelectorAll(DOMString selectors);
+
+ [LegacyNullToEmptyString] attribute DOMString innerHTML;
+ [Reflect] attribute DOMString id;
+ [Reflect=class] attribute DOMString className;
+
+ readonly attribute Element? nextElementSibling;
+ readonly attribute Element? previousElementSibling;
+};
diff --git a/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp b/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp
new file mode 100644
index 0000000000..810d82efdc
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/ElementFactory.h>
+#include <LibWeb/HTML/HTMLAnchorElement.h>
+#include <LibWeb/HTML/HTMLAreaElement.h>
+#include <LibWeb/HTML/HTMLAudioElement.h>
+#include <LibWeb/HTML/HTMLBRElement.h>
+#include <LibWeb/HTML/HTMLBaseElement.h>
+#include <LibWeb/HTML/HTMLBlinkElement.h>
+#include <LibWeb/HTML/HTMLBodyElement.h>
+#include <LibWeb/HTML/HTMLButtonElement.h>
+#include <LibWeb/HTML/HTMLCanvasElement.h>
+#include <LibWeb/HTML/HTMLDListElement.h>
+#include <LibWeb/HTML/HTMLDataElement.h>
+#include <LibWeb/HTML/HTMLDataListElement.h>
+#include <LibWeb/HTML/HTMLDetailsElement.h>
+#include <LibWeb/HTML/HTMLDialogElement.h>
+#include <LibWeb/HTML/HTMLDirectoryElement.h>
+#include <LibWeb/HTML/HTMLDivElement.h>
+#include <LibWeb/HTML/HTMLEmbedElement.h>
+#include <LibWeb/HTML/HTMLFieldSetElement.h>
+#include <LibWeb/HTML/HTMLFontElement.h>
+#include <LibWeb/HTML/HTMLFormElement.h>
+#include <LibWeb/HTML/HTMLFrameElement.h>
+#include <LibWeb/HTML/HTMLFrameSetElement.h>
+#include <LibWeb/HTML/HTMLHRElement.h>
+#include <LibWeb/HTML/HTMLHeadElement.h>
+#include <LibWeb/HTML/HTMLHeadingElement.h>
+#include <LibWeb/HTML/HTMLHtmlElement.h>
+#include <LibWeb/HTML/HTMLIFrameElement.h>
+#include <LibWeb/HTML/HTMLImageElement.h>
+#include <LibWeb/HTML/HTMLInputElement.h>
+#include <LibWeb/HTML/HTMLLIElement.h>
+#include <LibWeb/HTML/HTMLLabelElement.h>
+#include <LibWeb/HTML/HTMLLegendElement.h>
+#include <LibWeb/HTML/HTMLLinkElement.h>
+#include <LibWeb/HTML/HTMLMapElement.h>
+#include <LibWeb/HTML/HTMLMarqueeElement.h>
+#include <LibWeb/HTML/HTMLMenuElement.h>
+#include <LibWeb/HTML/HTMLMetaElement.h>
+#include <LibWeb/HTML/HTMLMeterElement.h>
+#include <LibWeb/HTML/HTMLModElement.h>
+#include <LibWeb/HTML/HTMLOListElement.h>
+#include <LibWeb/HTML/HTMLObjectElement.h>
+#include <LibWeb/HTML/HTMLOptGroupElement.h>
+#include <LibWeb/HTML/HTMLOptionElement.h>
+#include <LibWeb/HTML/HTMLOutputElement.h>
+#include <LibWeb/HTML/HTMLParagraphElement.h>
+#include <LibWeb/HTML/HTMLParamElement.h>
+#include <LibWeb/HTML/HTMLPictureElement.h>
+#include <LibWeb/HTML/HTMLPreElement.h>
+#include <LibWeb/HTML/HTMLProgressElement.h>
+#include <LibWeb/HTML/HTMLQuoteElement.h>
+#include <LibWeb/HTML/HTMLScriptElement.h>
+#include <LibWeb/HTML/HTMLSelectElement.h>
+#include <LibWeb/HTML/HTMLSlotElement.h>
+#include <LibWeb/HTML/HTMLSourceElement.h>
+#include <LibWeb/HTML/HTMLSpanElement.h>
+#include <LibWeb/HTML/HTMLStyleElement.h>
+#include <LibWeb/HTML/HTMLTableCaptionElement.h>
+#include <LibWeb/HTML/HTMLTableCellElement.h>
+#include <LibWeb/HTML/HTMLTableColElement.h>
+#include <LibWeb/HTML/HTMLTableElement.h>
+#include <LibWeb/HTML/HTMLTableRowElement.h>
+#include <LibWeb/HTML/HTMLTableSectionElement.h>
+#include <LibWeb/HTML/HTMLTemplateElement.h>
+#include <LibWeb/HTML/HTMLTextAreaElement.h>
+#include <LibWeb/HTML/HTMLTimeElement.h>
+#include <LibWeb/HTML/HTMLTitleElement.h>
+#include <LibWeb/HTML/HTMLTrackElement.h>
+#include <LibWeb/HTML/HTMLUListElement.h>
+#include <LibWeb/HTML/HTMLUnknownElement.h>
+#include <LibWeb/HTML/HTMLVideoElement.h>
+#include <LibWeb/SVG/SVGPathElement.h>
+#include <LibWeb/SVG/SVGSVGElement.h>
+#include <LibWeb/SVG/TagNames.h>
+
+namespace Web::DOM {
+
+NonnullRefPtr<Element> create_element(Document& document, const FlyString& tag_name, const FlyString& namespace_)
+{
+ auto lowercase_tag_name = tag_name.to_lowercase();
+ // FIXME: Add prefix when we support it.
+ auto qualified_name = QualifiedName(tag_name, {}, namespace_);
+ if (lowercase_tag_name == HTML::TagNames::a)
+ return adopt(*new HTML::HTMLAnchorElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::area)
+ return adopt(*new HTML::HTMLAreaElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::audio)
+ return adopt(*new HTML::HTMLAudioElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::base)
+ return adopt(*new HTML::HTMLBaseElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::blink)
+ return adopt(*new HTML::HTMLBlinkElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::body)
+ return adopt(*new HTML::HTMLBodyElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::br)
+ return adopt(*new HTML::HTMLBRElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::button)
+ return adopt(*new HTML::HTMLButtonElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::canvas)
+ return adopt(*new HTML::HTMLCanvasElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::data)
+ return adopt(*new HTML::HTMLDataElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::datalist)
+ return adopt(*new HTML::HTMLDataListElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::details)
+ return adopt(*new HTML::HTMLDetailsElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::dialog)
+ return adopt(*new HTML::HTMLDialogElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::dir)
+ return adopt(*new HTML::HTMLDirectoryElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::div)
+ return adopt(*new HTML::HTMLDivElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::dl)
+ return adopt(*new HTML::HTMLDListElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::embed)
+ return adopt(*new HTML::HTMLEmbedElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::fieldset)
+ return adopt(*new HTML::HTMLFieldSetElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::font)
+ return adopt(*new HTML::HTMLFontElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::form)
+ return adopt(*new HTML::HTMLFormElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::frame)
+ return adopt(*new HTML::HTMLFrameElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::frameset)
+ return adopt(*new HTML::HTMLFrameSetElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::head)
+ return adopt(*new HTML::HTMLHeadElement(document, qualified_name));
+ if (lowercase_tag_name.is_one_of(HTML::TagNames::h1, HTML::TagNames::h2, HTML::TagNames::h3, HTML::TagNames::h4, HTML::TagNames::h5, HTML::TagNames::h6))
+ return adopt(*new HTML::HTMLHeadingElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::hr)
+ return adopt(*new HTML::HTMLHRElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::html)
+ return adopt(*new HTML::HTMLHtmlElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::iframe)
+ return adopt(*new HTML::HTMLIFrameElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::img)
+ return adopt(*new HTML::HTMLImageElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::input)
+ return adopt(*new HTML::HTMLInputElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::label)
+ return adopt(*new HTML::HTMLLabelElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::legend)
+ return adopt(*new HTML::HTMLLegendElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::li)
+ return adopt(*new HTML::HTMLLIElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::link)
+ return adopt(*new HTML::HTMLLinkElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::map)
+ return adopt(*new HTML::HTMLMapElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::marquee)
+ return adopt(*new HTML::HTMLMarqueeElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::menu)
+ return adopt(*new HTML::HTMLMenuElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::meta)
+ return adopt(*new HTML::HTMLMetaElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::meter)
+ return adopt(*new HTML::HTMLMeterElement(document, qualified_name));
+ if (lowercase_tag_name.is_one_of(HTML::TagNames::ins, HTML::TagNames::del))
+ return adopt(*new HTML::HTMLModElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::object)
+ return adopt(*new HTML::HTMLObjectElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::ol)
+ return adopt(*new HTML::HTMLOListElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::optgroup)
+ return adopt(*new HTML::HTMLOptGroupElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::option)
+ return adopt(*new HTML::HTMLOptionElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::output)
+ return adopt(*new HTML::HTMLOutputElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::p)
+ return adopt(*new HTML::HTMLParagraphElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::param)
+ return adopt(*new HTML::HTMLParamElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::picture)
+ return adopt(*new HTML::HTMLPictureElement(document, qualified_name));
+ // NOTE: The obsolete elements "listing" and "xmp" are explicitly mapped to HTMLPreElement in the specification.
+ if (lowercase_tag_name.is_one_of(HTML::TagNames::pre, HTML::TagNames::listing, HTML::TagNames::xmp))
+ return adopt(*new HTML::HTMLPreElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::progress)
+ return adopt(*new HTML::HTMLProgressElement(document, qualified_name));
+ if (lowercase_tag_name.is_one_of(HTML::TagNames::blockquote, HTML::TagNames::q))
+ return adopt(*new HTML::HTMLQuoteElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::script)
+ return adopt(*new HTML::HTMLScriptElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::select)
+ return adopt(*new HTML::HTMLSelectElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::slot)
+ return adopt(*new HTML::HTMLSlotElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::source)
+ return adopt(*new HTML::HTMLSourceElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::span)
+ return adopt(*new HTML::HTMLSpanElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::style)
+ return adopt(*new HTML::HTMLStyleElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::caption)
+ return adopt(*new HTML::HTMLTableCaptionElement(document, qualified_name));
+ if (lowercase_tag_name.is_one_of(Web::HTML::TagNames::td, Web::HTML::TagNames::th))
+ return adopt(*new HTML::HTMLTableCellElement(document, qualified_name));
+ if (lowercase_tag_name.is_one_of(HTML::TagNames::colgroup, HTML::TagNames::col))
+ return adopt(*new HTML::HTMLTableColElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::table)
+ return adopt(*new HTML::HTMLTableElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::tr)
+ return adopt(*new HTML::HTMLTableRowElement(document, qualified_name));
+ if (lowercase_tag_name.is_one_of(HTML::TagNames::tbody, HTML::TagNames::thead, HTML::TagNames::tfoot))
+ return adopt(*new HTML::HTMLTableSectionElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::template_)
+ return adopt(*new HTML::HTMLTemplateElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::textarea)
+ return adopt(*new HTML::HTMLTextAreaElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::time)
+ return adopt(*new HTML::HTMLTimeElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::title)
+ return adopt(*new HTML::HTMLTitleElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::track)
+ return adopt(*new HTML::HTMLTrackElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::ul)
+ return adopt(*new HTML::HTMLUListElement(document, qualified_name));
+ if (lowercase_tag_name == HTML::TagNames::video)
+ return adopt(*new HTML::HTMLVideoElement(document, qualified_name));
+ if (lowercase_tag_name.is_one_of(
+ HTML::TagNames::article, HTML::TagNames::section, HTML::TagNames::nav, HTML::TagNames::aside, HTML::TagNames::hgroup, HTML::TagNames::header, HTML::TagNames::footer, HTML::TagNames::address, HTML::TagNames::dt, HTML::TagNames::dd, HTML::TagNames::figure, HTML::TagNames::figcaption, HTML::TagNames::main, HTML::TagNames::em, HTML::TagNames::strong, HTML::TagNames::small, HTML::TagNames::s, HTML::TagNames::cite, HTML::TagNames::dfn, HTML::TagNames::abbr, HTML::TagNames::ruby, HTML::TagNames::rt, HTML::TagNames::rp, HTML::TagNames::code, HTML::TagNames::var, HTML::TagNames::samp, HTML::TagNames::kbd, HTML::TagNames::sub, HTML::TagNames::sup, HTML::TagNames::i, HTML::TagNames::b, HTML::TagNames::u, HTML::TagNames::mark, HTML::TagNames::bdi, HTML::TagNames::bdo, HTML::TagNames::wbr, HTML::TagNames::summary, HTML::TagNames::noscript,
+ // Obsolete
+ HTML::TagNames::acronym, HTML::TagNames::basefont, HTML::TagNames::big, HTML::TagNames::center, HTML::TagNames::nobr, HTML::TagNames::noembed, HTML::TagNames::noframes, HTML::TagNames::plaintext, HTML::TagNames::rb, HTML::TagNames::rtc, HTML::TagNames::strike, HTML::TagNames::tt))
+ return adopt(*new HTML::HTMLElement(document, qualified_name));
+ if (lowercase_tag_name == SVG::TagNames::svg)
+ return adopt(*new SVG::SVGSVGElement(document, qualified_name));
+ if (lowercase_tag_name == SVG::TagNames::path)
+ return adopt(*new SVG::SVGPathElement(document, qualified_name));
+
+ // FIXME: If name is a valid custom element name, then return HTMLElement.
+
+ return adopt(*new HTML::HTMLUnknownElement(document, qualified_name));
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/ElementFactory.h b/Userland/Libraries/LibWeb/DOM/ElementFactory.h
new file mode 100644
index 0000000000..c1c811a9a4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/ElementFactory.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/DOM/Element.h>
+
+namespace Web::DOM {
+
+NonnullRefPtr<Element> create_element(Document&, const FlyString& tag_name, const FlyString& namespace_);
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Event.cpp b/Userland/Libraries/LibWeb/DOM/Event.cpp
new file mode 100644
index 0000000000..9fd75ab226
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Event.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/TypeCasts.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/DOM/Node.h>
+#include <LibWeb/DOM/ShadowRoot.h>
+
+namespace Web::DOM {
+
+void Event::append_to_path(EventTarget& invocation_target, RefPtr<EventTarget> shadow_adjusted_target, RefPtr<EventTarget> related_target, TouchTargetList& touch_targets, bool slot_in_closed_tree)
+{
+ bool invocation_target_in_shadow_tree = false;
+ bool root_of_closed_tree = false;
+
+ if (is<Node>(invocation_target)) {
+ auto& invocation_target_node = downcast<Node>(invocation_target);
+ if (is<ShadowRoot>(invocation_target_node.root()))
+ invocation_target_in_shadow_tree = true;
+ if (is<ShadowRoot>(invocation_target_node)) {
+ auto& invocation_target_shadow_root = downcast<ShadowRoot>(invocation_target_node);
+ root_of_closed_tree = invocation_target_shadow_root.closed();
+ }
+ }
+
+ m_path.append({ invocation_target, invocation_target_in_shadow_tree, shadow_adjusted_target, related_target, touch_targets, root_of_closed_tree, slot_in_closed_tree, m_path.size() });
+}
+
+void Event::set_cancelled_flag()
+{
+ if (m_cancelable && !m_in_passive_listener)
+ m_cancelled = true;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Event.h b/Userland/Libraries/LibWeb/DOM/Event.h
new file mode 100644
index 0000000000..1acc60a386
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Event.h
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <LibWeb/Bindings/Wrappable.h>
+#include <LibWeb/DOM/EventTarget.h>
+
+namespace Web::DOM {
+
+class Event
+ : public RefCounted<Event>
+ , public Bindings::Wrappable {
+public:
+ using WrapperType = Bindings::EventWrapper;
+
+ enum Phase : u16 {
+ None = 0,
+ CapturingPhase = 1,
+ AtTarget = 2,
+ BubblingPhase = 3,
+ };
+
+ using TouchTargetList = Vector<RefPtr<EventTarget>>;
+
+ struct PathEntry {
+ RefPtr<EventTarget> invocation_target;
+ bool invocation_target_in_shadow_tree { false };
+ RefPtr<EventTarget> shadow_adjusted_target;
+ RefPtr<EventTarget> related_target;
+ TouchTargetList touch_target_list;
+ bool root_of_closed_tree { false };
+ bool slot_in_closed_tree { false };
+ size_t index;
+ };
+
+ using Path = Vector<PathEntry>;
+
+ static NonnullRefPtr<Event> create(const FlyString& event_name)
+ {
+ return adopt(*new Event(event_name));
+ }
+
+ virtual ~Event() { }
+
+ const FlyString& type() const { return m_type; }
+ void set_type(const StringView& type) { m_type = type; }
+
+ RefPtr<EventTarget> target() const { return m_target; }
+ void set_target(EventTarget* target) { m_target = target; }
+
+ // NOTE: This is intended for the JS bindings.
+ RefPtr<EventTarget> src_target() const { return target(); }
+
+ RefPtr<EventTarget> related_target() const { return m_related_target; }
+ void set_related_target(EventTarget* related_target) { m_related_target = related_target; }
+
+ bool should_stop_propagation() const { return m_stop_propagation; }
+ void set_stop_propagation(bool stop_propagation) { m_stop_propagation = stop_propagation; }
+
+ bool should_stop_immediate_propagation() const { return m_stop_immediate_propagation; }
+ void set_stop_immediate_propagation(bool stop_immediate_propagation) { m_stop_immediate_propagation = stop_immediate_propagation; }
+
+ bool cancelled() const { return m_cancelled; }
+ void set_cancelled(bool cancelled) { m_cancelled = cancelled; }
+
+ bool in_passive_listener() const { return m_in_passive_listener; }
+ void set_in_passive_listener(bool in_passive_listener) { m_in_passive_listener = in_passive_listener; }
+
+ bool composed() const { return m_composed; }
+ void set_composed(bool composed) { m_composed = composed; }
+
+ bool initialized() const { return m_initialized; }
+ void set_initialized(bool initialized) { m_initialized = initialized; }
+
+ bool dispatched() const { return m_dispatch; }
+ void set_dispatched(bool dispatched) { m_dispatch = dispatched; }
+
+ void prevent_default() { set_cancelled_flag(); }
+ bool default_prevented() const { return cancelled(); }
+
+ u16 event_phase() const { return m_phase; }
+ void set_phase(Phase phase) { m_phase = phase; }
+
+ RefPtr<EventTarget> current_target() const { return m_current_target; }
+ void set_current_target(EventTarget* current_target) { m_current_target = current_target; }
+
+ bool return_value() const { return !m_cancelled; }
+ void set_return_value(bool return_value)
+ {
+ if (!return_value)
+ set_cancelled_flag();
+ }
+
+ void append_to_path(EventTarget&, RefPtr<EventTarget>, RefPtr<EventTarget>, TouchTargetList&, bool);
+ Path& path() { return m_path; }
+ const Path& path() const { return m_path; }
+ void clear_path() { m_path.clear(); }
+
+ void set_touch_target_list(TouchTargetList& touch_target_list) { m_touch_target_list = touch_target_list; }
+ TouchTargetList& touch_target_list() { return m_touch_target_list; };
+ void clear_touch_target_list() { m_touch_target_list.clear(); }
+
+ bool bubbles() const { return m_bubbles; }
+ void set_bubbles(bool bubbles) { m_bubbles = bubbles; }
+
+ bool cancelable() const { return m_cancelable; }
+ void set_cancelable(bool cancelable) { m_cancelable = cancelable; }
+
+ bool is_trusted() const { return m_is_trusted; }
+ void set_is_trusted(bool is_trusted) { m_is_trusted = is_trusted; }
+
+ void stop_propagation() { m_stop_propagation = true; }
+
+ bool cancel_bubble() const { return m_stop_propagation; }
+ void set_cancel_bubble(bool cancel_bubble)
+ {
+ if (cancel_bubble)
+ m_stop_propagation = true;
+ }
+
+ void stop_immediate_propagation()
+ {
+ m_stop_propagation = true;
+ m_stop_immediate_propagation = true;
+ }
+
+protected:
+ explicit Event(const FlyString& type)
+ : m_type(type)
+ , m_initialized(true)
+ {
+ }
+
+private:
+ FlyString m_type;
+ RefPtr<EventTarget> m_target;
+ RefPtr<EventTarget> m_related_target;
+ RefPtr<EventTarget> m_current_target;
+
+ Phase m_phase { None };
+
+ bool m_bubbles { false };
+ bool m_cancelable { false };
+
+ bool m_stop_propagation { false };
+ bool m_stop_immediate_propagation { false };
+ bool m_cancelled { false };
+ bool m_in_passive_listener { false };
+ bool m_composed { false };
+ bool m_initialized { false };
+ bool m_dispatch { false };
+
+ bool m_is_trusted { true };
+
+ Path m_path;
+ TouchTargetList m_touch_target_list;
+
+ void set_cancelled_flag();
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Event.idl b/Userland/Libraries/LibWeb/DOM/Event.idl
new file mode 100644
index 0000000000..a577092cae
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Event.idl
@@ -0,0 +1,23 @@
+interface Event {
+
+ readonly attribute DOMString type;
+ readonly attribute EventTarget? target;
+ readonly attribute EventTarget? srcTarget;
+ readonly attribute EventTarget? currentTarget;
+
+ readonly attribute unsigned short eventPhase;
+
+ undefined stopPropagation();
+ attribute boolean cancelBubble;
+ undefined stopImmediatePropagation();
+
+ readonly attribute boolean bubbles;
+ readonly attribute boolean cancelable;
+ attribute boolean returnValue;
+ undefined preventDefault();
+ readonly attribute boolean defaultPrevented;
+ readonly attribute boolean composed;
+
+ readonly attribute boolean isTrusted;
+
+};
diff --git a/Userland/Libraries/LibWeb/DOM/EventDispatcher.cpp b/Userland/Libraries/LibWeb/DOM/EventDispatcher.cpp
new file mode 100644
index 0000000000..e45b42c23e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/EventDispatcher.cpp
@@ -0,0 +1,332 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/TypeCasts.h>
+#include <LibJS/Runtime/Function.h>
+#include <LibWeb/Bindings/EventTargetWrapper.h>
+#include <LibWeb/Bindings/EventTargetWrapperFactory.h>
+#include <LibWeb/Bindings/EventWrapper.h>
+#include <LibWeb/Bindings/EventWrapperFactory.h>
+#include <LibWeb/Bindings/ScriptExecutionContext.h>
+#include <LibWeb/Bindings/WindowObject.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/DOM/EventDispatcher.h>
+#include <LibWeb/DOM/EventListener.h>
+#include <LibWeb/DOM/EventTarget.h>
+#include <LibWeb/DOM/Node.h>
+#include <LibWeb/DOM/ShadowRoot.h>
+#include <LibWeb/DOM/Window.h>
+#include <LibWeb/HTML/EventNames.h>
+#include <LibWeb/UIEvents/MouseEvent.h>
+
+namespace Web::DOM {
+
+// FIXME: This shouldn't be here, as retargeting is not only used by the event dispatcher.
+// When moving this function, it needs to be generalized. https://dom.spec.whatwg.org/#retarget
+static EventTarget* retarget(EventTarget* left, [[maybe_unused]] EventTarget* right)
+{
+ // FIXME
+ for (;;) {
+ if (!is<Node>(left))
+ return left;
+
+ auto* left_node = downcast<Node>(left);
+ auto* left_root = left_node->root();
+ if (!is<ShadowRoot>(left_root))
+ return left;
+
+ // FIXME: If right is a node and left’s root is a shadow-including inclusive ancestor of right, return left.
+
+ auto* left_shadow_root = downcast<ShadowRoot>(left_root);
+ left = left_shadow_root->host();
+ }
+}
+
+// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke
+bool EventDispatcher::inner_invoke(Event& event, Vector<EventTarget::EventListenerRegistration>& listeners, Event::Phase phase, bool invocation_target_in_shadow_tree)
+{
+ bool found = false;
+
+ for (auto& listener : listeners) {
+ if (listener.listener->removed())
+ continue;
+
+ if (event.type() != listener.listener->type())
+ continue;
+
+ found = true;
+
+ if (phase == Event::Phase::CapturingPhase && !listener.listener->capture())
+ continue;
+
+ if (phase == Event::Phase::BubblingPhase && listener.listener->capture())
+ continue;
+
+ if (listener.listener->once())
+ event.current_target()->remove_from_event_listener_list(listener.listener);
+
+ auto& function = listener.listener->function();
+ auto& global = function.global_object();
+
+ RefPtr<Event> current_event;
+
+ if (is<Bindings::WindowObject>(global)) {
+ auto& bindings_window_global = downcast<Bindings::WindowObject>(global);
+ auto& window_impl = bindings_window_global.impl();
+ current_event = window_impl.current_event();
+ if (!invocation_target_in_shadow_tree)
+ window_impl.set_current_event(&event);
+ }
+
+ if (listener.listener->passive())
+ event.set_in_passive_listener(true);
+
+ auto* this_value = Bindings::wrap(global, *event.current_target());
+ auto* wrapped_event = Bindings::wrap(global, event);
+ auto& vm = global.vm();
+ [[maybe_unused]] auto rc = vm.call(listener.listener->function(), this_value, wrapped_event);
+ if (vm.exception()) {
+ vm.clear_exception();
+ // FIXME: Set legacyOutputDidListenersThrowFlag if given. (Only used by IndexedDB currently)
+ }
+
+ event.set_in_passive_listener(false);
+ if (is<Bindings::WindowObject>(global)) {
+ auto& bindings_window_global = downcast<Bindings::WindowObject>(global);
+ auto& window_impl = bindings_window_global.impl();
+ window_impl.set_current_event(current_event);
+ }
+
+ if (event.should_stop_immediate_propagation())
+ return found;
+ }
+
+ return found;
+}
+
+// https://dom.spec.whatwg.org/#concept-event-listener-invoke
+void EventDispatcher::invoke(Event::PathEntry& struct_, Event& event, Event::Phase phase)
+{
+ auto last_valid_shadow_adjusted_target = event.path().last_matching([&struct_](auto& entry) {
+ return entry.index <= struct_.index && !entry.shadow_adjusted_target.is_null();
+ });
+
+ ASSERT(last_valid_shadow_adjusted_target.has_value());
+
+ event.set_target(last_valid_shadow_adjusted_target.value().shadow_adjusted_target);
+ event.set_related_target(struct_.related_target);
+ event.set_touch_target_list(struct_.touch_target_list);
+
+ if (event.should_stop_propagation())
+ return;
+
+ event.set_current_target(struct_.invocation_target);
+
+ // NOTE: This is an intentional copy. Any event listeners added after this point will not be invoked.
+ auto listeners = event.current_target()->listeners();
+ bool invocation_target_in_shadow_tree = struct_.invocation_target_in_shadow_tree;
+
+ bool found = inner_invoke(event, listeners, phase, invocation_target_in_shadow_tree);
+
+ if (!found && event.is_trusted()) {
+ auto original_event_type = event.type();
+
+ if (event.type() == "animationend")
+ event.set_type("webkitAnimationEnd");
+ else if (event.type() == "animationiteration")
+ event.set_type("webkitAnimationIteration");
+ else if (event.type() == "animationstart")
+ event.set_type("webkitAnimationStart");
+ else if (event.type() == "transitionend")
+ event.set_type("webkitTransitionEnd");
+ else
+ return;
+
+ inner_invoke(event, listeners, phase, invocation_target_in_shadow_tree);
+ event.set_type(original_event_type);
+ }
+}
+
+// https://dom.spec.whatwg.org/#concept-event-dispatch
+bool EventDispatcher::dispatch(NonnullRefPtr<EventTarget> target, NonnullRefPtr<Event> event, bool legacy_target_override)
+{
+ event->set_dispatched(true);
+ RefPtr<EventTarget> target_override;
+
+ if (!legacy_target_override) {
+ target_override = target;
+ } else {
+ // NOTE: This can be done because legacy_target_override is only set for events targeted at Window.
+ target_override = downcast<Window>(*target).document();
+ }
+
+ RefPtr<EventTarget> activation_target;
+ RefPtr<EventTarget> related_target = retarget(event->related_target(), target);
+
+ bool clear_targets = false;
+
+ if (related_target != target || event->related_target() == target) {
+ Event::TouchTargetList touch_targets;
+
+ for (auto& touch_target : event->touch_target_list()) {
+ touch_targets.append(retarget(touch_target, target));
+ }
+
+ event->append_to_path(*target, target_override, related_target, touch_targets, false);
+
+ bool is_activation_event = is<UIEvents::MouseEvent>(*event) && event->type() == HTML::EventNames::click;
+
+ if (is_activation_event && target->activation_behaviour)
+ activation_target = target;
+
+ // FIXME: Let slottable be target, if target is a slottable and is assigned, and null otherwise.
+
+ bool slot_in_closed_tree = false;
+ auto* parent = target->get_parent(event);
+
+ while (parent) {
+ // FIXME: If slottable is non-null:
+
+ // FIXME: If parent is a slottable and is assigned, then set slottable to parent.
+
+ related_target = retarget(event->related_target(), parent);
+ touch_targets.clear();
+
+ for (auto& touch_target : event->touch_target_list()) {
+ touch_targets.append(retarget(touch_target, parent));
+ }
+
+ // FIXME: or parent is a node and target’s root is a shadow-including inclusive ancestor of parent, then:
+ if (is<Window>(parent)) {
+ if (is_activation_event && event->bubbles() && !activation_target && parent->activation_behaviour)
+ activation_target = parent;
+
+ event->append_to_path(*parent, nullptr, related_target, touch_targets, slot_in_closed_tree);
+ } else if (related_target == parent) {
+ parent = nullptr;
+ } else {
+ target = *parent;
+
+ if (is_activation_event && !activation_target && target->activation_behaviour)
+ activation_target = target;
+
+ event->append_to_path(*parent, target, related_target, touch_targets, slot_in_closed_tree);
+ }
+
+ if (parent) {
+ parent = parent->get_parent(event);
+ }
+
+ slot_in_closed_tree = false;
+ }
+
+ auto clear_targets_struct = event->path().last_matching([](auto& entry) {
+ return !entry.shadow_adjusted_target.is_null();
+ });
+
+ ASSERT(clear_targets_struct.has_value());
+
+ if (is<Node>(clear_targets_struct.value().shadow_adjusted_target.ptr())) {
+ auto& shadow_adjusted_target_node = downcast<Node>(*clear_targets_struct.value().shadow_adjusted_target);
+ if (is<ShadowRoot>(shadow_adjusted_target_node.root()))
+ clear_targets = true;
+ }
+
+ if (!clear_targets && is<Node>(clear_targets_struct.value().related_target.ptr())) {
+ auto& related_target_node = downcast<Node>(*clear_targets_struct.value().related_target);
+ if (is<ShadowRoot>(related_target_node.root()))
+ clear_targets = true;
+ }
+
+ if (!clear_targets) {
+ for (auto touch_target : clear_targets_struct.value().touch_target_list) {
+ if (is<Node>(*touch_target.ptr())) {
+ auto& touch_target_node = downcast<Node>(*touch_target.ptr());
+ if (is<ShadowRoot>(touch_target_node.root())) {
+ clear_targets = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (activation_target && activation_target->legacy_pre_activation_behaviour)
+ activation_target->legacy_pre_activation_behaviour();
+
+ for (ssize_t i = event->path().size() - 1; i >= 0; --i) {
+ auto& entry = event->path().at(i);
+
+ if (entry.shadow_adjusted_target)
+ event->set_phase(Event::Phase::AtTarget);
+ else
+ event->set_phase(Event::Phase::CapturingPhase);
+
+ invoke(entry, event, Event::Phase::CapturingPhase);
+ }
+
+ for (auto& entry : event->path()) {
+ if (entry.shadow_adjusted_target) {
+ event->set_phase(Event::Phase::AtTarget);
+ } else {
+ if (!event->bubbles())
+ continue;
+
+ event->set_phase(Event::Phase::BubblingPhase);
+ }
+
+ invoke(entry, event, Event::Phase::BubblingPhase);
+ }
+ }
+
+ event->set_phase(Event::Phase::None);
+ event->set_current_target(nullptr);
+ event->clear_path();
+ event->set_dispatched(false);
+ event->set_stop_propagation(false);
+ event->set_stop_immediate_propagation(false);
+
+ if (clear_targets) {
+ event->set_target(nullptr);
+ event->set_related_target(nullptr);
+ event->clear_touch_target_list();
+ }
+
+ if (activation_target) {
+ if (!event->cancelled()) {
+ // NOTE: Since activation_target is set, it will have activation behaviour.
+ activation_target->activation_behaviour(event);
+ } else {
+ if (activation_target->legacy_cancelled_activation_behaviour)
+ activation_target->legacy_cancelled_activation_behaviour();
+ }
+ }
+
+ return !event->cancelled();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/EventDispatcher.h b/Userland/Libraries/LibWeb/DOM/EventDispatcher.h
new file mode 100644
index 0000000000..c5c380c597
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/EventDispatcher.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::DOM {
+
+class EventDispatcher {
+public:
+ static bool dispatch(NonnullRefPtr<EventTarget>, NonnullRefPtr<Event>, bool legacy_target_override = false);
+
+private:
+ static void invoke(Event::PathEntry&, Event&, Event::Phase);
+ static bool inner_invoke(Event&, Vector<EventTarget::EventListenerRegistration>&, Event::Phase, bool);
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/EventListener.cpp b/Userland/Libraries/LibWeb/DOM/EventListener.cpp
new file mode 100644
index 0000000000..eb64bfa503
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/EventListener.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/Function.h>
+#include <LibWeb/DOM/EventListener.h>
+
+namespace Web::DOM {
+
+JS::Function& EventListener::function()
+{
+ ASSERT(m_function.cell());
+ return *m_function.cell();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/EventListener.h b/Userland/Libraries/LibWeb/DOM/EventListener.h
new file mode 100644
index 0000000000..1d5705c2b6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/EventListener.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/RefCounted.h>
+#include <LibJS/Heap/Handle.h>
+#include <LibWeb/Bindings/Wrappable.h>
+
+namespace Web::DOM {
+
+class EventListener
+ : public RefCounted<EventListener>
+ , public Bindings::Wrappable {
+public:
+ using WrapperType = Bindings::EventListenerWrapper;
+
+ explicit EventListener(JS::Handle<JS::Function> function)
+ : m_function(move(function))
+ {
+ }
+
+ JS::Function& function();
+
+ const FlyString& type() const { return m_type; }
+ void set_type(const FlyString& type) { m_type = type; }
+
+ bool capture() const { return m_capture; }
+ void set_capture(bool capture) { m_capture = capture; }
+
+ bool passive() const { return m_passive; }
+ void set_passive(bool passive) { m_capture = passive; }
+
+ bool once() const { return m_once; }
+ void set_once(bool once) { m_once = once; }
+
+ bool removed() const { return m_removed; }
+ void set_removed(bool removed) { m_removed = removed; }
+
+private:
+ FlyString m_type;
+ JS::Handle<JS::Function> m_function;
+ bool m_capture { false };
+ bool m_passive { false };
+ bool m_once { false };
+ bool m_removed { false };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/EventTarget.cpp b/Userland/Libraries/LibWeb/DOM/EventTarget.cpp
new file mode 100644
index 0000000000..5f8ca71c8a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/EventTarget.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Bindings/ScriptExecutionContext.h>
+#include <LibWeb/DOM/EventListener.h>
+#include <LibWeb/DOM/EventTarget.h>
+
+namespace Web::DOM {
+
+EventTarget::EventTarget(Bindings::ScriptExecutionContext& script_execution_context)
+ : m_script_execution_context(&script_execution_context)
+{
+}
+
+EventTarget::~EventTarget()
+{
+}
+
+void EventTarget::add_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener> listener)
+{
+ auto existing_listener = m_listeners.first_matching([&](auto& entry) {
+ return entry.listener->type() == event_name && &entry.listener->function() == &listener->function() && entry.listener->capture() == listener->capture();
+ });
+ if (existing_listener.has_value())
+ return;
+ listener->set_type(event_name);
+ m_listeners.append({ event_name, move(listener) });
+}
+
+void EventTarget::remove_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener> listener)
+{
+ m_listeners.remove_first_matching([&](auto& entry) {
+ auto matches = entry.event_name == event_name && &entry.listener->function() == &listener->function() && entry.listener->capture() == listener->capture();
+ if (matches)
+ entry.listener->set_removed(true);
+ return matches;
+ });
+}
+
+void EventTarget::remove_from_event_listener_list(NonnullRefPtr<EventListener> listener)
+{
+ m_listeners.remove_first_matching([&](auto& entry) {
+ return entry.listener->type() == listener->type() && &entry.listener->function() == &listener->function() && entry.listener->capture() == listener->capture();
+ });
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/EventTarget.h b/Userland/Libraries/LibWeb/DOM/EventTarget.h
new file mode 100644
index 0000000000..32399a6a7a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/EventTarget.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <AK/Function.h>
+#include <AK/Noncopyable.h>
+#include <AK/Vector.h>
+#include <LibJS/Forward.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::DOM {
+
+class EventTarget {
+ AK_MAKE_NONCOPYABLE(EventTarget);
+ AK_MAKE_NONMOVABLE(EventTarget);
+
+public:
+ virtual ~EventTarget();
+
+ void ref() { ref_event_target(); }
+ void unref() { unref_event_target(); }
+
+ void add_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener>);
+ void remove_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener>);
+
+ void remove_from_event_listener_list(NonnullRefPtr<EventListener>);
+
+ virtual bool dispatch_event(NonnullRefPtr<Event>) = 0;
+ virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) = 0;
+ Bindings::ScriptExecutionContext* script_execution_context() { return m_script_execution_context; }
+
+ virtual EventTarget* get_parent(const Event&) { return nullptr; }
+
+ struct EventListenerRegistration {
+ FlyString event_name;
+ NonnullRefPtr<EventListener> listener;
+ };
+
+ const Vector<EventListenerRegistration>& listeners() const { return m_listeners; }
+
+ Function<void(const Event&)> activation_behaviour;
+
+ // NOTE: These only exist for checkbox and radio input elements.
+ Function<void()> legacy_pre_activation_behaviour;
+ Function<void()> legacy_cancelled_activation_behaviour;
+
+protected:
+ explicit EventTarget(Bindings::ScriptExecutionContext&);
+
+ virtual void ref_event_target() = 0;
+ virtual void unref_event_target() = 0;
+
+private:
+ // FIXME: This should not be a raw pointer.
+ Bindings::ScriptExecutionContext* m_script_execution_context { nullptr };
+
+ Vector<EventListenerRegistration> m_listeners;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/EventTarget.idl b/Userland/Libraries/LibWeb/DOM/EventTarget.idl
new file mode 100644
index 0000000000..2dacfae0e2
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/EventTarget.idl
@@ -0,0 +1,6 @@
+interface EventTarget {
+
+ undefined addEventListener(DOMString type, EventListener? callback);
+ undefined removeEventListener(DOMString type, EventListener? callback);
+
+};
diff --git a/Userland/Libraries/LibWeb/DOM/Node.cpp b/Userland/Libraries/LibWeb/DOM/Node.cpp
new file mode 100644
index 0000000000..6890e4fda5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Node.cpp
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibJS/AST.h>
+#include <LibJS/Runtime/Function.h>
+#include <LibJS/Runtime/ScriptFunction.h>
+#include <LibWeb/Bindings/EventWrapper.h>
+#include <LibWeb/Bindings/EventWrapperFactory.h>
+#include <LibWeb/Bindings/NodeWrapper.h>
+#include <LibWeb/Bindings/NodeWrapperFactory.h>
+#include <LibWeb/CSS/StyleResolver.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/DOM/EventDispatcher.h>
+#include <LibWeb/DOM/EventListener.h>
+#include <LibWeb/DOM/Node.h>
+#include <LibWeb/DOM/ShadowRoot.h>
+#include <LibWeb/HTML/HTMLAnchorElement.h>
+#include <LibWeb/Layout/BlockBox.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+#include <LibWeb/Layout/InlineNode.h>
+#include <LibWeb/Layout/Node.h>
+#include <LibWeb/Layout/TextNode.h>
+
+//#define EVENT_DEBUG
+
+namespace Web::DOM {
+
+Node::Node(Document& document, NodeType type)
+ : EventTarget(static_cast<Bindings::ScriptExecutionContext&>(document))
+ , m_document(&document)
+ , m_type(type)
+{
+ if (!is_document())
+ m_document->ref_from_node({});
+}
+
+Node::~Node()
+{
+ ASSERT(m_deletion_has_begun);
+ if (layout_node() && layout_node()->parent())
+ layout_node()->parent()->remove_child(*layout_node());
+
+ if (!is_document())
+ m_document->unref_from_node({});
+}
+
+const HTML::HTMLAnchorElement* Node::enclosing_link_element() const
+{
+ for (auto* node = this; node; node = node->parent()) {
+ if (is<HTML::HTMLAnchorElement>(*node) && downcast<HTML::HTMLAnchorElement>(*node).has_attribute(HTML::AttributeNames::href))
+ return downcast<HTML::HTMLAnchorElement>(node);
+ }
+ return nullptr;
+}
+
+const HTML::HTMLElement* Node::enclosing_html_element() const
+{
+ return first_ancestor_of_type<HTML::HTMLElement>();
+}
+
+String Node::text_content() const
+{
+ StringBuilder builder;
+ for (auto* child = first_child(); child; child = child->next_sibling()) {
+ builder.append(child->text_content());
+ }
+ return builder.to_string();
+}
+
+void Node::set_text_content(const String& content)
+{
+ if (is_text()) {
+ downcast<Text>(this)->set_data(content);
+ } else {
+ remove_all_children();
+ append_child(document().create_text_node(content));
+ }
+
+ set_needs_style_update(true);
+ document().invalidate_layout();
+}
+
+RefPtr<Layout::Node> Node::create_layout_node()
+{
+ return nullptr;
+}
+
+void Node::invalidate_style()
+{
+ for_each_in_subtree_of_type<Element>([&](auto& element) {
+ element.set_needs_style_update(true);
+ return IterationDecision::Continue;
+ });
+ document().schedule_style_update();
+}
+
+bool Node::is_link() const
+{
+ return enclosing_link_element();
+}
+
+bool Node::dispatch_event(NonnullRefPtr<Event> event)
+{
+ return EventDispatcher::dispatch(*this, event);
+}
+
+String Node::child_text_content() const
+{
+ if (!is<ParentNode>(*this))
+ return String::empty();
+
+ StringBuilder builder;
+ downcast<ParentNode>(*this).for_each_child([&](auto& child) {
+ if (is<Text>(child))
+ builder.append(downcast<Text>(child).text_content());
+ });
+ return builder.build();
+}
+
+Node* Node::root()
+{
+ Node* root = this;
+ while (root->parent())
+ root = root->parent();
+ return root;
+}
+
+Node* Node::shadow_including_root()
+{
+ auto node_root = root();
+ if (is<ShadowRoot>(node_root))
+ return downcast<ShadowRoot>(node_root)->host()->shadow_including_root();
+ return node_root;
+}
+
+bool Node::is_connected() const
+{
+ return shadow_including_root() && shadow_including_root()->is_document();
+}
+
+Element* Node::parent_element()
+{
+ if (!parent() || !is<Element>(parent()))
+ return nullptr;
+ return downcast<Element>(parent());
+}
+
+const Element* Node::parent_element() const
+{
+ if (!parent() || !is<Element>(parent()))
+ return nullptr;
+ return downcast<Element>(parent());
+}
+
+RefPtr<Node> Node::append_child(NonnullRefPtr<Node> node, bool notify)
+{
+ if (&node->document() != &document())
+ document().adopt_node(node);
+ TreeNode<Node>::append_child(node, notify);
+ return node;
+}
+
+RefPtr<Node> Node::insert_before(NonnullRefPtr<Node> node, RefPtr<Node> child, bool notify)
+{
+ if (!child)
+ return append_child(move(node), notify);
+ if (child->parent_node() != this) {
+ dbgln("FIXME: Trying to insert_before() a bogus child");
+ return nullptr;
+ }
+ if (&node->document() != &document())
+ document().adopt_node(node);
+ TreeNode<Node>::insert_before(node, child, notify);
+ return node;
+}
+
+void Node::set_document(Badge<Document>, Document& document)
+{
+ if (m_document == &document)
+ return;
+ document.ref_from_node({});
+ m_document->unref_from_node({});
+ m_document = &document;
+}
+
+bool Node::is_editable() const
+{
+ return parent() && parent()->is_editable();
+}
+
+Bindings::EventTargetWrapper* Node::create_wrapper(JS::GlobalObject& global_object)
+{
+ return wrap(global_object, *this);
+}
+
+void Node::removed_last_ref()
+{
+ if (is<Document>(*this)) {
+ downcast<Document>(*this).removed_last_ref();
+ return;
+ }
+ m_deletion_has_begun = true;
+ delete this;
+}
+
+void Node::set_layout_node(Badge<Layout::Node>, Layout::Node* layout_node) const
+{
+ if (layout_node)
+ m_layout_node = layout_node->make_weak_ptr();
+ else
+ m_layout_node = nullptr;
+}
+
+EventTarget* Node::get_parent(const Event&)
+{
+ // FIXME: returns the node’s assigned slot, if node is assigned, and node’s parent otherwise.
+ return parent();
+}
+
+void Node::set_needs_style_update(bool value)
+{
+ if (m_needs_style_update == value)
+ return;
+ m_needs_style_update = value;
+
+ if (m_needs_style_update) {
+ for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent())
+ ancestor->m_child_needs_style_update = true;
+ document().schedule_style_update();
+ }
+}
+
+void Node::inserted_into(Node&)
+{
+ set_needs_style_update(true);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Node.h b/Userland/Libraries/LibWeb/DOM/Node.h
new file mode 100644
index 0000000000..624b41b951
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Node.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Badge.h>
+#include <AK/RefPtr.h>
+#include <AK/String.h>
+#include <AK/TypeCasts.h>
+#include <AK/Vector.h>
+#include <LibWeb/Bindings/Wrappable.h>
+#include <LibWeb/DOM/EventTarget.h>
+#include <LibWeb/TreeNode.h>
+
+namespace Web::DOM {
+
+enum class NodeType : unsigned {
+ INVALID = 0,
+ ELEMENT_NODE = 1,
+ TEXT_NODE = 3,
+ COMMENT_NODE = 8,
+ DOCUMENT_NODE = 9,
+ DOCUMENT_TYPE_NODE = 10,
+ DOCUMENT_FRAGMENT_NODE = 11,
+};
+
+class Node
+ : public TreeNode<Node>
+ , public EventTarget
+ , public Bindings::Wrappable {
+public:
+ using WrapperType = Bindings::NodeWrapper;
+
+ using TreeNode<Node>::ref;
+ using TreeNode<Node>::unref;
+
+ // ^EventTarget
+ virtual void ref_event_target() final { ref(); }
+ virtual void unref_event_target() final { unref(); }
+ virtual bool dispatch_event(NonnullRefPtr<Event>) final;
+ virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
+
+ virtual ~Node();
+
+ void removed_last_ref();
+
+ NodeType type() const { return m_type; }
+ bool is_element() const { return type() == NodeType::ELEMENT_NODE; }
+ bool is_text() const { return type() == NodeType::TEXT_NODE; }
+ bool is_document() const { return type() == NodeType::DOCUMENT_NODE; }
+ bool is_document_type() const { return type() == NodeType::DOCUMENT_TYPE_NODE; }
+ bool is_comment() const { return type() == NodeType::COMMENT_NODE; }
+ bool is_character_data() const { return type() == NodeType::TEXT_NODE || type() == NodeType::COMMENT_NODE; }
+ bool is_document_fragment() const { return type() == NodeType::DOCUMENT_FRAGMENT_NODE; }
+ bool is_parent_node() const { return is_element() || is_document() || is_document_fragment(); }
+ bool is_slottable() const { return is_element() || is_text(); }
+
+ virtual bool is_editable() const;
+
+ RefPtr<Node> append_child(NonnullRefPtr<Node>, bool notify = true);
+ RefPtr<Node> insert_before(NonnullRefPtr<Node> node, RefPtr<Node> child, bool notify = true);
+
+ virtual RefPtr<Layout::Node> create_layout_node();
+
+ virtual FlyString node_name() const = 0;
+
+ virtual String text_content() const;
+ void set_text_content(const String&);
+
+ Document& document() { return *m_document; }
+ const Document& document() const { return *m_document; }
+
+ const HTML::HTMLAnchorElement* enclosing_link_element() const;
+ const HTML::HTMLElement* enclosing_html_element() const;
+
+ String child_text_content() const;
+
+ Node* root();
+ const Node* root() const
+ {
+ return const_cast<Node*>(this)->root();
+ }
+
+ Node* shadow_including_root();
+ const Node* shadow_including_root() const
+ {
+ return const_cast<Node*>(this)->shadow_including_root();
+ }
+
+ bool is_connected() const;
+
+ Node* parent_node() { return parent(); }
+ const Node* parent_node() const { return parent(); }
+
+ Element* parent_element();
+ const Element* parent_element() const;
+
+ virtual void inserted_into(Node&);
+ virtual void removed_from(Node&) { }
+ virtual void children_changed() { }
+
+ const Layout::Node* layout_node() const { return m_layout_node; }
+ Layout::Node* layout_node() { return m_layout_node; }
+
+ void set_layout_node(Badge<Layout::Node>, Layout::Node*) const;
+
+ virtual bool is_child_allowed(const Node&) const { return true; }
+
+ bool needs_style_update() const { return m_needs_style_update; }
+ void set_needs_style_update(bool);
+
+ bool child_needs_style_update() const { return m_child_needs_style_update; }
+ void set_child_needs_style_update(bool b) { m_child_needs_style_update = b; }
+
+ void invalidate_style();
+
+ bool is_link() const;
+
+ void set_document(Badge<Document>, Document&);
+
+ virtual EventTarget* get_parent(const Event&) override;
+
+protected:
+ Node(Document&, NodeType);
+
+ Document* m_document { nullptr };
+ mutable WeakPtr<Layout::Node> m_layout_node;
+ NodeType m_type { NodeType::INVALID };
+ bool m_needs_style_update { false };
+ bool m_child_needs_style_update { false };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Node.idl b/Userland/Libraries/LibWeb/DOM/Node.idl
new file mode 100644
index 0000000000..2ba27688e6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Node.idl
@@ -0,0 +1,16 @@
+interface Node : EventTarget {
+
+ readonly attribute DOMString nodeName;
+ readonly attribute Node? firstChild;
+ readonly attribute Node? lastChild;
+ readonly attribute Node? previousSibling;
+ readonly attribute Node? nextSibling;
+ readonly attribute Node? parentNode;
+ readonly attribute Element? parentElement;
+ attribute DOMString textContent;
+
+ Node appendChild(Node node);
+ Node insertBefore(Node node, Node? child);
+
+};
+
diff --git a/Userland/Libraries/LibWeb/DOM/NonDocumentTypeChildNode.h b/Userland/Libraries/LibWeb/DOM/NonDocumentTypeChildNode.h
new file mode 100644
index 0000000000..2e3b3996d9
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/NonDocumentTypeChildNode.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibWeb/Forward.h>
+#include <LibWeb/TreeNode.h>
+
+namespace Web::DOM {
+
+template<typename NodeType>
+class NonDocumentTypeChildNode {
+public:
+ Element* previous_element_sibling()
+ {
+ for (auto* sibling = static_cast<NodeType*>(this)->previous_sibling(); sibling; sibling = sibling->previous_sibling()) {
+ if (is<Element>(*sibling))
+ return downcast<Element>(sibling);
+ }
+ return nullptr;
+ }
+
+ Element* next_element_sibling()
+ {
+ for (auto* sibling = static_cast<NodeType*>(this)->next_sibling(); sibling; sibling = sibling->next_sibling()) {
+ if (is<Element>(*sibling))
+ return downcast<Element>(sibling);
+ }
+ return nullptr;
+ }
+
+ Element* next_element_in_pre_order()
+ {
+ for (auto* node = static_cast<NodeType*>(this)->next_in_pre_order(); node; node = node->next_in_pre_order()) {
+ if (is<Element>(*node))
+ return downcast<Element>(node);
+ }
+ return nullptr;
+ }
+
+ const Element* previous_element_sibling() const { return const_cast<NonDocumentTypeChildNode*>(this)->previous_element_sibling(); }
+ const Element* next_element_sibling() const { return const_cast<NonDocumentTypeChildNode*>(this)->next_element_sibling(); }
+ const Element* next_element_in_pre_order() const { return const_cast<NonDocumentTypeChildNode*>(this)->next_element_in_pre_order(); }
+
+protected:
+ NonDocumentTypeChildNode() { }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/NonElementParentNode.h b/Userland/Libraries/LibWeb/DOM/NonElementParentNode.h
new file mode 100644
index 0000000000..e362fbc5c0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/NonElementParentNode.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibWeb/Forward.h>
+#include <LibWeb/HTML/AttributeNames.h>
+#include <LibWeb/TreeNode.h>
+
+namespace Web::DOM {
+
+template<typename NodeType>
+class NonElementParentNode {
+public:
+ RefPtr<Element> get_element_by_id(const FlyString& id) const
+ {
+ RefPtr<Element> found_element;
+ static_cast<const NodeType*>(this)->template for_each_in_subtree_of_type<Element>([&](auto& element) {
+ if (element.attribute(HTML::AttributeNames::id) == id) {
+ found_element = &element;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+ return found_element;
+ }
+ RefPtr<Element> get_element_by_id(const FlyString& id)
+ {
+ return const_cast<const NonElementParentNode*>(this)->get_element_by_id(id);
+ }
+
+protected:
+ NonElementParentNode() { }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/ParentNode.cpp b/Userland/Libraries/LibWeb/DOM/ParentNode.cpp
new file mode 100644
index 0000000000..42f435c19e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/ParentNode.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/Parser/CSSParser.h>
+#include <LibWeb/CSS/SelectorEngine.h>
+#include <LibWeb/DOM/ParentNode.h>
+#include <LibWeb/Dump.h>
+
+namespace Web::DOM {
+
+RefPtr<Element> ParentNode::query_selector(const StringView& selector_text)
+{
+ auto selector = parse_selector(CSS::ParsingContext(*this), selector_text);
+ if (!selector.has_value())
+ return {};
+
+ dump_selector(selector.value());
+
+ RefPtr<Element> result;
+ for_each_in_subtree_of_type<Element>([&](auto& element) {
+ if (SelectorEngine::matches(selector.value(), element)) {
+ result = element;
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+
+ return result;
+}
+
+NonnullRefPtrVector<Element> ParentNode::query_selector_all(const StringView& selector_text)
+{
+ auto selector = parse_selector(CSS::ParsingContext(*this), selector_text);
+ if (!selector.has_value())
+ return {};
+
+ dump_selector(selector.value());
+
+ NonnullRefPtrVector<Element> elements;
+ for_each_in_subtree_of_type<Element>([&](auto& element) {
+ if (SelectorEngine::matches(selector.value(), element)) {
+ elements.append(element);
+ }
+ return IterationDecision::Continue;
+ });
+
+ return elements;
+}
+
+RefPtr<Element> ParentNode::first_element_child()
+{
+ return first_child_of_type<Element>();
+}
+
+RefPtr<Element> ParentNode::last_element_child()
+{
+ return last_child_of_type<Element>();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/ParentNode.h b/Userland/Libraries/LibWeb/DOM/ParentNode.h
new file mode 100644
index 0000000000..0fa6e8f1fe
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/ParentNode.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtrVector.h>
+#include <LibWeb/DOM/Node.h>
+
+namespace Web::DOM {
+
+class ParentNode : public Node {
+public:
+ template<typename F>
+ void for_each_child(F) const;
+ template<typename F>
+ void for_each_child(F);
+
+ RefPtr<Element> first_element_child();
+ RefPtr<Element> last_element_child();
+
+ RefPtr<Element> query_selector(const StringView&);
+ NonnullRefPtrVector<Element> query_selector_all(const StringView&);
+
+protected:
+ ParentNode(Document& document, NodeType type)
+ : Node(document, type)
+ {
+ }
+};
+
+template<typename Callback>
+inline void ParentNode::for_each_child(Callback callback) const
+{
+ for (auto* node = first_child(); node; node = node->next_sibling())
+ callback(*node);
+}
+
+template<typename Callback>
+inline void ParentNode::for_each_child(Callback callback)
+{
+ for (auto* node = first_child(); node; node = node->next_sibling())
+ callback(*node);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Position.cpp b/Userland/Libraries/LibWeb/DOM/Position.cpp
new file mode 100644
index 0000000000..e78139f788
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Position.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Node.h>
+#include <LibWeb/DOM/Position.h>
+
+namespace Web::DOM {
+
+Position::Position(Node& node, unsigned offset)
+ : m_node(node)
+ , m_offset(offset)
+{
+}
+
+Position::~Position()
+{
+}
+
+String Position::to_string() const
+{
+ if (!node())
+ return String::formatted("DOM::Position(nullptr, {})", offset());
+ return String::formatted("DOM::Position({} ({})), {})", node()->node_name(), node(), offset());
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Position.h b/Userland/Libraries/LibWeb/DOM/Position.h
new file mode 100644
index 0000000000..b2c5a17ceb
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Position.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/RefPtr.h>
+#include <LibWeb/DOM/Node.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::DOM {
+
+class Position {
+public:
+ Position() { }
+ Position(Node&, unsigned offset);
+
+ ~Position();
+
+ bool is_valid() const { return m_node; }
+
+ Node* node() { return m_node; }
+ const Node* node() const { return m_node; }
+
+ unsigned offset() const { return m_offset; }
+ void set_offset(unsigned value) { m_offset = value; }
+
+ bool operator==(const Position& other) const
+ {
+ return m_node == other.m_node && m_offset == other.m_offset;
+ }
+
+ bool operator!=(const Position& other) const
+ {
+ return !(*this == other);
+ }
+
+ String to_string() const;
+
+private:
+ RefPtr<Node> m_node;
+ unsigned m_offset { 0 };
+};
+
+}
+
+namespace AK {
+template<>
+struct Formatter<Web::DOM::Position> : Formatter<StringView> {
+ void format(FormatBuilder& builder, const Web::DOM::Position& value)
+ {
+ Formatter<StringView>::format(builder, value.to_string());
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Range.cpp b/Userland/Libraries/LibWeb/DOM/Range.cpp
new file mode 100644
index 0000000000..377eb7f882
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Range.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Node.h>
+#include <LibWeb/DOM/Range.h>
+#include <LibWeb/DOM/Window.h>
+
+namespace Web::DOM {
+
+Range::Range(Window& window)
+ : m_start_container(window.document())
+ , m_start_offset(0)
+ , m_end_container(window.document())
+ , m_end_offset(0)
+{
+}
+
+Range::Range(Node& start_container, size_t start_offset, Node& end_container, size_t end_offset)
+ : m_start_container(start_container)
+ , m_start_offset(start_offset)
+ , m_end_container(end_container)
+ , m_end_offset(end_offset)
+{
+}
+
+NonnullRefPtr<Range> Range::clone_range() const
+{
+ return adopt(*new Range(const_cast<Node&>(*m_start_container), m_start_offset, const_cast<Node&>(*m_end_container), m_end_offset));
+}
+
+NonnullRefPtr<Range> Range::inverted() const
+{
+ return adopt(*new Range(const_cast<Node&>(*m_end_container), m_end_offset, const_cast<Node&>(*m_start_container), m_start_offset));
+}
+
+NonnullRefPtr<Range> Range::normalized() const
+{
+ if (m_start_container.ptr() == m_end_container.ptr()) {
+ if (m_start_offset <= m_end_offset)
+ return clone_range();
+
+ return inverted();
+ }
+
+ if (m_start_container->is_before(m_end_container))
+ return clone_range();
+
+ return inverted();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Range.h b/Userland/Libraries/LibWeb/DOM/Range.h
new file mode 100644
index 0000000000..608112fb14
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Range.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/RefCounted.h>
+#include <LibWeb/Bindings/Wrappable.h>
+#include <LibWeb/DOM/Node.h>
+
+namespace Web::DOM {
+
+class Range final
+ : public RefCounted<Range>
+ , public Bindings::Wrappable {
+public:
+ using WrapperType = Bindings::RangeWrapper;
+
+ static NonnullRefPtr<Range> create(Window& window)
+ {
+ return adopt(*new Range(window));
+ }
+ static NonnullRefPtr<Range> create(Node& start_container, size_t start_offset, Node& end_container, size_t end_offset)
+ {
+ return adopt(*new Range(start_container, start_offset, end_container, end_offset));
+ }
+
+ // FIXME: There are a ton of methods missing here.
+
+ Node* start_container() { return m_start_container; }
+ unsigned start_offset() { return m_start_offset; }
+
+ Node* end_container() { return m_end_container; }
+ unsigned end_offset() { return m_end_offset; }
+
+ bool collapsed()
+ {
+ return start_container() == end_container() && start_offset() == end_offset();
+ }
+
+ void set_start(Node& container, unsigned offset)
+ {
+ m_start_container = container;
+ m_start_offset = offset;
+ }
+
+ void set_end(Node& container, unsigned offset)
+ {
+ m_end_container = container;
+ m_end_offset = offset;
+ }
+
+ NonnullRefPtr<Range> inverted() const;
+ NonnullRefPtr<Range> normalized() const;
+ NonnullRefPtr<Range> clone_range() const;
+
+private:
+ explicit Range(Window&);
+ Range(Node& start_container, size_t start_offset, Node& end_container, size_t end_offset);
+
+ NonnullRefPtr<Node> m_start_container;
+ unsigned m_start_offset;
+
+ NonnullRefPtr<Node> m_end_container;
+ unsigned m_end_offset;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/ShadowRoot.cpp b/Userland/Libraries/LibWeb/DOM/ShadowRoot.cpp
new file mode 100644
index 0000000000..237216379b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/ShadowRoot.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/DOM/ShadowRoot.h>
+
+namespace Web::DOM {
+
+ShadowRoot::ShadowRoot(Document& document, Element& host)
+ : DocumentFragment(document)
+{
+ set_host(host);
+}
+
+EventTarget* ShadowRoot::get_parent(const Event& event)
+{
+ if (!event.composed()) {
+ auto& events_first_invocation_target = downcast<Node>(*event.path().first().invocation_target);
+ if (events_first_invocation_target.root() == this)
+ return nullptr;
+ }
+
+ return host();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/ShadowRoot.h b/Userland/Libraries/LibWeb/DOM/ShadowRoot.h
new file mode 100644
index 0000000000..4bee76470d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/ShadowRoot.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/DOM/DocumentFragment.h>
+
+namespace Web::DOM {
+
+class ShadowRoot final : public DocumentFragment {
+public:
+ ShadowRoot(Document&, Element&);
+
+ bool closed() const { return m_closed; }
+
+ bool delegates_focus() const { return m_delegates_focus; }
+ void set_delegates_focus(bool delegates_focus) { m_delegates_focus = delegates_focus; }
+
+ bool available_to_element_internals() const { return m_available_to_element_internals; }
+ void set_available_to_element_internals(bool available_to_element_internals) { m_available_to_element_internals = available_to_element_internals; }
+
+ // ^EventTarget
+ virtual EventTarget* get_parent(const Event&) override;
+
+ // NOTE: This is intended for the JS bindings.
+ String mode() const { return m_closed ? "closed" : "open"; }
+
+private:
+ // NOTE: The specification doesn't seem to specify a default value for closed. Assuming false for now.
+ bool m_closed { false };
+ bool m_delegates_focus { false };
+ bool m_available_to_element_internals { false };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/ShadowRoot.idl b/Userland/Libraries/LibWeb/DOM/ShadowRoot.idl
new file mode 100644
index 0000000000..320ca53e80
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/ShadowRoot.idl
@@ -0,0 +1,6 @@
+interface ShadowRoot : DocumentFragment {
+
+ readonly attribute DOMString mode;
+ readonly attribute Element host;
+
+};
diff --git a/Userland/Libraries/LibWeb/DOM/Text.cpp b/Userland/Libraries/LibWeb/DOM/Text.cpp
new file mode 100644
index 0000000000..de7beec7b6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Text.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/Layout/TextNode.h>
+
+namespace Web::DOM {
+
+Text::Text(Document& document, const String& data)
+ : CharacterData(document, NodeType::TEXT_NODE, data)
+{
+}
+
+Text::~Text()
+{
+}
+
+RefPtr<Layout::Node> Text::create_layout_node()
+{
+ return adopt(*new Layout::TextNode(document(), *this));
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Text.h b/Userland/Libraries/LibWeb/DOM/Text.h
new file mode 100644
index 0000000000..0e6f4a2dbd
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Text.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <AK/String.h>
+#include <LibWeb/DOM/CharacterData.h>
+
+namespace Web::DOM {
+
+class Text final : public CharacterData {
+public:
+ using WrapperType = Bindings::TextWrapper;
+
+ explicit Text(Document&, const String&);
+ virtual ~Text() override;
+
+ virtual FlyString node_name() const override { return "#text"; }
+
+private:
+ virtual RefPtr<Layout::Node> create_layout_node() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Text.idl b/Userland/Libraries/LibWeb/DOM/Text.idl
new file mode 100644
index 0000000000..25a6fdb405
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Text.idl
@@ -0,0 +1,3 @@
+interface Text : CharacterData {
+
+};
diff --git a/Userland/Libraries/LibWeb/DOM/Timer.cpp b/Userland/Libraries/LibWeb/DOM/Timer.cpp
new file mode 100644
index 0000000000..04e3c778f5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Timer.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/Timer.h>
+#include <LibJS/Runtime/Function.h>
+#include <LibWeb/DOM/Timer.h>
+#include <LibWeb/DOM/Window.h>
+
+namespace Web::DOM {
+
+NonnullRefPtr<Timer> Timer::create_interval(Window& window, int milliseconds, JS::Function& callback)
+{
+ return adopt(*new Timer(window, Type::Interval, milliseconds, callback));
+}
+
+NonnullRefPtr<Timer> Timer::create_timeout(Window& window, int milliseconds, JS::Function& callback)
+{
+ return adopt(*new Timer(window, Type::Timeout, milliseconds, callback));
+}
+
+Timer::Timer(Window& window, Type type, int milliseconds, JS::Function& callback)
+ : m_window(window)
+ , m_type(type)
+ , m_callback(JS::make_handle(&callback))
+{
+ m_id = window.allocate_timer_id({});
+ m_timer = Core::Timer::construct(milliseconds, [this] { m_window.timer_did_fire({}, *this); });
+ if (m_type == Type::Timeout)
+ m_timer->set_single_shot(true);
+}
+
+Timer::~Timer()
+{
+ m_window.deallocate_timer_id({}, m_id);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Timer.h b/Userland/Libraries/LibWeb/DOM/Timer.h
new file mode 100644
index 0000000000..bdecde92d8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Timer.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibCore/Forward.h>
+#include <LibJS/Heap/Handle.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::DOM {
+
+class Timer final : public RefCounted<Timer> {
+public:
+ enum class Type {
+ Interval,
+ Timeout,
+ };
+
+ static NonnullRefPtr<Timer> create_interval(Window&, int milliseconds, JS::Function&);
+ static NonnullRefPtr<Timer> create_timeout(Window&, int milliseconds, JS::Function&);
+
+ ~Timer();
+
+ i32 id() const { return m_id; }
+ Type type() const { return m_type; }
+
+ JS::Function& callback() { return *m_callback.cell(); }
+
+private:
+ Timer(Window&, Type, int ms, JS::Function&);
+
+ Window& m_window;
+ RefPtr<Core::Timer> m_timer;
+ Type m_type;
+ int m_id { 0 };
+ JS::Handle<JS::Function> m_callback;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Window.cpp b/Userland/Libraries/LibWeb/DOM/Window.cpp
new file mode 100644
index 0000000000..d10620bc1e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Window.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/DisplayLink.h>
+#include <LibGUI/MessageBox.h>
+#include <LibJS/Runtime/Function.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/DOM/EventDispatcher.h>
+#include <LibWeb/DOM/Timer.h>
+#include <LibWeb/DOM/Window.h>
+#include <LibWeb/HighResolutionTime/Performance.h>
+#include <LibWeb/InProcessWebView.h>
+#include <LibWeb/Page/Frame.h>
+
+namespace Web::DOM {
+
+NonnullRefPtr<Window> Window::create_with_document(Document& document)
+{
+ return adopt(*new Window(document));
+}
+
+Window::Window(Document& document)
+ : EventTarget(static_cast<Bindings::ScriptExecutionContext&>(document))
+ , m_document(document)
+ , m_performance(make<HighResolutionTime::Performance>(*this))
+{
+}
+
+Window::~Window()
+{
+}
+
+void Window::set_wrapper(Badge<Bindings::WindowObject>, Bindings::WindowObject& wrapper)
+{
+ m_wrapper = wrapper.make_weak_ptr();
+}
+
+void Window::alert(const String& message)
+{
+ if (auto* page = m_document.page())
+ page->client().page_did_request_alert(message);
+}
+
+bool Window::confirm(const String& message)
+{
+ auto confirm_result = GUI::MessageBox::show(nullptr, message, "Confirm", GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::OKCancel);
+ return confirm_result == GUI::Dialog::ExecResult::ExecOK;
+}
+
+i32 Window::set_interval(JS::Function& callback, i32 interval)
+{
+ auto timer = Timer::create_interval(*this, interval, callback);
+ m_timers.set(timer->id(), timer);
+ return timer->id();
+}
+
+i32 Window::set_timeout(JS::Function& callback, i32 interval)
+{
+ auto timer = Timer::create_timeout(*this, interval, callback);
+ m_timers.set(timer->id(), timer);
+ return timer->id();
+}
+
+void Window::timer_did_fire(Badge<Timer>, Timer& timer)
+{
+ // We should not be here if there's no JS wrapper for the Window object.
+ ASSERT(wrapper());
+ auto& vm = wrapper()->vm();
+
+ // NOTE: This protector pointer keeps the timer alive until the end of this function no matter what.
+ NonnullRefPtr protector(timer);
+
+ if (timer.type() == Timer::Type::Timeout) {
+ m_timers.remove(timer.id());
+ }
+
+ [[maybe_unused]] auto rc = vm.call(timer.callback(), wrapper());
+ if (vm.exception())
+ vm.clear_exception();
+}
+
+i32 Window::allocate_timer_id(Badge<Timer>)
+{
+ return m_timer_id_allocator.allocate();
+}
+
+void Window::deallocate_timer_id(Badge<Timer>, i32 id)
+{
+ m_timer_id_allocator.deallocate(id);
+}
+
+void Window::clear_timeout(i32 timer_id)
+{
+ m_timers.remove(timer_id);
+}
+
+void Window::clear_interval(i32 timer_id)
+{
+ m_timers.remove(timer_id);
+}
+
+i32 Window::request_animation_frame(JS::Function& callback)
+{
+ // FIXME: This is extremely fake!
+ static double fake_timestamp = 0;
+
+ i32 link_id = GUI::DisplayLink::register_callback([handle = make_handle(&callback)](i32 link_id) {
+ auto& function = const_cast<JS::Function&>(static_cast<const JS::Function&>(*handle.cell()));
+ auto& vm = function.vm();
+ fake_timestamp += 10;
+ [[maybe_unused]] auto rc = vm.call(function, {}, JS::Value(fake_timestamp));
+ if (vm.exception())
+ vm.clear_exception();
+ GUI::DisplayLink::unregister_callback(link_id);
+ });
+
+ // FIXME: Don't hand out raw DisplayLink ID's to JavaScript!
+ return link_id;
+}
+
+void Window::cancel_animation_frame(i32 id)
+{
+ // FIXME: We should not be passing untrusted numbers to DisplayLink::unregister_callback()!
+ GUI::DisplayLink::unregister_callback(id);
+}
+
+void Window::did_set_location_href(Badge<Bindings::LocationObject>, const URL& new_href)
+{
+ auto* frame = document().frame();
+ if (!frame)
+ return;
+ frame->loader().load(new_href, FrameLoader::Type::Navigation);
+}
+
+void Window::did_call_location_reload(Badge<Bindings::LocationObject>)
+{
+ auto* frame = document().frame();
+ if (!frame)
+ return;
+ frame->loader().load(document().url(), FrameLoader::Type::Reload);
+}
+
+bool Window::dispatch_event(NonnullRefPtr<Event> event)
+{
+ return EventDispatcher::dispatch(*this, event, true);
+}
+
+Bindings::EventTargetWrapper* Window::create_wrapper(JS::GlobalObject&)
+{
+ ASSERT_NOT_REACHED();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/Window.h b/Userland/Libraries/LibWeb/DOM/Window.h
new file mode 100644
index 0000000000..4118bebbe2
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/Window.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Badge.h>
+#include <AK/IDAllocator.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <LibWeb/Bindings/WindowObject.h>
+#include <LibWeb/Bindings/Wrappable.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/DOM/EventTarget.h>
+
+namespace Web::DOM {
+
+class Window final
+ : public RefCounted<Window>
+ , public EventTarget {
+public:
+ static NonnullRefPtr<Window> create_with_document(Document&);
+ ~Window();
+
+ using RefCounted::ref;
+ using RefCounted::unref;
+
+ virtual void ref_event_target() override { RefCounted::ref(); }
+ virtual void unref_event_target() override { RefCounted::unref(); }
+ virtual bool dispatch_event(NonnullRefPtr<Event>) override;
+ virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
+
+ const Document& document() const { return m_document; }
+ Document& document() { return m_document; }
+
+ void alert(const String&);
+ bool confirm(const String&);
+ i32 request_animation_frame(JS::Function&);
+ void cancel_animation_frame(i32);
+
+ i32 set_timeout(JS::Function&, i32);
+ i32 set_interval(JS::Function&, i32);
+ void clear_timeout(i32);
+ void clear_interval(i32);
+
+ void did_set_location_href(Badge<Bindings::LocationObject>, const URL& new_href);
+ void did_call_location_reload(Badge<Bindings::LocationObject>);
+
+ Bindings::WindowObject* wrapper() { return m_wrapper; }
+ const Bindings::WindowObject* wrapper() const { return m_wrapper; }
+
+ void set_wrapper(Badge<Bindings::WindowObject>, Bindings::WindowObject&);
+
+ i32 allocate_timer_id(Badge<Timer>);
+ void deallocate_timer_id(Badge<Timer>, i32);
+ void timer_did_fire(Badge<Timer>, Timer&);
+
+ HighResolutionTime::Performance& performance() { return *m_performance; }
+
+ const Event* current_event() const { return m_current_event; }
+ void set_current_event(Event* event) { m_current_event = event; }
+
+private:
+ explicit Window(Document&);
+
+ Document& m_document;
+ WeakPtr<Bindings::WindowObject> m_wrapper;
+
+ IDAllocator m_timer_id_allocator;
+ HashMap<int, NonnullRefPtr<Timer>> m_timers;
+
+ NonnullOwnPtr<HighResolutionTime::Performance> m_performance;
+ RefPtr<Event> m_current_event;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/XMLHttpRequest.cpp b/Userland/Libraries/LibWeb/DOM/XMLHttpRequest.cpp
new file mode 100644
index 0000000000..f085ade4fd
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/XMLHttpRequest.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/Function.h>
+#include <LibWeb/Bindings/EventWrapper.h>
+#include <LibWeb/Bindings/EventWrapperFactory.h>
+#include <LibWeb/Bindings/XMLHttpRequestWrapper.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/DOM/EventDispatcher.h>
+#include <LibWeb/DOM/EventListener.h>
+#include <LibWeb/DOM/Window.h>
+#include <LibWeb/DOM/XMLHttpRequest.h>
+#include <LibWeb/HTML/EventNames.h>
+#include <LibWeb/Loader/ResourceLoader.h>
+#include <LibWeb/Origin.h>
+
+namespace Web {
+
+XMLHttpRequest::XMLHttpRequest(DOM::Window& window)
+ : EventTarget(static_cast<Bindings::ScriptExecutionContext&>(window.document()))
+ , m_window(window)
+{
+}
+
+XMLHttpRequest::~XMLHttpRequest()
+{
+}
+
+void XMLHttpRequest::set_ready_state(ReadyState ready_state)
+{
+ // FIXME: call onreadystatechange once we have that
+ m_ready_state = ready_state;
+}
+
+String XMLHttpRequest::response_text() const
+{
+ if (m_response.is_null())
+ return {};
+ return String::copy(m_response);
+}
+
+void XMLHttpRequest::open(const String& method, const String& url)
+{
+ m_method = method;
+ m_url = url;
+ set_ready_state(ReadyState::Opened);
+}
+
+void XMLHttpRequest::send()
+{
+ URL request_url = m_window->document().complete_url(m_url);
+ dbg() << "XHR send from " << m_window->document().url() << " to " << request_url;
+
+ // TODO: Add support for preflight requests to support CORS requests
+ Origin request_url_origin = Origin(request_url.protocol(), request_url.host(), request_url.port());
+
+ if (!m_window->document().origin().is_same(request_url_origin)) {
+ dbg() << "XHR failed to load: Same-Origin Policy violation: " << m_window->document().url() << " may not load " << request_url;
+ auto weak_this = make_weak_ptr();
+ if (!weak_this)
+ return;
+ const_cast<XMLHttpRequest&>(*weak_this).set_ready_state(ReadyState::Done);
+ const_cast<XMLHttpRequest&>(*weak_this).dispatch_event(DOM::Event::create(HTML::EventNames::error));
+ return;
+ }
+
+ // FIXME: in order to properly set ReadyState::HeadersReceived and ReadyState::Loading,
+ // we need to make ResourceLoader give us more detailed updates than just "done" and "error".
+ ResourceLoader::the().load(
+ m_window->document().complete_url(m_url),
+ [weak_this = make_weak_ptr()](auto data, auto&) {
+ if (!weak_this)
+ return;
+ const_cast<XMLHttpRequest&>(*weak_this).m_response = ByteBuffer::copy(data);
+ const_cast<XMLHttpRequest&>(*weak_this).set_ready_state(ReadyState::Done);
+ const_cast<XMLHttpRequest&>(*weak_this).dispatch_event(DOM::Event::create(HTML::EventNames::load));
+ },
+ [weak_this = make_weak_ptr()](auto& error) {
+ if (!weak_this)
+ return;
+ dbg() << "XHR failed to load: " << error;
+ const_cast<XMLHttpRequest&>(*weak_this).set_ready_state(ReadyState::Done);
+ const_cast<XMLHttpRequest&>(*weak_this).dispatch_event(DOM::Event::create(HTML::EventNames::error));
+ });
+}
+
+bool XMLHttpRequest::dispatch_event(NonnullRefPtr<DOM::Event> event)
+{
+ return DOM::EventDispatcher::dispatch(*this, move(event));
+}
+
+Bindings::EventTargetWrapper* XMLHttpRequest::create_wrapper(JS::GlobalObject& global_object)
+{
+ return wrap(global_object, *this);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOM/XMLHttpRequest.h b/Userland/Libraries/LibWeb/DOM/XMLHttpRequest.h
new file mode 100644
index 0000000000..33a6038863
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/XMLHttpRequest.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/RefCounted.h>
+#include <AK/Weakable.h>
+#include <LibWeb/Bindings/Wrappable.h>
+#include <LibWeb/DOM/EventTarget.h>
+
+namespace Web {
+
+class XMLHttpRequest final
+ : public RefCounted<XMLHttpRequest>
+ , public Weakable<XMLHttpRequest>
+ , public DOM::EventTarget
+ , public Bindings::Wrappable {
+public:
+ enum class ReadyState {
+ Unsent,
+ Opened,
+ HeadersReceived,
+ Loading,
+ Done,
+ };
+
+ using WrapperType = Bindings::XMLHttpRequestWrapper;
+
+ static NonnullRefPtr<XMLHttpRequest> create(DOM::Window& window) { return adopt(*new XMLHttpRequest(window)); }
+
+ virtual ~XMLHttpRequest() override;
+
+ using RefCounted::ref;
+ using RefCounted::unref;
+
+ ReadyState ready_state() const { return m_ready_state; };
+ String response_text() const;
+ void open(const String& method, const String& url);
+ void send();
+
+private:
+ virtual void ref_event_target() override { ref(); }
+ virtual void unref_event_target() override { unref(); }
+ virtual bool dispatch_event(NonnullRefPtr<DOM::Event>) override;
+ virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
+
+ void set_ready_state(ReadyState);
+
+ explicit XMLHttpRequest(DOM::Window&);
+
+ NonnullRefPtr<DOM::Window> m_window;
+
+ ReadyState m_ready_state { ReadyState::Unsent };
+
+ String m_method;
+ String m_url;
+
+ ByteBuffer m_response;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/DOMTreeModel.cpp b/Userland/Libraries/LibWeb/DOMTreeModel.cpp
new file mode 100644
index 0000000000..76c0e39769
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOMTreeModel.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DOMTreeModel.h"
+#include <AK/StringBuilder.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/DOM/Text.h>
+#include <ctype.h>
+#include <stdio.h>
+
+namespace Web {
+
+DOMTreeModel::DOMTreeModel(DOM::Document& document)
+ : m_document(document)
+{
+ m_document_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-html.png"));
+ m_element_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/inspector-object.png"));
+ m_text_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-unknown.png"));
+}
+
+DOMTreeModel::~DOMTreeModel()
+{
+}
+
+GUI::ModelIndex DOMTreeModel::index(int row, int column, const GUI::ModelIndex& parent) const
+{
+ if (!parent.is_valid()) {
+ return create_index(row, column, m_document.ptr());
+ }
+ auto& parent_node = *static_cast<DOM::Node*>(parent.internal_data());
+ return create_index(row, column, parent_node.child_at_index(row));
+}
+
+GUI::ModelIndex DOMTreeModel::parent_index(const GUI::ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return {};
+ auto& node = *static_cast<DOM::Node*>(index.internal_data());
+ if (!node.parent())
+ return {};
+
+ // FIXME: Handle the template element (child elements are not stored in it, all of its children are in its document fragment "content")
+
+ // No grandparent? Parent is the document!
+ if (!node.parent()->parent()) {
+ return create_index(0, 0, m_document.ptr());
+ }
+
+ // Walk the grandparent's children to find the index of node's parent in its parent.
+ // (This is needed to produce the row number of the GUI::ModelIndex corresponding to node's parent.)
+ int grandparent_child_index = 0;
+ for (auto* grandparent_child = node.parent()->parent()->first_child(); grandparent_child; grandparent_child = grandparent_child->next_sibling()) {
+ if (grandparent_child == node.parent())
+ return create_index(grandparent_child_index, 0, node.parent());
+ ++grandparent_child_index;
+ }
+
+ ASSERT_NOT_REACHED();
+ return {};
+}
+
+int DOMTreeModel::row_count(const GUI::ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return 1;
+ auto& node = *static_cast<DOM::Node*>(index.internal_data());
+ return node.child_count();
+}
+
+int DOMTreeModel::column_count(const GUI::ModelIndex&) const
+{
+ return 1;
+}
+
+static String with_whitespace_collapsed(const StringView& string)
+{
+ StringBuilder builder;
+ for (size_t i = 0; i < string.length(); ++i) {
+ if (isspace(string[i])) {
+ builder.append(' ');
+ while (i < string.length()) {
+ if (isspace(string[i])) {
+ ++i;
+ continue;
+ }
+ builder.append(string[i]);
+ break;
+ }
+ continue;
+ }
+ builder.append(string[i]);
+ }
+ return builder.to_string();
+}
+
+GUI::Variant DOMTreeModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
+{
+ auto& node = *static_cast<DOM::Node*>(index.internal_data());
+ if (role == GUI::ModelRole::Icon) {
+ if (node.is_document())
+ return m_document_icon;
+ if (node.is_element())
+ return m_element_icon;
+ // FIXME: More node type icons?
+ return m_text_icon;
+ }
+ if (role == GUI::ModelRole::Display) {
+ if (node.is_text())
+ return with_whitespace_collapsed(downcast<DOM::Text>(node).data());
+ if (!node.is_element())
+ return node.node_name();
+ auto& element = downcast<DOM::Element>(node);
+ StringBuilder builder;
+ builder.append('<');
+ builder.append(element.local_name());
+ element.for_each_attribute([&](auto& name, auto& value) {
+ builder.append(' ');
+ builder.append(name);
+ builder.append('=');
+ builder.append('"');
+ builder.append(value);
+ builder.append('"');
+ });
+ builder.append('>');
+ return builder.to_string();
+ }
+ return {};
+}
+
+void DOMTreeModel::update()
+{
+ did_update();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/DOMTreeModel.h b/Userland/Libraries/LibWeb/DOMTreeModel.h
new file mode 100644
index 0000000000..42a568b306
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOMTreeModel.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Model.h>
+#include <LibWeb/Forward.h>
+
+namespace Web {
+
+class DOMTreeModel final : public GUI::Model {
+public:
+ static NonnullRefPtr<DOMTreeModel> create(DOM::Document& document)
+ {
+ return adopt(*new DOMTreeModel(document));
+ }
+
+ virtual ~DOMTreeModel() override;
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
+ virtual GUI::ModelIndex index(int row, int column, const GUI::ModelIndex& parent = GUI::ModelIndex()) const override;
+ virtual GUI::ModelIndex parent_index(const GUI::ModelIndex&) const override;
+ virtual void update() override;
+
+private:
+ explicit DOMTreeModel(DOM::Document&);
+
+ NonnullRefPtr<DOM::Document> m_document;
+
+ GUI::Icon m_document_icon;
+ GUI::Icon m_element_icon;
+ GUI::Icon m_text_icon;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Dump.cpp b/Userland/Libraries/LibWeb/Dump.cpp
new file mode 100644
index 0000000000..f8f83ebc87
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Dump.cpp
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/QuickSort.h>
+#include <AK/StringBuilder.h>
+#include <AK/Utf8View.h>
+#include <LibWeb/CSS/PropertyID.h>
+#include <LibWeb/CSS/StyleSheet.h>
+#include <LibWeb/DOM/Comment.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/DocumentFragment.h>
+#include <LibWeb/DOM/DocumentType.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/Dump.h>
+#include <LibWeb/HTML/HTMLTemplateElement.h>
+#include <LibWeb/Layout/BlockBox.h>
+#include <LibWeb/Layout/Node.h>
+#include <LibWeb/Layout/TextNode.h>
+#include <stdio.h>
+
+namespace Web {
+
+void dump_tree(const DOM::Node& node)
+{
+ static int indent = 0;
+ for (int i = 0; i < indent; ++i)
+ dbgprintf(" ");
+ if (is<DOM::Document>(node)) {
+ dbgprintf("*Document*\n");
+ } else if (is<DOM::Element>(node)) {
+ dbgprintf("<%s", downcast<DOM::Element>(node).local_name().characters());
+ downcast<DOM::Element>(node).for_each_attribute([](auto& name, auto& value) {
+ dbgprintf(" %s=%s", name.characters(), value.characters());
+ });
+ dbgprintf(">\n");
+ } else if (is<DOM::Text>(node)) {
+ dbgprintf("\"%s\"\n", downcast<DOM::Text>(node).data().characters());
+ } else if (is<DOM::DocumentType>(node)) {
+ dbgprintf("<!DOCTYPE html>\n");
+ } else if (is<DOM::Comment>(node)) {
+ dbgprintf("<!--%s-->\n", downcast<DOM::Comment>(node).data().characters());
+ } else if (is<DOM::DocumentFragment>(node)) {
+ dbgprintf("#document-fragment\n");
+ }
+ ++indent;
+ if (is<DOM::ParentNode>(node)) {
+ if (!is<HTML::HTMLTemplateElement>(node)) {
+ static_cast<const DOM::ParentNode&>(node).for_each_child([](auto& child) {
+ dump_tree(child);
+ });
+ } else {
+ auto& template_element = downcast<HTML::HTMLTemplateElement>(node);
+ dump_tree(template_element.content());
+ }
+ }
+ --indent;
+}
+
+void dump_tree(const Layout::Node& layout_node, bool show_box_model, bool show_specified_style)
+{
+ StringBuilder builder;
+ dump_tree(builder, layout_node, show_box_model, show_specified_style, true);
+ dbgprintf("%s", builder.to_string().characters());
+}
+
+void dump_tree(StringBuilder& builder, const Layout::Node& layout_node, bool show_box_model, bool show_specified_style, bool interactive)
+{
+ static size_t indent = 0;
+ for (size_t i = 0; i < indent; ++i)
+ builder.append(" ");
+
+ FlyString tag_name;
+ if (layout_node.is_anonymous())
+ tag_name = "(anonymous)";
+ else if (is<DOM::Text>(layout_node.dom_node()))
+ tag_name = "#text";
+ else if (is<DOM::Document>(layout_node.dom_node()))
+ tag_name = "#document";
+ else if (is<DOM::Element>(layout_node.dom_node()))
+ tag_name = downcast<DOM::Element>(*layout_node.dom_node()).local_name();
+ else
+ tag_name = "???";
+
+ String identifier = "";
+ if (layout_node.dom_node() && is<DOM::Element>(*layout_node.dom_node())) {
+ auto& element = downcast<DOM::Element>(*layout_node.dom_node());
+ StringBuilder builder;
+ auto id = element.attribute(HTML::AttributeNames::id);
+ if (!id.is_empty()) {
+ builder.append('#');
+ builder.append(id);
+ }
+ for (auto& class_name : element.class_names()) {
+ builder.append('.');
+ builder.append(class_name);
+ }
+ identifier = builder.to_string();
+ }
+
+ const char* nonbox_color_on = "";
+ const char* box_color_on = "";
+ const char* positioned_color_on = "";
+ const char* floating_color_on = "";
+ const char* inline_block_color_on = "";
+ const char* line_box_color_on = "";
+ const char* fragment_color_on = "";
+ const char* color_off = "";
+
+ if (interactive) {
+ nonbox_color_on = "\033[33m";
+ box_color_on = "\033[34m";
+ positioned_color_on = "\033[31;1m";
+ floating_color_on = "\033[32;1m";
+ inline_block_color_on = "\033[36;1m";
+ line_box_color_on = "\033[34;1m";
+ fragment_color_on = "\033[35;1m";
+ color_off = "\033[0m";
+ }
+
+ if (!is<Layout::Box>(layout_node)) {
+ builder.appendff("{}{}{} <{}{}{}>",
+ nonbox_color_on,
+ layout_node.class_name().substring_view(13),
+ color_off,
+ tag_name,
+ nonbox_color_on,
+ identifier,
+ color_off);
+ if (interactive)
+ builder.appendff(" @{:p}", &layout_node);
+ builder.append("\n");
+ } else {
+ auto& box = downcast<Layout::Box>(layout_node);
+ builder.appendff("{}{}{} <{}{}{}{}> ",
+ box_color_on,
+ box.class_name().substring_view(13),
+ color_off,
+ box_color_on,
+ tag_name,
+ color_off,
+ identifier.characters());
+
+ if (interactive)
+ builder.appendff("@{:p} ", &layout_node);
+
+ builder.appendff("at ({},{}) size {}x{}",
+ (int)box.absolute_x(),
+ (int)box.absolute_y(),
+ (int)box.width(),
+ (int)box.height());
+
+ if (box.is_positioned())
+ builder.appendff(" {}positioned{}", positioned_color_on, color_off);
+ if (box.is_floating())
+ builder.appendff(" {}floating{}", floating_color_on, color_off);
+ if (box.is_inline_block())
+ builder.appendff(" {}inline-block{}", inline_block_color_on, color_off);
+
+ if (show_box_model) {
+ // Dump the horizontal box properties
+ builder.appendf(" [%g+%g+%g %g %g+%g+%g]",
+ box.box_model().margin.left,
+ box.box_model().border.left,
+ box.box_model().padding.left,
+ box.width(),
+ box.box_model().padding.right,
+ box.box_model().border.right,
+ box.box_model().margin.right);
+
+ // And the vertical box properties
+ builder.appendf(" [%g+%g+%g %g %g+%g+%g]",
+ box.box_model().margin.top,
+ box.box_model().border.top,
+ box.box_model().padding.top,
+ box.height(),
+ box.box_model().padding.bottom,
+ box.box_model().border.bottom,
+ box.box_model().margin.bottom);
+ }
+
+ builder.append("\n");
+ }
+
+ if (is<Layout::BlockBox>(layout_node) && static_cast<const Layout::BlockBox&>(layout_node).children_are_inline()) {
+ auto& block = static_cast<const Layout::BlockBox&>(layout_node);
+ for (size_t line_box_index = 0; line_box_index < block.line_boxes().size(); ++line_box_index) {
+ auto& line_box = block.line_boxes()[line_box_index];
+ for (size_t i = 0; i < indent; ++i)
+ builder.append(" ");
+ builder.appendff(" {}line {}{} width: {}\n",
+ line_box_color_on,
+ line_box_index,
+ color_off,
+ (int)line_box.width());
+ for (size_t fragment_index = 0; fragment_index < line_box.fragments().size(); ++fragment_index) {
+ auto& fragment = line_box.fragments()[fragment_index];
+ for (size_t i = 0; i < indent; ++i)
+ builder.append(" ");
+ builder.appendff(" {}frag {}{} from {} ",
+ fragment_color_on,
+ fragment_index,
+ color_off,
+ fragment.layout_node().class_name());
+ if (interactive)
+ builder.appendff("@{:p}, ", &fragment.layout_node());
+ builder.appendff("start: {}, length: {}, rect: {}\n",
+ fragment.start(),
+ fragment.length(),
+ enclosing_int_rect(fragment.absolute_rect()).to_string());
+ if (is<Layout::TextNode>(fragment.layout_node())) {
+ for (size_t i = 0; i < indent; ++i)
+ builder.append(" ");
+ auto& layout_text = static_cast<const Layout::TextNode&>(fragment.layout_node());
+ auto fragment_text = layout_text.text_for_rendering().substring(fragment.start(), fragment.length());
+ builder.appendff(" \"{}\"\n", fragment_text);
+ }
+ }
+ }
+ }
+
+ if (show_specified_style && layout_node.dom_node() && layout_node.dom_node()->is_element() && downcast<DOM::Element>(layout_node.dom_node())->specified_css_values()) {
+ struct NameAndValue {
+ String name;
+ String value;
+ };
+ Vector<NameAndValue> properties;
+ downcast<DOM::Element>(*layout_node.dom_node()).specified_css_values()->for_each_property([&](auto property_id, auto& value) {
+ properties.append({ CSS::string_from_property_id(property_id), value.to_string() });
+ });
+ quick_sort(properties, [](auto& a, auto& b) { return a.name < b.name; });
+
+ for (auto& property : properties) {
+ for (size_t i = 0; i < indent; ++i)
+ builder.append(" ");
+ builder.appendf(" (%s: %s)\n", property.name.characters(), property.value.characters());
+ }
+ }
+
+ ++indent;
+ layout_node.for_each_child([&](auto& child) {
+ dump_tree(builder, child, show_box_model, show_specified_style, interactive);
+ });
+ --indent;
+}
+
+void dump_selector(const CSS::Selector& selector)
+{
+ dbgprintf(" CSS::Selector:\n");
+
+ for (auto& complex_selector : selector.complex_selectors()) {
+ dbgprintf(" ");
+
+ const char* relation_description = "";
+ switch (complex_selector.relation) {
+ case CSS::Selector::ComplexSelector::Relation::None:
+ relation_description = "None";
+ break;
+ case CSS::Selector::ComplexSelector::Relation::ImmediateChild:
+ relation_description = "ImmediateChild";
+ break;
+ case CSS::Selector::ComplexSelector::Relation::Descendant:
+ relation_description = "Descendant";
+ break;
+ case CSS::Selector::ComplexSelector::Relation::AdjacentSibling:
+ relation_description = "AdjacentSibling";
+ break;
+ case CSS::Selector::ComplexSelector::Relation::GeneralSibling:
+ relation_description = "GeneralSibling";
+ break;
+ }
+
+ if (*relation_description)
+ dbgprintf("{%s} ", relation_description);
+
+ for (size_t i = 0; i < complex_selector.compound_selector.size(); ++i) {
+ auto& simple_selector = complex_selector.compound_selector[i];
+ const char* type_description = "Unknown";
+ switch (simple_selector.type) {
+ case CSS::Selector::SimpleSelector::Type::Invalid:
+ type_description = "Invalid";
+ break;
+ case CSS::Selector::SimpleSelector::Type::Universal:
+ type_description = "Universal";
+ break;
+ case CSS::Selector::SimpleSelector::Type::Id:
+ type_description = "Id";
+ break;
+ case CSS::Selector::SimpleSelector::Type::Class:
+ type_description = "Class";
+ break;
+ case CSS::Selector::SimpleSelector::Type::TagName:
+ type_description = "TagName";
+ break;
+ }
+ const char* attribute_match_type_description = "";
+ switch (simple_selector.attribute_match_type) {
+ case CSS::Selector::SimpleSelector::AttributeMatchType::None:
+ break;
+ case CSS::Selector::SimpleSelector::AttributeMatchType::HasAttribute:
+ attribute_match_type_description = "HasAttribute";
+ break;
+ case CSS::Selector::SimpleSelector::AttributeMatchType::ExactValueMatch:
+ attribute_match_type_description = "ExactValueMatch";
+ break;
+ case CSS::Selector::SimpleSelector::AttributeMatchType::Contains:
+ attribute_match_type_description = "Contains";
+ break;
+ }
+
+ const char* pseudo_class_description = "";
+ switch (simple_selector.pseudo_class) {
+ case CSS::Selector::SimpleSelector::PseudoClass::Link:
+ pseudo_class_description = "Link";
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::Visited:
+ pseudo_class_description = "Visited";
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::None:
+ pseudo_class_description = "None";
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::Root:
+ pseudo_class_description = "Root";
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::Focus:
+ pseudo_class_description = "Focus";
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::Empty:
+ pseudo_class_description = "Empty";
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::Hover:
+ pseudo_class_description = "Hover";
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::LastChild:
+ pseudo_class_description = "LastChild";
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::FirstChild:
+ pseudo_class_description = "FirstChild";
+ break;
+ case CSS::Selector::SimpleSelector::PseudoClass::OnlyChild:
+ pseudo_class_description = "OnlyChild";
+ break;
+ }
+
+ dbgprintf("%s:%s", type_description, simple_selector.value.characters());
+ if (simple_selector.pseudo_class != CSS::Selector::SimpleSelector::PseudoClass::None)
+ dbgprintf(" pseudo_class=%s", pseudo_class_description);
+ if (simple_selector.attribute_match_type != CSS::Selector::SimpleSelector::AttributeMatchType::None) {
+ dbgprintf(" [%s, name='%s', value='%s']", attribute_match_type_description, simple_selector.attribute_name.characters(), simple_selector.attribute_value.characters());
+ }
+
+ if (i != complex_selector.compound_selector.size() - 1)
+ dbgprintf(", ");
+ }
+ dbgprintf("\n");
+ }
+}
+
+void dump_rule(const CSS::StyleRule& rule)
+{
+ dbgprintf("Rule:\n");
+ for (auto& selector : rule.selectors()) {
+ dump_selector(selector);
+ }
+ dbgprintf(" Declarations:\n");
+ for (auto& property : rule.declaration().properties()) {
+ dbgprintf(" %s: '%s'\n", CSS::string_from_property_id(property.property_id), property.value->to_string().characters());
+ }
+}
+
+void dump_sheet(const CSS::StyleSheet& sheet)
+{
+ dbgprintf("StyleSheet{%p}: %zu rule(s)\n", &sheet, sheet.rules().size());
+
+ for (auto& rule : sheet.rules()) {
+ dump_rule(rule);
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Dump.h b/Userland/Libraries/LibWeb/Dump.h
new file mode 100644
index 0000000000..ab1d271a42
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Dump.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibWeb/Forward.h>
+
+namespace Web {
+
+void dump_tree(const DOM::Node&);
+void dump_tree(StringBuilder&, const Layout::Node&, bool show_box_model = false, bool show_specified_style = false, bool colorize = false);
+void dump_tree(const Layout::Node&, bool show_box_model = false, bool show_specified_style = false);
+void dump_sheet(const CSS::StyleSheet&);
+void dump_rule(const CSS::StyleRule&);
+void dump_selector(const CSS::Selector&);
+
+}
diff --git a/Userland/Libraries/LibWeb/DumpLayoutTree/CMakeLists.txt b/Userland/Libraries/LibWeb/DumpLayoutTree/CMakeLists.txt
new file mode 100644
index 0000000000..11a7ce62f5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DumpLayoutTree/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(SOURCES
+ main.cpp
+)
+
+serenity_bin(DumpLayoutTree)
+target_link_libraries(DumpLayoutTree LibWeb)
diff --git a/Userland/Libraries/LibWeb/DumpLayoutTree/main.cpp b/Userland/Libraries/LibWeb/DumpLayoutTree/main.cpp
new file mode 100644
index 0000000000..f1def66b6e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DumpLayoutTree/main.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/ArgsParser.h>
+#include <LibGUI/Application.h>
+#include <LibWeb/Dump.h>
+#include <LibWeb/InProcessWebView.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+#include <stdlib.h>
+
+int main(int argc, char** argv)
+{
+ auto app = GUI::Application::construct(argc, argv);
+ auto window = GUI::Window::construct();
+ window->set_title("DumpLayoutTree");
+ window->resize(800, 600);
+ window->show();
+ auto& web_view = window->set_main_widget<Web::InProcessWebView>();
+ web_view.load(URL::create_with_file_protocol(argv[1]));
+ web_view.on_load_finish = [&](auto&) {
+ auto* document = web_view.document();
+ if (!document) {
+ warnln("No document.");
+ _exit(1);
+ }
+ auto* layout_root = document->layout_node();
+ if (!layout_root) {
+ warnln("No layout tree.");
+ _exit(1);
+ }
+ StringBuilder builder;
+ Web::dump_tree(builder, *layout_root);
+ write(STDOUT_FILENO, builder.string_view().characters_without_null_termination(), builder.length());
+ _exit(0);
+ };
+ return app->exec();
+}
diff --git a/Userland/Libraries/LibWeb/FontCache.cpp b/Userland/Libraries/LibWeb/FontCache.cpp
new file mode 100644
index 0000000000..c2ba8e7050
--- /dev/null
+++ b/Userland/Libraries/LibWeb/FontCache.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/Font.h>
+#include <LibWeb/FontCache.h>
+
+FontCache& FontCache::the()
+{
+ static FontCache cache;
+ return cache;
+}
+
+RefPtr<Gfx::Font> FontCache::get(const FontSelector& font_selector) const
+{
+ auto cached_font = m_fonts.get(font_selector);
+ if (cached_font.has_value())
+ return cached_font.value();
+ return nullptr;
+}
+
+void FontCache::set(const FontSelector& font_selector, NonnullRefPtr<Gfx::Font> font)
+{
+ m_fonts.set(font_selector, move(font));
+}
diff --git a/Userland/Libraries/LibWeb/FontCache.h b/Userland/Libraries/LibWeb/FontCache.h
new file mode 100644
index 0000000000..8cc554a8f4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/FontCache.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <AK/HashMap.h>
+#include <AK/String.h>
+#include <LibGfx/Forward.h>
+
+struct FontSelector {
+ FlyString family;
+ int size { 0 };
+ int weight { 0 };
+
+ bool operator==(const FontSelector& other) const
+ {
+ return family == other.family && size == other.size && weight == other.weight;
+ }
+};
+
+namespace AK {
+template<>
+struct Traits<FontSelector> : public GenericTraits<FontSelector> {
+ static unsigned hash(const FontSelector& key) { return pair_int_hash(pair_int_hash(key.family.hash(), key.weight), key.size); }
+};
+}
+
+class FontCache {
+public:
+ static FontCache& the();
+ RefPtr<Gfx::Font> get(const FontSelector&) const;
+ void set(const FontSelector&, NonnullRefPtr<Gfx::Font>);
+
+private:
+ FontCache() { }
+ mutable HashMap<FontSelector, NonnullRefPtr<Gfx::Font>> m_fonts;
+};
diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h
new file mode 100644
index 0000000000..445b7222fa
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Forward.h
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace Web::CSS {
+class Length;
+class Selector;
+class StyleDeclaration;
+class StyleProperties;
+class StyleResolver;
+class StyleRule;
+class StyleSheet;
+enum class Display;
+}
+
+namespace Web::DOM {
+class CharacterData;
+class Comment;
+class Document;
+class DocumentFragment;
+class DocumentType;
+class DOMImplementation;
+class Element;
+class Event;
+class EventHandler;
+class EventListener;
+class EventTarget;
+class MouseEvent;
+class Node;
+class ParentNode;
+class Position;
+class Text;
+class Timer;
+class Window;
+class Range;
+enum class QuirksMode;
+}
+
+namespace Web::HTML {
+class CanvasRenderingContext2D;
+class HTMLAnchorElement;
+class HTMLAreaElement;
+class HTMLAudioElement;
+class HTMLBaseElement;
+class HTMLBlinkElement;
+class HTMLBodyElement;
+class HTMLBRElement;
+class HTMLButtonElement;
+class HTMLCanvasElement;
+class HTMLDataElement;
+class HTMLDataListElement;
+class HTMLDetailsElement;
+class HTMLDialogElement;
+class HTMLDirectoryElement;
+class HTMLDivElement;
+class HTMLDListElement;
+class HTMLDocumentParser;
+class HTMLElement;
+class HTMLEmbedElement;
+class HTMLFieldSetElement;
+class HTMLFontElement;
+class HTMLFormElement;
+class HTMLFrameElement;
+class HTMLFrameSetElement;
+class HTMLHeadElement;
+class HTMLHeadingElement;
+class HTMLHRElement;
+class HTMLHtmlElement;
+class HTMLIFrameElement;
+class HTMLImageElement;
+class HTMLInputElement;
+class HTMLLabelElement;
+class HTMLLegendElement;
+class HTMLLIElement;
+class HTMLLinkElement;
+class HTMLMapElement;
+class HTMLMarqueeElement;
+class HTMLMediaElement;
+class HTMLMenuElement;
+class HTMLMetaElement;
+class HTMLMeterElement;
+class HTMLModElement;
+class HTMLObjectElement;
+class HTMLOListElement;
+class HTMLOptGroupElement;
+class HTMLOptionElement;
+class HTMLOutputElement;
+class HTMLParagraphElement;
+class HTMLParamElement;
+class HTMLPictureElement;
+class HTMLPreElement;
+class HTMLProgressElement;
+class HTMLQuoteElement;
+class HTMLScriptElement;
+class HTMLSelectElement;
+class HTMLSlotElement;
+class HTMLSourceElement;
+class HTMLSpanElement;
+class HTMLStyleElement;
+class HTMLTableCaptionElement;
+class HTMLTableCellElement;
+class HTMLTableColElement;
+class HTMLTableElement;
+class HTMLTableRowElement;
+class HTMLTableSectionElement;
+class HTMLTemplateElement;
+class HTMLTextAreaElement;
+class HTMLTimeElement;
+class HTMLTitleElement;
+class HTMLTrackElement;
+class HTMLUListElement;
+class HTMLUnknownElement;
+class HTMLVideoElement;
+class ImageData;
+}
+
+namespace Web::HighResolutionTime {
+class Performance;
+}
+
+namespace Web::SVG {
+class SVGElement;
+class SVGGeometryElement;
+class SVGGraphicsElement;
+class SVGPathElement;
+class SVGSVGElement;
+}
+
+namespace Web::Layout {
+enum class LayoutMode;
+enum class PaintPhase;
+class BlockBox;
+class BlockFormattingContext;
+class Box;
+class ButtonBox;
+class CheckBox;
+class FormattingContext;
+class InitialContainingBlockBox;
+class InlineFormattingContext;
+class LineBox;
+class LineBoxFragment;
+class Node;
+class NodeWithStyle;
+class ReplacedBox;
+}
+
+namespace Web {
+class EventHandler;
+class EditEventHandler;
+class Frame;
+class FrameLoader;
+class InProcessWebView;
+class LoadRequest;
+class Origin;
+class OutOfProcessWebView;
+class Page;
+class PageClient;
+class PaintContext;
+class Resource;
+class ResourceLoader;
+class StackingContext;
+class XMLHttpRequest;
+}
+
+namespace Web::Bindings {
+
+class CanvasRenderingContext2DWrapper;
+class CharacterDataWrapper;
+class CommentWrapper;
+class DocumentFragmentWrapper;
+class DocumentTypeWrapper;
+class DocumentWrapper;
+class DOMImplementationWrapper;
+class ElementWrapper;
+class EventListenerWrapper;
+class EventTargetWrapper;
+class EventWrapper;
+class HTMLAnchorElementWrapper;
+class HTMLAreaElementWrapper;
+class HTMLAudioElementWrapper;
+class HTMLBaseElementWrapper;
+class HTMLBodyElementWrapper;
+class HTMLBRElementWrapper;
+class HTMLButtonElementWrapper;
+class HTMLCanvasElementWrapper;
+class HTMLDataElementWrapper;
+class HTMLDataListElementWrapper;
+class HTMLDetailsElementWrapper;
+class HTMLDialogElementWrapper;
+class HTMLDirectoryElementWrapper;
+class HTMLDivElementWrapper;
+class HTMLDListElementWrapper;
+class HTMLElementWrapper;
+class HTMLEmbedElementWrapper;
+class HTMLFieldSetElementWrapper;
+class HTMLFontElementWrapper;
+class HTMLFormElementWrapper;
+class HTMLFrameElementWrapper;
+class HTMLFrameSetElementWrapper;
+class HTMLHRElementWrapper;
+class HTMLHeadElementWrapper;
+class HTMLHeadingElementWrapper;
+class HTMLHtmlElementWrapper;
+class HTMLIFrameElementWrapper;
+class HTMLImageElementWrapper;
+class HTMLInputElementWrapper;
+class HTMLLabelElementWrapper;
+class HTMLLegendElementWrapper;
+class HTMLLIElementWrapper;
+class HTMLLinkElementWrapper;
+class HTMLMapElementWrapper;
+class HTMLMarqueeElementWrapper;
+class HTMLMediaElementWrapper;
+class HTMLMenuElementWrapper;
+class HTMLMetaElementWrapper;
+class HTMLMeterElementWrapper;
+class HTMLModElementWrapper;
+class HTMLObjectElementWrapper;
+class HTMLOListElementWrapper;
+class HTMLOptGroupElementWrapper;
+class HTMLOptionElementWrapper;
+class HTMLOutputElementWrapper;
+class HTMLParagraphElementWrapper;
+class HTMLParamElementWrapper;
+class HTMLPictureElementWrapper;
+class HTMLPreElementWrapper;
+class HTMLProgressElementWrapper;
+class HTMLQuoteElementWrapper;
+class HTMLScriptElementWrapper;
+class HTMLSelectElementWrapper;
+class HTMLSlotElementWrapper;
+class HTMLSourceElementWrapper;
+class HTMLSpanElementWrapper;
+class HTMLStyleElementWrapper;
+class HTMLTableCaptionElementWrapper;
+class HTMLTableCellElementWrapper;
+class HTMLTableColElementWrapper;
+class HTMLTableElementWrapper;
+class HTMLTableRowElementWrapper;
+class HTMLTableSectionElementWrapper;
+class HTMLTemplateElementWrapper;
+class HTMLTextAreaElementWrapper;
+class HTMLTimeElementWrapper;
+class HTMLTitleElementWrapper;
+class HTMLTrackElementWrapper;
+class HTMLUListElementWrapper;
+class HTMLUnknownElementWrapper;
+class HTMLVideoElementWrapper;
+class ImageDataWrapper;
+class LocationObject;
+class MouseEventWrapper;
+class NodeWrapper;
+class PerformanceWrapper;
+class ScriptExecutionContext;
+class SubmitEventWrapper;
+class SVGElementWrapper;
+class SVGGeometryElementWrapper;
+class SVGGraphicsElementWrapper;
+class SVGPathElementWrapper;
+class SVGSVGElementWrapper;
+class TextWrapper;
+class UIEventWrapper;
+class WindowObject;
+class Wrappable;
+class Wrapper;
+class XMLHttpRequestConstructor;
+class XMLHttpRequestPrototype;
+class XMLHttpRequestWrapper;
+class RangeConstructor;
+class RangePrototype;
+class RangeWrapper;
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/AttributeNames.cpp b/Userland/Libraries/LibWeb/HTML/AttributeNames.cpp
new file mode 100644
index 0000000000..e7a21e6b39
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/AttributeNames.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/AttributeNames.h>
+
+namespace Web {
+namespace HTML {
+namespace AttributeNames {
+
+#define __ENUMERATE_HTML_ATTRIBUTE(name) FlyString name;
+ENUMERATE_HTML_ATTRIBUTES
+#undef __ENUMERATE_HTML_ATTRIBUTE
+
+[[gnu::constructor]] static void initialize()
+{
+ static bool s_initialized = false;
+ if (s_initialized)
+ return;
+
+#define __ENUMERATE_HTML_ATTRIBUTE(name) \
+ name = #name;
+ ENUMERATE_HTML_ATTRIBUTES
+#undef __ENUMERATE_HTML_ATTRIBUTE
+
+ // NOTE: Special cases for C++ keywords.
+ class_ = "class";
+ for_ = "for";
+ default_ = "default";
+ char_ = "char";
+
+ // NOTE: Special cases for attributes with dashes in them.
+ accept_charset = "accept-charset";
+ http_equiv = "http-equiv";
+
+ s_initialized = true;
+}
+
+}
+}
+}
diff --git a/Userland/Libraries/LibWeb/HTML/AttributeNames.h b/Userland/Libraries/LibWeb/HTML/AttributeNames.h
new file mode 100644
index 0000000000..670ec9bdb4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/AttributeNames.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+
+namespace Web {
+namespace HTML {
+namespace AttributeNames {
+
+#define ENUMERATE_HTML_ATTRIBUTES \
+ __ENUMERATE_HTML_ATTRIBUTE(abbr) \
+ __ENUMERATE_HTML_ATTRIBUTE(accept) \
+ __ENUMERATE_HTML_ATTRIBUTE(accept_charset) \
+ __ENUMERATE_HTML_ATTRIBUTE(action) \
+ __ENUMERATE_HTML_ATTRIBUTE(align) \
+ __ENUMERATE_HTML_ATTRIBUTE(alink) \
+ __ENUMERATE_HTML_ATTRIBUTE(allow) \
+ __ENUMERATE_HTML_ATTRIBUTE(allowfullscreen) \
+ __ENUMERATE_HTML_ATTRIBUTE(alt) \
+ __ENUMERATE_HTML_ATTRIBUTE(archive) \
+ __ENUMERATE_HTML_ATTRIBUTE(async) \
+ __ENUMERATE_HTML_ATTRIBUTE(autoplay) \
+ __ENUMERATE_HTML_ATTRIBUTE(axis) \
+ __ENUMERATE_HTML_ATTRIBUTE(background) \
+ __ENUMERATE_HTML_ATTRIBUTE(behaviour) \
+ __ENUMERATE_HTML_ATTRIBUTE(bgcolor) \
+ __ENUMERATE_HTML_ATTRIBUTE(border) \
+ __ENUMERATE_HTML_ATTRIBUTE(cellpadding) \
+ __ENUMERATE_HTML_ATTRIBUTE(cellspacing) \
+ __ENUMERATE_HTML_ATTRIBUTE(char_) \
+ __ENUMERATE_HTML_ATTRIBUTE(charoff) \
+ __ENUMERATE_HTML_ATTRIBUTE(charset) \
+ __ENUMERATE_HTML_ATTRIBUTE(checked) \
+ __ENUMERATE_HTML_ATTRIBUTE(cite) \
+ __ENUMERATE_HTML_ATTRIBUTE(class_) \
+ __ENUMERATE_HTML_ATTRIBUTE(clear) \
+ __ENUMERATE_HTML_ATTRIBUTE(code) \
+ __ENUMERATE_HTML_ATTRIBUTE(codetype) \
+ __ENUMERATE_HTML_ATTRIBUTE(color) \
+ __ENUMERATE_HTML_ATTRIBUTE(cols) \
+ __ENUMERATE_HTML_ATTRIBUTE(colspan) \
+ __ENUMERATE_HTML_ATTRIBUTE(compact) \
+ __ENUMERATE_HTML_ATTRIBUTE(content) \
+ __ENUMERATE_HTML_ATTRIBUTE(contenteditable) \
+ __ENUMERATE_HTML_ATTRIBUTE(controls) \
+ __ENUMERATE_HTML_ATTRIBUTE(coords) \
+ __ENUMERATE_HTML_ATTRIBUTE(data) \
+ __ENUMERATE_HTML_ATTRIBUTE(datetime) \
+ __ENUMERATE_HTML_ATTRIBUTE(declare) \
+ __ENUMERATE_HTML_ATTRIBUTE(default_) \
+ __ENUMERATE_HTML_ATTRIBUTE(defer) \
+ __ENUMERATE_HTML_ATTRIBUTE(disabled) \
+ __ENUMERATE_HTML_ATTRIBUTE(download) \
+ __ENUMERATE_HTML_ATTRIBUTE(direction) \
+ __ENUMERATE_HTML_ATTRIBUTE(dirname) \
+ __ENUMERATE_HTML_ATTRIBUTE(event) \
+ __ENUMERATE_HTML_ATTRIBUTE(face) \
+ __ENUMERATE_HTML_ATTRIBUTE(for_) \
+ __ENUMERATE_HTML_ATTRIBUTE(formnovalidate) \
+ __ENUMERATE_HTML_ATTRIBUTE(formtarget) \
+ __ENUMERATE_HTML_ATTRIBUTE(frame) \
+ __ENUMERATE_HTML_ATTRIBUTE(frameborder) \
+ __ENUMERATE_HTML_ATTRIBUTE(headers) \
+ __ENUMERATE_HTML_ATTRIBUTE(height) \
+ __ENUMERATE_HTML_ATTRIBUTE(hidden) \
+ __ENUMERATE_HTML_ATTRIBUTE(href) \
+ __ENUMERATE_HTML_ATTRIBUTE(hreflang) \
+ __ENUMERATE_HTML_ATTRIBUTE(http_equiv) \
+ __ENUMERATE_HTML_ATTRIBUTE(id) \
+ __ENUMERATE_HTML_ATTRIBUTE(imagesizes) \
+ __ENUMERATE_HTML_ATTRIBUTE(imagesrcset) \
+ __ENUMERATE_HTML_ATTRIBUTE(integrity) \
+ __ENUMERATE_HTML_ATTRIBUTE(ismap) \
+ __ENUMERATE_HTML_ATTRIBUTE(label) \
+ __ENUMERATE_HTML_ATTRIBUTE(lang) \
+ __ENUMERATE_HTML_ATTRIBUTE(link) \
+ __ENUMERATE_HTML_ATTRIBUTE(longdesc) \
+ __ENUMERATE_HTML_ATTRIBUTE(loop) \
+ __ENUMERATE_HTML_ATTRIBUTE(max) \
+ __ENUMERATE_HTML_ATTRIBUTE(marginheight) \
+ __ENUMERATE_HTML_ATTRIBUTE(marginwidth) \
+ __ENUMERATE_HTML_ATTRIBUTE(media) \
+ __ENUMERATE_HTML_ATTRIBUTE(method) \
+ __ENUMERATE_HTML_ATTRIBUTE(min) \
+ __ENUMERATE_HTML_ATTRIBUTE(multiple) \
+ __ENUMERATE_HTML_ATTRIBUTE(name) \
+ __ENUMERATE_HTML_ATTRIBUTE(nohref) \
+ __ENUMERATE_HTML_ATTRIBUTE(nomodule) \
+ __ENUMERATE_HTML_ATTRIBUTE(noshade) \
+ __ENUMERATE_HTML_ATTRIBUTE(novalidate) \
+ __ENUMERATE_HTML_ATTRIBUTE(nowrap) \
+ __ENUMERATE_HTML_ATTRIBUTE(open) \
+ __ENUMERATE_HTML_ATTRIBUTE(pattern) \
+ __ENUMERATE_HTML_ATTRIBUTE(ping) \
+ __ENUMERATE_HTML_ATTRIBUTE(placeholder) \
+ __ENUMERATE_HTML_ATTRIBUTE(playsinline) \
+ __ENUMERATE_HTML_ATTRIBUTE(poster) \
+ __ENUMERATE_HTML_ATTRIBUTE(readonly) \
+ __ENUMERATE_HTML_ATTRIBUTE(rel) \
+ __ENUMERATE_HTML_ATTRIBUTE(required) \
+ __ENUMERATE_HTML_ATTRIBUTE(rev) \
+ __ENUMERATE_HTML_ATTRIBUTE(reversed) \
+ __ENUMERATE_HTML_ATTRIBUTE(rows) \
+ __ENUMERATE_HTML_ATTRIBUTE(rules) \
+ __ENUMERATE_HTML_ATTRIBUTE(scheme) \
+ __ENUMERATE_HTML_ATTRIBUTE(scrolling) \
+ __ENUMERATE_HTML_ATTRIBUTE(selected) \
+ __ENUMERATE_HTML_ATTRIBUTE(shape) \
+ __ENUMERATE_HTML_ATTRIBUTE(size) \
+ __ENUMERATE_HTML_ATTRIBUTE(sizes) \
+ __ENUMERATE_HTML_ATTRIBUTE(src) \
+ __ENUMERATE_HTML_ATTRIBUTE(srcdoc) \
+ __ENUMERATE_HTML_ATTRIBUTE(srclang) \
+ __ENUMERATE_HTML_ATTRIBUTE(srcset) \
+ __ENUMERATE_HTML_ATTRIBUTE(standby) \
+ __ENUMERATE_HTML_ATTRIBUTE(step) \
+ __ENUMERATE_HTML_ATTRIBUTE(style) \
+ __ENUMERATE_HTML_ATTRIBUTE(summary) \
+ __ENUMERATE_HTML_ATTRIBUTE(target) \
+ __ENUMERATE_HTML_ATTRIBUTE(text) \
+ __ENUMERATE_HTML_ATTRIBUTE(title) \
+ __ENUMERATE_HTML_ATTRIBUTE(type) \
+ __ENUMERATE_HTML_ATTRIBUTE(usemap) \
+ __ENUMERATE_HTML_ATTRIBUTE(value) \
+ __ENUMERATE_HTML_ATTRIBUTE(valuetype) \
+ __ENUMERATE_HTML_ATTRIBUTE(valign) \
+ __ENUMERATE_HTML_ATTRIBUTE(version) \
+ __ENUMERATE_HTML_ATTRIBUTE(vlink) \
+ __ENUMERATE_HTML_ATTRIBUTE(width) \
+ __ENUMERATE_HTML_ATTRIBUTE(wrap)
+
+#define __ENUMERATE_HTML_ATTRIBUTE(name) extern FlyString name;
+ENUMERATE_HTML_ATTRIBUTES
+#undef __ENUMERATE_HTML_ATTRIBUTE
+
+}
+}
+}
diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp
new file mode 100644
index 0000000000..d029e77eb8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/OwnPtr.h>
+#include <LibGfx/Painter.h>
+#include <LibWeb/Bindings/CanvasRenderingContext2DWrapper.h>
+#include <LibWeb/HTML/CanvasRenderingContext2D.h>
+#include <LibWeb/HTML/HTMLCanvasElement.h>
+#include <LibWeb/HTML/HTMLImageElement.h>
+#include <LibWeb/HTML/ImageData.h>
+
+namespace Web::HTML {
+
+CanvasRenderingContext2D::CanvasRenderingContext2D(HTMLCanvasElement& element)
+ : m_element(element)
+{
+}
+
+CanvasRenderingContext2D::~CanvasRenderingContext2D()
+{
+}
+
+void CanvasRenderingContext2D::set_fill_style(String style)
+{
+ m_fill_style = Gfx::Color::from_string(style).value_or(Color::Black);
+}
+
+String CanvasRenderingContext2D::fill_style() const
+{
+ return m_fill_style.to_string();
+}
+
+void CanvasRenderingContext2D::fill_rect(float x, float y, float width, float height)
+{
+ auto painter = this->painter();
+ if (!painter)
+ return;
+
+ auto rect = m_transform.map(Gfx::FloatRect(x, y, width, height));
+ painter->fill_rect(enclosing_int_rect(rect), m_fill_style);
+ did_draw(rect);
+}
+
+void CanvasRenderingContext2D::set_stroke_style(String style)
+{
+ m_stroke_style = Gfx::Color::from_string(style).value_or(Color::Black);
+}
+
+String CanvasRenderingContext2D::stroke_style() const
+{
+ return m_fill_style.to_string();
+}
+
+void CanvasRenderingContext2D::stroke_rect(float x, float y, float width, float height)
+{
+ auto painter = this->painter();
+ if (!painter)
+ return;
+
+ auto rect = m_transform.map(Gfx::FloatRect(x, y, width, height));
+
+ auto top_left = m_transform.map(Gfx::FloatPoint(x, y)).to_type<int>();
+ auto top_right = m_transform.map(Gfx::FloatPoint(x + width - 1, y)).to_type<int>();
+ auto bottom_left = m_transform.map(Gfx::FloatPoint(x, y + height - 1)).to_type<int>();
+ auto bottom_right = m_transform.map(Gfx::FloatPoint(x + width - 1, y + height - 1)).to_type<int>();
+
+ painter->draw_line(top_left, top_right, m_stroke_style, m_line_width);
+ painter->draw_line(top_right, bottom_right, m_stroke_style, m_line_width);
+ painter->draw_line(bottom_right, bottom_left, m_stroke_style, m_line_width);
+ painter->draw_line(bottom_left, top_left, m_stroke_style, m_line_width);
+
+ did_draw(rect);
+}
+
+void CanvasRenderingContext2D::draw_image(const HTMLImageElement& image_element, float x, float y)
+{
+ if (!image_element.bitmap())
+ return;
+
+ auto painter = this->painter();
+ if (!painter)
+ return;
+
+ auto src_rect = image_element.bitmap()->rect();
+ Gfx::FloatRect dst_rect = { x, y, (float)image_element.bitmap()->width(), (float)image_element.bitmap()->height() };
+ auto rect = m_transform.map(dst_rect);
+
+ painter->draw_scaled_bitmap(enclosing_int_rect(rect), *image_element.bitmap(), src_rect);
+}
+
+void CanvasRenderingContext2D::scale(float sx, float sy)
+{
+ dbg() << "CanvasRenderingContext2D::scale(): " << sx << ", " << sy;
+ m_transform.scale(sx, sy);
+}
+
+void CanvasRenderingContext2D::translate(float tx, float ty)
+{
+ dbg() << "CanvasRenderingContext2D::translate(): " << tx << ", " << ty;
+ m_transform.translate(tx, ty);
+}
+
+void CanvasRenderingContext2D::rotate(float radians)
+{
+ dbg() << "CanvasRenderingContext2D::rotate(): " << radians;
+ m_transform.rotate_radians(radians);
+}
+
+void CanvasRenderingContext2D::did_draw(const Gfx::FloatRect&)
+{
+ // FIXME: Make use of the rect to reduce the invalidated area when possible.
+ if (!m_element)
+ return;
+ if (!m_element->layout_node())
+ return;
+ m_element->layout_node()->set_needs_display();
+}
+
+OwnPtr<Gfx::Painter> CanvasRenderingContext2D::painter()
+{
+ if (!m_element)
+ return {};
+
+ if (!m_element->bitmap()) {
+ if (!m_element->create_bitmap())
+ return {};
+ }
+
+ return make<Gfx::Painter>(*m_element->bitmap());
+}
+
+void CanvasRenderingContext2D::begin_path()
+{
+ m_path = Gfx::Path();
+}
+
+void CanvasRenderingContext2D::close_path()
+{
+ m_path.close();
+}
+
+void CanvasRenderingContext2D::move_to(float x, float y)
+{
+ m_path.move_to({ x, y });
+}
+
+void CanvasRenderingContext2D::line_to(float x, float y)
+{
+ m_path.line_to({ x, y });
+}
+
+void CanvasRenderingContext2D::quadratic_curve_to(float cx, float cy, float x, float y)
+{
+ m_path.quadratic_bezier_curve_to({ cx, cy }, { x, y });
+}
+
+void CanvasRenderingContext2D::stroke()
+{
+ auto painter = this->painter();
+ if (!painter)
+ return;
+
+ painter->stroke_path(m_path, m_stroke_style, m_line_width);
+}
+
+void CanvasRenderingContext2D::fill(Gfx::Painter::WindingRule winding)
+{
+ auto painter = this->painter();
+ if (!painter)
+ return;
+
+ auto path = m_path;
+ path.close_all_subpaths();
+ painter->fill_path(path, m_fill_style, winding);
+}
+
+void CanvasRenderingContext2D::fill(const String& fill_rule)
+{
+ if (fill_rule == "evenodd")
+ return fill(Gfx::Painter::WindingRule::EvenOdd);
+ return fill(Gfx::Painter::WindingRule::Nonzero);
+}
+
+RefPtr<ImageData> CanvasRenderingContext2D::create_image_data(int width, int height) const
+{
+ if (!wrapper()) {
+ dbgln("Hmm! Attempted to create ImageData for wrapper-less CRC2D.");
+ return {};
+ }
+ return ImageData::create_with_size(wrapper()->global_object(), width, height);
+}
+
+void CanvasRenderingContext2D::put_image_data(const ImageData& image_data, float x, float y)
+{
+ auto painter = this->painter();
+ if (!painter)
+ return;
+
+ painter->blit(Gfx::IntPoint(x, y), image_data.bitmap(), image_data.bitmap().rect());
+
+ did_draw(Gfx::FloatRect(x, y, image_data.width(), image_data.height()));
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h
new file mode 100644
index 0000000000..58cccfc6e6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/RefCounted.h>
+#include <LibGfx/AffineTransform.h>
+#include <LibGfx/Color.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/Painter.h>
+#include <LibGfx/Path.h>
+#include <LibWeb/Bindings/Wrappable.h>
+
+namespace Web::HTML {
+
+class CanvasRenderingContext2D
+ : public RefCounted<CanvasRenderingContext2D>
+ , public Bindings::Wrappable {
+
+ AK_MAKE_NONCOPYABLE(CanvasRenderingContext2D);
+ AK_MAKE_NONMOVABLE(CanvasRenderingContext2D);
+
+public:
+ using WrapperType = Bindings::CanvasRenderingContext2DWrapper;
+
+ static NonnullRefPtr<CanvasRenderingContext2D> create(HTMLCanvasElement& element) { return adopt(*new CanvasRenderingContext2D(element)); }
+ ~CanvasRenderingContext2D();
+
+ void set_fill_style(String);
+ String fill_style() const;
+
+ void set_stroke_style(String);
+ String stroke_style() const;
+
+ void fill_rect(float x, float y, float width, float height);
+ void stroke_rect(float x, float y, float width, float height);
+
+ void draw_image(const HTMLImageElement&, float x, float y);
+
+ void scale(float sx, float sy);
+ void translate(float x, float y);
+ void rotate(float degrees);
+
+ void set_line_width(float line_width) { m_line_width = line_width; }
+ float line_width() const { return m_line_width; }
+
+ void begin_path();
+ void close_path();
+ void move_to(float x, float y);
+ void line_to(float x, float y);
+ void quadratic_curve_to(float cx, float cy, float x, float y);
+ void stroke();
+
+ // FIXME: We should only have one fill(), really. Fix the wrapper generator!
+ void fill(Gfx::Painter::WindingRule);
+ void fill(const String& fill_rule);
+
+ RefPtr<ImageData> create_image_data(int width, int height) const;
+ void put_image_data(const ImageData&, float x, float y);
+
+ HTMLCanvasElement* canvas() { return m_element; }
+
+private:
+ explicit CanvasRenderingContext2D(HTMLCanvasElement&);
+
+ void did_draw(const Gfx::FloatRect&);
+
+ OwnPtr<Gfx::Painter> painter();
+
+ WeakPtr<HTMLCanvasElement> m_element;
+
+ Gfx::AffineTransform m_transform;
+ Gfx::Color m_fill_style;
+ Gfx::Color m_stroke_style;
+ float m_line_width { 1 };
+
+ Gfx::Path m_path;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl
new file mode 100644
index 0000000000..bcfa544c67
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl
@@ -0,0 +1,29 @@
+interface CanvasRenderingContext2D {
+
+ undefined fillRect(double x, double y, double w, double h);
+ undefined strokeRect(double x, double y, double w, double h);
+
+ undefined scale(double x, double y);
+ undefined translate(double x, double y);
+ undefined rotate(double radians);
+
+ undefined beginPath();
+ undefined closePath();
+ undefined fill(DOMString fillRule);
+ undefined stroke();
+ undefined moveTo(double x, double y);
+ undefined lineTo(double x, double y);
+ undefined quadraticCurveTo(double cpx, double cpy, double x, double y);
+
+ undefined drawImage(HTMLImageElement image, double dx, double dy);
+
+ attribute DOMString fillStyle;
+ attribute DOMString strokeStyle;
+ attribute double lineWidth;
+
+ ImageData createImageData(double sw, double sh);
+ undefined putImageData(ImageData imagedata, double dx, double dy);
+
+ readonly attribute HTMLCanvasElement canvas;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/EventNames.cpp b/Userland/Libraries/LibWeb/HTML/EventNames.cpp
new file mode 100644
index 0000000000..6856aa2d03
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/EventNames.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/EventNames.h>
+
+namespace Web::HTML::EventNames {
+
+#define __ENUMERATE_HTML_EVENT(name) FlyString name;
+ENUMERATE_HTML_EVENTS
+#undef __ENUMERATE_HTML_EVENT
+
+[[gnu::constructor]] static void initialize()
+{
+ static bool s_initialized = false;
+ if (s_initialized)
+ return;
+
+#define __ENUMERATE_HTML_EVENT(name) \
+ name = #name;
+ ENUMERATE_HTML_EVENTS
+#undef __ENUMERATE_HTML_EVENT
+
+ s_initialized = true;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/EventNames.h b/Userland/Libraries/LibWeb/HTML/EventNames.h
new file mode 100644
index 0000000000..d36056ef01
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/EventNames.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+
+namespace Web::HTML::EventNames {
+
+// FIXME: Add media events https://html.spec.whatwg.org/multipage/media.html#mediaevents
+// FIXME: Add app cache events https://html.spec.whatwg.org/multipage/offline.html#appcacheevents
+// FIXME: Add drag and drop events https://html.spec.whatwg.org/multipage/dnd.html#dndevents
+
+#define ENUMERATE_HTML_EVENTS \
+ __ENUMERATE_HTML_EVENT(abort) \
+ __ENUMERATE_HTML_EVENT(DOMContentLoaded) \
+ __ENUMERATE_HTML_EVENT(afterprint) \
+ __ENUMERATE_HTML_EVENT(beforeprint) \
+ __ENUMERATE_HTML_EVENT(beforeunload) \
+ __ENUMERATE_HTML_EVENT(blur) \
+ __ENUMERATE_HTML_EVENT(cancel) \
+ __ENUMERATE_HTML_EVENT(change) \
+ __ENUMERATE_HTML_EVENT(click) \
+ __ENUMERATE_HTML_EVENT(close) \
+ __ENUMERATE_HTML_EVENT(connect) \
+ __ENUMERATE_HTML_EVENT(contextmenu) \
+ __ENUMERATE_HTML_EVENT(copy) \
+ __ENUMERATE_HTML_EVENT(cut) \
+ __ENUMERATE_HTML_EVENT(error) \
+ __ENUMERATE_HTML_EVENT(focus) \
+ __ENUMERATE_HTML_EVENT(formdata) \
+ __ENUMERATE_HTML_EVENT(hashchange) \
+ __ENUMERATE_HTML_EVENT(input) \
+ __ENUMERATE_HTML_EVENT(invalid) \
+ __ENUMERATE_HTML_EVENT(languagechange) \
+ __ENUMERATE_HTML_EVENT(load) \
+ __ENUMERATE_HTML_EVENT(message) \
+ __ENUMERATE_HTML_EVENT(messageerror) \
+ __ENUMERATE_HTML_EVENT(offline) \
+ __ENUMERATE_HTML_EVENT(online) \
+ __ENUMERATE_HTML_EVENT(open) \
+ __ENUMERATE_HTML_EVENT(pagehide) \
+ __ENUMERATE_HTML_EVENT(pageshow) \
+ __ENUMERATE_HTML_EVENT(paste) \
+ __ENUMERATE_HTML_EVENT(popstate) \
+ __ENUMERATE_HTML_EVENT(readystatechange) \
+ __ENUMERATE_HTML_EVENT(rejectionhandled) \
+ __ENUMERATE_HTML_EVENT(reset) \
+ __ENUMERATE_HTML_EVENT(securitypolicyviolation) \
+ __ENUMERATE_HTML_EVENT(select) \
+ __ENUMERATE_HTML_EVENT(slotchange) \
+ __ENUMERATE_HTML_EVENT(storage) \
+ __ENUMERATE_HTML_EVENT(submit) \
+ __ENUMERATE_HTML_EVENT(toggle) \
+ __ENUMERATE_HTML_EVENT(unhandledrejection) \
+ __ENUMERATE_HTML_EVENT(unload)
+
+#define __ENUMERATE_HTML_EVENT(name) extern FlyString name;
+ENUMERATE_HTML_EVENTS
+#undef __ENUMERATE_HTML_EVENT
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLAnchorElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLAnchorElement.cpp
new file mode 100644
index 0000000000..f79debf4cc
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLAnchorElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLAnchorElement.h>
+
+namespace Web::HTML {
+
+HTMLAnchorElement::HTMLAnchorElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLAnchorElement::~HTMLAnchorElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLAnchorElement.h b/Userland/Libraries/LibWeb/HTML/HTMLAnchorElement.h
new file mode 100644
index 0000000000..02d7349116
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLAnchorElement.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLAnchorElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLAnchorElementWrapper;
+
+ HTMLAnchorElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLAnchorElement() override;
+
+ String href() const { return attribute(HTML::AttributeNames::href); }
+ String target() const { return attribute(HTML::AttributeNames::target); }
+
+ virtual bool is_focusable() const override { return has_attribute(HTML::AttributeNames::href); }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLAnchorElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLAnchorElement.idl
new file mode 100644
index 0000000000..829b9039ee
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLAnchorElement.idl
@@ -0,0 +1,16 @@
+interface HTMLAnchorElement : HTMLElement {
+
+ [Reflect] attribute DOMString target;
+ [Reflect] attribute DOMString download;
+ [Reflect] attribute DOMString ping;
+ [Reflect] attribute DOMString rel;
+ [Reflect] attribute DOMString hreflang;
+ [Reflect] attribute DOMString type;
+
+ [Reflect] attribute DOMString coords;
+ [Reflect] attribute DOMString charset;
+ [Reflect] attribute DOMString name;
+ [Reflect] attribute DOMString rev;
+ [Reflect] attribute DOMString shape;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLAreaElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLAreaElement.cpp
new file mode 100644
index 0000000000..fe9c9a2147
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLAreaElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLAreaElement.h>
+
+namespace Web::HTML {
+
+HTMLAreaElement::HTMLAreaElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLAreaElement::~HTMLAreaElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLAreaElement.h b/Userland/Libraries/LibWeb/HTML/HTMLAreaElement.h
new file mode 100644
index 0000000000..29fa82ffa0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLAreaElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLAreaElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLAreaElementWrapper;
+
+ HTMLAreaElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLAreaElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLAreaElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLAreaElement.idl
new file mode 100644
index 0000000000..7cd0058305
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLAreaElement.idl
@@ -0,0 +1,5 @@
+interface HTMLAreaElement : HTMLElement {
+
+ [Reflect=nohref] attribute boolean noHref;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.cpp
new file mode 100644
index 0000000000..2653ebf944
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLAudioElement.h>
+
+namespace Web::HTML {
+
+HTMLAudioElement::HTMLAudioElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLMediaElement(document, qualified_name)
+{
+}
+
+HTMLAudioElement::~HTMLAudioElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.h b/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.h
new file mode 100644
index 0000000000..ce6a43b021
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLMediaElement.h>
+
+namespace Web::HTML {
+
+class HTMLAudioElement final : public HTMLMediaElement {
+public:
+ using WrapperType = Bindings::HTMLAudioElementWrapper;
+
+ HTMLAudioElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLAudioElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.idl
new file mode 100644
index 0000000000..c56ca47938
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.idl
@@ -0,0 +1,5 @@
+interface HTMLAudioElement : HTMLMediaElement {
+
+
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLBRElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLBRElement.cpp
new file mode 100644
index 0000000000..d3a0bc1b9e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLBRElement.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLBRElement.h>
+#include <LibWeb/Layout/BreakNode.h>
+
+namespace Web::HTML {
+
+HTMLBRElement::HTMLBRElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLBRElement::~HTMLBRElement()
+{
+}
+
+RefPtr<Layout::Node> HTMLBRElement::create_layout_node()
+{
+ return adopt(*new Layout::BreakNode(document(), *this));
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLBRElement.h b/Userland/Libraries/LibWeb/HTML/HTMLBRElement.h
new file mode 100644
index 0000000000..5e453c6ecb
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLBRElement.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLBRElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLBRElementWrapper;
+
+ HTMLBRElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLBRElement() override;
+
+ virtual RefPtr<Layout::Node> create_layout_node() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLBRElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLBRElement.idl
new file mode 100644
index 0000000000..a71383c890
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLBRElement.idl
@@ -0,0 +1,5 @@
+interface HTMLBRElement : HTMLElement {
+
+ [Reflect] attribute DOMString clear;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLBaseElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLBaseElement.cpp
new file mode 100644
index 0000000000..975d0bddda
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLBaseElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLBaseElement.h>
+
+namespace Web::HTML {
+
+HTMLBaseElement::HTMLBaseElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLBaseElement::~HTMLBaseElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLBaseElement.h b/Userland/Libraries/LibWeb/HTML/HTMLBaseElement.h
new file mode 100644
index 0000000000..ce2be21e0d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLBaseElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLBaseElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLBaseElementWrapper;
+
+ HTMLBaseElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLBaseElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLBaseElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLBaseElement.idl
new file mode 100644
index 0000000000..868e4a84a4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLBaseElement.idl
@@ -0,0 +1,5 @@
+interface HTMLBaseElement : HTMLElement {
+
+ [Reflect] attribute DOMString target;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLBlinkElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLBlinkElement.cpp
new file mode 100644
index 0000000000..911a8940bd
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLBlinkElement.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/Timer.h>
+#include <LibWeb/CSS/StyleProperties.h>
+#include <LibWeb/CSS/StyleValue.h>
+#include <LibWeb/HTML/HTMLBlinkElement.h>
+#include <LibWeb/Layout/Node.h>
+
+namespace Web::HTML {
+
+HTMLBlinkElement::HTMLBlinkElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+ , m_timer(Core::Timer::construct())
+{
+ m_timer->set_interval(500);
+ m_timer->on_timeout = [this] { blink(); };
+ m_timer->start();
+}
+
+HTMLBlinkElement::~HTMLBlinkElement()
+{
+}
+
+void HTMLBlinkElement::blink()
+{
+ if (!layout_node())
+ return;
+
+ layout_node()->set_visible(!layout_node()->is_visible());
+ layout_node()->set_needs_display();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLBlinkElement.h b/Userland/Libraries/LibWeb/HTML/HTMLBlinkElement.h
new file mode 100644
index 0000000000..e4c4c3421f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLBlinkElement.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibCore/Forward.h>
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLBlinkElement final : public HTMLElement {
+public:
+ HTMLBlinkElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLBlinkElement() override;
+
+private:
+ void blink();
+
+ NonnullRefPtr<Core::Timer> m_timer;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLBodyElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLBodyElement.cpp
new file mode 100644
index 0000000000..e6b7272f8d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLBodyElement.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/StyleProperties.h>
+#include <LibWeb/CSS/StyleValue.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/HTML/HTMLBodyElement.h>
+
+namespace Web::HTML {
+
+HTMLBodyElement::HTMLBodyElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLBodyElement::~HTMLBodyElement()
+{
+}
+
+void HTMLBodyElement::apply_presentational_hints(CSS::StyleProperties& style) const
+{
+ for_each_attribute([&](auto& name, auto& value) {
+ if (name.equals_ignoring_case("bgcolor")) {
+ auto color = Color::from_string(value);
+ if (color.has_value())
+ style.set_property(CSS::PropertyID::BackgroundColor, CSS::ColorStyleValue::create(color.value()));
+ } else if (name.equals_ignoring_case("text")) {
+ auto color = Color::from_string(value);
+ if (color.has_value())
+ style.set_property(CSS::PropertyID::Color, CSS::ColorStyleValue::create(color.value()));
+ } else if (name.equals_ignoring_case("background")) {
+ ASSERT(m_background_style_value);
+ style.set_property(CSS::PropertyID::BackgroundImage, *m_background_style_value);
+ }
+ });
+}
+
+void HTMLBodyElement::parse_attribute(const FlyString& name, const String& value)
+{
+ HTMLElement::parse_attribute(name, value);
+ if (name.equals_ignoring_case("link")) {
+ auto color = Color::from_string(value);
+ if (color.has_value())
+ document().set_link_color(color.value());
+ } else if (name.equals_ignoring_case("alink")) {
+ auto color = Color::from_string(value);
+ if (color.has_value())
+ document().set_active_link_color(color.value());
+ } else if (name.equals_ignoring_case("vlink")) {
+ auto color = Color::from_string(value);
+ if (color.has_value())
+ document().set_visited_link_color(color.value());
+ } else if (name.equals_ignoring_case("background")) {
+ m_background_style_value = CSS::ImageStyleValue::create(document().complete_url(value), const_cast<DOM::Document&>(document()));
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLBodyElement.h b/Userland/Libraries/LibWeb/HTML/HTMLBodyElement.h
new file mode 100644
index 0000000000..d8d41cbb7b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLBodyElement.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLBodyElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLBodyElementWrapper;
+
+ HTMLBodyElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLBodyElement() override;
+
+ virtual void parse_attribute(const FlyString&, const String&) override;
+ virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
+
+private:
+ RefPtr<CSS::ImageStyleValue> m_background_style_value;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLBodyElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLBodyElement.idl
new file mode 100644
index 0000000000..dcd0b4d753
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLBodyElement.idl
@@ -0,0 +1,10 @@
+interface HTMLBodyElement : HTMLElement {
+
+ [LegacyNullToEmptyString, Reflect] attribute DOMString text;
+ [LegacyNullToEmptyString, Reflect] attribute DOMString link;
+ [LegacyNullToEmptyString, Reflect=vlink] attribute DOMString vLink;
+ [LegacyNullToEmptyString, Reflect=alink] attribute DOMString aLink;
+ [LegacyNullToEmptyString, Reflect=bgcolor] attribute DOMString bgColor;
+ [Reflect] attribute DOMString background;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.cpp
new file mode 100644
index 0000000000..ec4925965b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLButtonElement.h>
+
+namespace Web::HTML {
+
+HTMLButtonElement::HTMLButtonElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLButtonElement::~HTMLButtonElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.h b/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.h
new file mode 100644
index 0000000000..234656a97d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLButtonElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLButtonElementWrapper;
+
+ HTMLButtonElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLButtonElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.idl
new file mode 100644
index 0000000000..7e50852e64
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.idl
@@ -0,0 +1,8 @@
+interface HTMLButtonElement : HTMLElement {
+
+ [Reflect=formnovalidate] attribute boolean formNoValidate;
+ [Reflect=formtarget] attribute DOMString formTarget;
+ [Reflect] attribute DOMString name;
+ [Reflect] attribute DOMString value;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp
new file mode 100644
index 0000000000..d015225460
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Checked.h>
+#include <LibGfx/Bitmap.h>
+#include <LibWeb/CSS/StyleResolver.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/HTML/CanvasRenderingContext2D.h>
+#include <LibWeb/HTML/HTMLCanvasElement.h>
+#include <LibWeb/Layout/CanvasBox.h>
+
+namespace Web::HTML {
+
+static constexpr auto max_canvas_area = 16384 * 16384;
+
+HTMLCanvasElement::HTMLCanvasElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLCanvasElement::~HTMLCanvasElement()
+{
+}
+
+unsigned HTMLCanvasElement::width() const
+{
+ return attribute(HTML::AttributeNames::width).to_uint().value_or(300);
+}
+
+unsigned HTMLCanvasElement::height() const
+{
+ return attribute(HTML::AttributeNames::height).to_uint().value_or(150);
+}
+
+RefPtr<Layout::Node> HTMLCanvasElement::create_layout_node()
+{
+ auto style = document().style_resolver().resolve_style(*this);
+ if (style->display() == CSS::Display::None)
+ return nullptr;
+ return adopt(*new Layout::CanvasBox(document(), *this, move(style)));
+}
+
+CanvasRenderingContext2D* HTMLCanvasElement::get_context(String type)
+{
+ ASSERT(type == "2d");
+ if (!m_context)
+ m_context = CanvasRenderingContext2D::create(*this);
+ return m_context;
+}
+
+static Gfx::IntSize bitmap_size_for_canvas(const HTMLCanvasElement& canvas)
+{
+ auto width = canvas.width();
+ auto height = canvas.height();
+
+ Checked<size_t> area = width;
+ area *= height;
+
+ if (area.has_overflow()) {
+ dbgln("Refusing to create {}x{} canvas (overflow)", width, height);
+ return {};
+ }
+ if (area.value() > max_canvas_area) {
+ dbgln("Refusing to create {}x{} canvas (exceeds maximum size)", width, height);
+ return {};
+ }
+ return Gfx::IntSize(width, height);
+}
+
+bool HTMLCanvasElement::create_bitmap()
+{
+ auto size = bitmap_size_for_canvas(*this);
+ if (size.is_empty()) {
+ m_bitmap = nullptr;
+ return false;
+ }
+ if (!m_bitmap || m_bitmap->size() != size)
+ m_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, size);
+ return m_bitmap;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.h b/Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.h
new file mode 100644
index 0000000000..9616af98b7
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <LibGfx/Forward.h>
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLCanvasElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLCanvasElementWrapper;
+
+ HTMLCanvasElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLCanvasElement() override;
+
+ const Gfx::Bitmap* bitmap() const { return m_bitmap; }
+ Gfx::Bitmap* bitmap() { return m_bitmap; }
+ bool create_bitmap();
+
+ CanvasRenderingContext2D* get_context(String type);
+
+ unsigned width() const;
+ unsigned height() const;
+
+private:
+ virtual RefPtr<Layout::Node> create_layout_node() override;
+
+ RefPtr<Gfx::Bitmap> m_bitmap;
+ RefPtr<CanvasRenderingContext2D> m_context;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.idl
new file mode 100644
index 0000000000..5a04895fb4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.idl
@@ -0,0 +1,7 @@
+interface HTMLCanvasElement : HTMLElement {
+
+ CanvasRenderingContext2D? getContext(DOMString contextId);
+ readonly attribute unsigned long width;
+ readonly attribute unsigned long height;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDListElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLDListElement.cpp
new file mode 100644
index 0000000000..ce68845ca6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDListElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLDListElement.h>
+
+namespace Web::HTML {
+
+HTMLDListElement::HTMLDListElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLDListElement::~HTMLDListElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDListElement.h b/Userland/Libraries/LibWeb/HTML/HTMLDListElement.h
new file mode 100644
index 0000000000..cf0fe2a9c2
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDListElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLDListElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLDListElementWrapper;
+
+ HTMLDListElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLDListElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDListElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLDListElement.idl
new file mode 100644
index 0000000000..16b3b881ac
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDListElement.idl
@@ -0,0 +1,5 @@
+interface HTMLDListElement : HTMLElement {
+
+ [Reflect] attribute boolean compact;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDataElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLDataElement.cpp
new file mode 100644
index 0000000000..1a23f48fd9
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDataElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLDataElement.h>
+
+namespace Web::HTML {
+
+HTMLDataElement::HTMLDataElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLDataElement::~HTMLDataElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDataElement.h b/Userland/Libraries/LibWeb/HTML/HTMLDataElement.h
new file mode 100644
index 0000000000..21918958cd
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDataElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLDataElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLDataElementWrapper;
+
+ HTMLDataElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLDataElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDataElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLDataElement.idl
new file mode 100644
index 0000000000..4edce306fa
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDataElement.idl
@@ -0,0 +1,5 @@
+interface HTMLDataElement : HTMLElement {
+
+ [Reflect] attribute DOMString value;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDataListElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLDataListElement.cpp
new file mode 100644
index 0000000000..32bae03035
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDataListElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLDataListElement.h>
+
+namespace Web::HTML {
+
+HTMLDataListElement::HTMLDataListElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLDataListElement::~HTMLDataListElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDataListElement.h b/Userland/Libraries/LibWeb/HTML/HTMLDataListElement.h
new file mode 100644
index 0000000000..85bc7ccf7d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDataListElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLDataListElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLDataListElementWrapper;
+
+ HTMLDataListElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLDataListElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDataListElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLDataListElement.idl
new file mode 100644
index 0000000000..c9383448e4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDataListElement.idl
@@ -0,0 +1,5 @@
+interface HTMLDataListElement : HTMLElement {
+
+
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.cpp
new file mode 100644
index 0000000000..abcdf0801a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLDetailsElement.h>
+
+namespace Web::HTML {
+
+HTMLDetailsElement::HTMLDetailsElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLDetailsElement::~HTMLDetailsElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.h b/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.h
new file mode 100644
index 0000000000..2bdbf673f8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLDetailsElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLDetailsElementWrapper;
+
+ HTMLDetailsElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLDetailsElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.idl
new file mode 100644
index 0000000000..2b0daa6c7f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.idl
@@ -0,0 +1,5 @@
+interface HTMLDetailsElement : HTMLElement {
+
+ [Reflect] attribute boolean open;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDialogElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLDialogElement.cpp
new file mode 100644
index 0000000000..7437ca2261
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDialogElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLDialogElement.h>
+
+namespace Web::HTML {
+
+HTMLDialogElement::HTMLDialogElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLDialogElement::~HTMLDialogElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDialogElement.h b/Userland/Libraries/LibWeb/HTML/HTMLDialogElement.h
new file mode 100644
index 0000000000..133f65ddbd
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDialogElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLDialogElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLDialogElementWrapper;
+
+ HTMLDialogElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLDialogElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDialogElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLDialogElement.idl
new file mode 100644
index 0000000000..0d37547ed3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDialogElement.idl
@@ -0,0 +1,5 @@
+interface HTMLDialogElement : HTMLElement {
+
+ [Reflect] attribute boolean open;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDirectoryElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLDirectoryElement.cpp
new file mode 100644
index 0000000000..53fee8b14a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDirectoryElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLDirectoryElement.h>
+
+namespace Web::HTML {
+
+HTMLDirectoryElement::HTMLDirectoryElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLDirectoryElement::~HTMLDirectoryElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDirectoryElement.h b/Userland/Libraries/LibWeb/HTML/HTMLDirectoryElement.h
new file mode 100644
index 0000000000..39a9f5ae94
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDirectoryElement.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+// NOTE: This element is marked as obsolete, but is still listed as required by the specification.
+class HTMLDirectoryElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLDirectoryElementWrapper;
+
+ HTMLDirectoryElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLDirectoryElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDirectoryElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLDirectoryElement.idl
new file mode 100644
index 0000000000..9adb6ad670
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDirectoryElement.idl
@@ -0,0 +1,5 @@
+interface HTMLDirectoryElement : HTMLElement {
+
+ [Reflect] attribute boolean compact;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDivElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLDivElement.cpp
new file mode 100644
index 0000000000..7392f8ec80
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDivElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLDivElement.h>
+
+namespace Web::HTML {
+
+HTMLDivElement::HTMLDivElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLDivElement::~HTMLDivElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDivElement.h b/Userland/Libraries/LibWeb/HTML/HTMLDivElement.h
new file mode 100644
index 0000000000..eade002544
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDivElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLDivElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLDivElementWrapper;
+
+ HTMLDivElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLDivElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDivElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLDivElement.idl
new file mode 100644
index 0000000000..bb5cfba9b4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDivElement.idl
@@ -0,0 +1,5 @@
+interface HTMLDivElement : HTMLElement {
+
+ [Reflect] attribute DOMString align;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp
new file mode 100644
index 0000000000..eb5356a594
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/HTML/HTMLAnchorElement.h>
+#include <LibWeb/HTML/HTMLElement.h>
+#include <LibWeb/Layout/BreakNode.h>
+#include <LibWeb/Layout/TextNode.h>
+
+namespace Web::HTML {
+
+HTMLElement::HTMLElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : Element(document, qualified_name)
+{
+}
+
+HTMLElement::~HTMLElement()
+{
+}
+
+HTMLElement::ContentEditableState HTMLElement::content_editable_state() const
+{
+ auto contenteditable = attribute(HTML::AttributeNames::contenteditable);
+ // "true" and the empty string map to the "true" state.
+ if ((!contenteditable.is_null() && contenteditable.is_empty()) || contenteditable.equals_ignoring_case("true"))
+ return ContentEditableState::True;
+ // "false" maps to the "false" state.
+ if (contenteditable.equals_ignoring_case("false"))
+ return ContentEditableState::False;
+ // "inherit", an invalid value, and a missing value all map to the "inherit" state.
+ return ContentEditableState::Inherit;
+}
+
+bool HTMLElement::is_editable() const
+{
+ switch (content_editable_state()) {
+ case ContentEditableState::True:
+ return true;
+ case ContentEditableState::False:
+ return false;
+ case ContentEditableState::Inherit:
+ return parent() && parent()->is_editable();
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+String HTMLElement::content_editable() const
+{
+ switch (content_editable_state()) {
+ case ContentEditableState::True:
+ return "true";
+ case ContentEditableState::False:
+ return "false";
+ case ContentEditableState::Inherit:
+ return "inherit";
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+void HTMLElement::set_content_editable(const String& content_editable)
+{
+ if (content_editable.equals_ignoring_case("inherit")) {
+ remove_attribute(HTML::AttributeNames::contenteditable);
+ return;
+ }
+ if (content_editable.equals_ignoring_case("true")) {
+ set_attribute(HTML::AttributeNames::contenteditable, "true");
+ return;
+ }
+ if (content_editable.equals_ignoring_case("false")) {
+ set_attribute(HTML::AttributeNames::contenteditable, "false");
+ return;
+ }
+ // FIXME: otherwise the attribute setter must throw a "SyntaxError" DOMException.
+}
+
+void HTMLElement::set_inner_text(StringView text)
+{
+ remove_all_children();
+ append_child(document().create_text_node(text));
+
+ set_needs_style_update(true);
+ document().invalidate_layout();
+}
+
+String HTMLElement::inner_text()
+{
+ StringBuilder builder;
+
+ // innerText for element being rendered takes visibility into account, so force a layout and then walk the layout tree.
+ document().update_layout();
+ if (!layout_node())
+ return text_content();
+
+ Function<void(const Layout::Node&)> recurse = [&](auto& node) {
+ for (auto* child = node.first_child(); child; child = child->next_sibling()) {
+ if (is<Layout::TextNode>(child))
+ builder.append(downcast<Layout::TextNode>(*child).text_for_rendering());
+ if (is<Layout::BreakNode>(child))
+ builder.append('\n');
+ recurse(*child);
+ }
+ };
+ recurse(*layout_node());
+
+ return builder.to_string();
+}
+
+bool HTMLElement::cannot_navigate() const
+{
+ // FIXME: Return true if element's node document is not fully active
+ return !is<HTML::HTMLAnchorElement>(this) && !is_connected();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLElement.h b/Userland/Libraries/LibWeb/HTML/HTMLElement.h
new file mode 100644
index 0000000000..ee602a62b5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLElement.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/DOM/Element.h>
+
+namespace Web::HTML {
+
+class HTMLElement : public DOM::Element {
+public:
+ using WrapperType = Bindings::HTMLElementWrapper;
+
+ HTMLElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLElement() override;
+
+ String title() const { return attribute(HTML::AttributeNames::title); }
+
+ virtual bool is_editable() const final;
+ String content_editable() const;
+ void set_content_editable(const String&);
+
+ String inner_text();
+ void set_inner_text(StringView);
+
+ bool cannot_navigate() const;
+
+private:
+ enum class ContentEditableState {
+ True,
+ False,
+ Inherit,
+ };
+ ContentEditableState content_editable_state() const;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLElement.idl
new file mode 100644
index 0000000000..6b5de34cdb
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLElement.idl
@@ -0,0 +1,11 @@
+interface HTMLElement : Element {
+
+ [Reflect] attribute DOMString title;
+ [Reflect] attribute DOMString lang;
+
+ [Reflect] attribute boolean hidden;
+
+ attribute DOMString contentEditable;
+
+ [LegacyNullToEmptyString] attribute DOMString innerText;
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLEmbedElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLEmbedElement.cpp
new file mode 100644
index 0000000000..1ec87b0fd0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLEmbedElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLEmbedElement.h>
+
+namespace Web::HTML {
+
+HTMLEmbedElement::HTMLEmbedElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLEmbedElement::~HTMLEmbedElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLEmbedElement.h b/Userland/Libraries/LibWeb/HTML/HTMLEmbedElement.h
new file mode 100644
index 0000000000..1ceaaae634
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLEmbedElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLEmbedElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLEmbedElementWrapper;
+
+ HTMLEmbedElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLEmbedElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLEmbedElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLEmbedElement.idl
new file mode 100644
index 0000000000..5eb48b3509
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLEmbedElement.idl
@@ -0,0 +1,11 @@
+interface HTMLEmbedElement : HTMLElement {
+
+ [Reflect] attribute DOMString src;
+ [Reflect] attribute DOMString type;
+ [Reflect] attribute DOMString width;
+ [Reflect] attribute DOMString height;
+
+ [Reflect] attribute DOMString align;
+ [Reflect] attribute DOMString name;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.cpp
new file mode 100644
index 0000000000..6a6c5bf87e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLFieldSetElement.h>
+
+namespace Web::HTML {
+
+HTMLFieldSetElement::HTMLFieldSetElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLFieldSetElement::~HTMLFieldSetElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.h b/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.h
new file mode 100644
index 0000000000..b9ffcf43be
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLFieldSetElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLFieldSetElementWrapper;
+
+ HTMLFieldSetElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLFieldSetElement() override;
+
+ const String& type() const
+ {
+ static String fieldset = "fieldset";
+ return fieldset;
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.idl
new file mode 100644
index 0000000000..4901c8aae8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.idl
@@ -0,0 +1,5 @@
+interface HTMLFieldSetElement : HTMLElement {
+
+ readonly attribute DOMString type;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFontElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLFontElement.cpp
new file mode 100644
index 0000000000..50ef0e37f5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLFontElement.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/StyleProperties.h>
+#include <LibWeb/CSS/StyleValue.h>
+#include <LibWeb/HTML/HTMLFontElement.h>
+
+namespace Web::HTML {
+
+HTMLFontElement::HTMLFontElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLFontElement::~HTMLFontElement()
+{
+}
+
+void HTMLFontElement::apply_presentational_hints(CSS::StyleProperties& style) const
+{
+ for_each_attribute([&](auto& name, auto& value) {
+ if (name.equals_ignoring_case("color")) {
+ auto color = Color::from_string(value);
+ if (color.has_value())
+ style.set_property(CSS::PropertyID::Color, CSS::ColorStyleValue::create(color.value()));
+ }
+ });
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFontElement.h b/Userland/Libraries/LibWeb/HTML/HTMLFontElement.h
new file mode 100644
index 0000000000..d414abc5a8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLFontElement.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLFontElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLFontElementWrapper;
+
+ HTMLFontElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLFontElement() override;
+
+ virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFontElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLFontElement.idl
new file mode 100644
index 0000000000..578e313ed0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLFontElement.idl
@@ -0,0 +1,7 @@
+interface HTMLFontElement : HTMLElement {
+
+ [LegacyNullToEmptyString, Reflect] attribute DOMString color;
+ [Reflect] attribute DOMString face;
+ [Reflect] attribute DOMString size;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFormElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLFormElement.cpp
new file mode 100644
index 0000000000..b632dab482
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLFormElement.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibWeb/HTML/EventNames.h>
+#include <LibWeb/HTML/HTMLFormElement.h>
+#include <LibWeb/HTML/HTMLInputElement.h>
+#include <LibWeb/HTML/SubmitEvent.h>
+#include <LibWeb/InProcessWebView.h>
+#include <LibWeb/Page/Frame.h>
+#include <LibWeb/URLEncoder.h>
+
+namespace Web::HTML {
+
+HTMLFormElement::HTMLFormElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLFormElement::~HTMLFormElement()
+{
+}
+
+void HTMLFormElement::submit_form(RefPtr<HTMLElement> submitter, bool from_submit_binding)
+{
+ if (cannot_navigate())
+ return;
+
+ if (action().is_null()) {
+ dbgln("Unsupported form action ''");
+ return;
+ }
+
+ auto effective_method = method().to_lowercase();
+
+ if (effective_method == "dialog") {
+ dbgln("Failed to submit form: Unsupported form method '{}'", method());
+ return;
+ }
+
+ if (effective_method != "get" && effective_method != "post") {
+ effective_method = "get";
+ }
+
+ if (!from_submit_binding) {
+ if (m_firing_submission_events)
+ return;
+
+ m_firing_submission_events = true;
+
+ // FIXME: If the submitter element's no-validate state is false...
+
+ RefPtr<HTMLElement> submitter_button;
+
+ if (submitter != this)
+ submitter_button = submitter;
+
+ auto submit_event = SubmitEvent::create(EventNames::submit, submitter_button);
+ submit_event->set_bubbles(true);
+ submit_event->set_cancelable(true);
+ bool continue_ = dispatch_event(submit_event);
+
+ m_firing_submission_events = false;
+
+ if (!continue_)
+ return;
+
+ // This is checked again because arbitrary JS may have run when handling submit,
+ // which may have changed the result.
+ if (cannot_navigate())
+ return;
+ }
+
+ URL url(document().complete_url(action()));
+
+ if (!url.is_valid()) {
+ dbgln("Failed to submit form: Invalid URL: {}", action());
+ return;
+ }
+
+ if (url.protocol() == "file") {
+ if (document().url().protocol() != "file") {
+ dbgln("Failed to submit form: Security violation: {} may not submit to {}", document().url(), url);
+ return;
+ }
+ if (effective_method != "get") {
+ dbgln("Failed to submit form: Unsupported form method '{}' for URL: {}", method(), url);
+ return;
+ }
+ } else if (url.protocol() != "http" && url.protocol() != "https") {
+ dbgln("Failed to submit form: Unsupported protocol for URL: {}", url);
+ return;
+ }
+
+ Vector<URLQueryParam> parameters;
+
+ for_each_in_subtree_of_type<HTMLInputElement>([&](auto& node) {
+ auto& input = downcast<HTMLInputElement>(node);
+ if (!input.name().is_null() && (input.type() != "submit" || &input == submitter))
+ parameters.append({ input.name(), input.value() });
+ return IterationDecision::Continue;
+ });
+
+ if (effective_method == "get") {
+ url.set_query(urlencode(parameters));
+ }
+
+ LoadRequest request;
+ request.set_url(url);
+
+ if (effective_method == "post") {
+ auto body = urlencode(parameters).to_byte_buffer();
+ request.set_method("POST");
+ request.set_header("Content-Type", "application/x-www-form-urlencoded");
+ request.set_header("Content-Length", String::number(body.size()));
+ request.set_body(body);
+ }
+
+ if (auto* page = document().page())
+ page->load(request);
+}
+
+void HTMLFormElement::submit()
+{
+ submit_form(this, true);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFormElement.h b/Userland/Libraries/LibWeb/HTML/HTMLFormElement.h
new file mode 100644
index 0000000000..8f90e8f3f7
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLFormElement.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+#include <LibWeb/HTML/HTMLInputElement.h>
+
+namespace Web::HTML {
+
+class HTMLFormElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLFormElementWrapper;
+
+ HTMLFormElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLFormElement() override;
+
+ String action() const { return attribute(HTML::AttributeNames::action); }
+ String method() const { return attribute(HTML::AttributeNames::method); }
+
+ void submit_form(RefPtr<HTMLElement> submitter, bool from_submit_binding = false);
+
+ // NOTE: This is for the JS bindings. Use submit_form instead.
+ void submit();
+
+private:
+ bool m_firing_submission_events { false };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFormElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLFormElement.idl
new file mode 100644
index 0000000000..81d4fe37f4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLFormElement.idl
@@ -0,0 +1,10 @@
+interface HTMLFormElement : HTMLElement {
+
+ [Reflect] attribute DOMString name;
+ [Reflect] attribute DOMString rel;
+ [Reflect=accept-charset] attribute DOMString acceptCharset;
+ [Reflect=novalidate] attribute boolean noValidate;
+
+ undefined submit();
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFrameElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLFrameElement.cpp
new file mode 100644
index 0000000000..ba2e0fb470
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLFrameElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLFrameElement.h>
+
+namespace Web::HTML {
+
+HTMLFrameElement::HTMLFrameElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLFrameElement::~HTMLFrameElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFrameElement.h b/Userland/Libraries/LibWeb/HTML/HTMLFrameElement.h
new file mode 100644
index 0000000000..7541bc80b5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLFrameElement.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+// NOTE: This element is marked as obsolete, but is still listed as required by the specification.
+class HTMLFrameElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLFrameElementWrapper;
+
+ HTMLFrameElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLFrameElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFrameElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLFrameElement.idl
new file mode 100644
index 0000000000..e1092ce79e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLFrameElement.idl
@@ -0,0 +1,9 @@
+interface HTMLFrameElement : HTMLElement {
+
+ [Reflect] attribute DOMString name;
+ [Reflect] attribute DOMString scrolling;
+ [Reflect] attribute DOMString src;
+ [Reflect=frameborder] attribute DOMString frameBorder;
+ [Reflect=longdesc] attribute DOMString longDesc;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFrameSetElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLFrameSetElement.cpp
new file mode 100644
index 0000000000..128da69ccf
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLFrameSetElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLFrameSetElement.h>
+
+namespace Web::HTML {
+
+HTMLFrameSetElement::HTMLFrameSetElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLFrameSetElement::~HTMLFrameSetElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFrameSetElement.h b/Userland/Libraries/LibWeb/HTML/HTMLFrameSetElement.h
new file mode 100644
index 0000000000..0c2af01678
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLFrameSetElement.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+// NOTE: This element is marked as obsolete, but is still listed as required by the specification.
+class HTMLFrameSetElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLFrameSetElementWrapper;
+
+ HTMLFrameSetElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLFrameSetElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFrameSetElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLFrameSetElement.idl
new file mode 100644
index 0000000000..abf5699ec4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLFrameSetElement.idl
@@ -0,0 +1,6 @@
+interface HTMLFrameSetElement : HTMLElement {
+
+ [Reflect] attribute DOMString cols;
+ [Reflect] attribute DOMString rows;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLHRElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLHRElement.cpp
new file mode 100644
index 0000000000..82574cf789
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLHRElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLHRElement.h>
+
+namespace Web::HTML {
+
+HTMLHRElement::HTMLHRElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLHRElement::~HTMLHRElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLHRElement.h b/Userland/Libraries/LibWeb/HTML/HTMLHRElement.h
new file mode 100644
index 0000000000..e8a23c1a19
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLHRElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLHRElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLHRElementWrapper;
+
+ HTMLHRElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLHRElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLHRElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLHRElement.idl
new file mode 100644
index 0000000000..5cfd1e700e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLHRElement.idl
@@ -0,0 +1,9 @@
+interface HTMLHRElement : HTMLElement {
+
+ [Reflect] attribute DOMString align;
+ [Reflect] attribute DOMString color;
+ [Reflect=noshade] attribute boolean noShade;
+ [Reflect] attribute DOMString size;
+ [Reflect] attribute DOMString width;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLHeadElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLHeadElement.cpp
new file mode 100644
index 0000000000..a17ead858b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLHeadElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLHeadElement.h>
+
+namespace Web::HTML {
+
+HTMLHeadElement::HTMLHeadElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLHeadElement::~HTMLHeadElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLHeadElement.h b/Userland/Libraries/LibWeb/HTML/HTMLHeadElement.h
new file mode 100644
index 0000000000..d378e1af77
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLHeadElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLHeadElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLHeadElementWrapper;
+
+ HTMLHeadElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLHeadElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLHeadElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLHeadElement.idl
new file mode 100644
index 0000000000..8730e7c1e4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLHeadElement.idl
@@ -0,0 +1,5 @@
+interface HTMLHeadElement : HTMLElement {
+
+
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLHeadingElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLHeadingElement.cpp
new file mode 100644
index 0000000000..b899c873a5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLHeadingElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLHeadingElement.h>
+
+namespace Web::HTML {
+
+HTMLHeadingElement::HTMLHeadingElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLHeadingElement::~HTMLHeadingElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLHeadingElement.h b/Userland/Libraries/LibWeb/HTML/HTMLHeadingElement.h
new file mode 100644
index 0000000000..654b75baf9
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLHeadingElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLHeadingElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLHeadingElementWrapper;
+
+ HTMLHeadingElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLHeadingElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLHeadingElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLHeadingElement.idl
new file mode 100644
index 0000000000..fa3d5a4ed7
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLHeadingElement.idl
@@ -0,0 +1,5 @@
+interface HTMLHeadingElement : HTMLElement {
+
+ [Reflect] attribute DOMString align;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLHtmlElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLHtmlElement.cpp
new file mode 100644
index 0000000000..ab1233f739
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLHtmlElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLHtmlElement.h>
+
+namespace Web::HTML {
+
+HTMLHtmlElement::HTMLHtmlElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLHtmlElement::~HTMLHtmlElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLHtmlElement.h b/Userland/Libraries/LibWeb/HTML/HTMLHtmlElement.h
new file mode 100644
index 0000000000..3e34c52f6c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLHtmlElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLHtmlElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLHtmlElementWrapper;
+
+ HTMLHtmlElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLHtmlElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLHtmlElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLHtmlElement.idl
new file mode 100644
index 0000000000..dad97564c8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLHtmlElement.idl
@@ -0,0 +1,5 @@
+interface HTMLHtmlElement : HTMLElement {
+
+ [Reflect] attribute DOMString version;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp
new file mode 100644
index 0000000000..e59818ba82
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Button.h>
+#include <LibGUI/TextBox.h>
+#include <LibWeb/Bindings/WindowObject.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/DOM/Window.h>
+#include <LibWeb/Dump.h>
+#include <LibWeb/HTML/EventNames.h>
+#include <LibWeb/HTML/HTMLFormElement.h>
+#include <LibWeb/HTML/HTMLIFrameElement.h>
+#include <LibWeb/HTML/Parser/HTMLDocumentParser.h>
+#include <LibWeb/InProcessWebView.h>
+#include <LibWeb/Layout/FrameBox.h>
+#include <LibWeb/Loader/ResourceLoader.h>
+#include <LibWeb/Origin.h>
+#include <LibWeb/Page/Frame.h>
+
+namespace Web::HTML {
+
+HTMLIFrameElement::HTMLIFrameElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+ ASSERT(document.frame());
+ m_content_frame = Frame::create_subframe(*this, document.frame()->main_frame());
+}
+
+HTMLIFrameElement::~HTMLIFrameElement()
+{
+}
+
+RefPtr<Layout::Node> HTMLIFrameElement::create_layout_node()
+{
+ auto style = document().style_resolver().resolve_style(*this);
+ return adopt(*new Layout::FrameBox(document(), *this, move(style)));
+}
+
+void HTMLIFrameElement::parse_attribute(const FlyString& name, const String& value)
+{
+ HTMLElement::parse_attribute(name, value);
+ if (name == HTML::AttributeNames::src)
+ load_src(value);
+}
+
+void HTMLIFrameElement::load_src(const String& value)
+{
+ auto url = document().complete_url(value);
+ if (!url.is_valid()) {
+ dbg() << "iframe failed to load URL: Invalid URL: " << value;
+ return;
+ }
+ if (url.protocol() == "file" && document().origin().protocol() != "file") {
+ dbg() << "iframe failed to load URL: Security violation: " << document().url() << " may not load " << url;
+ return;
+ }
+
+ dbg() << "Loading iframe document from " << value;
+ m_content_frame->loader().load(url, FrameLoader::Type::IFrame);
+}
+
+Origin HTMLIFrameElement::content_origin() const
+{
+ if (!m_content_frame || !m_content_frame->document())
+ return {};
+ return m_content_frame->document()->origin();
+}
+
+bool HTMLIFrameElement::may_access_from_origin(const Origin& origin) const
+{
+ return origin.is_same(content_origin());
+}
+
+const DOM::Document* HTMLIFrameElement::content_document() const
+{
+ return m_content_frame ? m_content_frame->document() : nullptr;
+}
+
+void HTMLIFrameElement::content_frame_did_load(Badge<FrameLoader>)
+{
+ dispatch_event(DOM::Event::create(EventNames::load));
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.h b/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.h
new file mode 100644
index 0000000000..0237ea3c91
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLIFrameElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLIFrameElementWrapper;
+
+ HTMLIFrameElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLIFrameElement() override;
+
+ virtual RefPtr<Layout::Node> create_layout_node() override;
+
+ Frame* content_frame() { return m_content_frame; }
+ const Frame* content_frame() const { return m_content_frame; }
+
+ const DOM::Document* content_document() const;
+
+ Origin content_origin() const;
+ bool may_access_from_origin(const Origin&) const;
+
+ void content_frame_did_load(Badge<FrameLoader>);
+
+private:
+ virtual void parse_attribute(const FlyString& name, const String& value) override;
+
+ void load_src(const String&);
+
+ RefPtr<Frame> m_content_frame;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.idl
new file mode 100644
index 0000000000..a219b66071
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.idl
@@ -0,0 +1,19 @@
+interface HTMLIFrameElement : HTMLElement {
+
+ [Reflect] attribute DOMString src;
+ [Reflect] attribute DOMString srcdoc;
+ [Reflect] attribute DOMString name;
+ [Reflect] attribute DOMString allow;
+ [Reflect] attribute DOMString width;
+ [Reflect] attribute DOMString height;
+ [Reflect=allowfullscreen] attribute boolean allowFullscreen;
+
+ [ReturnNullIfCrossOrigin] readonly attribute Document? contentDocument;
+
+ [Reflect] attribute DOMString align;
+ [Reflect] attribute DOMString scrolling;
+ [Reflect=frameborder] attribute DOMString frameBorder;
+
+ [LegacyNullToEmptyString, Reflect=marginheight] attribute DOMString marginHeight;
+ [LegacyNullToEmptyString, Reflect=marginwidth] attribute DOMString marginWidth;
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp
new file mode 100644
index 0000000000..61b99f0ffa
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+#include <LibWeb/CSS/Parser/CSSParser.h>
+#include <LibWeb/CSS/StyleResolver.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/HTML/EventNames.h>
+#include <LibWeb/HTML/HTMLImageElement.h>
+#include <LibWeb/Layout/ImageBox.h>
+#include <LibWeb/Loader/ResourceLoader.h>
+
+namespace Web::HTML {
+
+HTMLImageElement::HTMLImageElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+ m_image_loader.on_load = [this] {
+ this->document().update_layout();
+ dispatch_event(DOM::Event::create(EventNames::load));
+ };
+
+ m_image_loader.on_fail = [this] {
+ dbgln("HTMLImageElement: Resource did fail: {}", src());
+ this->document().update_layout();
+ dispatch_event(DOM::Event::create(EventNames::error));
+ };
+
+ m_image_loader.on_animate = [this] {
+ if (layout_node())
+ layout_node()->set_needs_display();
+ };
+}
+
+HTMLImageElement::~HTMLImageElement()
+{
+}
+
+void HTMLImageElement::apply_presentational_hints(CSS::StyleProperties& style) const
+{
+ for_each_attribute([&](auto& name, auto& value) {
+ if (name == HTML::AttributeNames::width) {
+ if (auto parsed_value = parse_html_length(document(), value)) {
+ style.set_property(CSS::PropertyID::Width, parsed_value.release_nonnull());
+ }
+ } else if (name == HTML::AttributeNames::height) {
+ if (auto parsed_value = parse_html_length(document(), value)) {
+ style.set_property(CSS::PropertyID::Height, parsed_value.release_nonnull());
+ }
+ }
+ });
+}
+
+void HTMLImageElement::parse_attribute(const FlyString& name, const String& value)
+{
+ HTMLElement::parse_attribute(name, value);
+
+ if (name == HTML::AttributeNames::src)
+ m_image_loader.load(document().complete_url(value));
+}
+
+RefPtr<Layout::Node> HTMLImageElement::create_layout_node()
+{
+ auto style = document().style_resolver().resolve_style(*this);
+ if (style->display() == CSS::Display::None)
+ return nullptr;
+ return adopt(*new Layout::ImageBox(document(), *this, move(style), m_image_loader));
+}
+
+const Gfx::Bitmap* HTMLImageElement::bitmap() const
+{
+ return m_image_loader.bitmap();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.h b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.h
new file mode 100644
index 0000000000..7cc97f1983
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/OwnPtr.h>
+#include <LibGfx/Forward.h>
+#include <LibWeb/HTML/HTMLElement.h>
+#include <LibWeb/Loader/ImageLoader.h>
+
+namespace Web::HTML {
+
+class HTMLImageElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLImageElementWrapper;
+
+ HTMLImageElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLImageElement() override;
+
+ virtual void parse_attribute(const FlyString& name, const String& value) override;
+
+ String alt() const { return attribute(HTML::AttributeNames::alt); }
+ String src() const { return attribute(HTML::AttributeNames::src); }
+
+ const Gfx::Bitmap* bitmap() const;
+
+private:
+ virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
+
+ void animate();
+
+ virtual RefPtr<Layout::Node> create_layout_node() override;
+
+ ImageLoader m_image_loader;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.idl
new file mode 100644
index 0000000000..d0d4330a9a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.idl
@@ -0,0 +1,14 @@
+interface HTMLImageElement : HTMLElement {
+
+ [Reflect] attribute DOMString src;
+ [Reflect] attribute DOMString alt;
+ [Reflect] attribute DOMString srcset;
+ [Reflect] attribute DOMString sizes;
+ [Reflect=usemap] attribute DOMString useMap;
+ [Reflect=ismap] attribute boolean isMap;
+
+ [Reflect] attribute DOMString name;
+ [Reflect] attribute DOMString align;
+ [LegacyNullToEmptyString, Reflect] attribute DOMString border;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
new file mode 100644
index 0000000000..c5654c3530
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Button.h>
+#include <LibGUI/TextBox.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/HTML/EventNames.h>
+#include <LibWeb/HTML/HTMLFormElement.h>
+#include <LibWeb/HTML/HTMLInputElement.h>
+#include <LibWeb/InProcessWebView.h>
+#include <LibWeb/Layout/ButtonBox.h>
+#include <LibWeb/Layout/CheckBox.h>
+#include <LibWeb/Layout/WidgetBox.h>
+#include <LibWeb/Page/Frame.h>
+
+namespace Web::HTML {
+
+HTMLInputElement::HTMLInputElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLInputElement::~HTMLInputElement()
+{
+}
+
+void HTMLInputElement::did_click_button(Badge<Layout::ButtonBox>)
+{
+ // FIXME: This should be a PointerEvent.
+ dispatch_event(DOM::Event::create(EventNames::click));
+
+ if (type().equals_ignoring_case("submit")) {
+ if (auto* form = first_ancestor_of_type<HTMLFormElement>()) {
+ form->submit_form(this);
+ }
+ return;
+ }
+}
+
+RefPtr<Layout::Node> HTMLInputElement::create_layout_node()
+{
+ ASSERT(document().page());
+ auto& page = *document().page();
+ auto& page_view = const_cast<InProcessWebView&>(static_cast<const InProcessWebView&>(page.client()));
+
+ if (type() == "hidden")
+ return nullptr;
+
+ auto style = document().style_resolver().resolve_style(*this);
+ if (style->display() == CSS::Display::None)
+ return nullptr;
+
+ if (type().equals_ignoring_case("submit") || type().equals_ignoring_case("button"))
+ return adopt(*new Layout::ButtonBox(document(), *this, move(style)));
+
+ if (type() == "checkbox")
+ return adopt(*new Layout::CheckBox(document(), *this, move(style)));
+
+ auto& text_box = page_view.add<GUI::TextBox>();
+ text_box.set_text(value());
+ text_box.on_change = [this] {
+ auto& widget = downcast<Layout::WidgetBox>(layout_node())->widget();
+ const_cast<HTMLInputElement*>(this)->set_attribute(HTML::AttributeNames::value, static_cast<const GUI::TextBox&>(widget).text());
+ };
+ int text_width = Gfx::FontDatabase::default_font().width(value());
+ auto size_value = attribute(HTML::AttributeNames::size);
+ if (!size_value.is_null()) {
+ auto size = size_value.to_uint();
+ if (size.has_value())
+ text_width = Gfx::FontDatabase::default_font().glyph_width('x') * size.value();
+ }
+ text_box.set_relative_rect(0, 0, text_width + 20, 20);
+ return adopt(*new Layout::WidgetBox(document(), *this, text_box));
+}
+
+void HTMLInputElement::set_checked(bool checked)
+{
+ if (m_checked == checked)
+ return;
+ m_checked = checked;
+ if (layout_node())
+ layout_node()->set_needs_display();
+
+ dispatch_event(DOM::Event::create(EventNames::change));
+}
+
+bool HTMLInputElement::enabled() const
+{
+ return !has_attribute(HTML::AttributeNames::disabled);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
new file mode 100644
index 0000000000..b887118263
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLInputElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLInputElementWrapper;
+
+ HTMLInputElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLInputElement() override;
+
+ virtual RefPtr<Layout::Node> create_layout_node() override;
+
+ String type() const { return attribute(HTML::AttributeNames::type); }
+ String value() const { return attribute(HTML::AttributeNames::value); }
+ String name() const { return attribute(HTML::AttributeNames::name); }
+
+ bool checked() const { return m_checked; }
+ void set_checked(bool);
+
+ bool enabled() const;
+
+ void did_click_button(Badge<Layout::ButtonBox>);
+
+private:
+ bool m_checked { false };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl
new file mode 100644
index 0000000000..037b65c280
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl
@@ -0,0 +1,27 @@
+interface HTMLInputElement : HTMLElement {
+
+ [Reflect] attribute DOMString accept;
+ [Reflect] attribute DOMString alt;
+ [Reflect] attribute DOMString max;
+ [Reflect] attribute DOMString min;
+ [Reflect] attribute DOMString pattern;
+ [Reflect] attribute DOMString placeholder;
+ [Reflect] attribute DOMString src;
+ [Reflect] attribute DOMString step;
+ [Reflect=dirname] attribute DOMString dirName;
+ [Reflect=value] attribute DOMString defaultValue;
+
+ attribute boolean checked;
+
+ [Reflect] attribute boolean disabled;
+ [Reflect=checked] attribute boolean defaultChecked;
+ [Reflect=formnovalidate] attribute boolean formNoValidate;
+ [Reflect=formtarget] attribute DOMString formTarget;
+ [Reflect] attribute boolean multiple;
+ [Reflect=readonly] attribute boolean readOnly;
+ [Reflect] attribute boolean required;
+
+ [Reflect] attribute DOMString align;
+ [Reflect=usemap] attribute DOMString useMap;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLIElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLLIElement.cpp
new file mode 100644
index 0000000000..fbdb4d0444
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLIElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLLIElement.h>
+
+namespace Web::HTML {
+
+HTMLLIElement::HTMLLIElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLLIElement::~HTMLLIElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLIElement.h b/Userland/Libraries/LibWeb/HTML/HTMLLIElement.h
new file mode 100644
index 0000000000..b0113aef9c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLIElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLLIElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLLIElementWrapper;
+
+ HTMLLIElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLLIElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLIElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLLIElement.idl
new file mode 100644
index 0000000000..9330aa88b1
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLIElement.idl
@@ -0,0 +1,5 @@
+interface HTMLLIElement : HTMLElement {
+
+ [Reflect] attribute DOMString type;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.cpp
new file mode 100644
index 0000000000..766c64eecd
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLLabelElement.h>
+
+namespace Web::HTML {
+
+HTMLLabelElement::HTMLLabelElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLLabelElement::~HTMLLabelElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.h b/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.h
new file mode 100644
index 0000000000..210e366ee0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLLabelElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLLabelElementWrapper;
+
+ HTMLLabelElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLLabelElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.idl
new file mode 100644
index 0000000000..bbddd2052f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.idl
@@ -0,0 +1,5 @@
+interface HTMLLabelElement : HTMLElement {
+
+ [Reflect=for] attribute DOMString htmlFor;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLegendElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLLegendElement.cpp
new file mode 100644
index 0000000000..aa2bbc2049
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLegendElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLLegendElement.h>
+
+namespace Web::HTML {
+
+HTMLLegendElement::HTMLLegendElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLLegendElement::~HTMLLegendElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLegendElement.h b/Userland/Libraries/LibWeb/HTML/HTMLLegendElement.h
new file mode 100644
index 0000000000..02a8b24585
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLegendElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLLegendElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLLegendElementWrapper;
+
+ HTMLLegendElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLLegendElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLegendElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLLegendElement.idl
new file mode 100644
index 0000000000..49ee03cd21
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLegendElement.idl
@@ -0,0 +1,5 @@
+interface HTMLLegendElement : HTMLElement {
+
+ [Reflect] attribute DOMString align;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp
new file mode 100644
index 0000000000..323c642d61
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ByteBuffer.h>
+#include <AK/URL.h>
+#include <LibCore/File.h>
+#include <LibWeb/CSS/Parser/CSSParser.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/HTML/HTMLLinkElement.h>
+#include <LibWeb/Loader/ResourceLoader.h>
+
+namespace Web::HTML {
+
+HTMLLinkElement::HTMLLinkElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLLinkElement::~HTMLLinkElement()
+{
+}
+
+void HTMLLinkElement::inserted_into(Node& node)
+{
+ HTMLElement::inserted_into(node);
+
+ if (m_relationship & Relationship::Stylesheet && !(m_relationship & Relationship::Alternate))
+ load_stylesheet(document().complete_url(href()));
+}
+
+void HTMLLinkElement::resource_did_fail()
+{
+}
+
+void HTMLLinkElement::resource_did_load()
+{
+ ASSERT(resource());
+ if (!resource()->has_encoded_data())
+ return;
+
+ dbg() << "HTMLLinkElement: Resource did load, looks good! " << href();
+
+ auto sheet = parse_css(CSS::ParsingContext(document()), resource()->encoded_data());
+ if (!sheet) {
+ dbg() << "HTMLLinkElement: Failed to parse stylesheet: " << href();
+ return;
+ }
+
+ // Transfer the rules from the successfully parsed sheet into the sheet we've already inserted.
+ m_style_sheet->rules() = sheet->rules();
+
+ document().update_style();
+}
+
+void HTMLLinkElement::load_stylesheet(const URL& url)
+{
+ // First insert an empty style sheet in the document sheet list.
+ // There's probably a nicer way to do this, but this ensures that sheets are in document order.
+ m_style_sheet = CSS::StyleSheet::create({});
+ document().style_sheets().add_sheet(*m_style_sheet);
+
+ LoadRequest request;
+ request.set_url(url);
+ set_resource(ResourceLoader::the().load_resource(Resource::Type::Generic, request));
+}
+
+void HTMLLinkElement::parse_attribute(const FlyString& name, const String& value)
+{
+ if (name == HTML::AttributeNames::rel) {
+ m_relationship = 0;
+ auto parts = value.split_view(' ');
+ for (auto& part : parts) {
+ if (part == "stylesheet")
+ m_relationship |= Relationship::Stylesheet;
+ else if (part == "alternate")
+ m_relationship |= Relationship::Alternate;
+ }
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.h b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.h
new file mode 100644
index 0000000000..ac91ecbe21
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+#include <LibWeb/Loader/Resource.h>
+
+namespace Web::HTML {
+
+class HTMLLinkElement final
+ : public HTMLElement
+ , public ResourceClient {
+public:
+ using WrapperType = Bindings::HTMLLinkElementWrapper;
+
+ HTMLLinkElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLLinkElement() override;
+
+ virtual void inserted_into(Node&) override;
+
+ String rel() const { return attribute(HTML::AttributeNames::rel); }
+ String type() const { return attribute(HTML::AttributeNames::type); }
+ String href() const { return attribute(HTML::AttributeNames::href); }
+
+private:
+ // ^ResourceClient
+ virtual void resource_did_fail() override;
+ virtual void resource_did_load() override;
+
+ void parse_attribute(const FlyString&, const String&) override;
+
+ void load_stylesheet(const URL&);
+
+ struct Relationship {
+ enum {
+ Alternate = 1 << 0,
+ Stylesheet = 1 << 1,
+ };
+ };
+
+ unsigned m_relationship { 0 };
+ RefPtr<CSS::StyleSheet> m_style_sheet;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.idl
new file mode 100644
index 0000000000..e1999c74b8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.idl
@@ -0,0 +1,17 @@
+interface HTMLLinkElement : HTMLElement {
+
+ [Reflect] attribute DOMString href;
+ [Reflect] attribute DOMString hreflang;
+ [Reflect] attribute DOMString integrity;
+ [Reflect] attribute DOMString media;
+ [Reflect] attribute DOMString rel;
+ [Reflect] attribute DOMString type;
+ [Reflect=imagesrcset] attribute DOMString imageSrcset;
+ [Reflect=imagesizes] attribute DOMString imageSizes;
+ [Reflect] attribute boolean disabled;
+
+ [Reflect] attribute DOMString charset;
+ [Reflect] attribute DOMString rev;
+ [Reflect] attribute DOMString target;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMapElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLMapElement.cpp
new file mode 100644
index 0000000000..c85b871c71
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMapElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLMapElement.h>
+
+namespace Web::HTML {
+
+HTMLMapElement::HTMLMapElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLMapElement::~HTMLMapElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMapElement.h b/Userland/Libraries/LibWeb/HTML/HTMLMapElement.h
new file mode 100644
index 0000000000..23b59ce6fc
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMapElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLMapElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLMapElementWrapper;
+
+ HTMLMapElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLMapElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMapElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLMapElement.idl
new file mode 100644
index 0000000000..636a96f8a5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMapElement.idl
@@ -0,0 +1,5 @@
+interface HTMLMapElement : HTMLElement {
+
+ [Reflect] attribute DOMString name;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMarqueeElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLMarqueeElement.cpp
new file mode 100644
index 0000000000..02e5e2ec6c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMarqueeElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLMarqueeElement.h>
+
+namespace Web::HTML {
+
+HTMLMarqueeElement::HTMLMarqueeElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLMarqueeElement::~HTMLMarqueeElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMarqueeElement.h b/Userland/Libraries/LibWeb/HTML/HTMLMarqueeElement.h
new file mode 100644
index 0000000000..0a2e5f5e32
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMarqueeElement.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+// NOTE: This element is marked as obsolete, but is still listed as required by the specification.
+class HTMLMarqueeElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLMarqueeElementWrapper;
+
+ HTMLMarqueeElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLMarqueeElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMarqueeElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLMarqueeElement.idl
new file mode 100644
index 0000000000..49ff70654d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMarqueeElement.idl
@@ -0,0 +1,9 @@
+interface HTMLMarqueeElement : HTMLElement {
+
+ [Reflect] attribute DOMString behaviour;
+ [Reflect=bgcolor] attribute DOMString bgColor;
+ [Reflect] attribute DOMString direction;
+ [Reflect] attribute DOMString height;
+ [Reflect] attribute DOMString width;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp
new file mode 100644
index 0000000000..15e4c01195
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLMediaElement.h>
+
+namespace Web::HTML {
+
+HTMLMediaElement::HTMLMediaElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLMediaElement::~HTMLMediaElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h
new file mode 100644
index 0000000000..53adde12e3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLMediaElement : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLMediaElementWrapper;
+
+ HTMLMediaElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLMediaElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl
new file mode 100644
index 0000000000..dda7615580
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl
@@ -0,0 +1,10 @@
+interface HTMLMediaElement : HTMLElement {
+
+ [Reflect] attribute DOMString src;
+
+ [Reflect] attribute boolean autoplay;
+ [Reflect] attribute boolean loop;
+
+ [Reflect] attribute boolean controls;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMenuElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLMenuElement.cpp
new file mode 100644
index 0000000000..bcda7e5f56
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMenuElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLMenuElement.h>
+
+namespace Web::HTML {
+
+HTMLMenuElement::HTMLMenuElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLMenuElement::~HTMLMenuElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMenuElement.h b/Userland/Libraries/LibWeb/HTML/HTMLMenuElement.h
new file mode 100644
index 0000000000..8fe2acca90
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMenuElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLMenuElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLMenuElementWrapper;
+
+ HTMLMenuElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLMenuElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMenuElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLMenuElement.idl
new file mode 100644
index 0000000000..f9dc7ed7a0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMenuElement.idl
@@ -0,0 +1,5 @@
+interface HTMLMenuElement : HTMLElement {
+
+ [Reflect] attribute boolean compact;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMetaElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLMetaElement.cpp
new file mode 100644
index 0000000000..64c50a19e3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMetaElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLMetaElement.h>
+
+namespace Web::HTML {
+
+HTMLMetaElement::HTMLMetaElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLMetaElement::~HTMLMetaElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMetaElement.h b/Userland/Libraries/LibWeb/HTML/HTMLMetaElement.h
new file mode 100644
index 0000000000..e48271c826
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMetaElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLMetaElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLMetaElementWrapper;
+
+ HTMLMetaElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLMetaElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMetaElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLMetaElement.idl
new file mode 100644
index 0000000000..de1868b65c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMetaElement.idl
@@ -0,0 +1,9 @@
+interface HTMLMetaElement : HTMLElement {
+
+ [Reflect] attribute DOMString name;
+ [Reflect] attribute DOMString content;
+ [Reflect=http-equiv] attribute DOMString httpEquiv;
+
+ [Reflect] attribute DOMString scheme;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMeterElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLMeterElement.cpp
new file mode 100644
index 0000000000..07bfeee98f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMeterElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLMeterElement.h>
+
+namespace Web::HTML {
+
+HTMLMeterElement::HTMLMeterElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLMeterElement::~HTMLMeterElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMeterElement.h b/Userland/Libraries/LibWeb/HTML/HTMLMeterElement.h
new file mode 100644
index 0000000000..f6d983a6b0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMeterElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLMeterElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLMeterElementWrapper;
+
+ HTMLMeterElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLMeterElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMeterElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLMeterElement.idl
new file mode 100644
index 0000000000..72f6c1d4fe
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMeterElement.idl
@@ -0,0 +1,5 @@
+interface HTMLMeterElement : HTMLElement {
+
+
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLModElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLModElement.cpp
new file mode 100644
index 0000000000..5fcd7603b0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLModElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLModElement.h>
+
+namespace Web::HTML {
+
+HTMLModElement::HTMLModElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLModElement::~HTMLModElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLModElement.h b/Userland/Libraries/LibWeb/HTML/HTMLModElement.h
new file mode 100644
index 0000000000..f75c8bef9e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLModElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLModElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLModElementWrapper;
+
+ HTMLModElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLModElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLModElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLModElement.idl
new file mode 100644
index 0000000000..8e3a489fa4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLModElement.idl
@@ -0,0 +1,6 @@
+interface HTMLModElement : HTMLElement {
+
+ [Reflect] attribute DOMString cite;
+ [Reflect=datetime] attribute DOMString dateTime;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOListElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLOListElement.cpp
new file mode 100644
index 0000000000..48ac66fd8d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOListElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLOListElement.h>
+
+namespace Web::HTML {
+
+HTMLOListElement::HTMLOListElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLOListElement::~HTMLOListElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOListElement.h b/Userland/Libraries/LibWeb/HTML/HTMLOListElement.h
new file mode 100644
index 0000000000..8f5a849c75
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOListElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLOListElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLOListElementWrapper;
+
+ HTMLOListElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLOListElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOListElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLOListElement.idl
new file mode 100644
index 0000000000..c81faf9c0e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOListElement.idl
@@ -0,0 +1,8 @@
+interface HTMLOListElement : HTMLElement {
+
+ [Reflect] attribute boolean reversed;
+ [Reflect] attribute DOMString type;
+
+ [Reflect] attribute boolean compact;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLObjectElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLObjectElement.cpp
new file mode 100644
index 0000000000..a7514179d4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLObjectElement.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+#include <LibWeb/CSS/StyleResolver.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/HTML/HTMLObjectElement.h>
+#include <LibWeb/Layout/ImageBox.h>
+#include <LibWeb/Loader/ResourceLoader.h>
+
+namespace Web::HTML {
+
+HTMLObjectElement::HTMLObjectElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+ m_image_loader.on_load = [this] {
+ m_should_show_fallback_content = false;
+ this->document().force_layout();
+ };
+
+ m_image_loader.on_fail = [this] {
+ m_should_show_fallback_content = true;
+ this->document().force_layout();
+ };
+}
+
+HTMLObjectElement::~HTMLObjectElement()
+{
+}
+
+void HTMLObjectElement::parse_attribute(const FlyString& name, const String& value)
+{
+ HTMLElement::parse_attribute(name, value);
+
+ if (name == HTML::AttributeNames::data)
+ m_image_loader.load(document().complete_url(value));
+}
+
+RefPtr<Layout::Node> HTMLObjectElement::create_layout_node()
+{
+ if (m_should_show_fallback_content)
+ return HTMLElement::create_layout_node();
+
+ auto style = document().style_resolver().resolve_style(*this);
+ if (style->display() == CSS::Display::None)
+ return nullptr;
+ if (m_image_loader.has_image())
+ return adopt(*new Layout::ImageBox(document(), *this, move(style), m_image_loader));
+ return nullptr;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLObjectElement.h b/Userland/Libraries/LibWeb/HTML/HTMLObjectElement.h
new file mode 100644
index 0000000000..f07e122ff7
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLObjectElement.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibCore/Forward.h>
+#include <LibGfx/Forward.h>
+#include <LibWeb/HTML/HTMLElement.h>
+#include <LibWeb/Loader/ImageLoader.h>
+
+namespace Web::HTML {
+
+class HTMLObjectElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLObjectElementWrapper;
+
+ HTMLObjectElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLObjectElement() override;
+
+ virtual void parse_attribute(const FlyString& name, const String& value) override;
+
+ String data() const { return attribute(HTML::AttributeNames::data); }
+ String type() const { return attribute(HTML::AttributeNames::type); }
+
+private:
+ virtual RefPtr<Layout::Node> create_layout_node() override;
+
+ ImageLoader m_image_loader;
+ bool m_should_show_fallback_content { false };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLObjectElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLObjectElement.idl
new file mode 100644
index 0000000000..3c5790bcc5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLObjectElement.idl
@@ -0,0 +1,19 @@
+interface HTMLObjectElement : HTMLElement {
+
+ [Reflect] attribute DOMString data;
+ [Reflect] attribute DOMString type;
+ [Reflect] attribute DOMString name;
+ [Reflect=usemap] attribute DOMString useMap;
+ [Reflect] attribute DOMString width;
+ [Reflect] attribute DOMString height;
+
+ [Reflect] attribute DOMString align;
+ [Reflect] attribute DOMString archive;
+ [Reflect] attribute DOMString code;
+ [Reflect] attribute boolean declare;
+ [Reflect] attribute DOMString standby;
+ [Reflect=codetype] attribute DOMString codeType;
+
+ [LegacyNullToEmptyString, Reflect] attribute DOMString border;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOptGroupElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLOptGroupElement.cpp
new file mode 100644
index 0000000000..50c7d23b6f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOptGroupElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLOptGroupElement.h>
+
+namespace Web::HTML {
+
+HTMLOptGroupElement::HTMLOptGroupElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLOptGroupElement::~HTMLOptGroupElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOptGroupElement.h b/Userland/Libraries/LibWeb/HTML/HTMLOptGroupElement.h
new file mode 100644
index 0000000000..77a90aebcb
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOptGroupElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLOptGroupElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLOptGroupElementWrapper;
+
+ HTMLOptGroupElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLOptGroupElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOptGroupElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLOptGroupElement.idl
new file mode 100644
index 0000000000..d442091b1a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOptGroupElement.idl
@@ -0,0 +1,6 @@
+interface HTMLOptGroupElement : HTMLElement {
+
+ [Reflect] attribute boolean disabled;
+ [Reflect] attribute DOMString label;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.cpp
new file mode 100644
index 0000000000..c22fbb3668
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLOptionElement.h>
+
+namespace Web::HTML {
+
+HTMLOptionElement::HTMLOptionElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLOptionElement::~HTMLOptionElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.h b/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.h
new file mode 100644
index 0000000000..e2bead49f6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLOptionElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLOptionElementWrapper;
+
+ HTMLOptionElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLOptionElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.idl
new file mode 100644
index 0000000000..96dcaec548
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.idl
@@ -0,0 +1,6 @@
+interface HTMLOptionElement : HTMLElement {
+
+ [Reflect] attribute boolean disabled;
+ [Reflect=selected] attribute boolean defaultSelected;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOutputElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLOutputElement.cpp
new file mode 100644
index 0000000000..80f9d82dd5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOutputElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLOutputElement.h>
+
+namespace Web::HTML {
+
+HTMLOutputElement::HTMLOutputElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLOutputElement::~HTMLOutputElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOutputElement.h b/Userland/Libraries/LibWeb/HTML/HTMLOutputElement.h
new file mode 100644
index 0000000000..110225535f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOutputElement.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLOutputElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLOutputElementWrapper;
+
+ HTMLOutputElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLOutputElement() override;
+
+ const String& type() const
+ {
+ static String output = "output";
+ return output;
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOutputElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLOutputElement.idl
new file mode 100644
index 0000000000..94c45e99f3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOutputElement.idl
@@ -0,0 +1,5 @@
+interface HTMLOutputElement : HTMLElement {
+
+ readonly attribute DOMString type;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLParagraphElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLParagraphElement.cpp
new file mode 100644
index 0000000000..9e0fab9859
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLParagraphElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLParagraphElement.h>
+
+namespace Web::HTML {
+
+HTMLParagraphElement::HTMLParagraphElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLParagraphElement::~HTMLParagraphElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLParagraphElement.h b/Userland/Libraries/LibWeb/HTML/HTMLParagraphElement.h
new file mode 100644
index 0000000000..660855edb9
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLParagraphElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLParagraphElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLParagraphElementWrapper;
+
+ HTMLParagraphElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLParagraphElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLParagraphElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLParagraphElement.idl
new file mode 100644
index 0000000000..e1248da905
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLParagraphElement.idl
@@ -0,0 +1,5 @@
+interface HTMLParagraphElement : HTMLElement {
+
+ [Reflect] attribute DOMString align;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLParamElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLParamElement.cpp
new file mode 100644
index 0000000000..838ae13ef8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLParamElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLParamElement.h>
+
+namespace Web::HTML {
+
+HTMLParamElement::HTMLParamElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLParamElement::~HTMLParamElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLParamElement.h b/Userland/Libraries/LibWeb/HTML/HTMLParamElement.h
new file mode 100644
index 0000000000..642551cdd0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLParamElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLParamElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLParamElementWrapper;
+
+ HTMLParamElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLParamElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLParamElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLParamElement.idl
new file mode 100644
index 0000000000..a848fc5364
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLParamElement.idl
@@ -0,0 +1,9 @@
+interface HTMLParamElement : HTMLElement {
+
+ [Reflect] attribute DOMString name;
+ [Reflect] attribute DOMString value;
+
+ [Reflect] attribute DOMString type;
+ [Reflect=valuetype] attribute DOMString valueType;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLPictureElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLPictureElement.cpp
new file mode 100644
index 0000000000..5aec65b51a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLPictureElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLPictureElement.h>
+
+namespace Web::HTML {
+
+HTMLPictureElement::HTMLPictureElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLPictureElement::~HTMLPictureElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLPictureElement.h b/Userland/Libraries/LibWeb/HTML/HTMLPictureElement.h
new file mode 100644
index 0000000000..8d12df2b16
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLPictureElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLPictureElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLPictureElementWrapper;
+
+ HTMLPictureElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLPictureElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLPictureElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLPictureElement.idl
new file mode 100644
index 0000000000..9bd6cefe16
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLPictureElement.idl
@@ -0,0 +1,5 @@
+interface HTMLPictureElement : HTMLElement {
+
+
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLPreElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLPreElement.cpp
new file mode 100644
index 0000000000..69349d7238
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLPreElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLPreElement.h>
+
+namespace Web::HTML {
+
+HTMLPreElement::HTMLPreElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLPreElement::~HTMLPreElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLPreElement.h b/Userland/Libraries/LibWeb/HTML/HTMLPreElement.h
new file mode 100644
index 0000000000..b87197f600
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLPreElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLPreElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLPreElementWrapper;
+
+ HTMLPreElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLPreElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLPreElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLPreElement.idl
new file mode 100644
index 0000000000..27a0404ba5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLPreElement.idl
@@ -0,0 +1,5 @@
+interface HTMLPreElement : HTMLElement {
+
+
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLProgressElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLProgressElement.cpp
new file mode 100644
index 0000000000..cfad730312
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLProgressElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLProgressElement.h>
+
+namespace Web::HTML {
+
+HTMLProgressElement::HTMLProgressElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLProgressElement::~HTMLProgressElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLProgressElement.h b/Userland/Libraries/LibWeb/HTML/HTMLProgressElement.h
new file mode 100644
index 0000000000..cb18e317f2
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLProgressElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLProgressElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLProgressElementWrapper;
+
+ HTMLProgressElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLProgressElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLProgressElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLProgressElement.idl
new file mode 100644
index 0000000000..1d00f9b3fc
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLProgressElement.idl
@@ -0,0 +1,5 @@
+interface HTMLProgressElement : HTMLElement {
+
+
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLQuoteElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLQuoteElement.cpp
new file mode 100644
index 0000000000..07bf149640
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLQuoteElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLQuoteElement.h>
+
+namespace Web::HTML {
+
+HTMLQuoteElement::HTMLQuoteElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLQuoteElement::~HTMLQuoteElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLQuoteElement.h b/Userland/Libraries/LibWeb/HTML/HTMLQuoteElement.h
new file mode 100644
index 0000000000..98e254f213
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLQuoteElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLQuoteElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLQuoteElementWrapper;
+
+ HTMLQuoteElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLQuoteElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLQuoteElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLQuoteElement.idl
new file mode 100644
index 0000000000..390074f746
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLQuoteElement.idl
@@ -0,0 +1,5 @@
+interface HTMLQuoteElement : HTMLElement {
+
+ [Reflect] attribute DOMString cite;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp
new file mode 100644
index 0000000000..cc3031984c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibJS/Parser.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/HTML/EventNames.h>
+#include <LibWeb/HTML/HTMLScriptElement.h>
+#include <LibWeb/Loader/ResourceLoader.h>
+
+namespace Web::HTML {
+
+HTMLScriptElement::HTMLScriptElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLScriptElement::~HTMLScriptElement()
+{
+}
+
+void HTMLScriptElement::set_parser_document(Badge<HTMLDocumentParser>, DOM::Document& document)
+{
+ m_parser_document = document;
+}
+
+void HTMLScriptElement::set_non_blocking(Badge<HTMLDocumentParser>, bool non_blocking)
+{
+ m_non_blocking = non_blocking;
+}
+
+void HTMLScriptElement::execute_script()
+{
+ document().run_javascript(m_script_source);
+
+ if (has_attribute(HTML::AttributeNames::src))
+ dispatch_event(DOM::Event::create(EventNames::load));
+}
+
+void HTMLScriptElement::prepare_script(Badge<HTMLDocumentParser>)
+{
+ if (m_already_started)
+ return;
+ RefPtr<DOM::Document> parser_document = m_parser_document.ptr();
+ m_parser_document = nullptr;
+
+ if (parser_document && !has_attribute(HTML::AttributeNames::async)) {
+ m_non_blocking = true;
+ }
+
+ auto source_text = child_text_content();
+ if (!has_attribute(HTML::AttributeNames::src) && source_text.is_empty())
+ return;
+
+ if (!is_connected())
+ return;
+
+ // FIXME: Check the "type" and "language" attributes
+
+ if (parser_document) {
+ m_parser_document = *parser_document;
+ m_non_blocking = false;
+ }
+
+ m_already_started = true;
+ m_preparation_time_document = document();
+
+ if (parser_document && parser_document.ptr() != m_preparation_time_document.ptr()) {
+ return;
+ }
+
+ // FIXME: Check if scripting is disabled, if so return
+ // FIXME: Check the "nomodule" content attribute
+ // FIXME: Check CSP
+ // FIXME: Check "event" and "for" attributes
+ // FIXME: Check "charset" attribute
+ // FIXME: Check CORS
+ // FIXME: Module script credentials mode
+ // FIXME: Cryptographic nonce
+ // FIXME: Check "integrity" attribute
+ // FIXME: Check "referrerpolicy" attribute
+
+ m_parser_inserted = !!m_parser_document;
+
+ // FIXME: Check fetch options
+
+ if (has_attribute(HTML::AttributeNames::src)) {
+ auto src = attribute(HTML::AttributeNames::src);
+ if (src.is_empty()) {
+ // FIXME: Fire an "error" event at the element and return
+ ASSERT_NOT_REACHED();
+ }
+ m_from_an_external_file = true;
+
+ auto url = document().complete_url(src);
+ if (!url.is_valid()) {
+ // FIXME: Fire an "error" event at the element and return
+ ASSERT_NOT_REACHED();
+ }
+
+ // FIXME: Check classic vs. module script type
+
+ // FIXME: This load should be made asynchronous and the parser should spin an event loop etc.
+ ResourceLoader::the().load_sync(
+ url,
+ [this, url](auto data, auto&) {
+ if (data.is_null()) {
+ dbgln("HTMLScriptElement: Failed to load {}", url);
+ return;
+ }
+ m_script_source = String::copy(data);
+ script_became_ready();
+ },
+ [this](auto&) {
+ m_failed_to_load = true;
+ });
+ } else {
+ // FIXME: Check classic vs. module script type
+ m_script_source = source_text;
+ script_became_ready();
+ }
+
+ // FIXME: Check classic vs. module
+ if (has_attribute(HTML::AttributeNames::src) && has_attribute(HTML::AttributeNames::defer) && m_parser_inserted && !has_attribute(HTML::AttributeNames::async)) {
+ document().add_script_to_execute_when_parsing_has_finished({}, *this);
+ }
+
+ else if (has_attribute(HTML::AttributeNames::src) && m_parser_inserted && !has_attribute(HTML::AttributeNames::async)) {
+
+ document().set_pending_parsing_blocking_script({}, this);
+ when_the_script_is_ready([this] {
+ m_ready_to_be_parser_executed = true;
+ });
+ }
+
+ else if (has_attribute(HTML::AttributeNames::src) && !has_attribute(HTML::AttributeNames::async) && !m_non_blocking) {
+ ASSERT_NOT_REACHED();
+ }
+
+ else if (has_attribute(HTML::AttributeNames::src)) {
+ m_preparation_time_document->add_script_to_execute_as_soon_as_possible({}, *this);
+ }
+
+ else {
+ // Immediately execute the script block, even if other scripts are already executing.
+ execute_script();
+ }
+}
+
+void HTMLScriptElement::script_became_ready()
+{
+ m_script_ready = true;
+ if (!m_script_ready_callback)
+ return;
+ m_script_ready_callback();
+ m_script_ready_callback = nullptr;
+}
+
+void HTMLScriptElement::when_the_script_is_ready(Function<void()> callback)
+{
+ if (m_script_ready) {
+ callback();
+ return;
+ }
+ m_script_ready_callback = move(callback);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.h b/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.h
new file mode 100644
index 0000000000..b15afcdd7a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLScriptElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLScriptElementWrapper;
+
+ HTMLScriptElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLScriptElement() override;
+
+ bool is_non_blocking() const { return m_non_blocking; }
+ bool is_ready_to_be_parser_executed() const { return m_ready_to_be_parser_executed; }
+ bool failed_to_load() const { return m_failed_to_load; }
+
+ void set_parser_document(Badge<HTMLDocumentParser>, DOM::Document&);
+ void set_non_blocking(Badge<HTMLDocumentParser>, bool);
+ void set_already_started(Badge<HTMLDocumentParser>, bool b) { m_already_started = b; }
+ void prepare_script(Badge<HTMLDocumentParser>);
+ void execute_script();
+
+private:
+ void script_became_ready();
+ void when_the_script_is_ready(Function<void()>);
+
+ WeakPtr<DOM::Document> m_parser_document;
+ WeakPtr<DOM::Document> m_preparation_time_document;
+ bool m_non_blocking { false };
+ bool m_already_started { false };
+ bool m_parser_inserted { false };
+ bool m_from_an_external_file { false };
+ bool m_script_ready { false };
+ bool m_ready_to_be_parser_executed { false };
+ bool m_failed_to_load { false };
+
+ Function<void()> m_script_ready_callback;
+
+ String m_script_source;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.idl
new file mode 100644
index 0000000000..370dadd29a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.idl
@@ -0,0 +1,13 @@
+interface HTMLScriptElement : HTMLElement {
+
+ [Reflect] attribute DOMString src;
+ [Reflect] attribute DOMString type;
+ [Reflect=nomodule] attribute boolean noModule;
+ [Reflect] attribute boolean defer;
+ [Reflect] attribute DOMString integrity;
+
+ [Reflect] attribute DOMString charset;
+ [Reflect] attribute DOMString event;
+ [Reflect=for] attribute DOMString htmlFor;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.cpp
new file mode 100644
index 0000000000..9c4b486b05
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLSelectElement.h>
+
+namespace Web::HTML {
+
+HTMLSelectElement::HTMLSelectElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLSelectElement::~HTMLSelectElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.h b/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.h
new file mode 100644
index 0000000000..49d66970b9
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLSelectElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLSelectElementWrapper;
+
+ HTMLSelectElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLSelectElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.idl
new file mode 100644
index 0000000000..808db0b45f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.idl
@@ -0,0 +1,7 @@
+interface HTMLSelectElement : HTMLElement {
+
+ [Reflect] attribute boolean disabled;
+ [Reflect] attribute boolean multiple;
+ [Reflect] attribute boolean required;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.cpp
new file mode 100644
index 0000000000..cb32ab7772
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLSlotElement.h>
+
+namespace Web::HTML {
+
+HTMLSlotElement::HTMLSlotElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLSlotElement::~HTMLSlotElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.h b/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.h
new file mode 100644
index 0000000000..96c42ff6f2
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLSlotElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLSlotElementWrapper;
+
+ HTMLSlotElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLSlotElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.idl
new file mode 100644
index 0000000000..04dc7a7111
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.idl
@@ -0,0 +1,5 @@
+interface HTMLSlotElement : HTMLElement {
+
+ [Reflect] attribute DOMString name;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSourceElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLSourceElement.cpp
new file mode 100644
index 0000000000..3dcf89815e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLSourceElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLSourceElement.h>
+
+namespace Web::HTML {
+
+HTMLSourceElement::HTMLSourceElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLSourceElement::~HTMLSourceElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSourceElement.h b/Userland/Libraries/LibWeb/HTML/HTMLSourceElement.h
new file mode 100644
index 0000000000..f126968ddd
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLSourceElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLSourceElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLSourceElementWrapper;
+
+ HTMLSourceElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLSourceElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSourceElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLSourceElement.idl
new file mode 100644
index 0000000000..5496d55a1a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLSourceElement.idl
@@ -0,0 +1,9 @@
+interface HTMLSourceElement : HTMLElement {
+
+ [Reflect] attribute DOMString src;
+ [Reflect] attribute DOMString type;
+ [Reflect] attribute DOMString srcset;
+ [Reflect] attribute DOMString sizes;
+ [Reflect] attribute DOMString media;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSpanElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLSpanElement.cpp
new file mode 100644
index 0000000000..568d24f891
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLSpanElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLSpanElement.h>
+
+namespace Web::HTML {
+
+HTMLSpanElement::HTMLSpanElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLSpanElement::~HTMLSpanElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSpanElement.h b/Userland/Libraries/LibWeb/HTML/HTMLSpanElement.h
new file mode 100644
index 0000000000..1906eb2a56
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLSpanElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLSpanElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLSpanElementWrapper;
+
+ HTMLSpanElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLSpanElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSpanElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLSpanElement.idl
new file mode 100644
index 0000000000..a87dda0eb7
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLSpanElement.idl
@@ -0,0 +1,5 @@
+interface HTMLSpanElement : HTMLElement {
+
+
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLStyleElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLStyleElement.cpp
new file mode 100644
index 0000000000..44fa6daea0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLStyleElement.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibWeb/CSS/Parser/CSSParser.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/HTML/HTMLStyleElement.h>
+
+namespace Web::HTML {
+
+HTMLStyleElement::HTMLStyleElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLStyleElement::~HTMLStyleElement()
+{
+}
+
+void HTMLStyleElement::children_changed()
+{
+ StringBuilder builder;
+ for_each_child([&](auto& child) {
+ if (is<DOM::Text>(child))
+ builder.append(downcast<DOM::Text>(child).text_content());
+ });
+ m_stylesheet = parse_css(CSS::ParsingContext(document()), builder.to_string());
+ if (m_stylesheet)
+ document().style_sheets().add_sheet(*m_stylesheet);
+ else
+ document().style_sheets().add_sheet(CSS::StyleSheet::create({}));
+ HTMLElement::children_changed();
+}
+
+void HTMLStyleElement::removed_from(Node& old_parent)
+{
+ if (m_stylesheet) {
+ // FIXME: Remove the sheet from the document
+ }
+ return HTMLElement::removed_from(old_parent);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLStyleElement.h b/Userland/Libraries/LibWeb/HTML/HTMLStyleElement.h
new file mode 100644
index 0000000000..9e3b615c0c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLStyleElement.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLStyleElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLStyleElementWrapper;
+
+ HTMLStyleElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLStyleElement() override;
+
+ virtual void children_changed() override;
+ virtual void removed_from(Node&) override;
+
+private:
+ RefPtr<CSS::StyleSheet> m_stylesheet;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLStyleElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLStyleElement.idl
new file mode 100644
index 0000000000..a5ab2934f3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLStyleElement.idl
@@ -0,0 +1,7 @@
+interface HTMLStyleElement : HTMLElement {
+
+ [Reflect] attribute DOMString media;
+
+ [Reflect] attribute DOMString type;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableCaptionElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLTableCaptionElement.cpp
new file mode 100644
index 0000000000..a9c3e62a98
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableCaptionElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLTableCaptionElement.h>
+
+namespace Web::HTML {
+
+HTMLTableCaptionElement::HTMLTableCaptionElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLTableCaptionElement::~HTMLTableCaptionElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableCaptionElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTableCaptionElement.h
new file mode 100644
index 0000000000..691b49ab3c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableCaptionElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLTableCaptionElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLTableCaptionElementWrapper;
+
+ HTMLTableCaptionElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLTableCaptionElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableCaptionElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLTableCaptionElement.idl
new file mode 100644
index 0000000000..97d9419ccf
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableCaptionElement.idl
@@ -0,0 +1,5 @@
+interface HTMLTableCaptionElement : HTMLElement {
+
+ [Reflect] attribute DOMString align;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp
new file mode 100644
index 0000000000..5dd60f2cbb
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/Parser/CSSParser.h>
+#include <LibWeb/HTML/HTMLTableCellElement.h>
+
+namespace Web::HTML {
+
+HTMLTableCellElement::HTMLTableCellElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLTableCellElement::~HTMLTableCellElement()
+{
+}
+
+void HTMLTableCellElement::apply_presentational_hints(CSS::StyleProperties& style) const
+{
+ for_each_attribute([&](auto& name, auto& value) {
+ if (name == HTML::AttributeNames::bgcolor) {
+ auto color = Color::from_string(value);
+ if (color.has_value())
+ style.set_property(CSS::PropertyID::BackgroundColor, CSS::ColorStyleValue::create(color.value()));
+ return;
+ }
+ if (name == HTML::AttributeNames::align) {
+ if (value.equals_ignoring_case("center") || value.equals_ignoring_case("middle"))
+ style.set_property(CSS::PropertyID::TextAlign, StringView("-libweb-center"));
+ else
+ style.set_property(CSS::PropertyID::TextAlign, value.view());
+ return;
+ }
+ if (name == HTML::AttributeNames::width) {
+ if (auto parsed_value = parse_html_length(document(), value))
+ style.set_property(CSS::PropertyID::Width, parsed_value.release_nonnull());
+ return;
+ }
+ });
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableCellElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTableCellElement.h
new file mode 100644
index 0000000000..7a2b9e4f09
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableCellElement.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLTableCellElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLTableCellElementWrapper;
+
+ HTMLTableCellElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLTableCellElement() override;
+
+private:
+ virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableCellElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLTableCellElement.idl
new file mode 100644
index 0000000000..bcf8f1a04a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableCellElement.idl
@@ -0,0 +1,18 @@
+interface HTMLTableCellElement : HTMLElement {
+
+ [Reflect] attribute DOMString headers;
+ [Reflect] attribute DOMString abbr;
+
+ [Reflect] attribute DOMString align;
+ [Reflect] attribute DOMString axis;
+ [Reflect] attribute DOMString height;
+ [Reflect] attribute DOMString width;
+
+ [Reflect=char] attribute DOMString ch;
+ [Reflect=charoff] attribute DOMString chOff;
+ [Reflect=nowrap] attribute boolean noWrap;
+ [Reflect=valign] attribute DOMString vAlign;
+
+ [LegacyNullToEmptyString, Reflect=bgcolor] attribute DOMString bgColor;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableColElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLTableColElement.cpp
new file mode 100644
index 0000000000..17a3e494c8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableColElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLTableColElement.h>
+
+namespace Web::HTML {
+
+HTMLTableColElement::HTMLTableColElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLTableColElement::~HTMLTableColElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableColElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTableColElement.h
new file mode 100644
index 0000000000..c6b8e706ff
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableColElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLTableColElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLTableColElementWrapper;
+
+ HTMLTableColElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLTableColElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableColElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLTableColElement.idl
new file mode 100644
index 0000000000..d76f0da76b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableColElement.idl
@@ -0,0 +1,9 @@
+interface HTMLTableColElement : HTMLElement {
+
+ [Reflect] attribute DOMString align;
+ [Reflect=char] attribute DOMString ch;
+ [Reflect=charoff] attribute DOMString chOff;
+ [Reflect=valign] attribute DOMString vAlign;
+ [Reflect] attribute DOMString width;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLTableElement.cpp
new file mode 100644
index 0000000000..d31641af42
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableElement.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/Parser/CSSParser.h>
+#include <LibWeb/HTML/HTMLTableElement.h>
+
+namespace Web::HTML {
+
+HTMLTableElement::HTMLTableElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLTableElement::~HTMLTableElement()
+{
+}
+
+void HTMLTableElement::apply_presentational_hints(CSS::StyleProperties& style) const
+{
+ for_each_attribute([&](auto& name, auto& value) {
+ if (name == HTML::AttributeNames::width) {
+ if (auto parsed_value = parse_html_length(document(), value))
+ style.set_property(CSS::PropertyID::Width, parsed_value.release_nonnull());
+ return;
+ }
+ if (name == HTML::AttributeNames::height) {
+ if (auto parsed_value = parse_html_length(document(), value))
+ style.set_property(CSS::PropertyID::Height, parsed_value.release_nonnull());
+ return;
+ }
+ if (name == HTML::AttributeNames::bgcolor) {
+ auto color = Color::from_string(value);
+ if (color.has_value())
+ style.set_property(CSS::PropertyID::BackgroundColor, CSS::ColorStyleValue::create(color.value()));
+ return;
+ }
+ });
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTableElement.h
new file mode 100644
index 0000000000..39d65833c6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableElement.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLTableElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLTableElementWrapper;
+
+ HTMLTableElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLTableElement() override;
+
+private:
+ virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLTableElement.idl
new file mode 100644
index 0000000000..0990efa4a1
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableElement.idl
@@ -0,0 +1,14 @@
+interface HTMLTableElement : HTMLElement {
+
+ [Reflect] attribute DOMString align;
+ [Reflect] attribute DOMString border;
+ [Reflect] attribute DOMString frame;
+ [Reflect] attribute DOMString rules;
+ [Reflect] attribute DOMString summary;
+ [Reflect] attribute DOMString width;
+
+ [LegacyNullToEmptyString, Reflect=bgcolor] attribute DOMString bgColor;
+ [LegacyNullToEmptyString, Reflect=cellpadding] attribute DOMString cellPadding;
+ [LegacyNullToEmptyString, Reflect=cellspacing] attribute DOMString cellSpacing;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableRowElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLTableRowElement.cpp
new file mode 100644
index 0000000000..28f0d6d910
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableRowElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLTableRowElement.h>
+
+namespace Web::HTML {
+
+HTMLTableRowElement::HTMLTableRowElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLTableRowElement::~HTMLTableRowElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableRowElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTableRowElement.h
new file mode 100644
index 0000000000..dc64d13322
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableRowElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLTableRowElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLTableRowElementWrapper;
+
+ HTMLTableRowElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLTableRowElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableRowElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLTableRowElement.idl
new file mode 100644
index 0000000000..1b999948f4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableRowElement.idl
@@ -0,0 +1,10 @@
+interface HTMLTableRowElement : HTMLElement {
+
+ [Reflect] attribute DOMString align;
+ [Reflect=char] attribute DOMString ch;
+ [Reflect=charoff] attribute DOMString chOff;
+ [Reflect=valign] attribute DOMString vAlign;
+
+ [LegacyNullToEmptyString, Reflect=bgcolor] attribute DOMString bgColor;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableSectionElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLTableSectionElement.cpp
new file mode 100644
index 0000000000..2ce4394b94
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableSectionElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLTableSectionElement.h>
+
+namespace Web::HTML {
+
+HTMLTableSectionElement::HTMLTableSectionElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLTableSectionElement::~HTMLTableSectionElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableSectionElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTableSectionElement.h
new file mode 100644
index 0000000000..dc58ea8a1b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableSectionElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLTableSectionElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLTableSectionElementWrapper;
+
+ HTMLTableSectionElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLTableSectionElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTableSectionElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLTableSectionElement.idl
new file mode 100644
index 0000000000..4201411b8c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTableSectionElement.idl
@@ -0,0 +1,8 @@
+interface HTMLTableSectionElement : HTMLElement {
+
+ [Reflect] attribute DOMString align;
+ [Reflect=char] attribute DOMString ch;
+ [Reflect=charoff] attribute DOMString chOff;
+ [Reflect=valign] attribute DOMString vAlign;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.cpp
new file mode 100644
index 0000000000..8a0209337b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/HTML/HTMLTemplateElement.h>
+
+namespace Web::HTML {
+
+HTMLTemplateElement::HTMLTemplateElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+ m_content = adopt(*new DOM::DocumentFragment(appropriate_template_contents_owner_document(document)));
+ m_content->set_host(*this);
+}
+
+HTMLTemplateElement::~HTMLTemplateElement()
+{
+}
+
+DOM::Document& HTMLTemplateElement::appropriate_template_contents_owner_document(DOM::Document& document)
+{
+ if (!document.created_for_appropriate_template_contents()) {
+ if (!document.associated_inert_template_document()) {
+ auto new_document = DOM::Document::create();
+ new_document->set_created_for_appropriate_template_contents(true);
+
+ // FIXME: If doc is an HTML document, mark new doc as an HTML document also.
+
+ document.set_associated_inert_template_document(new_document);
+ }
+
+ return *document.associated_inert_template_document();
+ }
+
+ return document;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.h
new file mode 100644
index 0000000000..8e26320684
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/DOM/DocumentFragment.h>
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLTemplateElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLTemplateElementWrapper;
+
+ HTMLTemplateElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLTemplateElement() override;
+
+ NonnullRefPtr<DOM::DocumentFragment> content() { return *m_content; }
+ const NonnullRefPtr<DOM::DocumentFragment> content() const { return *m_content; }
+
+private:
+ DOM::Document& appropriate_template_contents_owner_document(DOM::Document&);
+
+ RefPtr<DOM::DocumentFragment> m_content;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.idl
new file mode 100644
index 0000000000..efbbc57e02
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.idl
@@ -0,0 +1,5 @@
+interface HTMLTemplateElement : HTMLElement {
+
+ readonly attribute DocumentFragment content;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp
new file mode 100644
index 0000000000..29433042c4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLTextAreaElement.h>
+
+namespace Web::HTML {
+
+HTMLTextAreaElement::HTMLTextAreaElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLTextAreaElement::~HTMLTextAreaElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h
new file mode 100644
index 0000000000..e09b95541a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLTextAreaElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLTextAreaElementWrapper;
+
+ HTMLTextAreaElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLTextAreaElement() override;
+
+ const String& type() const
+ {
+ static String textarea = "textarea";
+ return textarea;
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl
new file mode 100644
index 0000000000..878cc67db0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl
@@ -0,0 +1,12 @@
+interface HTMLTextAreaElement : HTMLElement {
+
+ [Reflect] attribute DOMString placeholder;
+ [Reflect] attribute DOMString name;
+ [Reflect] attribute DOMString wrap;
+ readonly attribute DOMString type;
+
+ [Reflect] attribute boolean disabled;
+ [Reflect=readonly] attribute boolean readOnly;
+ [Reflect] attribute boolean required;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTimeElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLTimeElement.cpp
new file mode 100644
index 0000000000..613f8aadbe
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTimeElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLTimeElement.h>
+
+namespace Web::HTML {
+
+HTMLTimeElement::HTMLTimeElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLTimeElement::~HTMLTimeElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTimeElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTimeElement.h
new file mode 100644
index 0000000000..aacef209a9
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTimeElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLTimeElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLTimeElementWrapper;
+
+ HTMLTimeElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLTimeElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTimeElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLTimeElement.idl
new file mode 100644
index 0000000000..f82d1e1aea
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTimeElement.idl
@@ -0,0 +1,5 @@
+interface HTMLTimeElement : HTMLElement {
+
+ [Reflect=datetime] attribute DOMString dateTime;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTitleElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLTitleElement.cpp
new file mode 100644
index 0000000000..a1404dfe9e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTitleElement.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/HTML/HTMLTitleElement.h>
+#include <LibWeb/Page/Page.h>
+
+namespace Web::HTML {
+
+HTMLTitleElement::HTMLTitleElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLTitleElement::~HTMLTitleElement()
+{
+}
+
+void HTMLTitleElement::children_changed()
+{
+ HTMLElement::children_changed();
+ if (auto* page = document().page())
+ page->client().page_did_change_title(document().title());
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTitleElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTitleElement.h
new file mode 100644
index 0000000000..42c28f467f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTitleElement.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLTitleElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLTitleElementWrapper;
+
+ HTMLTitleElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLTitleElement() override;
+
+private:
+ virtual void children_changed() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTitleElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLTitleElement.idl
new file mode 100644
index 0000000000..6cfbc9ef29
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTitleElement.idl
@@ -0,0 +1,5 @@
+interface HTMLTitleElement : HTMLElement {
+
+
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTrackElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLTrackElement.cpp
new file mode 100644
index 0000000000..2f0cee7f28
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTrackElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLTrackElement.h>
+
+namespace Web::HTML {
+
+HTMLTrackElement::HTMLTrackElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLTrackElement::~HTMLTrackElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTrackElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTrackElement.h
new file mode 100644
index 0000000000..6a0c78c176
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTrackElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLTrackElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLTrackElementWrapper;
+
+ HTMLTrackElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLTrackElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTrackElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLTrackElement.idl
new file mode 100644
index 0000000000..d58d662cb0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTrackElement.idl
@@ -0,0 +1,8 @@
+interface HTMLTrackElement : HTMLElement {
+
+ [Reflect] attribute DOMString src;
+ [Reflect] attribute DOMString srclang;
+ [Reflect] attribute DOMString label;
+ [Reflect] attribute boolean default;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLUListElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLUListElement.cpp
new file mode 100644
index 0000000000..d9cbcb9cd7
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLUListElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLUListElement.h>
+
+namespace Web::HTML {
+
+HTMLUListElement::HTMLUListElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLUListElement::~HTMLUListElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLUListElement.h b/Userland/Libraries/LibWeb/HTML/HTMLUListElement.h
new file mode 100644
index 0000000000..c4efa7949a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLUListElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLUListElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLUListElementWrapper;
+
+ HTMLUListElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLUListElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLUListElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLUListElement.idl
new file mode 100644
index 0000000000..6490a6d5c8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLUListElement.idl
@@ -0,0 +1,6 @@
+interface HTMLUListElement : HTMLElement {
+
+ [Reflect] attribute boolean compact;
+ [Reflect] attribute DOMString type;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLUnknownElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLUnknownElement.cpp
new file mode 100644
index 0000000000..3ef559692d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLUnknownElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLUnknownElement.h>
+
+namespace Web::HTML {
+
+HTMLUnknownElement::HTMLUnknownElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLElement(document, qualified_name)
+{
+}
+
+HTMLUnknownElement::~HTMLUnknownElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLUnknownElement.h b/Userland/Libraries/LibWeb/HTML/HTMLUnknownElement.h
new file mode 100644
index 0000000000..896df0d064
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLUnknownElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLElement.h>
+
+namespace Web::HTML {
+
+class HTMLUnknownElement final : public HTMLElement {
+public:
+ using WrapperType = Bindings::HTMLUnknownElementWrapper;
+
+ HTMLUnknownElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLUnknownElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLUnknownElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLUnknownElement.idl
new file mode 100644
index 0000000000..7a6672d4f4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLUnknownElement.idl
@@ -0,0 +1,5 @@
+interface HTMLUnknownElement : HTMLElement {
+
+
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.cpp
new file mode 100644
index 0000000000..cbd97199cf
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/HTMLVideoElement.h>
+
+namespace Web::HTML {
+
+HTMLVideoElement::HTMLVideoElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : HTMLMediaElement(document, qualified_name)
+{
+}
+
+HTMLVideoElement::~HTMLVideoElement()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.h b/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.h
new file mode 100644
index 0000000000..9349f0feb6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLMediaElement.h>
+
+namespace Web::HTML {
+
+class HTMLVideoElement final : public HTMLMediaElement {
+public:
+ using WrapperType = Bindings::HTMLVideoElementWrapper;
+
+ HTMLVideoElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~HTMLVideoElement() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.idl
new file mode 100644
index 0000000000..6fb6ba3a50
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.idl
@@ -0,0 +1,6 @@
+interface HTMLVideoElement : HTMLMediaElement {
+
+ [Reflect] attribute DOMString poster;
+ [Reflect=playsinline] attribute boolean playsInline;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/ImageData.cpp b/Userland/Libraries/LibWeb/HTML/ImageData.cpp
new file mode 100644
index 0000000000..085cc88728
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/ImageData.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/Bitmap.h>
+#include <LibJS/Runtime/Uint8ClampedArray.h>
+#include <LibWeb/HTML/ImageData.h>
+
+namespace Web::HTML {
+
+RefPtr<ImageData> ImageData::create_with_size(JS::GlobalObject& global_object, int width, int height)
+{
+ if (width <= 0 || height <= 0)
+ return nullptr;
+
+ if (width > 16384 || height > 16384)
+ return nullptr;
+
+ dbgln("Creating ImageData with {}x{}", width, height);
+
+ auto* data = JS::Uint8ClampedArray::create(global_object, width * height * 4);
+ if (!data)
+ return nullptr;
+
+ auto data_handle = JS::make_handle(data);
+
+ auto bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGBA32, Gfx::IntSize(width, height), width * sizeof(u32), (u32*)data->data());
+ if (!bitmap)
+ return nullptr;
+ return adopt(*new ImageData(bitmap.release_nonnull(), move(data_handle)));
+}
+
+ImageData::ImageData(NonnullRefPtr<Gfx::Bitmap> bitmap, JS::Handle<JS::Uint8ClampedArray> data)
+ : m_bitmap(move(bitmap))
+ , m_data(move(data))
+{
+}
+
+ImageData::~ImageData()
+{
+}
+
+unsigned ImageData::width() const
+{
+ return m_bitmap->width();
+}
+
+unsigned ImageData::height() const
+{
+ return m_bitmap->height();
+}
+
+JS::Uint8ClampedArray* ImageData::data()
+{
+ return m_data.cell();
+}
+
+const JS::Uint8ClampedArray* ImageData::data() const
+{
+ return m_data.cell();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/ImageData.h b/Userland/Libraries/LibWeb/HTML/ImageData.h
new file mode 100644
index 0000000000..810400d187
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/ImageData.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Forward.h>
+#include <LibJS/Heap/Handle.h>
+#include <LibWeb/Bindings/Wrappable.h>
+
+namespace Web::HTML {
+
+class ImageData
+ : public RefCounted<ImageData>
+ , public Bindings::Wrappable {
+public:
+ using WrapperType = Bindings::ImageDataWrapper;
+
+ static RefPtr<ImageData> create_with_size(JS::GlobalObject&, int width, int height);
+
+ ~ImageData();
+
+ unsigned width() const;
+ unsigned height() const;
+
+ Gfx::Bitmap& bitmap() { return m_bitmap; }
+ const Gfx::Bitmap& bitmap() const { return m_bitmap; }
+
+ JS::Uint8ClampedArray* data();
+ const JS::Uint8ClampedArray* data() const;
+
+private:
+ explicit ImageData(NonnullRefPtr<Gfx::Bitmap>, JS::Handle<JS::Uint8ClampedArray>);
+
+ NonnullRefPtr<Gfx::Bitmap> m_bitmap;
+ JS::Handle<JS::Uint8ClampedArray> m_data;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/ImageData.idl b/Userland/Libraries/LibWeb/HTML/ImageData.idl
new file mode 100644
index 0000000000..29b683b547
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/ImageData.idl
@@ -0,0 +1,7 @@
+interface ImageData {
+
+ readonly attribute unsigned long width;
+ readonly attribute unsigned long height;
+ readonly attribute Uint8ClampedArray data;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/Parser/Entities.cpp b/Userland/Libraries/LibWeb/HTML/Parser/Entities.cpp
new file mode 100644
index 0000000000..edda468b0a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Parser/Entities.cpp
@@ -0,0 +1,2302 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LogStream.h>
+#include <AK/StringView.h>
+#include <LibWeb/HTML/Parser/Entities.h>
+
+namespace Web {
+namespace HTML {
+
+Optional<EntityMatch> code_points_from_entity(const StringView& entity)
+{
+ constexpr struct {
+ StringView entity;
+ u32 code_point;
+ } single_code_point_entities[] = {
+ { "AElig;", 0x000C6 },
+ { "AElig", 0x000C6 },
+ { "AMP;", 0x00026 },
+ { "AMP", 0x00026 },
+ { "Aacute;", 0x000C1 },
+ { "Aacute", 0x000C1 },
+ { "Abreve;", 0x00102 },
+ { "Acirc;", 0x000C2 },
+ { "Acirc", 0x000C2 },
+ { "Acy;", 0x00410 },
+ { "Afr;", 0x1D504 },
+ { "Agrave;", 0x000C0 },
+ { "Agrave", 0x000C0 },
+ { "Alpha;", 0x00391 },
+ { "Amacr;", 0x00100 },
+ { "And;", 0x02A53 },
+ { "Aogon;", 0x00104 },
+ { "Aopf;", 0x1D538 },
+ { "ApplyFunction;", 0x02061 },
+ { "Aring;", 0x000C5 },
+ { "Aring", 0x000C5 },
+ { "Ascr;", 0x1D49C },
+ { "Assign;", 0x02254 },
+ { "Atilde;", 0x000C3 },
+ { "Atilde", 0x000C3 },
+ { "Auml;", 0x000C4 },
+ { "Auml", 0x000C4 },
+ { "Backslash;", 0x02216 },
+ { "Barv;", 0x02AE7 },
+ { "Barwed;", 0x02306 },
+ { "Bcy;", 0x00411 },
+ { "Because;", 0x02235 },
+ { "Bernoullis;", 0x0212C },
+ { "Beta;", 0x00392 },
+ { "Bfr;", 0x1D505 },
+ { "Bopf;", 0x1D539 },
+ { "Breve;", 0x002D8 },
+ { "Bscr;", 0x0212C },
+ { "Bumpeq;", 0x0224E },
+ { "CHcy;", 0x00427 },
+ { "COPY;", 0x000A9 },
+ { "COPY", 0x000A9 },
+ { "Cacute;", 0x00106 },
+ { "Cap;", 0x022D2 },
+ { "CapitalDifferentialD;", 0x02145 },
+ { "Cayleys;", 0x0212D },
+ { "Ccaron;", 0x0010C },
+ { "Ccedil;", 0x000C7 },
+ { "Ccedil", 0x000C7 },
+ { "Ccirc;", 0x00108 },
+ { "Cconint;", 0x02230 },
+ { "Cdot;", 0x0010A },
+ { "Cedilla;", 0x000B8 },
+ { "CenterDot;", 0x000B7 },
+ { "Cfr;", 0x0212D },
+ { "Chi;", 0x003A7 },
+ { "CircleDot;", 0x02299 },
+ { "CircleMinus;", 0x02296 },
+ { "CirclePlus;", 0x02295 },
+ { "CircleTimes;", 0x02297 },
+ { "ClockwiseContourIntegral;", 0x02232 },
+ { "CloseCurlyDoubleQuote;", 0x0201D },
+ { "CloseCurlyQuote;", 0x02019 },
+ { "Colon;", 0x02237 },
+ { "Colone;", 0x02A74 },
+ { "Congruent;", 0x02261 },
+ { "Conint;", 0x0222F },
+ { "ContourIntegral;", 0x0222E },
+ { "Copf;", 0x02102 },
+ { "Coproduct;", 0x02210 },
+ { "CounterClockwiseContourIntegral;", 0x02233 },
+ { "Cross;", 0x02A2F },
+ { "Cscr;", 0x1D49E },
+ { "Cup;", 0x022D3 },
+ { "CupCap;", 0x0224D },
+ { "DD;", 0x02145 },
+ { "DDotrahd;", 0x02911 },
+ { "DJcy;", 0x00402 },
+ { "DScy;", 0x00405 },
+ { "DZcy;", 0x0040F },
+ { "Dagger;", 0x02021 },
+ { "Darr;", 0x021A1 },
+ { "Dashv;", 0x02AE4 },
+ { "Dcaron;", 0x0010E },
+ { "Dcy;", 0x00414 },
+ { "Del;", 0x02207 },
+ { "Delta;", 0x00394 },
+ { "Dfr;", 0x1D507 },
+ { "DiacriticalAcute;", 0x000B4 },
+ { "DiacriticalDot;", 0x002D9 },
+ { "DiacriticalDoubleAcute;", 0x002DD },
+ { "DiacriticalGrave;", 0x00060 },
+ { "DiacriticalTilde;", 0x002DC },
+ { "Diamond;", 0x022C4 },
+ { "DifferentialD;", 0x02146 },
+ { "Dopf;", 0x1D53B },
+ { "Dot;", 0x000A8 },
+ { "DotDot;", 0x020DC },
+ { "DotEqual;", 0x02250 },
+ { "DoubleContourIntegral;", 0x0222F },
+ { "DoubleDot;", 0x000A8 },
+ { "DoubleDownArrow;", 0x021D3 },
+ { "DoubleLeftArrow;", 0x021D0 },
+ { "DoubleLeftRightArrow;", 0x021D4 },
+ { "DoubleLeftTee;", 0x02AE4 },
+ { "DoubleLongLeftArrow;", 0x027F8 },
+ { "DoubleLongLeftRightArrow;", 0x027FA },
+ { "DoubleLongRightArrow;", 0x027F9 },
+ { "DoubleRightArrow;", 0x021D2 },
+ { "DoubleRightTee;", 0x022A8 },
+ { "DoubleUpArrow;", 0x021D1 },
+ { "DoubleUpDownArrow;", 0x021D5 },
+ { "DoubleVerticalBar;", 0x02225 },
+ { "DownArrow;", 0x02193 },
+ { "DownArrowBar;", 0x02913 },
+ { "DownArrowUpArrow;", 0x021F5 },
+ { "DownBreve;", 0x00311 },
+ { "DownLeftRightVector;", 0x02950 },
+ { "DownLeftTeeVector;", 0x0295E },
+ { "DownLeftVector;", 0x021BD },
+ { "DownLeftVectorBar;", 0x02956 },
+ { "DownRightTeeVector;", 0x0295F },
+ { "DownRightVector;", 0x021C1 },
+ { "DownRightVectorBar;", 0x02957 },
+ { "DownTee;", 0x022A4 },
+ { "DownTeeArrow;", 0x021A7 },
+ { "Downarrow;", 0x021D3 },
+ { "Dscr;", 0x1D49F },
+ { "Dstrok;", 0x00110 },
+ { "ENG;", 0x0014A },
+ { "ETH;", 0x000D0 },
+ { "ETH", 0x000D0 },
+ { "Eacute;", 0x000C9 },
+ { "Eacute", 0x000C9 },
+ { "Ecaron;", 0x0011A },
+ { "Ecirc;", 0x000CA },
+ { "Ecirc", 0x000CA },
+ { "Ecy;", 0x0042D },
+ { "Edot;", 0x00116 },
+ { "Efr;", 0x1D508 },
+ { "Egrave;", 0x000C8 },
+ { "Egrave", 0x000C8 },
+ { "Element;", 0x02208 },
+ { "Emacr;", 0x00112 },
+ { "EmptySmallSquare;", 0x025FB },
+ { "EmptyVerySmallSquare;", 0x025AB },
+ { "Eogon;", 0x00118 },
+ { "Eopf;", 0x1D53C },
+ { "Epsilon;", 0x00395 },
+ { "Equal;", 0x02A75 },
+ { "EqualTilde;", 0x02242 },
+ { "Equilibrium;", 0x021CC },
+ { "Escr;", 0x02130 },
+ { "Esim;", 0x02A73 },
+ { "Eta;", 0x00397 },
+ { "Euml;", 0x000CB },
+ { "Euml", 0x000CB },
+ { "Exists;", 0x02203 },
+ { "ExponentialE;", 0x02147 },
+ { "Fcy;", 0x00424 },
+ { "Ffr;", 0x1D509 },
+ { "FilledSmallSquare;", 0x025FC },
+ { "FilledVerySmallSquare;", 0x025AA },
+ { "Fopf;", 0x1D53D },
+ { "ForAll;", 0x02200 },
+ { "Fouriertrf;", 0x02131 },
+ { "Fscr;", 0x02131 },
+ { "GJcy;", 0x00403 },
+ { "GT;", 0x0003E },
+ { "GT", 0x0003E },
+ { "Gamma;", 0x00393 },
+ { "Gammad;", 0x003DC },
+ { "Gbreve;", 0x0011E },
+ { "Gcedil;", 0x00122 },
+ { "Gcirc;", 0x0011C },
+ { "Gcy;", 0x00413 },
+ { "Gdot;", 0x00120 },
+ { "Gfr;", 0x1D50A },
+ { "Gg;", 0x022D9 },
+ { "Gopf;", 0x1D53E },
+ { "GreaterEqual;", 0x02265 },
+ { "GreaterEqualLess;", 0x022DB },
+ { "GreaterFullEqual;", 0x02267 },
+ { "GreaterGreater;", 0x02AA2 },
+ { "GreaterLess;", 0x02277 },
+ { "GreaterSlantEqual;", 0x02A7E },
+ { "GreaterTilde;", 0x02273 },
+ { "Gscr;", 0x1D4A2 },
+ { "Gt;", 0x0226B },
+ { "HARDcy;", 0x0042A },
+ { "Hacek;", 0x002C7 },
+ { "Hat;", 0x0005E },
+ { "Hcirc;", 0x00124 },
+ { "Hfr;", 0x0210C },
+ { "HilbertSpace;", 0x0210B },
+ { "Hopf;", 0x0210D },
+ { "HorizontalLine;", 0x02500 },
+ { "Hscr;", 0x0210B },
+ { "Hstrok;", 0x00126 },
+ { "HumpDownHump;", 0x0224E },
+ { "HumpEqual;", 0x0224F },
+ { "IEcy;", 0x00415 },
+ { "IJlig;", 0x00132 },
+ { "IOcy;", 0x00401 },
+ { "Iacute;", 0x000CD },
+ { "Iacute", 0x000CD },
+ { "Icirc;", 0x000CE },
+ { "Icirc", 0x000CE },
+ { "Icy;", 0x00418 },
+ { "Idot;", 0x00130 },
+ { "Ifr;", 0x02111 },
+ { "Igrave;", 0x000CC },
+ { "Igrave", 0x000CC },
+ { "Im;", 0x02111 },
+ { "Imacr;", 0x0012A },
+ { "ImaginaryI;", 0x02148 },
+ { "Implies;", 0x021D2 },
+ { "Int;", 0x0222C },
+ { "Integral;", 0x0222B },
+ { "Intersection;", 0x022C2 },
+ { "InvisibleComma;", 0x02063 },
+ { "InvisibleTimes;", 0x02062 },
+ { "Iogon;", 0x0012E },
+ { "Iopf;", 0x1D540 },
+ { "Iota;", 0x00399 },
+ { "Iscr;", 0x02110 },
+ { "Itilde;", 0x00128 },
+ { "Iukcy;", 0x00406 },
+ { "Iuml;", 0x000CF },
+ { "Iuml", 0x000CF },
+ { "Jcirc;", 0x00134 },
+ { "Jcy;", 0x00419 },
+ { "Jfr;", 0x1D50D },
+ { "Jopf;", 0x1D541 },
+ { "Jscr;", 0x1D4A5 },
+ { "Jsercy;", 0x00408 },
+ { "Jukcy;", 0x00404 },
+ { "KHcy;", 0x00425 },
+ { "KJcy;", 0x0040C },
+ { "Kappa;", 0x0039A },
+ { "Kcedil;", 0x00136 },
+ { "Kcy;", 0x0041A },
+ { "Kfr;", 0x1D50E },
+ { "Kopf;", 0x1D542 },
+ { "Kscr;", 0x1D4A6 },
+ { "LJcy;", 0x00409 },
+ { "LT;", 0x0003C },
+ { "LT", 0x0003C },
+ { "Lacute;", 0x00139 },
+ { "Lambda;", 0x0039B },
+ { "Lang;", 0x027EA },
+ { "Laplacetrf;", 0x02112 },
+ { "Larr;", 0x0219E },
+ { "Lcaron;", 0x0013D },
+ { "Lcedil;", 0x0013B },
+ { "Lcy;", 0x0041B },
+ { "LeftAngleBracket;", 0x027E8 },
+ { "LeftArrow;", 0x02190 },
+ { "LeftArrowBar;", 0x021E4 },
+ { "LeftArrowRightArrow;", 0x021C6 },
+ { "LeftCeiling;", 0x02308 },
+ { "LeftDoubleBracket;", 0x027E6 },
+ { "LeftDownTeeVector;", 0x02961 },
+ { "LeftDownVector;", 0x021C3 },
+ { "LeftDownVectorBar;", 0x02959 },
+ { "LeftFloor;", 0x0230A },
+ { "LeftRightArrow;", 0x02194 },
+ { "LeftRightVector;", 0x0294E },
+ { "LeftTee;", 0x022A3 },
+ { "LeftTeeArrow;", 0x021A4 },
+ { "LeftTeeVector;", 0x0295A },
+ { "LeftTriangle;", 0x022B2 },
+ { "LeftTriangleBar;", 0x029CF },
+ { "LeftTriangleEqual;", 0x022B4 },
+ { "LeftUpDownVector;", 0x02951 },
+ { "LeftUpTeeVector;", 0x02960 },
+ { "LeftUpVector;", 0x021BF },
+ { "LeftUpVectorBar;", 0x02958 },
+ { "LeftVector;", 0x021BC },
+ { "LeftVectorBar;", 0x02952 },
+ { "Leftarrow;", 0x021D0 },
+ { "Leftrightarrow;", 0x021D4 },
+ { "LessEqualGreater;", 0x022DA },
+ { "LessFullEqual;", 0x02266 },
+ { "LessGreater;", 0x02276 },
+ { "LessLess;", 0x02AA1 },
+ { "LessSlantEqual;", 0x02A7D },
+ { "LessTilde;", 0x02272 },
+ { "Lfr;", 0x1D50F },
+ { "Ll;", 0x022D8 },
+ { "Lleftarrow;", 0x021DA },
+ { "Lmidot;", 0x0013F },
+ { "LongLeftArrow;", 0x027F5 },
+ { "LongLeftRightArrow;", 0x027F7 },
+ { "LongRightArrow;", 0x027F6 },
+ { "Longleftarrow;", 0x027F8 },
+ { "Longleftrightarrow;", 0x027FA },
+ { "Longrightarrow;", 0x027F9 },
+ { "Lopf;", 0x1D543 },
+ { "LowerLeftArrow;", 0x02199 },
+ { "LowerRightArrow;", 0x02198 },
+ { "Lscr;", 0x02112 },
+ { "Lsh;", 0x021B0 },
+ { "Lstrok;", 0x00141 },
+ { "Lt;", 0x0226A },
+ { "Map;", 0x02905 },
+ { "Mcy;", 0x0041C },
+ { "MediumSpace;", 0x0205F },
+ { "Mellintrf;", 0x02133 },
+ { "Mfr;", 0x1D510 },
+ { "MinusPlus;", 0x02213 },
+ { "Mopf;", 0x1D544 },
+ { "Mscr;", 0x02133 },
+ { "Mu;", 0x0039C },
+ { "NJcy;", 0x0040A },
+ { "Nacute;", 0x00143 },
+ { "Ncaron;", 0x00147 },
+ { "Ncedil;", 0x00145 },
+ { "Ncy;", 0x0041D },
+ { "NegativeMediumSpace;", 0x0200B },
+ { "NegativeThickSpace;", 0x0200B },
+ { "NegativeThinSpace;", 0x0200B },
+ { "NegativeVeryThinSpace;", 0x0200B },
+ { "NestedGreaterGreater;", 0x0226B },
+ { "NestedLessLess;", 0x0226A },
+ { "NewLine;", 0x0000A },
+ { "Nfr;", 0x1D511 },
+ { "NoBreak;", 0x02060 },
+ { "NonBreakingSpace;", 0x000A0 },
+ { "Nopf;", 0x02115 },
+ { "Not;", 0x02AEC },
+ { "NotCongruent;", 0x02262 },
+ { "NotCupCap;", 0x0226D },
+ { "NotDoubleVerticalBar;", 0x02226 },
+ { "NotElement;", 0x02209 },
+ { "NotEqual;", 0x02260 },
+ { "NotExists;", 0x02204 },
+ { "NotGreater;", 0x0226F },
+ { "NotGreaterEqual;", 0x02271 },
+ { "NotGreaterLess;", 0x02279 },
+ { "NotGreaterTilde;", 0x02275 },
+ { "NotLeftTriangle;", 0x022EA },
+ { "NotLeftTriangleEqual;", 0x022EC },
+ { "NotLess;", 0x0226E },
+ { "NotLessEqual;", 0x02270 },
+ { "NotLessGreater;", 0x02278 },
+ { "NotLessTilde;", 0x02274 },
+ { "NotPrecedes;", 0x02280 },
+ { "NotPrecedesSlantEqual;", 0x022E0 },
+ { "NotReverseElement;", 0x0220C },
+ { "NotRightTriangle;", 0x022EB },
+ { "NotRightTriangleEqual;", 0x022ED },
+ { "NotSquareSubsetEqual;", 0x022E2 },
+ { "NotSquareSupersetEqual;", 0x022E3 },
+ { "NotSubsetEqual;", 0x02288 },
+ { "NotSucceeds;", 0x02281 },
+ { "NotSucceedsSlantEqual;", 0x022E1 },
+ { "NotSupersetEqual;", 0x02289 },
+ { "NotTilde;", 0x02241 },
+ { "NotTildeEqual;", 0x02244 },
+ { "NotTildeFullEqual;", 0x02247 },
+ { "NotTildeTilde;", 0x02249 },
+ { "NotVerticalBar;", 0x02224 },
+ { "Nscr;", 0x1D4A9 },
+ { "Ntilde;", 0x000D1 },
+ { "Ntilde", 0x000D1 },
+ { "Nu;", 0x0039D },
+ { "OElig;", 0x00152 },
+ { "Oacute;", 0x000D3 },
+ { "Oacute", 0x000D3 },
+ { "Ocirc;", 0x000D4 },
+ { "Ocirc", 0x000D4 },
+ { "Ocy;", 0x0041E },
+ { "Odblac;", 0x00150 },
+ { "Ofr;", 0x1D512 },
+ { "Ograve;", 0x000D2 },
+ { "Ograve", 0x000D2 },
+ { "Omacr;", 0x0014C },
+ { "Omega;", 0x003A9 },
+ { "Omicron;", 0x0039F },
+ { "Oopf;", 0x1D546 },
+ { "OpenCurlyDoubleQuote;", 0x0201C },
+ { "OpenCurlyQuote;", 0x02018 },
+ { "Or;", 0x02A54 },
+ { "Oscr;", 0x1D4AA },
+ { "Oslash;", 0x000D8 },
+ { "Oslash", 0x000D8 },
+ { "Otilde;", 0x000D5 },
+ { "Otilde", 0x000D5 },
+ { "Otimes;", 0x02A37 },
+ { "Ouml;", 0x000D6 },
+ { "Ouml", 0x000D6 },
+ { "OverBar;", 0x0203E },
+ { "OverBrace;", 0x023DE },
+ { "OverBracket;", 0x023B4 },
+ { "OverParenthesis;", 0x023DC },
+ { "PartialD;", 0x02202 },
+ { "Pcy;", 0x0041F },
+ { "Pfr;", 0x1D513 },
+ { "Phi;", 0x003A6 },
+ { "Pi;", 0x003A0 },
+ { "PlusMinus;", 0x000B1 },
+ { "Poincareplane;", 0x0210C },
+ { "Popf;", 0x02119 },
+ { "Pr;", 0x02ABB },
+ { "Precedes;", 0x0227A },
+ { "PrecedesEqual;", 0x02AAF },
+ { "PrecedesSlantEqual;", 0x0227C },
+ { "PrecedesTilde;", 0x0227E },
+ { "Prime;", 0x02033 },
+ { "Product;", 0x0220F },
+ { "Proportion;", 0x02237 },
+ { "Proportional;", 0x0221D },
+ { "Pscr;", 0x1D4AB },
+ { "Psi;", 0x003A8 },
+ { "QUOT;", 0x00022 },
+ { "QUOT", 0x00022 },
+ { "Qfr;", 0x1D514 },
+ { "Qopf;", 0x0211A },
+ { "Qscr;", 0x1D4AC },
+ { "RBarr;", 0x02910 },
+ { "REG;", 0x000AE },
+ { "REG", 0x000AE },
+ { "Racute;", 0x00154 },
+ { "Rang;", 0x027EB },
+ { "Rarr;", 0x021A0 },
+ { "Rarrtl;", 0x02916 },
+ { "Rcaron;", 0x00158 },
+ { "Rcedil;", 0x00156 },
+ { "Rcy;", 0x00420 },
+ { "Re;", 0x0211C },
+ { "ReverseElement;", 0x0220B },
+ { "ReverseEquilibrium;", 0x021CB },
+ { "ReverseUpEquilibrium;", 0x0296F },
+ { "Rfr;", 0x0211C },
+ { "Rho;", 0x003A1 },
+ { "RightAngleBracket;", 0x027E9 },
+ { "RightArrow;", 0x02192 },
+ { "RightArrowBar;", 0x021E5 },
+ { "RightArrowLeftArrow;", 0x021C4 },
+ { "RightCeiling;", 0x02309 },
+ { "RightDoubleBracket;", 0x027E7 },
+ { "RightDownTeeVector;", 0x0295D },
+ { "RightDownVector;", 0x021C2 },
+ { "RightDownVectorBar;", 0x02955 },
+ { "RightFloor;", 0x0230B },
+ { "RightTee;", 0x022A2 },
+ { "RightTeeArrow;", 0x021A6 },
+ { "RightTeeVector;", 0x0295B },
+ { "RightTriangle;", 0x022B3 },
+ { "RightTriangleBar;", 0x029D0 },
+ { "RightTriangleEqual;", 0x022B5 },
+ { "RightUpDownVector;", 0x0294F },
+ { "RightUpTeeVector;", 0x0295C },
+ { "RightUpVector;", 0x021BE },
+ { "RightUpVectorBar;", 0x02954 },
+ { "RightVector;", 0x021C0 },
+ { "RightVectorBar;", 0x02953 },
+ { "Rightarrow;", 0x021D2 },
+ { "Ropf;", 0x0211D },
+ { "RoundImplies;", 0x02970 },
+ { "Rrightarrow;", 0x021DB },
+ { "Rscr;", 0x0211B },
+ { "Rsh;", 0x021B1 },
+ { "RuleDelayed;", 0x029F4 },
+ { "SHCHcy;", 0x00429 },
+ { "SHcy;", 0x00428 },
+ { "SOFTcy;", 0x0042C },
+ { "Sacute;", 0x0015A },
+ { "Sc;", 0x02ABC },
+ { "Scaron;", 0x00160 },
+ { "Scedil;", 0x0015E },
+ { "Scirc;", 0x0015C },
+ { "Scy;", 0x00421 },
+ { "Sfr;", 0x1D516 },
+ { "ShortDownArrow;", 0x02193 },
+ { "ShortLeftArrow;", 0x02190 },
+ { "ShortRightArrow;", 0x02192 },
+ { "ShortUpArrow;", 0x02191 },
+ { "Sigma;", 0x003A3 },
+ { "SmallCircle;", 0x02218 },
+ { "Sopf;", 0x1D54A },
+ { "Sqrt;", 0x0221A },
+ { "Square;", 0x025A1 },
+ { "SquareIntersection;", 0x02293 },
+ { "SquareSubset;", 0x0228F },
+ { "SquareSubsetEqual;", 0x02291 },
+ { "SquareSuperset;", 0x02290 },
+ { "SquareSupersetEqual;", 0x02292 },
+ { "SquareUnion;", 0x02294 },
+ { "Sscr;", 0x1D4AE },
+ { "Star;", 0x022C6 },
+ { "Sub;", 0x022D0 },
+ { "Subset;", 0x022D0 },
+ { "SubsetEqual;", 0x02286 },
+ { "Succeeds;", 0x0227B },
+ { "SucceedsEqual;", 0x02AB0 },
+ { "SucceedsSlantEqual;", 0x0227D },
+ { "SucceedsTilde;", 0x0227F },
+ { "SuchThat;", 0x0220B },
+ { "Sum;", 0x02211 },
+ { "Sup;", 0x022D1 },
+ { "Superset;", 0x02283 },
+ { "SupersetEqual;", 0x02287 },
+ { "Supset;", 0x022D1 },
+ { "THORN;", 0x000DE },
+ { "THORN", 0x000DE },
+ { "TRADE;", 0x02122 },
+ { "TSHcy;", 0x0040B },
+ { "TScy;", 0x00426 },
+ { "Tab;", 0x00009 },
+ { "Tau;", 0x003A4 },
+ { "Tcaron;", 0x00164 },
+ { "Tcedil;", 0x00162 },
+ { "Tcy;", 0x00422 },
+ { "Tfr;", 0x1D517 },
+ { "Therefore;", 0x02234 },
+ { "Theta;", 0x00398 },
+ { "ThinSpace;", 0x02009 },
+ { "Tilde;", 0x0223C },
+ { "TildeEqual;", 0x02243 },
+ { "TildeFullEqual;", 0x02245 },
+ { "TildeTilde;", 0x02248 },
+ { "Topf;", 0x1D54B },
+ { "TripleDot;", 0x020DB },
+ { "Tscr;", 0x1D4AF },
+ { "Tstrok;", 0x00166 },
+ { "Uacute;", 0x000DA },
+ { "Uacute", 0x000DA },
+ { "Uarr;", 0x0219F },
+ { "Uarrocir;", 0x02949 },
+ { "Ubrcy;", 0x0040E },
+ { "Ubreve;", 0x0016C },
+ { "Ucirc;", 0x000DB },
+ { "Ucirc", 0x000DB },
+ { "Ucy;", 0x00423 },
+ { "Udblac;", 0x00170 },
+ { "Ufr;", 0x1D518 },
+ { "Ugrave;", 0x000D9 },
+ { "Ugrave", 0x000D9 },
+ { "Umacr;", 0x0016A },
+ { "UnderBar;", 0x0005F },
+ { "UnderBrace;", 0x023DF },
+ { "UnderBracket;", 0x023B5 },
+ { "UnderParenthesis;", 0x023DD },
+ { "Union;", 0x022C3 },
+ { "UnionPlus;", 0x0228E },
+ { "Uogon;", 0x00172 },
+ { "Uopf;", 0x1D54C },
+ { "UpArrow;", 0x02191 },
+ { "UpArrowBar;", 0x02912 },
+ { "UpArrowDownArrow;", 0x021C5 },
+ { "UpDownArrow;", 0x02195 },
+ { "UpEquilibrium;", 0x0296E },
+ { "UpTee;", 0x022A5 },
+ { "UpTeeArrow;", 0x021A5 },
+ { "Uparrow;", 0x021D1 },
+ { "Updownarrow;", 0x021D5 },
+ { "UpperLeftArrow;", 0x02196 },
+ { "UpperRightArrow;", 0x02197 },
+ { "Upsi;", 0x003D2 },
+ { "Upsilon;", 0x003A5 },
+ { "Uring;", 0x0016E },
+ { "Uscr;", 0x1D4B0 },
+ { "Utilde;", 0x00168 },
+ { "Uuml;", 0x000DC },
+ { "Uuml", 0x000DC },
+ { "VDash;", 0x022AB },
+ { "Vbar;", 0x02AEB },
+ { "Vcy;", 0x00412 },
+ { "Vdash;", 0x022A9 },
+ { "Vdashl;", 0x02AE6 },
+ { "Vee;", 0x022C1 },
+ { "Verbar;", 0x02016 },
+ { "Vert;", 0x02016 },
+ { "VerticalBar;", 0x02223 },
+ { "VerticalLine;", 0x0007C },
+ { "VerticalSeparator;", 0x02758 },
+ { "VerticalTilde;", 0x02240 },
+ { "VeryThinSpace;", 0x0200A },
+ { "Vfr;", 0x1D519 },
+ { "Vopf;", 0x1D54D },
+ { "Vscr;", 0x1D4B1 },
+ { "Vvdash;", 0x022AA },
+ { "Wcirc;", 0x00174 },
+ { "Wedge;", 0x022C0 },
+ { "Wfr;", 0x1D51A },
+ { "Wopf;", 0x1D54E },
+ { "Wscr;", 0x1D4B2 },
+ { "Xfr;", 0x1D51B },
+ { "Xi;", 0x0039E },
+ { "Xopf;", 0x1D54F },
+ { "Xscr;", 0x1D4B3 },
+ { "YAcy;", 0x0042F },
+ { "YIcy;", 0x00407 },
+ { "YUcy;", 0x0042E },
+ { "Yacute;", 0x000DD },
+ { "Yacute", 0x000DD },
+ { "Ycirc;", 0x00176 },
+ { "Ycy;", 0x0042B },
+ { "Yfr;", 0x1D51C },
+ { "Yopf;", 0x1D550 },
+ { "Yscr;", 0x1D4B4 },
+ { "Yuml;", 0x00178 },
+ { "ZHcy;", 0x00416 },
+ { "Zacute;", 0x00179 },
+ { "Zcaron;", 0x0017D },
+ { "Zcy;", 0x00417 },
+ { "Zdot;", 0x0017B },
+ { "ZeroWidthSpace;", 0x0200B },
+ { "Zeta;", 0x00396 },
+ { "Zfr;", 0x02128 },
+ { "Zopf;", 0x02124 },
+ { "Zscr;", 0x1D4B5 },
+ { "aacute;", 0x000E1 },
+ { "aacute", 0x000E1 },
+ { "abreve;", 0x00103 },
+ { "ac;", 0x0223E },
+ { "acd;", 0x0223F },
+ { "acirc;", 0x000E2 },
+ { "acirc", 0x000E2 },
+ { "acute;", 0x000B4 },
+ { "acute", 0x000B4 },
+ { "acy;", 0x00430 },
+ { "aelig;", 0x000E6 },
+ { "aelig", 0x000E6 },
+ { "af;", 0x02061 },
+ { "afr;", 0x1D51E },
+ { "agrave;", 0x000E0 },
+ { "agrave", 0x000E0 },
+ { "alefsym;", 0x02135 },
+ { "aleph;", 0x02135 },
+ { "alpha;", 0x003B1 },
+ { "amacr;", 0x00101 },
+ { "amalg;", 0x02A3F },
+ { "amp;", 0x00026 },
+ { "amp", 0x00026 },
+ { "and;", 0x02227 },
+ { "andand;", 0x02A55 },
+ { "andd;", 0x02A5C },
+ { "andslope;", 0x02A58 },
+ { "andv;", 0x02A5A },
+ { "ang;", 0x02220 },
+ { "ange;", 0x029A4 },
+ { "angle;", 0x02220 },
+ { "angmsd;", 0x02221 },
+ { "angmsdaa;", 0x029A8 },
+ { "angmsdab;", 0x029A9 },
+ { "angmsdac;", 0x029AA },
+ { "angmsdad;", 0x029AB },
+ { "angmsdae;", 0x029AC },
+ { "angmsdaf;", 0x029AD },
+ { "angmsdag;", 0x029AE },
+ { "angmsdah;", 0x029AF },
+ { "angrt;", 0x0221F },
+ { "angrtvb;", 0x022BE },
+ { "angrtvbd;", 0x0299D },
+ { "angsph;", 0x02222 },
+ { "angst;", 0x000C5 },
+ { "angzarr;", 0x0237C },
+ { "aogon;", 0x00105 },
+ { "aopf;", 0x1D552 },
+ { "ap;", 0x02248 },
+ { "apE;", 0x02A70 },
+ { "apacir;", 0x02A6F },
+ { "ape;", 0x0224A },
+ { "apid;", 0x0224B },
+ { "apos;", 0x00027 },
+ { "approx;", 0x02248 },
+ { "approxeq;", 0x0224A },
+ { "aring;", 0x000E5 },
+ { "aring", 0x000E5 },
+ { "ascr;", 0x1D4B6 },
+ { "ast;", 0x0002A },
+ { "asymp;", 0x02248 },
+ { "asympeq;", 0x0224D },
+ { "atilde;", 0x000E3 },
+ { "atilde", 0x000E3 },
+ { "auml;", 0x000E4 },
+ { "auml", 0x000E4 },
+ { "awconint;", 0x02233 },
+ { "awint;", 0x02A11 },
+ { "bNot;", 0x02AED },
+ { "backcong;", 0x0224C },
+ { "backepsilon;", 0x003F6 },
+ { "backprime;", 0x02035 },
+ { "backsim;", 0x0223D },
+ { "backsimeq;", 0x022CD },
+ { "barvee;", 0x022BD },
+ { "barwed;", 0x02305 },
+ { "barwedge;", 0x02305 },
+ { "bbrk;", 0x023B5 },
+ { "bbrktbrk;", 0x023B6 },
+ { "bcong;", 0x0224C },
+ { "bcy;", 0x00431 },
+ { "bdquo;", 0x0201E },
+ { "becaus;", 0x02235 },
+ { "because;", 0x02235 },
+ { "bemptyv;", 0x029B0 },
+ { "bepsi;", 0x003F6 },
+ { "bernou;", 0x0212C },
+ { "beta;", 0x003B2 },
+ { "beth;", 0x02136 },
+ { "between;", 0x0226C },
+ { "bfr;", 0x1D51F },
+ { "bigcap;", 0x022C2 },
+ { "bigcirc;", 0x025EF },
+ { "bigcup;", 0x022C3 },
+ { "bigodot;", 0x02A00 },
+ { "bigoplus;", 0x02A01 },
+ { "bigotimes;", 0x02A02 },
+ { "bigsqcup;", 0x02A06 },
+ { "bigstar;", 0x02605 },
+ { "bigtriangledown;", 0x025BD },
+ { "bigtriangleup;", 0x025B3 },
+ { "biguplus;", 0x02A04 },
+ { "bigvee;", 0x022C1 },
+ { "bigwedge;", 0x022C0 },
+ { "bkarow;", 0x0290D },
+ { "blacklozenge;", 0x029EB },
+ { "blacksquare;", 0x025AA },
+ { "blacktriangle;", 0x025B4 },
+ { "blacktriangledown;", 0x025BE },
+ { "blacktriangleleft;", 0x025C2 },
+ { "blacktriangleright;", 0x025B8 },
+ { "blank;", 0x02423 },
+ { "blk12;", 0x02592 },
+ { "blk14;", 0x02591 },
+ { "blk34;", 0x02593 },
+ { "block;", 0x02588 },
+ { "bnot;", 0x02310 },
+ { "bopf;", 0x1D553 },
+ { "bot;", 0x022A5 },
+ { "bottom;", 0x022A5 },
+ { "bowtie;", 0x022C8 },
+ { "boxDL;", 0x02557 },
+ { "boxDR;", 0x02554 },
+ { "boxDl;", 0x02556 },
+ { "boxDr;", 0x02553 },
+ { "boxH;", 0x02550 },
+ { "boxHD;", 0x02566 },
+ { "boxHU;", 0x02569 },
+ { "boxHd;", 0x02564 },
+ { "boxHu;", 0x02567 },
+ { "boxUL;", 0x0255D },
+ { "boxUR;", 0x0255A },
+ { "boxUl;", 0x0255C },
+ { "boxUr;", 0x02559 },
+ { "boxV;", 0x02551 },
+ { "boxVH;", 0x0256C },
+ { "boxVL;", 0x02563 },
+ { "boxVR;", 0x02560 },
+ { "boxVh;", 0x0256B },
+ { "boxVl;", 0x02562 },
+ { "boxVr;", 0x0255F },
+ { "boxbox;", 0x029C9 },
+ { "boxdL;", 0x02555 },
+ { "boxdR;", 0x02552 },
+ { "boxdl;", 0x02510 },
+ { "boxdr;", 0x0250C },
+ { "boxh;", 0x02500 },
+ { "boxhD;", 0x02565 },
+ { "boxhU;", 0x02568 },
+ { "boxhd;", 0x0252C },
+ { "boxhu;", 0x02534 },
+ { "boxminus;", 0x0229F },
+ { "boxplus;", 0x0229E },
+ { "boxtimes;", 0x022A0 },
+ { "boxuL;", 0x0255B },
+ { "boxuR;", 0x02558 },
+ { "boxul;", 0x02518 },
+ { "boxur;", 0x02514 },
+ { "boxv;", 0x02502 },
+ { "boxvH;", 0x0256A },
+ { "boxvL;", 0x02561 },
+ { "boxvR;", 0x0255E },
+ { "boxvh;", 0x0253C },
+ { "boxvl;", 0x02524 },
+ { "boxvr;", 0x0251C },
+ { "bprime;", 0x02035 },
+ { "breve;", 0x002D8 },
+ { "brvbar;", 0x000A6 },
+ { "brvbar", 0x000A6 },
+ { "bscr;", 0x1D4B7 },
+ { "bsemi;", 0x0204F },
+ { "bsim;", 0x0223D },
+ { "bsime;", 0x022CD },
+ { "bsol;", 0x0005C },
+ { "bsolb;", 0x029C5 },
+ { "bsolhsub;", 0x027C8 },
+ { "bull;", 0x02022 },
+ { "bullet;", 0x02022 },
+ { "bump;", 0x0224E },
+ { "bumpE;", 0x02AAE },
+ { "bumpe;", 0x0224F },
+ { "bumpeq;", 0x0224F },
+ { "cacute;", 0x00107 },
+ { "cap;", 0x02229 },
+ { "capand;", 0x02A44 },
+ { "capbrcup;", 0x02A49 },
+ { "capcap;", 0x02A4B },
+ { "capcup;", 0x02A47 },
+ { "capdot;", 0x02A40 },
+ { "caret;", 0x02041 },
+ { "caron;", 0x002C7 },
+ { "ccaps;", 0x02A4D },
+ { "ccaron;", 0x0010D },
+ { "ccedil;", 0x000E7 },
+ { "ccedil", 0x000E7 },
+ { "ccirc;", 0x00109 },
+ { "ccups;", 0x02A4C },
+ { "ccupssm;", 0x02A50 },
+ { "cdot;", 0x0010B },
+ { "cedil;", 0x000B8 },
+ { "cedil", 0x000B8 },
+ { "cemptyv;", 0x029B2 },
+ { "cent;", 0x000A2 },
+ { "cent", 0x000A2 },
+ { "centerdot;", 0x000B7 },
+ { "cfr;", 0x1D520 },
+ { "chcy;", 0x00447 },
+ { "check;", 0x02713 },
+ { "checkmark;", 0x02713 },
+ { "chi;", 0x003C7 },
+ { "cir;", 0x025CB },
+ { "cirE;", 0x029C3 },
+ { "circ;", 0x002C6 },
+ { "circeq;", 0x02257 },
+ { "circlearrowleft;", 0x021BA },
+ { "circlearrowright;", 0x021BB },
+ { "circledR;", 0x000AE },
+ { "circledS;", 0x024C8 },
+ { "circledast;", 0x0229B },
+ { "circledcirc;", 0x0229A },
+ { "circleddash;", 0x0229D },
+ { "cire;", 0x02257 },
+ { "cirfnint;", 0x02A10 },
+ { "cirmid;", 0x02AEF },
+ { "cirscir;", 0x029C2 },
+ { "clubs;", 0x02663 },
+ { "clubsuit;", 0x02663 },
+ { "colon;", 0x0003A },
+ { "colone;", 0x02254 },
+ { "coloneq;", 0x02254 },
+ { "comma;", 0x0002C },
+ { "commat;", 0x00040 },
+ { "comp;", 0x02201 },
+ { "compfn;", 0x02218 },
+ { "complement;", 0x02201 },
+ { "complexes;", 0x02102 },
+ { "cong;", 0x02245 },
+ { "congdot;", 0x02A6D },
+ { "conint;", 0x0222E },
+ { "copf;", 0x1D554 },
+ { "coprod;", 0x02210 },
+ { "copy;", 0x000A9 },
+ { "copy", 0x000A9 },
+ { "copysr;", 0x02117 },
+ { "crarr;", 0x021B5 },
+ { "cross;", 0x02717 },
+ { "cscr;", 0x1D4B8 },
+ { "csub;", 0x02ACF },
+ { "csube;", 0x02AD1 },
+ { "csup;", 0x02AD0 },
+ { "csupe;", 0x02AD2 },
+ { "ctdot;", 0x022EF },
+ { "cudarrl;", 0x02938 },
+ { "cudarrr;", 0x02935 },
+ { "cuepr;", 0x022DE },
+ { "cuesc;", 0x022DF },
+ { "cularr;", 0x021B6 },
+ { "cularrp;", 0x0293D },
+ { "cup;", 0x0222A },
+ { "cupbrcap;", 0x02A48 },
+ { "cupcap;", 0x02A46 },
+ { "cupcup;", 0x02A4A },
+ { "cupdot;", 0x0228D },
+ { "cupor;", 0x02A45 },
+ { "curarr;", 0x021B7 },
+ { "curarrm;", 0x0293C },
+ { "curlyeqprec;", 0x022DE },
+ { "curlyeqsucc;", 0x022DF },
+ { "curlyvee;", 0x022CE },
+ { "curlywedge;", 0x022CF },
+ { "curren;", 0x000A4 },
+ { "curren", 0x000A4 },
+ { "curvearrowleft;", 0x021B6 },
+ { "curvearrowright;", 0x021B7 },
+ { "cuvee;", 0x022CE },
+ { "cuwed;", 0x022CF },
+ { "cwconint;", 0x02232 },
+ { "cwint;", 0x02231 },
+ { "cylcty;", 0x0232D },
+ { "dArr;", 0x021D3 },
+ { "dHar;", 0x02965 },
+ { "dagger;", 0x02020 },
+ { "daleth;", 0x02138 },
+ { "darr;", 0x02193 },
+ { "dash;", 0x02010 },
+ { "dashv;", 0x022A3 },
+ { "dbkarow;", 0x0290F },
+ { "dblac;", 0x002DD },
+ { "dcaron;", 0x0010F },
+ { "dcy;", 0x00434 },
+ { "dd;", 0x02146 },
+ { "ddagger;", 0x02021 },
+ { "ddarr;", 0x021CA },
+ { "ddotseq;", 0x02A77 },
+ { "deg;", 0x000B0 },
+ { "deg", 0x000B0 },
+ { "delta;", 0x003B4 },
+ { "demptyv;", 0x029B1 },
+ { "dfisht;", 0x0297F },
+ { "dfr;", 0x1D521 },
+ { "dharl;", 0x021C3 },
+ { "dharr;", 0x021C2 },
+ { "diam;", 0x022C4 },
+ { "diamond;", 0x022C4 },
+ { "diamondsuit;", 0x02666 },
+ { "diams;", 0x02666 },
+ { "die;", 0x000A8 },
+ { "digamma;", 0x003DD },
+ { "disin;", 0x022F2 },
+ { "div;", 0x000F7 },
+ { "divide;", 0x000F7 },
+ { "divide", 0x000F7 },
+ { "divideontimes;", 0x022C7 },
+ { "divonx;", 0x022C7 },
+ { "djcy;", 0x00452 },
+ { "dlcorn;", 0x0231E },
+ { "dlcrop;", 0x0230D },
+ { "dollar;", 0x00024 },
+ { "dopf;", 0x1D555 },
+ { "dot;", 0x002D9 },
+ { "doteq;", 0x02250 },
+ { "doteqdot;", 0x02251 },
+ { "dotminus;", 0x02238 },
+ { "dotplus;", 0x02214 },
+ { "dotsquare;", 0x022A1 },
+ { "doublebarwedge;", 0x02306 },
+ { "downarrow;", 0x02193 },
+ { "downdownarrows;", 0x021CA },
+ { "downharpoonleft;", 0x021C3 },
+ { "downharpoonright;", 0x021C2 },
+ { "drbkarow;", 0x02910 },
+ { "drcorn;", 0x0231F },
+ { "drcrop;", 0x0230C },
+ { "dscr;", 0x1D4B9 },
+ { "dscy;", 0x00455 },
+ { "dsol;", 0x029F6 },
+ { "dstrok;", 0x00111 },
+ { "dtdot;", 0x022F1 },
+ { "dtri;", 0x025BF },
+ { "dtrif;", 0x025BE },
+ { "duarr;", 0x021F5 },
+ { "duhar;", 0x0296F },
+ { "dwangle;", 0x029A6 },
+ { "dzcy;", 0x0045F },
+ { "dzigrarr;", 0x027FF },
+ { "eDDot;", 0x02A77 },
+ { "eDot;", 0x02251 },
+ { "eacute;", 0x000E9 },
+ { "eacute", 0x000E9 },
+ { "easter;", 0x02A6E },
+ { "ecaron;", 0x0011B },
+ { "ecir;", 0x02256 },
+ { "ecirc;", 0x000EA },
+ { "ecirc", 0x000EA },
+ { "ecolon;", 0x02255 },
+ { "ecy;", 0x0044D },
+ { "edot;", 0x00117 },
+ { "ee;", 0x02147 },
+ { "efDot;", 0x02252 },
+ { "efr;", 0x1D522 },
+ { "eg;", 0x02A9A },
+ { "egrave;", 0x000E8 },
+ { "egrave", 0x000E8 },
+ { "egs;", 0x02A96 },
+ { "egsdot;", 0x02A98 },
+ { "el;", 0x02A99 },
+ { "elinters;", 0x023E7 },
+ { "ell;", 0x02113 },
+ { "els;", 0x02A95 },
+ { "elsdot;", 0x02A97 },
+ { "emacr;", 0x00113 },
+ { "empty;", 0x02205 },
+ { "emptyset;", 0x02205 },
+ { "emptyv;", 0x02205 },
+ { "emsp13;", 0x02004 },
+ { "emsp14;", 0x02005 },
+ { "emsp;", 0x02003 },
+ { "eng;", 0x0014B },
+ { "ensp;", 0x02002 },
+ { "eogon;", 0x00119 },
+ { "eopf;", 0x1D556 },
+ { "epar;", 0x022D5 },
+ { "eparsl;", 0x029E3 },
+ { "eplus;", 0x02A71 },
+ { "epsi;", 0x003B5 },
+ { "epsilon;", 0x003B5 },
+ { "epsiv;", 0x003F5 },
+ { "eqcirc;", 0x02256 },
+ { "eqcolon;", 0x02255 },
+ { "eqsim;", 0x02242 },
+ { "eqslantgtr;", 0x02A96 },
+ { "eqslantless;", 0x02A95 },
+ { "equals;", 0x0003D },
+ { "equest;", 0x0225F },
+ { "equiv;", 0x02261 },
+ { "equivDD;", 0x02A78 },
+ { "eqvparsl;", 0x029E5 },
+ { "erDot;", 0x02253 },
+ { "erarr;", 0x02971 },
+ { "escr;", 0x0212F },
+ { "esdot;", 0x02250 },
+ { "esim;", 0x02242 },
+ { "eta;", 0x003B7 },
+ { "eth;", 0x000F0 },
+ { "eth", 0x000F0 },
+ { "euml;", 0x000EB },
+ { "euml", 0x000EB },
+ { "euro;", 0x020AC },
+ { "excl;", 0x00021 },
+ { "exist;", 0x02203 },
+ { "expectation;", 0x02130 },
+ { "exponentiale;", 0x02147 },
+ { "fallingdotseq;", 0x02252 },
+ { "fcy;", 0x00444 },
+ { "female;", 0x02640 },
+ { "ffilig;", 0x0FB03 },
+ { "fflig;", 0x0FB00 },
+ { "ffllig;", 0x0FB04 },
+ { "ffr;", 0x1D523 },
+ { "filig;", 0x0FB01 },
+ { "flat;", 0x0266D },
+ { "fllig;", 0x0FB02 },
+ { "fltns;", 0x025B1 },
+ { "fnof;", 0x00192 },
+ { "fopf;", 0x1D557 },
+ { "forall;", 0x02200 },
+ { "fork;", 0x022D4 },
+ { "forkv;", 0x02AD9 },
+ { "fpartint;", 0x02A0D },
+ { "frac12;", 0x000BD },
+ { "frac12", 0x000BD },
+ { "frac13;", 0x02153 },
+ { "frac14;", 0x000BC },
+ { "frac14", 0x000BC },
+ { "frac15;", 0x02155 },
+ { "frac16;", 0x02159 },
+ { "frac18;", 0x0215B },
+ { "frac23;", 0x02154 },
+ { "frac25;", 0x02156 },
+ { "frac34;", 0x000BE },
+ { "frac34", 0x000BE },
+ { "frac35;", 0x02157 },
+ { "frac38;", 0x0215C },
+ { "frac45;", 0x02158 },
+ { "frac56;", 0x0215A },
+ { "frac58;", 0x0215D },
+ { "frac78;", 0x0215E },
+ { "frasl;", 0x02044 },
+ { "frown;", 0x02322 },
+ { "fscr;", 0x1D4BB },
+ { "gE;", 0x02267 },
+ { "gEl;", 0x02A8C },
+ { "gacute;", 0x001F5 },
+ { "gamma;", 0x003B3 },
+ { "gammad;", 0x003DD },
+ { "gap;", 0x02A86 },
+ { "gbreve;", 0x0011F },
+ { "gcirc;", 0x0011D },
+ { "gcy;", 0x00433 },
+ { "gdot;", 0x00121 },
+ { "ge;", 0x02265 },
+ { "gel;", 0x022DB },
+ { "geq;", 0x02265 },
+ { "geqq;", 0x02267 },
+ { "geqslant;", 0x02A7E },
+ { "ges;", 0x02A7E },
+ { "gescc;", 0x02AA9 },
+ { "gesdot;", 0x02A80 },
+ { "gesdoto;", 0x02A82 },
+ { "gesdotol;", 0x02A84 },
+ { "gesles;", 0x02A94 },
+ { "gfr;", 0x1D524 },
+ { "gg;", 0x0226B },
+ { "ggg;", 0x022D9 },
+ { "gimel;", 0x02137 },
+ { "gjcy;", 0x00453 },
+ { "gl;", 0x02277 },
+ { "glE;", 0x02A92 },
+ { "gla;", 0x02AA5 },
+ { "glj;", 0x02AA4 },
+ { "gnE;", 0x02269 },
+ { "gnap;", 0x02A8A },
+ { "gnapprox;", 0x02A8A },
+ { "gne;", 0x02A88 },
+ { "gneq;", 0x02A88 },
+ { "gneqq;", 0x02269 },
+ { "gnsim;", 0x022E7 },
+ { "gopf;", 0x1D558 },
+ { "grave;", 0x00060 },
+ { "gscr;", 0x0210A },
+ { "gsim;", 0x02273 },
+ { "gsime;", 0x02A8E },
+ { "gsiml;", 0x02A90 },
+ { "gt;", 0x0003E },
+ { "gt", 0x0003E },
+ { "gtcc;", 0x02AA7 },
+ { "gtcir;", 0x02A7A },
+ { "gtdot;", 0x022D7 },
+ { "gtlPar;", 0x02995 },
+ { "gtquest;", 0x02A7C },
+ { "gtrapprox;", 0x02A86 },
+ { "gtrarr;", 0x02978 },
+ { "gtrdot;", 0x022D7 },
+ { "gtreqless;", 0x022DB },
+ { "gtreqqless;", 0x02A8C },
+ { "gtrless;", 0x02277 },
+ { "gtrsim;", 0x02273 },
+ { "hArr;", 0x021D4 },
+ { "hairsp;", 0x0200A },
+ { "half;", 0x000BD },
+ { "hamilt;", 0x0210B },
+ { "hardcy;", 0x0044A },
+ { "harr;", 0x02194 },
+ { "harrcir;", 0x02948 },
+ { "harrw;", 0x021AD },
+ { "hbar;", 0x0210F },
+ { "hcirc;", 0x00125 },
+ { "hearts;", 0x02665 },
+ { "heartsuit;", 0x02665 },
+ { "hellip;", 0x02026 },
+ { "hercon;", 0x022B9 },
+ { "hfr;", 0x1D525 },
+ { "hksearow;", 0x02925 },
+ { "hkswarow;", 0x02926 },
+ { "hoarr;", 0x021FF },
+ { "homtht;", 0x0223B },
+ { "hookleftarrow;", 0x021A9 },
+ { "hookrightarrow;", 0x021AA },
+ { "hopf;", 0x1D559 },
+ { "horbar;", 0x02015 },
+ { "hscr;", 0x1D4BD },
+ { "hslash;", 0x0210F },
+ { "hstrok;", 0x00127 },
+ { "hybull;", 0x02043 },
+ { "hyphen;", 0x02010 },
+ { "iacute;", 0x000ED },
+ { "iacute", 0x000ED },
+ { "ic;", 0x02063 },
+ { "icirc;", 0x000EE },
+ { "icirc", 0x000EE },
+ { "icy;", 0x00438 },
+ { "iecy;", 0x00435 },
+ { "iexcl;", 0x000A1 },
+ { "iexcl", 0x000A1 },
+ { "iff;", 0x021D4 },
+ { "ifr;", 0x1D526 },
+ { "igrave;", 0x000EC },
+ { "igrave", 0x000EC },
+ { "ii;", 0x02148 },
+ { "iiiint;", 0x02A0C },
+ { "iiint;", 0x0222D },
+ { "iinfin;", 0x029DC },
+ { "iiota;", 0x02129 },
+ { "ijlig;", 0x00133 },
+ { "imacr;", 0x0012B },
+ { "image;", 0x02111 },
+ { "imagline;", 0x02110 },
+ { "imagpart;", 0x02111 },
+ { "imath;", 0x00131 },
+ { "imof;", 0x022B7 },
+ { "imped;", 0x001B5 },
+ { "in;", 0x02208 },
+ { "incare;", 0x02105 },
+ { "infin;", 0x0221E },
+ { "infintie;", 0x029DD },
+ { "inodot;", 0x00131 },
+ { "int;", 0x0222B },
+ { "intcal;", 0x022BA },
+ { "integers;", 0x02124 },
+ { "intercal;", 0x022BA },
+ { "intlarhk;", 0x02A17 },
+ { "intprod;", 0x02A3C },
+ { "iocy;", 0x00451 },
+ { "iogon;", 0x0012F },
+ { "iopf;", 0x1D55A },
+ { "iota;", 0x003B9 },
+ { "iprod;", 0x02A3C },
+ { "iquest;", 0x000BF },
+ { "iquest", 0x000BF },
+ { "iscr;", 0x1D4BE },
+ { "isin;", 0x02208 },
+ { "isinE;", 0x022F9 },
+ { "isindot;", 0x022F5 },
+ { "isins;", 0x022F4 },
+ { "isinsv;", 0x022F3 },
+ { "isinv;", 0x02208 },
+ { "it;", 0x02062 },
+ { "itilde;", 0x00129 },
+ { "iukcy;", 0x00456 },
+ { "iuml;", 0x000EF },
+ { "iuml", 0x000EF },
+ { "jcirc;", 0x00135 },
+ { "jcy;", 0x00439 },
+ { "jfr;", 0x1D527 },
+ { "jmath;", 0x00237 },
+ { "jopf;", 0x1D55B },
+ { "jscr;", 0x1D4BF },
+ { "jsercy;", 0x00458 },
+ { "jukcy;", 0x00454 },
+ { "kappa;", 0x003BA },
+ { "kappav;", 0x003F0 },
+ { "kcedil;", 0x00137 },
+ { "kcy;", 0x0043A },
+ { "kfr;", 0x1D528 },
+ { "kgreen;", 0x00138 },
+ { "khcy;", 0x00445 },
+ { "kjcy;", 0x0045C },
+ { "kopf;", 0x1D55C },
+ { "kscr;", 0x1D4C0 },
+ { "lAarr;", 0x021DA },
+ { "lArr;", 0x021D0 },
+ { "lAtail;", 0x0291B },
+ { "lBarr;", 0x0290E },
+ { "lE;", 0x02266 },
+ { "lEg;", 0x02A8B },
+ { "lHar;", 0x02962 },
+ { "lacute;", 0x0013A },
+ { "laemptyv;", 0x029B4 },
+ { "lagran;", 0x02112 },
+ { "lambda;", 0x003BB },
+ { "lang;", 0x027E8 },
+ { "langd;", 0x02991 },
+ { "langle;", 0x027E8 },
+ { "lap;", 0x02A85 },
+ { "laquo;", 0x000AB },
+ { "laquo", 0x000AB },
+ { "larr;", 0x02190 },
+ { "larrb;", 0x021E4 },
+ { "larrbfs;", 0x0291F },
+ { "larrfs;", 0x0291D },
+ { "larrhk;", 0x021A9 },
+ { "larrlp;", 0x021AB },
+ { "larrpl;", 0x02939 },
+ { "larrsim;", 0x02973 },
+ { "larrtl;", 0x021A2 },
+ { "lat;", 0x02AAB },
+ { "latail;", 0x02919 },
+ { "late;", 0x02AAD },
+ { "lbarr;", 0x0290C },
+ { "lbbrk;", 0x02772 },
+ { "lbrace;", 0x0007B },
+ { "lbrack;", 0x0005B },
+ { "lbrke;", 0x0298B },
+ { "lbrksld;", 0x0298F },
+ { "lbrkslu;", 0x0298D },
+ { "lcaron;", 0x0013E },
+ { "lcedil;", 0x0013C },
+ { "lceil;", 0x02308 },
+ { "lcub;", 0x0007B },
+ { "lcy;", 0x0043B },
+ { "ldca;", 0x02936 },
+ { "ldquo;", 0x0201C },
+ { "ldquor;", 0x0201E },
+ { "ldrdhar;", 0x02967 },
+ { "ldrushar;", 0x0294B },
+ { "ldsh;", 0x021B2 },
+ { "le;", 0x02264 },
+ { "leftarrow;", 0x02190 },
+ { "leftarrowtail;", 0x021A2 },
+ { "leftharpoondown;", 0x021BD },
+ { "leftharpoonup;", 0x021BC },
+ { "leftleftarrows;", 0x021C7 },
+ { "leftrightarrow;", 0x02194 },
+ { "leftrightarrows;", 0x021C6 },
+ { "leftrightharpoons;", 0x021CB },
+ { "leftrightsquigarrow;", 0x021AD },
+ { "leftthreetimes;", 0x022CB },
+ { "leg;", 0x022DA },
+ { "leq;", 0x02264 },
+ { "leqq;", 0x02266 },
+ { "leqslant;", 0x02A7D },
+ { "les;", 0x02A7D },
+ { "lescc;", 0x02AA8 },
+ { "lesdot;", 0x02A7F },
+ { "lesdoto;", 0x02A81 },
+ { "lesdotor;", 0x02A83 },
+ { "lesges;", 0x02A93 },
+ { "lessapprox;", 0x02A85 },
+ { "lessdot;", 0x022D6 },
+ { "lesseqgtr;", 0x022DA },
+ { "lesseqqgtr;", 0x02A8B },
+ { "lessgtr;", 0x02276 },
+ { "lesssim;", 0x02272 },
+ { "lfisht;", 0x0297C },
+ { "lfloor;", 0x0230A },
+ { "lfr;", 0x1D529 },
+ { "lg;", 0x02276 },
+ { "lgE;", 0x02A91 },
+ { "lhard;", 0x021BD },
+ { "lharu;", 0x021BC },
+ { "lharul;", 0x0296A },
+ { "lhblk;", 0x02584 },
+ { "ljcy;", 0x00459 },
+ { "ll;", 0x0226A },
+ { "llarr;", 0x021C7 },
+ { "llcorner;", 0x0231E },
+ { "llhard;", 0x0296B },
+ { "lltri;", 0x025FA },
+ { "lmidot;", 0x00140 },
+ { "lmoust;", 0x023B0 },
+ { "lmoustache;", 0x023B0 },
+ { "lnE;", 0x02268 },
+ { "lnap;", 0x02A89 },
+ { "lnapprox;", 0x02A89 },
+ { "lne;", 0x02A87 },
+ { "lneq;", 0x02A87 },
+ { "lneqq;", 0x02268 },
+ { "lnsim;", 0x022E6 },
+ { "loang;", 0x027EC },
+ { "loarr;", 0x021FD },
+ { "lobrk;", 0x027E6 },
+ { "longleftarrow;", 0x027F5 },
+ { "longleftrightarrow;", 0x027F7 },
+ { "longmapsto;", 0x027FC },
+ { "longrightarrow;", 0x027F6 },
+ { "looparrowleft;", 0x021AB },
+ { "looparrowright;", 0x021AC },
+ { "lopar;", 0x02985 },
+ { "lopf;", 0x1D55D },
+ { "loplus;", 0x02A2D },
+ { "lotimes;", 0x02A34 },
+ { "lowast;", 0x02217 },
+ { "lowbar;", 0x0005F },
+ { "loz;", 0x025CA },
+ { "lozenge;", 0x025CA },
+ { "lozf;", 0x029EB },
+ { "lpar;", 0x00028 },
+ { "lparlt;", 0x02993 },
+ { "lrarr;", 0x021C6 },
+ { "lrcorner;", 0x0231F },
+ { "lrhar;", 0x021CB },
+ { "lrhard;", 0x0296D },
+ { "lrm;", 0x0200E },
+ { "lrtri;", 0x022BF },
+ { "lsaquo;", 0x02039 },
+ { "lscr;", 0x1D4C1 },
+ { "lsh;", 0x021B0 },
+ { "lsim;", 0x02272 },
+ { "lsime;", 0x02A8D },
+ { "lsimg;", 0x02A8F },
+ { "lsqb;", 0x0005B },
+ { "lsquo;", 0x02018 },
+ { "lsquor;", 0x0201A },
+ { "lstrok;", 0x00142 },
+ { "lt;", 0x0003C },
+ { "lt", 0x0003C },
+ { "ltcc;", 0x02AA6 },
+ { "ltcir;", 0x02A79 },
+ { "ltdot;", 0x022D6 },
+ { "lthree;", 0x022CB },
+ { "ltimes;", 0x022C9 },
+ { "ltlarr;", 0x02976 },
+ { "ltquest;", 0x02A7B },
+ { "ltrPar;", 0x02996 },
+ { "ltri;", 0x025C3 },
+ { "ltrie;", 0x022B4 },
+ { "ltrif;", 0x025C2 },
+ { "lurdshar;", 0x0294A },
+ { "luruhar;", 0x02966 },
+ { "mDDot;", 0x0223A },
+ { "macr;", 0x000AF },
+ { "macr", 0x000AF },
+ { "male;", 0x02642 },
+ { "malt;", 0x02720 },
+ { "maltese;", 0x02720 },
+ { "map;", 0x021A6 },
+ { "mapsto;", 0x021A6 },
+ { "mapstodown;", 0x021A7 },
+ { "mapstoleft;", 0x021A4 },
+ { "mapstoup;", 0x021A5 },
+ { "marker;", 0x025AE },
+ { "mcomma;", 0x02A29 },
+ { "mcy;", 0x0043C },
+ { "mdash;", 0x02014 },
+ { "measuredangle;", 0x02221 },
+ { "mfr;", 0x1D52A },
+ { "mho;", 0x02127 },
+ { "micro;", 0x000B5 },
+ { "micro", 0x000B5 },
+ { "mid;", 0x02223 },
+ { "midast;", 0x0002A },
+ { "midcir;", 0x02AF0 },
+ { "middot;", 0x000B7 },
+ { "middot", 0x000B7 },
+ { "minus;", 0x02212 },
+ { "minusb;", 0x0229F },
+ { "minusd;", 0x02238 },
+ { "minusdu;", 0x02A2A },
+ { "mlcp;", 0x02ADB },
+ { "mldr;", 0x02026 },
+ { "mnplus;", 0x02213 },
+ { "models;", 0x022A7 },
+ { "mopf;", 0x1D55E },
+ { "mp;", 0x02213 },
+ { "mscr;", 0x1D4C2 },
+ { "mstpos;", 0x0223E },
+ { "mu;", 0x003BC },
+ { "multimap;", 0x022B8 },
+ { "mumap;", 0x022B8 },
+ { "nLeftarrow;", 0x021CD },
+ { "nLeftrightarrow;", 0x021CE },
+ { "nRightarrow;", 0x021CF },
+ { "nVDash;", 0x022AF },
+ { "nVdash;", 0x022AE },
+ { "nabla;", 0x02207 },
+ { "nacute;", 0x00144 },
+ { "nap;", 0x02249 },
+ { "napos;", 0x00149 },
+ { "napprox;", 0x02249 },
+ { "natur;", 0x0266E },
+ { "natural;", 0x0266E },
+ { "naturals;", 0x02115 },
+ { "nbsp;", 0x000A0 },
+ { "nbsp", 0x000A0 },
+ { "ncap;", 0x02A43 },
+ { "ncaron;", 0x00148 },
+ { "ncedil;", 0x00146 },
+ { "ncong;", 0x02247 },
+ { "ncup;", 0x02A42 },
+ { "ncy;", 0x0043D },
+ { "ndash;", 0x02013 },
+ { "ne;", 0x02260 },
+ { "neArr;", 0x021D7 },
+ { "nearhk;", 0x02924 },
+ { "nearr;", 0x02197 },
+ { "nearrow;", 0x02197 },
+ { "nequiv;", 0x02262 },
+ { "nesear;", 0x02928 },
+ { "nexist;", 0x02204 },
+ { "nexists;", 0x02204 },
+ { "nfr;", 0x1D52B },
+ { "nge;", 0x02271 },
+ { "ngeq;", 0x02271 },
+ { "ngsim;", 0x02275 },
+ { "ngt;", 0x0226F },
+ { "ngtr;", 0x0226F },
+ { "nhArr;", 0x021CE },
+ { "nharr;", 0x021AE },
+ { "nhpar;", 0x02AF2 },
+ { "ni;", 0x0220B },
+ { "nis;", 0x022FC },
+ { "nisd;", 0x022FA },
+ { "niv;", 0x0220B },
+ { "njcy;", 0x0045A },
+ { "nlArr;", 0x021CD },
+ { "nlarr;", 0x0219A },
+ { "nldr;", 0x02025 },
+ { "nle;", 0x02270 },
+ { "nleftarrow;", 0x0219A },
+ { "nleftrightarrow;", 0x021AE },
+ { "nleq;", 0x02270 },
+ { "nless;", 0x0226E },
+ { "nlsim;", 0x02274 },
+ { "nlt;", 0x0226E },
+ { "nltri;", 0x022EA },
+ { "nltrie;", 0x022EC },
+ { "nmid;", 0x02224 },
+ { "nopf;", 0x1D55F },
+ { "not;", 0x000AC },
+ { "not", 0x000AC },
+ { "notin;", 0x02209 },
+ { "notinva;", 0x02209 },
+ { "notinvb;", 0x022F7 },
+ { "notinvc;", 0x022F6 },
+ { "notni;", 0x0220C },
+ { "notniva;", 0x0220C },
+ { "notnivb;", 0x022FE },
+ { "notnivc;", 0x022FD },
+ { "npar;", 0x02226 },
+ { "nparallel;", 0x02226 },
+ { "npolint;", 0x02A14 },
+ { "npr;", 0x02280 },
+ { "nprcue;", 0x022E0 },
+ { "nprec;", 0x02280 },
+ { "nrArr;", 0x021CF },
+ { "nrarr;", 0x0219B },
+ { "nrightarrow;", 0x0219B },
+ { "nrtri;", 0x022EB },
+ { "nrtrie;", 0x022ED },
+ { "nsc;", 0x02281 },
+ { "nsccue;", 0x022E1 },
+ { "nscr;", 0x1D4C3 },
+ { "nshortmid;", 0x02224 },
+ { "nshortparallel;", 0x02226 },
+ { "nsim;", 0x02241 },
+ { "nsime;", 0x02244 },
+ { "nsimeq;", 0x02244 },
+ { "nsmid;", 0x02224 },
+ { "nspar;", 0x02226 },
+ { "nsqsube;", 0x022E2 },
+ { "nsqsupe;", 0x022E3 },
+ { "nsub;", 0x02284 },
+ { "nsube;", 0x02288 },
+ { "nsubseteq;", 0x02288 },
+ { "nsucc;", 0x02281 },
+ { "nsup;", 0x02285 },
+ { "nsupe;", 0x02289 },
+ { "nsupseteq;", 0x02289 },
+ { "ntgl;", 0x02279 },
+ { "ntilde;", 0x000F1 },
+ { "ntilde", 0x000F1 },
+ { "ntlg;", 0x02278 },
+ { "ntriangleleft;", 0x022EA },
+ { "ntrianglelefteq;", 0x022EC },
+ { "ntriangleright;", 0x022EB },
+ { "ntrianglerighteq;", 0x022ED },
+ { "nu;", 0x003BD },
+ { "num;", 0x00023 },
+ { "numero;", 0x02116 },
+ { "numsp;", 0x02007 },
+ { "nvDash;", 0x022AD },
+ { "nvHarr;", 0x02904 },
+ { "nvdash;", 0x022AC },
+ { "nvinfin;", 0x029DE },
+ { "nvlArr;", 0x02902 },
+ { "nvrArr;", 0x02903 },
+ { "nwArr;", 0x021D6 },
+ { "nwarhk;", 0x02923 },
+ { "nwarr;", 0x02196 },
+ { "nwarrow;", 0x02196 },
+ { "nwnear;", 0x02927 },
+ { "oS;", 0x024C8 },
+ { "oacute;", 0x000F3 },
+ { "oacute", 0x000F3 },
+ { "oast;", 0x0229B },
+ { "ocir;", 0x0229A },
+ { "ocirc;", 0x000F4 },
+ { "ocirc", 0x000F4 },
+ { "ocy;", 0x0043E },
+ { "odash;", 0x0229D },
+ { "odblac;", 0x00151 },
+ { "odiv;", 0x02A38 },
+ { "odot;", 0x02299 },
+ { "odsold;", 0x029BC },
+ { "oelig;", 0x00153 },
+ { "ofcir;", 0x029BF },
+ { "ofr;", 0x1D52C },
+ { "ogon;", 0x002DB },
+ { "ograve;", 0x000F2 },
+ { "ograve", 0x000F2 },
+ { "ogt;", 0x029C1 },
+ { "ohbar;", 0x029B5 },
+ { "ohm;", 0x003A9 },
+ { "oint;", 0x0222E },
+ { "olarr;", 0x021BA },
+ { "olcir;", 0x029BE },
+ { "olcross;", 0x029BB },
+ { "oline;", 0x0203E },
+ { "olt;", 0x029C0 },
+ { "omacr;", 0x0014D },
+ { "omega;", 0x003C9 },
+ { "omicron;", 0x003BF },
+ { "omid;", 0x029B6 },
+ { "ominus;", 0x02296 },
+ { "oopf;", 0x1D560 },
+ { "opar;", 0x029B7 },
+ { "operp;", 0x029B9 },
+ { "oplus;", 0x02295 },
+ { "or;", 0x02228 },
+ { "orarr;", 0x021BB },
+ { "ord;", 0x02A5D },
+ { "order;", 0x02134 },
+ { "orderof;", 0x02134 },
+ { "ordf;", 0x000AA },
+ { "ordf", 0x000AA },
+ { "ordm;", 0x000BA },
+ { "ordm", 0x000BA },
+ { "origof;", 0x022B6 },
+ { "oror;", 0x02A56 },
+ { "orslope;", 0x02A57 },
+ { "orv;", 0x02A5B },
+ { "oscr;", 0x02134 },
+ { "oslash;", 0x000F8 },
+ { "oslash", 0x000F8 },
+ { "osol;", 0x02298 },
+ { "otilde;", 0x000F5 },
+ { "otilde", 0x000F5 },
+ { "otimes;", 0x02297 },
+ { "otimesas;", 0x02A36 },
+ { "ouml;", 0x000F6 },
+ { "ouml", 0x000F6 },
+ { "ovbar;", 0x0233D },
+ { "par;", 0x02225 },
+ { "para;", 0x000B6 },
+ { "para", 0x000B6 },
+ { "parallel;", 0x02225 },
+ { "parsim;", 0x02AF3 },
+ { "parsl;", 0x02AFD },
+ { "part;", 0x02202 },
+ { "pcy;", 0x0043F },
+ { "percnt;", 0x00025 },
+ { "period;", 0x0002E },
+ { "permil;", 0x02030 },
+ { "perp;", 0x022A5 },
+ { "pertenk;", 0x02031 },
+ { "pfr;", 0x1D52D },
+ { "phi;", 0x003C6 },
+ { "phiv;", 0x003D5 },
+ { "phmmat;", 0x02133 },
+ { "phone;", 0x0260E },
+ { "pi;", 0x003C0 },
+ { "pitchfork;", 0x022D4 },
+ { "piv;", 0x003D6 },
+ { "planck;", 0x0210F },
+ { "planckh;", 0x0210E },
+ { "plankv;", 0x0210F },
+ { "plus;", 0x0002B },
+ { "plusacir;", 0x02A23 },
+ { "plusb;", 0x0229E },
+ { "pluscir;", 0x02A22 },
+ { "plusdo;", 0x02214 },
+ { "plusdu;", 0x02A25 },
+ { "pluse;", 0x02A72 },
+ { "plusmn;", 0x000B1 },
+ { "plusmn", 0x000B1 },
+ { "plussim;", 0x02A26 },
+ { "plustwo;", 0x02A27 },
+ { "pm;", 0x000B1 },
+ { "pointint;", 0x02A15 },
+ { "popf;", 0x1D561 },
+ { "pound;", 0x000A3 },
+ { "pound", 0x000A3 },
+ { "pr;", 0x0227A },
+ { "prE;", 0x02AB3 },
+ { "prap;", 0x02AB7 },
+ { "prcue;", 0x0227C },
+ { "pre;", 0x02AAF },
+ { "prec;", 0x0227A },
+ { "precapprox;", 0x02AB7 },
+ { "preccurlyeq;", 0x0227C },
+ { "preceq;", 0x02AAF },
+ { "precnapprox;", 0x02AB9 },
+ { "precneqq;", 0x02AB5 },
+ { "precnsim;", 0x022E8 },
+ { "precsim;", 0x0227E },
+ { "prime;", 0x02032 },
+ { "primes;", 0x02119 },
+ { "prnE;", 0x02AB5 },
+ { "prnap;", 0x02AB9 },
+ { "prnsim;", 0x022E8 },
+ { "prod;", 0x0220F },
+ { "profalar;", 0x0232E },
+ { "profline;", 0x02312 },
+ { "profsurf;", 0x02313 },
+ { "prop;", 0x0221D },
+ { "propto;", 0x0221D },
+ { "prsim;", 0x0227E },
+ { "prurel;", 0x022B0 },
+ { "pscr;", 0x1D4C5 },
+ { "psi;", 0x003C8 },
+ { "puncsp;", 0x02008 },
+ { "qfr;", 0x1D52E },
+ { "qint;", 0x02A0C },
+ { "qopf;", 0x1D562 },
+ { "qprime;", 0x02057 },
+ { "qscr;", 0x1D4C6 },
+ { "quaternions;", 0x0210D },
+ { "quatint;", 0x02A16 },
+ { "quest;", 0x0003F },
+ { "questeq;", 0x0225F },
+ { "quot;", 0x00022 },
+ { "quot", 0x00022 },
+ { "rAarr;", 0x021DB },
+ { "rArr;", 0x021D2 },
+ { "rAtail;", 0x0291C },
+ { "rBarr;", 0x0290F },
+ { "rHar;", 0x02964 },
+ { "racute;", 0x00155 },
+ { "radic;", 0x0221A },
+ { "raemptyv;", 0x029B3 },
+ { "rang;", 0x027E9 },
+ { "rangd;", 0x02992 },
+ { "range;", 0x029A5 },
+ { "rangle;", 0x027E9 },
+ { "raquo;", 0x000BB },
+ { "raquo", 0x000BB },
+ { "rarr;", 0x02192 },
+ { "rarrap;", 0x02975 },
+ { "rarrb;", 0x021E5 },
+ { "rarrbfs;", 0x02920 },
+ { "rarrc;", 0x02933 },
+ { "rarrfs;", 0x0291E },
+ { "rarrhk;", 0x021AA },
+ { "rarrlp;", 0x021AC },
+ { "rarrpl;", 0x02945 },
+ { "rarrsim;", 0x02974 },
+ { "rarrtl;", 0x021A3 },
+ { "rarrw;", 0x0219D },
+ { "ratail;", 0x0291A },
+ { "ratio;", 0x02236 },
+ { "rationals;", 0x0211A },
+ { "rbarr;", 0x0290D },
+ { "rbbrk;", 0x02773 },
+ { "rbrace;", 0x0007D },
+ { "rbrack;", 0x0005D },
+ { "rbrke;", 0x0298C },
+ { "rbrksld;", 0x0298E },
+ { "rbrkslu;", 0x02990 },
+ { "rcaron;", 0x00159 },
+ { "rcedil;", 0x00157 },
+ { "rceil;", 0x02309 },
+ { "rcub;", 0x0007D },
+ { "rcy;", 0x00440 },
+ { "rdca;", 0x02937 },
+ { "rdldhar;", 0x02969 },
+ { "rdquo;", 0x0201D },
+ { "rdquor;", 0x0201D },
+ { "rdsh;", 0x021B3 },
+ { "real;", 0x0211C },
+ { "realine;", 0x0211B },
+ { "realpart;", 0x0211C },
+ { "reals;", 0x0211D },
+ { "rect;", 0x025AD },
+ { "reg;", 0x000AE },
+ { "reg", 0x000AE },
+ { "rfisht;", 0x0297D },
+ { "rfloor;", 0x0230B },
+ { "rfr;", 0x1D52F },
+ { "rhard;", 0x021C1 },
+ { "rharu;", 0x021C0 },
+ { "rharul;", 0x0296C },
+ { "rho;", 0x003C1 },
+ { "rhov;", 0x003F1 },
+ { "rightarrow;", 0x02192 },
+ { "rightarrowtail;", 0x021A3 },
+ { "rightharpoondown;", 0x021C1 },
+ { "rightharpoonup;", 0x021C0 },
+ { "rightleftarrows;", 0x021C4 },
+ { "rightleftharpoons;", 0x021CC },
+ { "rightrightarrows;", 0x021C9 },
+ { "rightsquigarrow;", 0x0219D },
+ { "rightthreetimes;", 0x022CC },
+ { "ring;", 0x002DA },
+ { "risingdotseq;", 0x02253 },
+ { "rlarr;", 0x021C4 },
+ { "rlhar;", 0x021CC },
+ { "rlm;", 0x0200F },
+ { "rmoust;", 0x023B1 },
+ { "rmoustache;", 0x023B1 },
+ { "rnmid;", 0x02AEE },
+ { "roang;", 0x027ED },
+ { "roarr;", 0x021FE },
+ { "robrk;", 0x027E7 },
+ { "ropar;", 0x02986 },
+ { "ropf;", 0x1D563 },
+ { "roplus;", 0x02A2E },
+ { "rotimes;", 0x02A35 },
+ { "rpar;", 0x00029 },
+ { "rpargt;", 0x02994 },
+ { "rppolint;", 0x02A12 },
+ { "rrarr;", 0x021C9 },
+ { "rsaquo;", 0x0203A },
+ { "rscr;", 0x1D4C7 },
+ { "rsh;", 0x021B1 },
+ { "rsqb;", 0x0005D },
+ { "rsquo;", 0x02019 },
+ { "rsquor;", 0x02019 },
+ { "rthree;", 0x022CC },
+ { "rtimes;", 0x022CA },
+ { "rtri;", 0x025B9 },
+ { "rtrie;", 0x022B5 },
+ { "rtrif;", 0x025B8 },
+ { "rtriltri;", 0x029CE },
+ { "ruluhar;", 0x02968 },
+ { "rx;", 0x0211E },
+ { "sacute;", 0x0015B },
+ { "sbquo;", 0x0201A },
+ { "sc;", 0x0227B },
+ { "scE;", 0x02AB4 },
+ { "scap;", 0x02AB8 },
+ { "scaron;", 0x00161 },
+ { "sccue;", 0x0227D },
+ { "sce;", 0x02AB0 },
+ { "scedil;", 0x0015F },
+ { "scirc;", 0x0015D },
+ { "scnE;", 0x02AB6 },
+ { "scnap;", 0x02ABA },
+ { "scnsim;", 0x022E9 },
+ { "scpolint;", 0x02A13 },
+ { "scsim;", 0x0227F },
+ { "scy;", 0x00441 },
+ { "sdot;", 0x022C5 },
+ { "sdotb;", 0x022A1 },
+ { "sdote;", 0x02A66 },
+ { "seArr;", 0x021D8 },
+ { "searhk;", 0x02925 },
+ { "searr;", 0x02198 },
+ { "searrow;", 0x02198 },
+ { "sect;", 0x000A7 },
+ { "sect", 0x000A7 },
+ { "semi;", 0x0003B },
+ { "seswar;", 0x02929 },
+ { "setminus;", 0x02216 },
+ { "setmn;", 0x02216 },
+ { "sext;", 0x02736 },
+ { "sfr;", 0x1D530 },
+ { "sfrown;", 0x02322 },
+ { "sharp;", 0x0266F },
+ { "shchcy;", 0x00449 },
+ { "shcy;", 0x00448 },
+ { "shortmid;", 0x02223 },
+ { "shortparallel;", 0x02225 },
+ { "shy;", 0x000AD },
+ { "shy", 0x000AD },
+ { "sigma;", 0x003C3 },
+ { "sigmaf;", 0x003C2 },
+ { "sigmav;", 0x003C2 },
+ { "sim;", 0x0223C },
+ { "simdot;", 0x02A6A },
+ { "sime;", 0x02243 },
+ { "simeq;", 0x02243 },
+ { "simg;", 0x02A9E },
+ { "simgE;", 0x02AA0 },
+ { "siml;", 0x02A9D },
+ { "simlE;", 0x02A9F },
+ { "simne;", 0x02246 },
+ { "simplus;", 0x02A24 },
+ { "simrarr;", 0x02972 },
+ { "slarr;", 0x02190 },
+ { "smallsetminus;", 0x02216 },
+ { "smashp;", 0x02A33 },
+ { "smeparsl;", 0x029E4 },
+ { "smid;", 0x02223 },
+ { "smile;", 0x02323 },
+ { "smt;", 0x02AAA },
+ { "smte;", 0x02AAC },
+ { "softcy;", 0x0044C },
+ { "sol;", 0x0002F },
+ { "solb;", 0x029C4 },
+ { "solbar;", 0x0233F },
+ { "sopf;", 0x1D564 },
+ { "spades;", 0x02660 },
+ { "spadesuit;", 0x02660 },
+ { "spar;", 0x02225 },
+ { "sqcap;", 0x02293 },
+ { "sqcup;", 0x02294 },
+ { "sqsub;", 0x0228F },
+ { "sqsube;", 0x02291 },
+ { "sqsubset;", 0x0228F },
+ { "sqsubseteq;", 0x02291 },
+ { "sqsup;", 0x02290 },
+ { "sqsupe;", 0x02292 },
+ { "sqsupset;", 0x02290 },
+ { "sqsupseteq;", 0x02292 },
+ { "squ;", 0x025A1 },
+ { "square;", 0x025A1 },
+ { "squarf;", 0x025AA },
+ { "squf;", 0x025AA },
+ { "srarr;", 0x02192 },
+ { "sscr;", 0x1D4C8 },
+ { "ssetmn;", 0x02216 },
+ { "ssmile;", 0x02323 },
+ { "sstarf;", 0x022C6 },
+ { "star;", 0x02606 },
+ { "starf;", 0x02605 },
+ { "straightepsilon;", 0x003F5 },
+ { "straightphi;", 0x003D5 },
+ { "strns;", 0x000AF },
+ { "sub;", 0x02282 },
+ { "subE;", 0x02AC5 },
+ { "subdot;", 0x02ABD },
+ { "sube;", 0x02286 },
+ { "subedot;", 0x02AC3 },
+ { "submult;", 0x02AC1 },
+ { "subnE;", 0x02ACB },
+ { "subne;", 0x0228A },
+ { "subplus;", 0x02ABF },
+ { "subrarr;", 0x02979 },
+ { "subset;", 0x02282 },
+ { "subseteq;", 0x02286 },
+ { "subseteqq;", 0x02AC5 },
+ { "subsetneq;", 0x0228A },
+ { "subsetneqq;", 0x02ACB },
+ { "subsim;", 0x02AC7 },
+ { "subsub;", 0x02AD5 },
+ { "subsup;", 0x02AD3 },
+ { "succ;", 0x0227B },
+ { "succapprox;", 0x02AB8 },
+ { "succcurlyeq;", 0x0227D },
+ { "succeq;", 0x02AB0 },
+ { "succnapprox;", 0x02ABA },
+ { "succneqq;", 0x02AB6 },
+ { "succnsim;", 0x022E9 },
+ { "succsim;", 0x0227F },
+ { "sum;", 0x02211 },
+ { "sung;", 0x0266A },
+ { "sup1;", 0x000B9 },
+ { "sup1", 0x000B9 },
+ { "sup2;", 0x000B2 },
+ { "sup2", 0x000B2 },
+ { "sup3;", 0x000B3 },
+ { "sup3", 0x000B3 },
+ { "sup;", 0x02283 },
+ { "supE;", 0x02AC6 },
+ { "supdot;", 0x02ABE },
+ { "supdsub;", 0x02AD8 },
+ { "supe;", 0x02287 },
+ { "supedot;", 0x02AC4 },
+ { "suphsol;", 0x027C9 },
+ { "suphsub;", 0x02AD7 },
+ { "suplarr;", 0x0297B },
+ { "supmult;", 0x02AC2 },
+ { "supnE;", 0x02ACC },
+ { "supne;", 0x0228B },
+ { "supplus;", 0x02AC0 },
+ { "supset;", 0x02283 },
+ { "supseteq;", 0x02287 },
+ { "supseteqq;", 0x02AC6 },
+ { "supsetneq;", 0x0228B },
+ { "supsetneqq;", 0x02ACC },
+ { "supsim;", 0x02AC8 },
+ { "supsub;", 0x02AD4 },
+ { "supsup;", 0x02AD6 },
+ { "swArr;", 0x021D9 },
+ { "swarhk;", 0x02926 },
+ { "swarr;", 0x02199 },
+ { "swarrow;", 0x02199 },
+ { "swnwar;", 0x0292A },
+ { "szlig;", 0x000DF },
+ { "szlig", 0x000DF },
+ { "target;", 0x02316 },
+ { "tau;", 0x003C4 },
+ { "tbrk;", 0x023B4 },
+ { "tcaron;", 0x00165 },
+ { "tcedil;", 0x00163 },
+ { "tcy;", 0x00442 },
+ { "tdot;", 0x020DB },
+ { "telrec;", 0x02315 },
+ { "tfr;", 0x1D531 },
+ { "there4;", 0x02234 },
+ { "therefore;", 0x02234 },
+ { "theta;", 0x003B8 },
+ { "thetasym;", 0x003D1 },
+ { "thetav;", 0x003D1 },
+ { "thickapprox;", 0x02248 },
+ { "thicksim;", 0x0223C },
+ { "thinsp;", 0x02009 },
+ { "thkap;", 0x02248 },
+ { "thksim;", 0x0223C },
+ { "thorn;", 0x000FE },
+ { "thorn", 0x000FE },
+ { "tilde;", 0x002DC },
+ { "times;", 0x000D7 },
+ { "times", 0x000D7 },
+ { "timesb;", 0x022A0 },
+ { "timesbar;", 0x02A31 },
+ { "timesd;", 0x02A30 },
+ { "tint;", 0x0222D },
+ { "toea;", 0x02928 },
+ { "top;", 0x022A4 },
+ { "topbot;", 0x02336 },
+ { "topcir;", 0x02AF1 },
+ { "topf;", 0x1D565 },
+ { "topfork;", 0x02ADA },
+ { "tosa;", 0x02929 },
+ { "tprime;", 0x02034 },
+ { "trade;", 0x02122 },
+ { "triangle;", 0x025B5 },
+ { "triangledown;", 0x025BF },
+ { "triangleleft;", 0x025C3 },
+ { "trianglelefteq;", 0x022B4 },
+ { "triangleq;", 0x0225C },
+ { "triangleright;", 0x025B9 },
+ { "trianglerighteq;", 0x022B5 },
+ { "tridot;", 0x025EC },
+ { "trie;", 0x0225C },
+ { "triminus;", 0x02A3A },
+ { "triplus;", 0x02A39 },
+ { "trisb;", 0x029CD },
+ { "tritime;", 0x02A3B },
+ { "trpezium;", 0x023E2 },
+ { "tscr;", 0x1D4C9 },
+ { "tscy;", 0x00446 },
+ { "tshcy;", 0x0045B },
+ { "tstrok;", 0x00167 },
+ { "twixt;", 0x0226C },
+ { "twoheadleftarrow;", 0x0219E },
+ { "twoheadrightarrow;", 0x021A0 },
+ { "uArr;", 0x021D1 },
+ { "uHar;", 0x02963 },
+ { "uacute;", 0x000FA },
+ { "uacute", 0x000FA },
+ { "uarr;", 0x02191 },
+ { "ubrcy;", 0x0045E },
+ { "ubreve;", 0x0016D },
+ { "ucirc;", 0x000FB },
+ { "ucirc", 0x000FB },
+ { "ucy;", 0x00443 },
+ { "udarr;", 0x021C5 },
+ { "udblac;", 0x00171 },
+ { "udhar;", 0x0296E },
+ { "ufisht;", 0x0297E },
+ { "ufr;", 0x1D532 },
+ { "ugrave;", 0x000F9 },
+ { "ugrave", 0x000F9 },
+ { "uharl;", 0x021BF },
+ { "uharr;", 0x021BE },
+ { "uhblk;", 0x02580 },
+ { "ulcorn;", 0x0231C },
+ { "ulcorner;", 0x0231C },
+ { "ulcrop;", 0x0230F },
+ { "ultri;", 0x025F8 },
+ { "umacr;", 0x0016B },
+ { "uml;", 0x000A8 },
+ { "uml", 0x000A8 },
+ { "uogon;", 0x00173 },
+ { "uopf;", 0x1D566 },
+ { "uparrow;", 0x02191 },
+ { "updownarrow;", 0x02195 },
+ { "upharpoonleft;", 0x021BF },
+ { "upharpoonright;", 0x021BE },
+ { "uplus;", 0x0228E },
+ { "upsi;", 0x003C5 },
+ { "upsih;", 0x003D2 },
+ { "upsilon;", 0x003C5 },
+ { "upuparrows;", 0x021C8 },
+ { "urcorn;", 0x0231D },
+ { "urcorner;", 0x0231D },
+ { "urcrop;", 0x0230E },
+ { "uring;", 0x0016F },
+ { "urtri;", 0x025F9 },
+ { "uscr;", 0x1D4CA },
+ { "utdot;", 0x022F0 },
+ { "utilde;", 0x00169 },
+ { "utri;", 0x025B5 },
+ { "utrif;", 0x025B4 },
+ { "uuarr;", 0x021C8 },
+ { "uuml;", 0x000FC },
+ { "uuml", 0x000FC },
+ { "uwangle;", 0x029A7 },
+ { "vArr;", 0x021D5 },
+ { "vBar;", 0x02AE8 },
+ { "vBarv;", 0x02AE9 },
+ { "vDash;", 0x022A8 },
+ { "vangrt;", 0x0299C },
+ { "varepsilon;", 0x003F5 },
+ { "varkappa;", 0x003F0 },
+ { "varnothing;", 0x02205 },
+ { "varphi;", 0x003D5 },
+ { "varpi;", 0x003D6 },
+ { "varpropto;", 0x0221D },
+ { "varr;", 0x02195 },
+ { "varrho;", 0x003F1 },
+ { "varsigma;", 0x003C2 },
+ { "vartheta;", 0x003D1 },
+ { "vartriangleleft;", 0x022B2 },
+ { "vartriangleright;", 0x022B3 },
+ { "vcy;", 0x00432 },
+ { "vdash;", 0x022A2 },
+ { "vee;", 0x02228 },
+ { "veebar;", 0x022BB },
+ { "veeeq;", 0x0225A },
+ { "vellip;", 0x022EE },
+ { "verbar;", 0x0007C },
+ { "vert;", 0x0007C },
+ { "vfr;", 0x1D533 },
+ { "vltri;", 0x022B2 },
+ { "vopf;", 0x1D567 },
+ { "vprop;", 0x0221D },
+ { "vrtri;", 0x022B3 },
+ { "vscr;", 0x1D4CB },
+ { "vzigzag;", 0x0299A },
+ { "wcirc;", 0x00175 },
+ { "wedbar;", 0x02A5F },
+ { "wedge;", 0x02227 },
+ { "wedgeq;", 0x02259 },
+ { "weierp;", 0x02118 },
+ { "wfr;", 0x1D534 },
+ { "wopf;", 0x1D568 },
+ { "wp;", 0x02118 },
+ { "wr;", 0x02240 },
+ { "wreath;", 0x02240 },
+ { "wscr;", 0x1D4CC },
+ { "xcap;", 0x022C2 },
+ { "xcirc;", 0x025EF },
+ { "xcup;", 0x022C3 },
+ { "xdtri;", 0x025BD },
+ { "xfr;", 0x1D535 },
+ { "xhArr;", 0x027FA },
+ { "xharr;", 0x027F7 },
+ { "xi;", 0x003BE },
+ { "xlArr;", 0x027F8 },
+ { "xlarr;", 0x027F5 },
+ { "xmap;", 0x027FC },
+ { "xnis;", 0x022FB },
+ { "xodot;", 0x02A00 },
+ { "xopf;", 0x1D569 },
+ { "xoplus;", 0x02A01 },
+ { "xotime;", 0x02A02 },
+ { "xrArr;", 0x027F9 },
+ { "xrarr;", 0x027F6 },
+ { "xscr;", 0x1D4CD },
+ { "xsqcup;", 0x02A06 },
+ { "xuplus;", 0x02A04 },
+ { "xutri;", 0x025B3 },
+ { "xvee;", 0x022C1 },
+ { "xwedge;", 0x022C0 },
+ { "yacute;", 0x000FD },
+ { "yacute", 0x000FD },
+ { "yacy;", 0x0044F },
+ { "ycirc;", 0x00177 },
+ { "ycy;", 0x0044B },
+ { "yen;", 0x000A5 },
+ { "yen", 0x000A5 },
+ { "yfr;", 0x1D536 },
+ { "yicy;", 0x00457 },
+ { "yopf;", 0x1D56A },
+ { "yscr;", 0x1D4CE },
+ { "yucy;", 0x0044E },
+ { "yuml;", 0x000FF },
+ { "yuml", 0x000FF },
+ { "zacute;", 0x0017A },
+ { "zcaron;", 0x0017E },
+ { "zcy;", 0x00437 },
+ { "zdot;", 0x0017C },
+ { "zeetrf;", 0x02128 },
+ { "zeta;", 0x003B6 },
+ { "zfr;", 0x1D537 },
+ { "zhcy;", 0x00436 },
+ { "zigrarr;", 0x021DD },
+ { "zopf;", 0x1D56B },
+ { "zscr;", 0x1D4CF },
+ { "zwj;", 0x0200D },
+ { "zwnj;", 0x0200C }
+ };
+
+ constexpr struct {
+ StringView entity;
+ u32 code_point1;
+ u32 code_point2;
+ } double_code_point_entities[] = {
+ { "NotEqualTilde;", 0x02242, 0x00338 },
+ { "NotGreaterFullEqual;", 0x02267, 0x00338 },
+ { "NotGreaterGreater;", 0x0226B, 0x00338 },
+ { "NotGreaterSlantEqual;", 0x02A7E, 0x00338 },
+ { "NotHumpDownHump;", 0x0224E, 0x00338 },
+ { "NotHumpEqual;", 0x0224F, 0x00338 },
+ { "NotLeftTriangleBar;", 0x029CF, 0x00338 },
+ { "NotLessLess;", 0x0226A, 0x00338 },
+ { "NotLessSlantEqual;", 0x02A7D, 0x00338 },
+ { "NotNestedGreaterGreater;", 0x02AA2, 0x00338 },
+ { "NotNestedLessLess;", 0x02AA1, 0x00338 },
+ { "NotPrecedesEqual;", 0x02AAF, 0x00338 },
+ { "NotRightTriangleBar;", 0x029D0, 0x00338 },
+ { "NotSquareSubset;", 0x0228F, 0x00338 },
+ { "NotSquareSuperset;", 0x02290, 0x00338 },
+ { "NotSubset;", 0x02282, 0x020D2 },
+ { "NotSucceedsEqual;", 0x02AB0, 0x00338 },
+ { "NotSucceedsTilde;", 0x0227F, 0x00338 },
+ { "NotSuperset;", 0x02283, 0x020D2 },
+ { "ThickSpace;", 0x0205F, 0x0200A },
+ { "acE;", 0x0223E, 0x00333 },
+ { "bne;", 0x0003D, 0x020E5 },
+ { "bnequiv;", 0x02261, 0x020E5 },
+ { "caps;", 0x02229, 0x0FE00 },
+ { "cups;", 0x0222A, 0x0FE00 },
+ { "fjlig;", 0x00066, 0x0006A },
+ { "gesl;", 0x022DB, 0x0FE00 },
+ { "gvertneqq;", 0x02269, 0x0FE00 },
+ { "gvnE;", 0x02269, 0x0FE00 },
+ { "lates;", 0x02AAD, 0x0FE00 },
+ { "lesg;", 0x022DA, 0x0FE00 },
+ { "lvertneqq;", 0x02268, 0x0FE00 },
+ { "lvnE;", 0x02268, 0x0FE00 },
+ { "nGg;", 0x022D9, 0x00338 },
+ { "nGt;", 0x0226B, 0x020D2 },
+ { "nGtv;", 0x0226B, 0x00338 },
+ { "nLl;", 0x022D8, 0x00338 },
+ { "nLt;", 0x0226A, 0x020D2 },
+ { "nLtv;", 0x0226A, 0x00338 },
+ { "nang;", 0x02220, 0x020D2 },
+ { "napE;", 0x02A70, 0x00338 },
+ { "napid;", 0x0224B, 0x00338 },
+ { "nbump;", 0x0224E, 0x00338 },
+ { "nbumpe;", 0x0224F, 0x00338 },
+ { "ncongdot;", 0x02A6D, 0x00338 },
+ { "nedot;", 0x02250, 0x00338 },
+ { "nesim;", 0x02242, 0x00338 },
+ { "ngE;", 0x02267, 0x00338 },
+ { "ngeqq;", 0x02267, 0x00338 },
+ { "ngeqslant;", 0x02A7E, 0x00338 },
+ { "nges;", 0x02A7E, 0x00338 },
+ { "nlE;", 0x02266, 0x00338 },
+ { "nleqq;", 0x02266, 0x00338 },
+ { "nleqslant;", 0x02A7D, 0x00338 },
+ { "nles;", 0x02A7D, 0x00338 },
+ { "notinE;", 0x022F9, 0x00338 },
+ { "notindot;", 0x022F5, 0x00338 },
+ { "nparsl;", 0x02AFD, 0x020E5 },
+ { "npart;", 0x02202, 0x00338 },
+ { "npre;", 0x02AAF, 0x00338 },
+ { "npreceq;", 0x02AAF, 0x00338 },
+ { "nrarrc;", 0x02933, 0x00338 },
+ { "nrarrw;", 0x0219D, 0x00338 },
+ { "nsce;", 0x02AB0, 0x00338 },
+ { "nsubE;", 0x02AC5, 0x00338 },
+ { "nsubset;", 0x02282, 0x020D2 },
+ { "nsubseteqq;", 0x02AC5, 0x00338 },
+ { "nsucceq;", 0x02AB0, 0x00338 },
+ { "nsupE;", 0x02AC6, 0x00338 },
+ { "nsupset;", 0x02283, 0x020D2 },
+ { "nsupseteqq;", 0x02AC6, 0x00338 },
+ { "nvap;", 0x0224D, 0x020D2 },
+ { "nvge;", 0x02265, 0x020D2 },
+ { "nvgt;", 0x0003E, 0x020D2 },
+ { "nvle;", 0x02264, 0x020D2 },
+ { "nvlt;", 0x0003C, 0x020D2 },
+ { "nvltrie;", 0x022B4, 0x020D2 },
+ { "nvrtrie;", 0x022B5, 0x020D2 },
+ { "nvsim;", 0x0223C, 0x020D2 },
+ { "race;", 0x0223D, 0x00331 },
+ { "smtes;", 0x02AAC, 0x0FE00 },
+ { "sqcaps;", 0x02293, 0x0FE00 },
+ { "sqcups;", 0x02294, 0x0FE00 },
+ { "varsubsetneq;", 0x0228A, 0x0FE00 },
+ { "varsubsetneqq;", 0x02ACB, 0x0FE00 },
+ { "varsupsetneq;", 0x0228B, 0x0FE00 },
+ { "varsupsetneqq;", 0x02ACC, 0x0FE00 },
+ { "vnsub;", 0x02282, 0x020D2 },
+ { "vnsup;", 0x02283, 0x020D2 },
+ { "vsubnE;", 0x02ACB, 0x0FE00 },
+ { "vsubne;", 0x0228A, 0x0FE00 },
+ { "vsupnE;", 0x02ACC, 0x0FE00 },
+ { "vsupne;", 0x0228B, 0x0FE00 },
+ };
+
+ EntityMatch match;
+
+ for (auto& single_code_point_entity : single_code_point_entities) {
+ if (entity.starts_with(single_code_point_entity.entity)) {
+ if (match.entity.is_null() || single_code_point_entity.entity.length() > match.entity.length())
+ match = { { single_code_point_entity.code_point }, single_code_point_entity.entity };
+ }
+ }
+
+ for (auto& double_code_point_entity : double_code_point_entities) {
+ if (entity.starts_with(double_code_point_entity.entity)) {
+ if (match.entity.is_null() || double_code_point_entity.entity.length() > match.entity.length())
+ match = EntityMatch { { double_code_point_entity.code_point1, double_code_point_entity.code_point2 }, StringView(double_code_point_entity.entity) };
+ }
+ }
+
+ if (match.entity.is_empty())
+ return {};
+ return match;
+}
+
+}
+}
diff --git a/Userland/Libraries/LibWeb/HTML/Parser/Entities.h b/Userland/Libraries/LibWeb/HTML/Parser/Entities.h
new file mode 100644
index 0000000000..0701f3be8d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Parser/Entities.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/StringView.h>
+#include <AK/Vector.h>
+
+namespace Web {
+namespace HTML {
+
+struct EntityMatch {
+ Vector<u32, 2> code_points;
+ StringView entity;
+};
+
+Optional<EntityMatch> code_points_from_entity(const StringView&);
+
+}
+}
diff --git a/Userland/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp b/Userland/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp
new file mode 100644
index 0000000000..446888aeb3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp
@@ -0,0 +1,3027 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+//#define PARSER_DEBUG
+
+#include <AK/Utf32View.h>
+#include <LibTextCodec/Decoder.h>
+#include <LibWeb/DOM/Comment.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/DocumentType.h>
+#include <LibWeb/DOM/ElementFactory.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/DOM/Window.h>
+#include <LibWeb/HTML/EventNames.h>
+#include <LibWeb/HTML/HTMLFormElement.h>
+#include <LibWeb/HTML/HTMLHeadElement.h>
+#include <LibWeb/HTML/HTMLScriptElement.h>
+#include <LibWeb/HTML/HTMLTemplateElement.h>
+#include <LibWeb/HTML/Parser/HTMLDocumentParser.h>
+#include <LibWeb/HTML/Parser/HTMLToken.h>
+#include <LibWeb/Namespace.h>
+#include <LibWeb/SVG/TagNames.h>
+
+namespace Web::HTML {
+
+#define PARSE_ERROR() \
+ do { \
+ dbg() << "Parse error! " << __PRETTY_FUNCTION__ << " @ " << __LINE__; \
+ } while (0)
+
+static Vector<FlyString> s_quirks_public_ids = {
+ "+//Silmaril//dtd html Pro v0r11 19970101//",
+ "-//AS//DTD HTML 3.0 asWedit + extensions//",
+ "-//AdvaSoft Ltd//DTD HTML 3.0 asWedit + extensions//",
+ "-//IETF//DTD HTML 2.0 Level 1//",
+ "-//IETF//DTD HTML 2.0 Level 2//",
+ "-//IETF//DTD HTML 2.0 Strict Level 1//",
+ "-//IETF//DTD HTML 2.0 Strict Level 2//",
+ "-//IETF//DTD HTML 2.0 Strict//",
+ "-//IETF//DTD HTML 2.0//",
+ "-//IETF//DTD HTML 2.1E//",
+ "-//IETF//DTD HTML 3.0//",
+ "-//IETF//DTD HTML 3.2 Final//",
+ "-//IETF//DTD HTML 3.2//",
+ "-//IETF//DTD HTML 3//",
+ "-//IETF//DTD HTML Level 0//",
+ "-//IETF//DTD HTML Level 1//",
+ "-//IETF//DTD HTML Level 2//",
+ "-//IETF//DTD HTML Level 3//",
+ "-//IETF//DTD HTML Strict Level 0//",
+ "-//IETF//DTD HTML Strict Level 1//",
+ "-//IETF//DTD HTML Strict Level 2//",
+ "-//IETF//DTD HTML Strict Level 3//",
+ "-//IETF//DTD HTML Strict//",
+ "-//IETF//DTD HTML//",
+ "-//Metrius//DTD Metrius Presentational//",
+ "-//Microsoft//DTD Internet Explorer 2.0 HTML Strict//",
+ "-//Microsoft//DTD Internet Explorer 2.0 HTML//",
+ "-//Microsoft//DTD Internet Explorer 2.0 Tables//",
+ "-//Microsoft//DTD Internet Explorer 3.0 HTML Strict//",
+ "-//Microsoft//DTD Internet Explorer 3.0 HTML//",
+ "-//Microsoft//DTD Internet Explorer 3.0 Tables//",
+ "-//Netscape Comm. Corp.//DTD HTML//",
+ "-//Netscape Comm. Corp.//DTD Strict HTML//",
+ "-//O'Reilly and Associates//DTD HTML 2.0//",
+ "-//O'Reilly and Associates//DTD HTML Extended 1.0//",
+ "-//O'Reilly and Associates//DTD HTML Extended Relaxed 1.0//",
+ "-//SQ//DTD HTML 2.0 HoTMetaL + extensions//",
+ "-//SoftQuad Software//DTD HoTMetaL PRO 6.0::19990601::extensions to HTML 4.0//",
+ "-//SoftQuad//DTD HoTMetaL PRO 4.0::19971010::extensions to HTML 4.0//",
+ "-//Spyglass//DTD HTML 2.0 Extended//",
+ "-//Sun Microsystems Corp.//DTD HotJava HTML//",
+ "-//Sun Microsystems Corp.//DTD HotJava Strict HTML//",
+ "-//W3C//DTD HTML 3 1995-03-24//",
+ "-//W3C//DTD HTML 3.2 Draft//",
+ "-//W3C//DTD HTML 3.2 Final//",
+ "-//W3C//DTD HTML 3.2//",
+ "-//W3C//DTD HTML 3.2S Draft//",
+ "-//W3C//DTD HTML 4.0 Frameset//",
+ "-//W3C//DTD HTML 4.0 Transitional//",
+ "-//W3C//DTD HTML Experimental 19960712//",
+ "-//W3C//DTD HTML Experimental 970421//",
+ "-//W3C//DTD W3 HTML//",
+ "-//W3O//DTD W3 HTML 3.0//",
+ "-//WebTechs//DTD Mozilla HTML 2.0//",
+ "-//WebTechs//DTD Mozilla HTML//"
+};
+
+RefPtr<DOM::Document> parse_html_document(const StringView& data, const URL& url, const String& encoding)
+{
+ auto document = DOM::Document::create(url);
+ HTMLDocumentParser parser(document, data, encoding);
+ parser.run(url);
+ return document;
+}
+
+HTMLDocumentParser::HTMLDocumentParser(DOM::Document& document, const StringView& input, const String& encoding)
+ : m_tokenizer(input, encoding)
+ , m_document(document)
+{
+ m_document->set_should_invalidate_styles_on_attribute_changes(false);
+ m_document->set_encoding(TextCodec::get_standardized_encoding(encoding));
+}
+
+HTMLDocumentParser::~HTMLDocumentParser()
+{
+ m_document->set_should_invalidate_styles_on_attribute_changes(true);
+}
+
+void HTMLDocumentParser::run(const URL& url)
+{
+ m_document->set_url(url);
+ m_document->set_source(m_tokenizer.source());
+
+ for (;;) {
+ auto optional_token = m_tokenizer.next_token();
+ if (!optional_token.has_value())
+ break;
+ auto& token = optional_token.value();
+
+#ifdef PARSER_DEBUG
+ dbg() << "[" << insertion_mode_name() << "] " << token.to_string();
+#endif
+ // FIXME: If the adjusted current node is a MathML text integration point and the token is a start tag whose tag name is neither "mglyph" nor "malignmark"
+ // FIXME: If the adjusted current node is a MathML text integration point and the token is a character token
+ // FIXME: If the adjusted current node is a MathML annotation-xml element and the token is a start tag whose tag name is "svg"
+ // FIXME: If the adjusted current node is an HTML integration point and the token is a start tag
+ // FIXME: If the adjusted current node is an HTML integration point and the token is a character token
+ if (m_stack_of_open_elements.is_empty()
+ || adjusted_current_node().namespace_() == Namespace::HTML
+ || token.is_end_of_file()) {
+ process_using_the_rules_for(m_insertion_mode, token);
+ } else {
+ process_using_the_rules_for_foreign_content(token);
+ }
+
+ if (m_stop_parsing) {
+#ifdef PARSER_DEBUG
+ dbg() << "Stop parsing" << (m_parsing_fragment ? " fragment" : "") << "! :^)";
+#endif
+ break;
+ }
+ }
+
+ flush_character_insertions();
+
+ // "The end"
+
+ m_document->set_ready_state("interactive");
+
+ auto scripts_to_execute_when_parsing_has_finished = m_document->take_scripts_to_execute_when_parsing_has_finished({});
+ for (auto& script : scripts_to_execute_when_parsing_has_finished) {
+ script.execute_script();
+ }
+
+ auto content_loaded_event = DOM::Event::create(HTML::EventNames::DOMContentLoaded);
+ content_loaded_event->set_bubbles(true);
+ m_document->dispatch_event(content_loaded_event);
+
+ auto scripts_to_execute_as_soon_as_possible = m_document->take_scripts_to_execute_as_soon_as_possible({});
+ for (auto& script : scripts_to_execute_as_soon_as_possible) {
+ script.execute_script();
+ }
+
+ // FIXME: Spin the event loop until there is nothing that delays the load event in the Document.
+
+ m_document->set_ready_state("complete");
+ m_document->window().dispatch_event(DOM::Event::create(HTML::EventNames::load));
+
+ m_document->set_ready_for_post_load_tasks(true);
+ m_document->completely_finish_loading();
+}
+
+void HTMLDocumentParser::process_using_the_rules_for(InsertionMode mode, HTMLToken& token)
+{
+ switch (mode) {
+ case InsertionMode::Initial:
+ handle_initial(token);
+ break;
+ case InsertionMode::BeforeHTML:
+ handle_before_html(token);
+ break;
+ case InsertionMode::BeforeHead:
+ handle_before_head(token);
+ break;
+ case InsertionMode::InHead:
+ handle_in_head(token);
+ break;
+ case InsertionMode::InHeadNoscript:
+ handle_in_head_noscript(token);
+ break;
+ case InsertionMode::AfterHead:
+ handle_after_head(token);
+ break;
+ case InsertionMode::InBody:
+ handle_in_body(token);
+ break;
+ case InsertionMode::AfterBody:
+ handle_after_body(token);
+ break;
+ case InsertionMode::AfterAfterBody:
+ handle_after_after_body(token);
+ break;
+ case InsertionMode::Text:
+ handle_text(token);
+ break;
+ case InsertionMode::InTable:
+ handle_in_table(token);
+ break;
+ case InsertionMode::InTableBody:
+ handle_in_table_body(token);
+ break;
+ case InsertionMode::InRow:
+ handle_in_row(token);
+ break;
+ case InsertionMode::InCell:
+ handle_in_cell(token);
+ break;
+ case InsertionMode::InTableText:
+ handle_in_table_text(token);
+ break;
+ case InsertionMode::InSelectInTable:
+ handle_in_select_in_table(token);
+ break;
+ case InsertionMode::InSelect:
+ handle_in_select(token);
+ break;
+ case InsertionMode::InCaption:
+ handle_in_caption(token);
+ break;
+ case InsertionMode::InColumnGroup:
+ handle_in_column_group(token);
+ break;
+ case InsertionMode::InTemplate:
+ handle_in_template(token);
+ break;
+ case InsertionMode::InFrameset:
+ handle_in_frameset(token);
+ break;
+ case InsertionMode::AfterFrameset:
+ handle_after_frameset(token);
+ break;
+ case InsertionMode::AfterAfterFrameset:
+ handle_after_after_frameset(token);
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+DOM::QuirksMode HTMLDocumentParser::which_quirks_mode(const HTMLToken& doctype_token) const
+{
+ if (doctype_token.m_doctype.force_quirks)
+ return DOM::QuirksMode::Yes;
+
+ // NOTE: The tokenizer puts the name into lower case for us.
+ if (doctype_token.m_doctype.name.to_string() != "html")
+ return DOM::QuirksMode::Yes;
+
+ auto public_identifier = doctype_token.m_doctype.public_identifier.to_string();
+ auto system_identifier = doctype_token.m_doctype.system_identifier.to_string();
+
+ if (public_identifier.equals_ignoring_case("-//W3O//DTD W3 HTML Strict 3.0//EN//"))
+ return DOM::QuirksMode::Yes;
+
+ if (public_identifier.equals_ignoring_case("-/W3C/DTD HTML 4.0 Transitional/EN"))
+ return DOM::QuirksMode::Yes;
+
+ if (public_identifier.equals_ignoring_case("HTML"))
+ return DOM::QuirksMode::Yes;
+
+ if (system_identifier.equals_ignoring_case("http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd"))
+ return DOM::QuirksMode::Yes;
+
+ for (auto& public_id : s_quirks_public_ids) {
+ if (public_identifier.starts_with(public_id, CaseSensitivity::CaseInsensitive))
+ return DOM::QuirksMode::Yes;
+ }
+
+ if (doctype_token.m_doctype.missing_system_identifier) {
+ if (public_identifier.starts_with("-//W3C//DTD HTML 4.01 Frameset//", CaseSensitivity::CaseInsensitive))
+ return DOM::QuirksMode::Yes;
+
+ if (public_identifier.starts_with("-//W3C//DTD HTML 4.01 Transitional//", CaseSensitivity::CaseInsensitive))
+ return DOM::QuirksMode::Yes;
+ }
+
+ if (public_identifier.starts_with("-//W3C//DTD XHTML 1.0 Frameset//", CaseSensitivity::CaseInsensitive))
+ return DOM::QuirksMode::Limited;
+
+ if (public_identifier.starts_with("-//W3C//DTD XHTML 1.0 Transitional//", CaseSensitivity::CaseInsensitive))
+ return DOM::QuirksMode::Limited;
+
+ if (!doctype_token.m_doctype.missing_system_identifier) {
+ if (public_identifier.starts_with("-//W3C//DTD HTML 4.01 Frameset//", CaseSensitivity::CaseInsensitive))
+ return DOM::QuirksMode::Limited;
+
+ if (public_identifier.starts_with("-//W3C//DTD HTML 4.01 Transitional//", CaseSensitivity::CaseInsensitive))
+ return DOM::QuirksMode::Limited;
+ }
+
+ return DOM::QuirksMode::No;
+}
+
+void HTMLDocumentParser::handle_initial(HTMLToken& token)
+{
+ if (token.is_character() && token.is_parser_whitespace()) {
+ return;
+ }
+
+ if (token.is_comment()) {
+ auto comment = adopt(*new DOM::Comment(document(), token.m_comment_or_character.data.to_string()));
+ document().append_child(move(comment));
+ return;
+ }
+
+ if (token.is_doctype()) {
+ auto doctype = adopt(*new DOM::DocumentType(document()));
+ doctype->set_name(token.m_doctype.name.to_string());
+ doctype->set_public_id(token.m_doctype.public_identifier.to_string());
+ doctype->set_system_id(token.m_doctype.system_identifier.to_string());
+ document().append_child(move(doctype));
+ document().set_quirks_mode(which_quirks_mode(token));
+ m_insertion_mode = InsertionMode::BeforeHTML;
+ return;
+ }
+
+ PARSE_ERROR();
+ document().set_quirks_mode(DOM::QuirksMode::Yes);
+ m_insertion_mode = InsertionMode::BeforeHTML;
+ process_using_the_rules_for(InsertionMode::BeforeHTML, token);
+}
+
+void HTMLDocumentParser::handle_before_html(HTMLToken& token)
+{
+ if (token.is_doctype()) {
+ PARSE_ERROR();
+ return;
+ }
+
+ if (token.is_comment()) {
+ auto comment = adopt(*new DOM::Comment(document(), token.m_comment_or_character.data.to_string()));
+ document().append_child(move(comment));
+ return;
+ }
+
+ if (token.is_character() && token.is_parser_whitespace()) {
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::html) {
+ auto element = create_element_for(token, Namespace::HTML);
+ document().append_child(element);
+ m_stack_of_open_elements.push(move(element));
+ m_insertion_mode = InsertionMode::BeforeHead;
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::head, HTML::TagNames::body, HTML::TagNames::html, HTML::TagNames::br)) {
+ goto AnythingElse;
+ }
+
+ if (token.is_end_tag()) {
+ PARSE_ERROR();
+ return;
+ }
+
+AnythingElse:
+ auto element = create_element(document(), HTML::TagNames::html, Namespace::HTML);
+ document().append_child(element);
+ m_stack_of_open_elements.push(element);
+ // FIXME: If the Document is being loaded as part of navigation of a browsing context, then: run the application cache selection algorithm with no manifest, passing it the Document object.
+ m_insertion_mode = InsertionMode::BeforeHead;
+ process_using_the_rules_for(InsertionMode::BeforeHead, token);
+ return;
+}
+
+DOM::Element& HTMLDocumentParser::current_node()
+{
+ return m_stack_of_open_elements.current_node();
+}
+
+DOM::Element& HTMLDocumentParser::adjusted_current_node()
+{
+ if (m_parsing_fragment && m_stack_of_open_elements.elements().size() == 1)
+ return *m_context_element;
+
+ return current_node();
+}
+
+DOM::Element& HTMLDocumentParser::node_before_current_node()
+{
+ return m_stack_of_open_elements.elements().at(m_stack_of_open_elements.elements().size() - 2);
+}
+
+HTMLDocumentParser::AdjustedInsertionLocation HTMLDocumentParser::find_appropriate_place_for_inserting_node()
+{
+ auto& target = current_node();
+ HTMLDocumentParser::AdjustedInsertionLocation adjusted_insertion_location;
+
+ if (m_foster_parenting && target.local_name().is_one_of(HTML::TagNames::table, HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead, HTML::TagNames::tr)) {
+ auto last_template = m_stack_of_open_elements.last_element_with_tag_name(HTML::TagNames::template_);
+ auto last_table = m_stack_of_open_elements.last_element_with_tag_name(HTML::TagNames::table);
+ if (last_template.element && (!last_table.element || last_template.index > last_table.index)) {
+ // This returns the template content, so no need to check the parent is a template.
+ return { downcast<HTMLTemplateElement>(last_template.element)->content(), nullptr };
+ }
+ if (!last_table.element) {
+ ASSERT(m_parsing_fragment);
+ // Guaranteed not to be a template element (it will be the html element),
+ // so no need to check the parent is a template.
+ return { m_stack_of_open_elements.elements().first(), nullptr };
+ }
+ if (last_table.element->parent_node())
+ adjusted_insertion_location = { last_table.element->parent_node(), last_table.element };
+ else
+ adjusted_insertion_location = { m_stack_of_open_elements.element_before(*last_table.element), nullptr };
+ } else {
+ adjusted_insertion_location = { target, nullptr };
+ }
+
+ if (is<HTMLTemplateElement>(*adjusted_insertion_location.parent))
+ return { downcast<HTMLTemplateElement>(*adjusted_insertion_location.parent).content(), nullptr };
+
+ return adjusted_insertion_location;
+}
+
+NonnullRefPtr<DOM::Element> HTMLDocumentParser::create_element_for(const HTMLToken& token, const FlyString& namespace_)
+{
+ auto element = create_element(document(), token.tag_name(), namespace_);
+ for (auto& attribute : token.m_tag.attributes) {
+ element->set_attribute(attribute.local_name_builder.to_string(), attribute.value_builder.to_string());
+ }
+ return element;
+}
+
+RefPtr<DOM::Element> HTMLDocumentParser::insert_foreign_element(const HTMLToken& token, const FlyString& namespace_)
+{
+ auto adjusted_insertion_location = find_appropriate_place_for_inserting_node();
+ auto element = create_element_for(token, namespace_);
+ // FIXME: Check if it's possible to insert `element` at `adjusted_insertion_location`
+ adjusted_insertion_location.parent->insert_before(element, adjusted_insertion_location.insert_before_sibling);
+ m_stack_of_open_elements.push(element);
+ return element;
+}
+
+RefPtr<DOM::Element> HTMLDocumentParser::insert_html_element(const HTMLToken& token)
+{
+ return insert_foreign_element(token, Namespace::HTML);
+}
+
+void HTMLDocumentParser::handle_before_head(HTMLToken& token)
+{
+ if (token.is_character() && token.is_parser_whitespace()) {
+ return;
+ }
+
+ if (token.is_comment()) {
+ insert_comment(token);
+ return;
+ }
+
+ if (token.is_doctype()) {
+ PARSE_ERROR();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::html) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::head) {
+ auto element = insert_html_element(token);
+ m_head_element = downcast<HTMLHeadElement>(*element);
+ m_insertion_mode = InsertionMode::InHead;
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::head, HTML::TagNames::body, HTML::TagNames::html, HTML::TagNames::br)) {
+ goto AnythingElse;
+ }
+
+ if (token.is_end_tag()) {
+ PARSE_ERROR();
+ return;
+ }
+
+AnythingElse:
+ m_head_element = downcast<HTMLHeadElement>(*insert_html_element(HTMLToken::make_start_tag(HTML::TagNames::head)));
+ m_insertion_mode = InsertionMode::InHead;
+ process_using_the_rules_for(InsertionMode::InHead, token);
+ return;
+}
+
+void HTMLDocumentParser::insert_comment(HTMLToken& token)
+{
+ auto data = token.m_comment_or_character.data.to_string();
+ auto adjusted_insertion_location = find_appropriate_place_for_inserting_node();
+ adjusted_insertion_location.parent->insert_before(adopt(*new DOM::Comment(document(), data)), adjusted_insertion_location.insert_before_sibling);
+}
+
+void HTMLDocumentParser::handle_in_head(HTMLToken& token)
+{
+ if (token.is_parser_whitespace()) {
+ insert_character(token.code_point());
+ return;
+ }
+
+ if (token.is_comment()) {
+ insert_comment(token);
+ return;
+ }
+
+ if (token.is_doctype()) {
+ PARSE_ERROR();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::html) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::base, HTML::TagNames::basefont, HTML::TagNames::bgsound, HTML::TagNames::link)) {
+ insert_html_element(token);
+ m_stack_of_open_elements.pop();
+ token.acknowledge_self_closing_flag_if_set();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::meta) {
+ auto element = insert_html_element(token);
+ m_stack_of_open_elements.pop();
+ token.acknowledge_self_closing_flag_if_set();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::title) {
+ insert_html_element(token);
+ m_tokenizer.switch_to({}, HTMLTokenizer::State::RCDATA);
+ m_original_insertion_mode = m_insertion_mode;
+ m_insertion_mode = InsertionMode::Text;
+ return;
+ }
+
+ if (token.is_start_tag() && ((token.tag_name() == HTML::TagNames::noscript && m_scripting_enabled) || token.tag_name() == HTML::TagNames::noframes || token.tag_name() == HTML::TagNames::style)) {
+ parse_generic_raw_text_element(token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::noscript && !m_scripting_enabled) {
+ insert_html_element(token);
+ m_insertion_mode = InsertionMode::InHeadNoscript;
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::script) {
+ auto adjusted_insertion_location = find_appropriate_place_for_inserting_node();
+ auto element = create_element_for(token, Namespace::HTML);
+ auto& script_element = downcast<HTMLScriptElement>(*element);
+ script_element.set_parser_document({}, document());
+ script_element.set_non_blocking({}, false);
+
+ if (m_parsing_fragment) {
+ TODO();
+ }
+
+ if (m_invoked_via_document_write) {
+ TODO();
+ }
+
+ adjusted_insertion_location.parent->insert_before(element, adjusted_insertion_location.insert_before_sibling, false);
+ m_stack_of_open_elements.push(element);
+ m_tokenizer.switch_to({}, HTMLTokenizer::State::ScriptData);
+ m_original_insertion_mode = m_insertion_mode;
+ m_insertion_mode = InsertionMode::Text;
+ return;
+ }
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::head) {
+ m_stack_of_open_elements.pop();
+ m_insertion_mode = InsertionMode::AfterHead;
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::body, HTML::TagNames::html, HTML::TagNames::br)) {
+ goto AnythingElse;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::template_) {
+ insert_html_element(token);
+ m_list_of_active_formatting_elements.add_marker();
+ m_frameset_ok = false;
+ m_insertion_mode = InsertionMode::InTemplate;
+ m_stack_of_template_insertion_modes.append(InsertionMode::InTemplate);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::template_) {
+ if (!m_stack_of_open_elements.contains(HTML::TagNames::template_)) {
+ PARSE_ERROR();
+ return;
+ }
+
+ generate_all_implied_end_tags_thoroughly();
+
+ if (current_node().local_name() != HTML::TagNames::template_)
+ PARSE_ERROR();
+
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::template_);
+ m_list_of_active_formatting_elements.clear_up_to_the_last_marker();
+ m_stack_of_template_insertion_modes.take_last();
+ reset_the_insertion_mode_appropriately();
+ return;
+ }
+
+ if ((token.is_start_tag() && token.tag_name() == HTML::TagNames::head) || token.is_end_tag()) {
+ PARSE_ERROR();
+ return;
+ }
+
+AnythingElse:
+ m_stack_of_open_elements.pop();
+ m_insertion_mode = InsertionMode::AfterHead;
+ process_using_the_rules_for(m_insertion_mode, token);
+}
+
+void HTMLDocumentParser::handle_in_head_noscript(HTMLToken& token)
+{
+ if (token.is_doctype()) {
+ PARSE_ERROR();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::html) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::noscript) {
+ m_stack_of_open_elements.pop();
+ m_insertion_mode = InsertionMode::InHead;
+ return;
+ }
+
+ if (token.is_parser_whitespace() || token.is_comment() || (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::basefont, HTML::TagNames::bgsound, HTML::TagNames::link, HTML::TagNames::meta, HTML::TagNames::noframes, HTML::TagNames::style))) {
+ process_using_the_rules_for(InsertionMode::InHead, token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::br) {
+ goto AnythingElse;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::head, HTML::TagNames::noscript)) {
+ PARSE_ERROR();
+ return;
+ }
+
+AnythingElse:
+ PARSE_ERROR();
+ m_stack_of_open_elements.pop();
+ m_insertion_mode = InsertionMode::InHead;
+ process_using_the_rules_for(m_insertion_mode, token);
+}
+
+void HTMLDocumentParser::parse_generic_raw_text_element(HTMLToken& token)
+{
+ insert_html_element(token);
+ m_tokenizer.switch_to({}, HTMLTokenizer::State::RAWTEXT);
+ m_original_insertion_mode = m_insertion_mode;
+ m_insertion_mode = InsertionMode::Text;
+}
+
+DOM::Text* HTMLDocumentParser::find_character_insertion_node()
+{
+ auto adjusted_insertion_location = find_appropriate_place_for_inserting_node();
+ if (adjusted_insertion_location.insert_before_sibling) {
+ TODO();
+ }
+ if (adjusted_insertion_location.parent->is_document())
+ return nullptr;
+ if (adjusted_insertion_location.parent->last_child() && adjusted_insertion_location.parent->last_child()->is_text())
+ return downcast<DOM::Text>(adjusted_insertion_location.parent->last_child());
+ auto new_text_node = adopt(*new DOM::Text(document(), ""));
+ adjusted_insertion_location.parent->append_child(new_text_node);
+ return new_text_node;
+}
+
+void HTMLDocumentParser::flush_character_insertions()
+{
+ if (m_character_insertion_builder.is_empty())
+ return;
+ m_character_insertion_node->set_data(m_character_insertion_builder.to_string());
+ m_character_insertion_node->parent()->children_changed();
+ m_character_insertion_builder.clear();
+}
+
+void HTMLDocumentParser::insert_character(u32 data)
+{
+ auto node = find_character_insertion_node();
+ if (node == m_character_insertion_node) {
+ m_character_insertion_builder.append(Utf32View { &data, 1 });
+ return;
+ }
+ if (!m_character_insertion_node) {
+ m_character_insertion_node = node;
+ m_character_insertion_builder.append(Utf32View { &data, 1 });
+ return;
+ }
+ flush_character_insertions();
+ m_character_insertion_node = node;
+ m_character_insertion_builder.append(Utf32View { &data, 1 });
+}
+
+void HTMLDocumentParser::handle_after_head(HTMLToken& token)
+{
+ if (token.is_character() && token.is_parser_whitespace()) {
+ insert_character(token.code_point());
+ return;
+ }
+
+ if (token.is_comment()) {
+ insert_comment(token);
+ return;
+ }
+
+ if (token.is_doctype()) {
+ PARSE_ERROR();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::html) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::body) {
+ insert_html_element(token);
+ m_frameset_ok = false;
+ m_insertion_mode = InsertionMode::InBody;
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::frameset) {
+ insert_html_element(token);
+ m_insertion_mode = InsertionMode::InFrameset;
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::base, HTML::TagNames::basefont, HTML::TagNames::bgsound, HTML::TagNames::link, HTML::TagNames::meta, HTML::TagNames::noframes, HTML::TagNames::script, HTML::TagNames::style, HTML::TagNames::template_, HTML::TagNames::title)) {
+ PARSE_ERROR();
+ m_stack_of_open_elements.push(*m_head_element);
+ process_using_the_rules_for(InsertionMode::InHead, token);
+ m_stack_of_open_elements.elements().remove_first_matching([&](auto& entry) {
+ return entry.ptr() == m_head_element;
+ });
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::template_) {
+ process_using_the_rules_for(InsertionMode::InHead, token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::body, HTML::TagNames::html, HTML::TagNames::br)) {
+ goto AnythingElse;
+ }
+
+ if ((token.is_start_tag() && token.tag_name() == HTML::TagNames::head) || token.is_end_tag()) {
+ PARSE_ERROR();
+ return;
+ }
+
+AnythingElse:
+ insert_html_element(HTMLToken::make_start_tag(HTML::TagNames::body));
+ m_insertion_mode = InsertionMode::InBody;
+ process_using_the_rules_for(m_insertion_mode, token);
+}
+
+void HTMLDocumentParser::generate_implied_end_tags(const FlyString& exception)
+{
+ while (current_node().local_name() != exception && current_node().local_name().is_one_of(HTML::TagNames::dd, HTML::TagNames::dt, HTML::TagNames::li, HTML::TagNames::optgroup, HTML::TagNames::option, HTML::TagNames::p, HTML::TagNames::rb, HTML::TagNames::rp, HTML::TagNames::rt, HTML::TagNames::rtc))
+ m_stack_of_open_elements.pop();
+}
+
+void HTMLDocumentParser::generate_all_implied_end_tags_thoroughly()
+{
+ while (current_node().local_name().is_one_of(HTML::TagNames::caption, HTML::TagNames::colgroup, HTML::TagNames::dd, HTML::TagNames::dt, HTML::TagNames::li, HTML::TagNames::optgroup, HTML::TagNames::option, HTML::TagNames::p, HTML::TagNames::rb, HTML::TagNames::rp, HTML::TagNames::rt, HTML::TagNames::rtc, HTML::TagNames::tbody, HTML::TagNames::td, HTML::TagNames::tfoot, HTML::TagNames::th, HTML::TagNames::thead, HTML::TagNames::tr))
+ m_stack_of_open_elements.pop();
+}
+
+void HTMLDocumentParser::close_a_p_element()
+{
+ generate_implied_end_tags(HTML::TagNames::p);
+ if (current_node().local_name() != HTML::TagNames::p) {
+ PARSE_ERROR();
+ }
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::p);
+}
+
+void HTMLDocumentParser::handle_after_body(HTMLToken& token)
+{
+ if (token.is_character() && token.is_parser_whitespace()) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+ if (token.is_comment()) {
+ auto data = token.m_comment_or_character.data.to_string();
+ auto& insertion_location = m_stack_of_open_elements.first();
+ insertion_location.append_child(adopt(*new DOM::Comment(document(), data)));
+ return;
+ }
+
+ if (token.is_doctype()) {
+ PARSE_ERROR();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::html) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::html) {
+ if (m_parsing_fragment) {
+ PARSE_ERROR();
+ return;
+ }
+ m_insertion_mode = InsertionMode::AfterAfterBody;
+ return;
+ }
+
+ if (token.is_end_of_file()) {
+ stop_parsing();
+ return;
+ }
+
+ PARSE_ERROR();
+ m_insertion_mode = InsertionMode::InBody;
+ process_using_the_rules_for(InsertionMode::InBody, token);
+}
+
+void HTMLDocumentParser::handle_after_after_body(HTMLToken& token)
+{
+ if (token.is_comment()) {
+ auto comment = adopt(*new DOM::Comment(document(), token.m_comment_or_character.data.to_string()));
+ document().append_child(move(comment));
+ return;
+ }
+
+ if (token.is_doctype() || token.is_parser_whitespace() || (token.is_start_tag() && token.tag_name() == HTML::TagNames::html)) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+ if (token.is_end_of_file()) {
+ stop_parsing();
+ return;
+ }
+
+ PARSE_ERROR();
+ m_insertion_mode = InsertionMode::InBody;
+ process_using_the_rules_for(m_insertion_mode, token);
+}
+
+void HTMLDocumentParser::reconstruct_the_active_formatting_elements()
+{
+ // FIXME: This needs to care about "markers"
+
+ if (m_list_of_active_formatting_elements.is_empty())
+ return;
+
+ if (m_list_of_active_formatting_elements.entries().last().is_marker())
+ return;
+
+ if (m_stack_of_open_elements.contains(*m_list_of_active_formatting_elements.entries().last().element))
+ return;
+
+ ssize_t index = m_list_of_active_formatting_elements.entries().size() - 1;
+ RefPtr<DOM::Element> entry = m_list_of_active_formatting_elements.entries().at(index).element;
+ ASSERT(entry);
+
+Rewind:
+ if (index == 0) {
+ goto Create;
+ }
+
+ --index;
+ entry = m_list_of_active_formatting_elements.entries().at(index).element;
+ ASSERT(entry);
+
+ if (!m_stack_of_open_elements.contains(*entry))
+ goto Rewind;
+
+Advance:
+ ++index;
+ entry = m_list_of_active_formatting_elements.entries().at(index).element;
+ ASSERT(entry);
+
+Create:
+ // FIXME: Hold on to the real token!
+ auto new_element = insert_html_element(HTMLToken::make_start_tag(entry->local_name()));
+
+ m_list_of_active_formatting_elements.entries().at(index).element = *new_element;
+
+ if (index != (ssize_t)m_list_of_active_formatting_elements.entries().size() - 1)
+ goto Advance;
+}
+
+HTMLDocumentParser::AdoptionAgencyAlgorithmOutcome HTMLDocumentParser::run_the_adoption_agency_algorithm(HTMLToken& token)
+{
+ auto subject = token.tag_name();
+
+ // If the current node is an HTML element whose tag name is subject,
+ // and the current node is not in the list of active formatting elements,
+ // then pop the current node off the stack of open elements, and return.
+ if (current_node().local_name() == subject && !m_list_of_active_formatting_elements.contains(current_node())) {
+ m_stack_of_open_elements.pop();
+ return AdoptionAgencyAlgorithmOutcome::DoNothing;
+ }
+
+ size_t outer_loop_counter = 0;
+
+ //OuterLoop:
+ if (outer_loop_counter >= 8)
+ return AdoptionAgencyAlgorithmOutcome::DoNothing;
+
+ ++outer_loop_counter;
+
+ auto formatting_element = m_list_of_active_formatting_elements.last_element_with_tag_name_before_marker(subject);
+ if (!formatting_element)
+ return AdoptionAgencyAlgorithmOutcome::RunAnyOtherEndTagSteps;
+
+ if (!m_stack_of_open_elements.contains(*formatting_element)) {
+ PARSE_ERROR();
+ // FIXME: If formatting element is not in the stack of open elements,
+ // then this is a parse error; remove the element from the list, and return.
+ TODO();
+ }
+
+ if (!m_stack_of_open_elements.has_in_scope(*formatting_element)) {
+ PARSE_ERROR();
+ return AdoptionAgencyAlgorithmOutcome::DoNothing;
+ }
+
+ if (formatting_element != &current_node()) {
+ PARSE_ERROR();
+ }
+
+ RefPtr<DOM::Element> furthest_block = m_stack_of_open_elements.topmost_special_node_below(*formatting_element);
+
+ if (!furthest_block) {
+ while (&current_node() != formatting_element)
+ m_stack_of_open_elements.pop();
+ m_stack_of_open_elements.pop();
+
+ m_list_of_active_formatting_elements.remove(*formatting_element);
+ return AdoptionAgencyAlgorithmOutcome::DoNothing;
+ }
+
+ // FIXME: Implement the rest of the AAA :^)
+
+ TODO();
+}
+
+bool HTMLDocumentParser::is_special_tag(const FlyString& tag_name, const FlyString& namespace_)
+{
+ if (namespace_ == Namespace::HTML) {
+ return tag_name.is_one_of(
+ HTML::TagNames::address,
+ HTML::TagNames::applet,
+ HTML::TagNames::area,
+ HTML::TagNames::article,
+ HTML::TagNames::aside,
+ HTML::TagNames::base,
+ HTML::TagNames::basefont,
+ HTML::TagNames::bgsound,
+ HTML::TagNames::blockquote,
+ HTML::TagNames::body,
+ HTML::TagNames::br,
+ HTML::TagNames::button,
+ HTML::TagNames::caption,
+ HTML::TagNames::center,
+ HTML::TagNames::col,
+ HTML::TagNames::colgroup,
+ HTML::TagNames::dd,
+ HTML::TagNames::details,
+ HTML::TagNames::dir,
+ HTML::TagNames::div,
+ HTML::TagNames::dl,
+ HTML::TagNames::dt,
+ HTML::TagNames::embed,
+ HTML::TagNames::fieldset,
+ HTML::TagNames::figcaption,
+ HTML::TagNames::figure,
+ HTML::TagNames::footer,
+ HTML::TagNames::form,
+ HTML::TagNames::frame,
+ HTML::TagNames::frameset,
+ HTML::TagNames::h1,
+ HTML::TagNames::h2,
+ HTML::TagNames::h3,
+ HTML::TagNames::h4,
+ HTML::TagNames::h5,
+ HTML::TagNames::h6,
+ HTML::TagNames::head,
+ HTML::TagNames::header,
+ HTML::TagNames::hgroup,
+ HTML::TagNames::hr,
+ HTML::TagNames::html,
+ HTML::TagNames::iframe,
+ HTML::TagNames::img,
+ HTML::TagNames::input,
+ HTML::TagNames::keygen,
+ HTML::TagNames::li,
+ HTML::TagNames::link,
+ HTML::TagNames::listing,
+ HTML::TagNames::main,
+ HTML::TagNames::marquee,
+ HTML::TagNames::menu,
+ HTML::TagNames::meta,
+ HTML::TagNames::nav,
+ HTML::TagNames::noembed,
+ HTML::TagNames::noframes,
+ HTML::TagNames::noscript,
+ HTML::TagNames::object,
+ HTML::TagNames::ol,
+ HTML::TagNames::p,
+ HTML::TagNames::param,
+ HTML::TagNames::plaintext,
+ HTML::TagNames::pre,
+ HTML::TagNames::script,
+ HTML::TagNames::section,
+ HTML::TagNames::select,
+ HTML::TagNames::source,
+ HTML::TagNames::style,
+ HTML::TagNames::summary,
+ HTML::TagNames::table,
+ HTML::TagNames::tbody,
+ HTML::TagNames::td,
+ HTML::TagNames::template_,
+ HTML::TagNames::textarea,
+ HTML::TagNames::tfoot,
+ HTML::TagNames::th,
+ HTML::TagNames::thead,
+ HTML::TagNames::title,
+ HTML::TagNames::tr,
+ HTML::TagNames::track,
+ HTML::TagNames::ul,
+ HTML::TagNames::wbr,
+ HTML::TagNames::xmp);
+ } else if (namespace_ == Namespace::SVG) {
+ return tag_name.is_one_of(
+ SVG::TagNames::desc,
+ SVG::TagNames::foreignObject,
+ SVG::TagNames::title);
+ } else if (namespace_ == Namespace::MathML) {
+ TODO();
+ }
+
+ return false;
+}
+
+void HTMLDocumentParser::handle_in_body(HTMLToken& token)
+{
+ if (token.is_character()) {
+ if (token.code_point() == 0) {
+ PARSE_ERROR();
+ return;
+ }
+ if (token.is_parser_whitespace()) {
+ reconstruct_the_active_formatting_elements();
+ insert_character(token.code_point());
+ return;
+ }
+ reconstruct_the_active_formatting_elements();
+ insert_character(token.code_point());
+ m_frameset_ok = false;
+ return;
+ }
+
+ if (token.is_comment()) {
+ insert_comment(token);
+ return;
+ }
+
+ if (token.is_doctype()) {
+ PARSE_ERROR();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::html) {
+ PARSE_ERROR();
+ if (m_stack_of_open_elements.contains(HTML::TagNames::template_))
+ return;
+ for (auto& attribute : token.m_tag.attributes) {
+ if (current_node().has_attribute(attribute.local_name_builder.string_view()))
+ continue;
+ current_node().set_attribute(attribute.local_name_builder.to_string(), attribute.value_builder.to_string());
+ }
+ return;
+ }
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::base, HTML::TagNames::basefont, HTML::TagNames::bgsound, HTML::TagNames::link, HTML::TagNames::meta, HTML::TagNames::noframes, HTML::TagNames::script, HTML::TagNames::style, HTML::TagNames::template_, HTML::TagNames::title)) {
+ process_using_the_rules_for(InsertionMode::InHead, token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::template_) {
+ process_using_the_rules_for(InsertionMode::InHead, token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::body) {
+ PARSE_ERROR();
+ if (m_stack_of_open_elements.elements().size() == 1
+ || m_stack_of_open_elements.elements().at(1).local_name() != HTML::TagNames::body
+ || m_stack_of_open_elements.contains(HTML::TagNames::template_)) {
+ ASSERT(m_parsing_fragment);
+ return;
+ }
+ m_frameset_ok = false;
+ auto& body_element = m_stack_of_open_elements.elements().at(1);
+ for (auto& attribute : token.m_tag.attributes) {
+ if (body_element.has_attribute(attribute.local_name_builder.string_view()))
+ continue;
+ body_element.set_attribute(attribute.local_name_builder.to_string(), attribute.value_builder.to_string());
+ }
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::frameset) {
+ PARSE_ERROR();
+
+ if (m_stack_of_open_elements.elements().size() == 1
+ || m_stack_of_open_elements.elements().at(1).local_name() != HTML::TagNames::body) {
+ ASSERT(m_parsing_fragment);
+ return;
+ }
+
+ if (!m_frameset_ok)
+ return;
+
+ TODO();
+ }
+
+ if (token.is_end_of_file()) {
+ if (!m_stack_of_template_insertion_modes.is_empty()) {
+ process_using_the_rules_for(InsertionMode::InTemplate, token);
+ return;
+ }
+
+ for (auto& node : m_stack_of_open_elements.elements()) {
+ if (!node.local_name().is_one_of(HTML::TagNames::dd, HTML::TagNames::dt, HTML::TagNames::li, HTML::TagNames::optgroup, HTML::TagNames::option, HTML::TagNames::p, HTML::TagNames::rb, HTML::TagNames::rp, HTML::TagNames::rt, HTML::TagNames::rtc, HTML::TagNames::tbody, HTML::TagNames::td, HTML::TagNames::tfoot, HTML::TagNames::th, HTML::TagNames::thead, HTML::TagNames::tr, HTML::TagNames::body, HTML::TagNames::html)) {
+ PARSE_ERROR();
+ break;
+ }
+ }
+
+ stop_parsing();
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::body) {
+ if (!m_stack_of_open_elements.has_in_scope(HTML::TagNames::body)) {
+ PARSE_ERROR();
+ return;
+ }
+
+ for (auto& node : m_stack_of_open_elements.elements()) {
+ if (!node.local_name().is_one_of(HTML::TagNames::dd, HTML::TagNames::dt, HTML::TagNames::li, HTML::TagNames::optgroup, HTML::TagNames::option, HTML::TagNames::p, HTML::TagNames::rb, HTML::TagNames::rp, HTML::TagNames::rt, HTML::TagNames::rtc, HTML::TagNames::tbody, HTML::TagNames::td, HTML::TagNames::tfoot, HTML::TagNames::th, HTML::TagNames::thead, HTML::TagNames::tr, HTML::TagNames::body, HTML::TagNames::html)) {
+ PARSE_ERROR();
+ break;
+ }
+ }
+
+ m_insertion_mode = InsertionMode::AfterBody;
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::html) {
+ if (!m_stack_of_open_elements.has_in_scope(HTML::TagNames::body)) {
+ PARSE_ERROR();
+ return;
+ }
+
+ for (auto& node : m_stack_of_open_elements.elements()) {
+ if (!node.local_name().is_one_of(HTML::TagNames::dd, HTML::TagNames::dt, HTML::TagNames::li, HTML::TagNames::optgroup, HTML::TagNames::option, HTML::TagNames::p, HTML::TagNames::rb, HTML::TagNames::rp, HTML::TagNames::rt, HTML::TagNames::rtc, HTML::TagNames::tbody, HTML::TagNames::td, HTML::TagNames::tfoot, HTML::TagNames::th, HTML::TagNames::thead, HTML::TagNames::tr, HTML::TagNames::body, HTML::TagNames::html)) {
+ PARSE_ERROR();
+ break;
+ }
+ }
+
+ m_insertion_mode = InsertionMode::AfterBody;
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::address, HTML::TagNames::article, HTML::TagNames::aside, HTML::TagNames::blockquote, HTML::TagNames::center, HTML::TagNames::details, HTML::TagNames::dialog, HTML::TagNames::dir, HTML::TagNames::div, HTML::TagNames::dl, HTML::TagNames::fieldset, HTML::TagNames::figcaption, HTML::TagNames::figure, HTML::TagNames::footer, HTML::TagNames::header, HTML::TagNames::hgroup, HTML::TagNames::main, HTML::TagNames::menu, HTML::TagNames::nav, HTML::TagNames::ol, HTML::TagNames::p, HTML::TagNames::section, HTML::TagNames::summary, HTML::TagNames::ul)) {
+ if (m_stack_of_open_elements.has_in_button_scope(HTML::TagNames::p))
+ close_a_p_element();
+ insert_html_element(token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::h1, HTML::TagNames::h2, HTML::TagNames::h3, HTML::TagNames::h4, HTML::TagNames::h5, HTML::TagNames::h6)) {
+ if (m_stack_of_open_elements.has_in_button_scope(HTML::TagNames::p))
+ close_a_p_element();
+ if (current_node().local_name().is_one_of(HTML::TagNames::h1, HTML::TagNames::h2, HTML::TagNames::h3, HTML::TagNames::h4, HTML::TagNames::h5, HTML::TagNames::h6)) {
+ PARSE_ERROR();
+ m_stack_of_open_elements.pop();
+ }
+ insert_html_element(token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::pre, HTML::TagNames::listing)) {
+ if (m_stack_of_open_elements.has_in_button_scope(HTML::TagNames::p))
+ close_a_p_element();
+
+ insert_html_element(token);
+
+ m_frameset_ok = false;
+
+ // If the next token is a U+000A LINE FEED (LF) character token,
+ // then ignore that token and move on to the next one.
+ // (Newlines at the start of pre blocks are ignored as an authoring convenience.)
+ auto next_token = m_tokenizer.next_token();
+ if (next_token.has_value() && next_token.value().is_character() && next_token.value().code_point() == '\n') {
+ // Ignore it.
+ } else {
+ process_using_the_rules_for(m_insertion_mode, next_token.value());
+ }
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::form) {
+ if (m_form_element && !m_stack_of_open_elements.contains(HTML::TagNames::template_)) {
+ PARSE_ERROR();
+ return;
+ }
+ if (m_stack_of_open_elements.has_in_button_scope(HTML::TagNames::p))
+ close_a_p_element();
+ auto element = insert_html_element(token);
+ if (!m_stack_of_open_elements.contains(HTML::TagNames::template_))
+ m_form_element = downcast<HTMLFormElement>(*element);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::li) {
+ m_frameset_ok = false;
+
+ for (ssize_t i = m_stack_of_open_elements.elements().size() - 1; i >= 0; --i) {
+ RefPtr<DOM::Element> node = m_stack_of_open_elements.elements()[i];
+
+ if (node->local_name() == HTML::TagNames::li) {
+ generate_implied_end_tags(HTML::TagNames::li);
+ if (current_node().local_name() != HTML::TagNames::li) {
+ PARSE_ERROR();
+ }
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::li);
+ break;
+ }
+
+ if (is_special_tag(node->local_name(), node->namespace_()) && !node->local_name().is_one_of(HTML::TagNames::address, HTML::TagNames::div, HTML::TagNames::p))
+ break;
+ }
+
+ if (m_stack_of_open_elements.has_in_button_scope(HTML::TagNames::p))
+ close_a_p_element();
+
+ insert_html_element(token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::dd, HTML::TagNames::dt)) {
+ m_frameset_ok = false;
+ for (ssize_t i = m_stack_of_open_elements.elements().size() - 1; i >= 0; --i) {
+ RefPtr<DOM::Element> node = m_stack_of_open_elements.elements()[i];
+ if (node->local_name() == HTML::TagNames::dd) {
+ generate_implied_end_tags(HTML::TagNames::dd);
+ if (current_node().local_name() != HTML::TagNames::dd) {
+ PARSE_ERROR();
+ }
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::dd);
+ break;
+ }
+ if (node->local_name() == HTML::TagNames::dt) {
+ generate_implied_end_tags(HTML::TagNames::dt);
+ if (current_node().local_name() != HTML::TagNames::dt) {
+ PARSE_ERROR();
+ }
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::dt);
+ break;
+ }
+ if (is_special_tag(node->local_name(), node->namespace_()) && !node->local_name().is_one_of(HTML::TagNames::address, HTML::TagNames::div, HTML::TagNames::p))
+ break;
+ }
+ if (m_stack_of_open_elements.has_in_button_scope(HTML::TagNames::p))
+ close_a_p_element();
+ insert_html_element(token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::plaintext) {
+ if (m_stack_of_open_elements.has_in_button_scope(HTML::TagNames::p))
+ close_a_p_element();
+ insert_html_element(token);
+ m_tokenizer.switch_to({}, HTMLTokenizer::State::PLAINTEXT);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::button) {
+ if (m_stack_of_open_elements.has_in_button_scope(HTML::TagNames::button)) {
+ PARSE_ERROR();
+ generate_implied_end_tags();
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::button);
+ }
+ reconstruct_the_active_formatting_elements();
+ insert_html_element(token);
+ m_frameset_ok = false;
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::address, HTML::TagNames::article, HTML::TagNames::aside, HTML::TagNames::blockquote, HTML::TagNames::button, HTML::TagNames::center, HTML::TagNames::details, HTML::TagNames::dialog, HTML::TagNames::dir, HTML::TagNames::div, HTML::TagNames::dl, HTML::TagNames::fieldset, HTML::TagNames::figcaption, HTML::TagNames::figure, HTML::TagNames::footer, HTML::TagNames::header, HTML::TagNames::hgroup, HTML::TagNames::listing, HTML::TagNames::main, HTML::TagNames::menu, HTML::TagNames::nav, HTML::TagNames::ol, HTML::TagNames::pre, HTML::TagNames::section, HTML::TagNames::summary, HTML::TagNames::ul)) {
+ if (!m_stack_of_open_elements.has_in_scope(token.tag_name())) {
+ PARSE_ERROR();
+ return;
+ }
+
+ generate_implied_end_tags();
+
+ if (current_node().local_name() != token.tag_name()) {
+ PARSE_ERROR();
+ }
+
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(token.tag_name());
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::form) {
+ if (!m_stack_of_open_elements.contains(HTML::TagNames::template_)) {
+ auto node = m_form_element;
+ m_form_element = nullptr;
+ if (!node || !m_stack_of_open_elements.has_in_scope(*node)) {
+ PARSE_ERROR();
+ return;
+ }
+ generate_implied_end_tags();
+ if (&current_node() != node) {
+ PARSE_ERROR();
+ }
+ m_stack_of_open_elements.elements().remove_first_matching([&](auto& entry) { return entry.ptr() == node.ptr(); });
+ } else {
+ if (!m_stack_of_open_elements.has_in_scope(HTML::TagNames::form)) {
+ PARSE_ERROR();
+ return;
+ }
+ generate_implied_end_tags();
+ if (current_node().local_name() != HTML::TagNames::form) {
+ PARSE_ERROR();
+ }
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::form);
+ }
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::p) {
+ if (!m_stack_of_open_elements.has_in_button_scope(HTML::TagNames::p)) {
+ PARSE_ERROR();
+ insert_html_element(HTMLToken::make_start_tag(HTML::TagNames::p));
+ }
+ close_a_p_element();
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::li) {
+ if (!m_stack_of_open_elements.has_in_list_item_scope(HTML::TagNames::li)) {
+ PARSE_ERROR();
+ return;
+ }
+ generate_implied_end_tags(HTML::TagNames::li);
+ if (current_node().local_name() != HTML::TagNames::li) {
+ PARSE_ERROR();
+ dbg() << "Expected <li> current node, but had <" << current_node().local_name() << ">";
+ }
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::li);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::dd, HTML::TagNames::dt)) {
+ if (!m_stack_of_open_elements.has_in_scope(token.tag_name())) {
+ PARSE_ERROR();
+ return;
+ }
+ generate_implied_end_tags(token.tag_name());
+ if (current_node().local_name() != token.tag_name()) {
+ PARSE_ERROR();
+ }
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(token.tag_name());
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::h1, HTML::TagNames::h2, HTML::TagNames::h3, HTML::TagNames::h4, HTML::TagNames::h5, HTML::TagNames::h6)) {
+ if (!m_stack_of_open_elements.has_in_scope(HTML::TagNames::h1)
+ && !m_stack_of_open_elements.has_in_scope(HTML::TagNames::h2)
+ && !m_stack_of_open_elements.has_in_scope(HTML::TagNames::h3)
+ && !m_stack_of_open_elements.has_in_scope(HTML::TagNames::h4)
+ && !m_stack_of_open_elements.has_in_scope(HTML::TagNames::h5)
+ && !m_stack_of_open_elements.has_in_scope(HTML::TagNames::h6)) {
+ PARSE_ERROR();
+ return;
+ }
+
+ generate_implied_end_tags();
+ if (current_node().local_name() != token.tag_name()) {
+ PARSE_ERROR();
+ }
+
+ for (;;) {
+ auto popped_element = m_stack_of_open_elements.pop();
+ if (popped_element->local_name().is_one_of(HTML::TagNames::h1, HTML::TagNames::h2, HTML::TagNames::h3, HTML::TagNames::h4, HTML::TagNames::h5, HTML::TagNames::h6))
+ break;
+ }
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::a) {
+ if (auto* element = m_list_of_active_formatting_elements.last_element_with_tag_name_before_marker(HTML::TagNames::a)) {
+ PARSE_ERROR();
+ if (run_the_adoption_agency_algorithm(token) == AdoptionAgencyAlgorithmOutcome::RunAnyOtherEndTagSteps)
+ goto AnyOtherEndTag;
+ m_list_of_active_formatting_elements.remove(*element);
+ m_stack_of_open_elements.elements().remove_first_matching([&](auto& entry) {
+ return entry.ptr() == element;
+ });
+ }
+ reconstruct_the_active_formatting_elements();
+ auto element = insert_html_element(token);
+ m_list_of_active_formatting_elements.add(*element);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::b, HTML::TagNames::big, HTML::TagNames::code, HTML::TagNames::em, HTML::TagNames::font, HTML::TagNames::i, HTML::TagNames::s, HTML::TagNames::small, HTML::TagNames::strike, HTML::TagNames::strong, HTML::TagNames::tt, HTML::TagNames::u)) {
+ reconstruct_the_active_formatting_elements();
+ auto element = insert_html_element(token);
+ m_list_of_active_formatting_elements.add(*element);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::nobr) {
+ reconstruct_the_active_formatting_elements();
+ if (m_stack_of_open_elements.has_in_scope(HTML::TagNames::nobr)) {
+ PARSE_ERROR();
+ run_the_adoption_agency_algorithm(token);
+ reconstruct_the_active_formatting_elements();
+ }
+ auto element = insert_html_element(token);
+ m_list_of_active_formatting_elements.add(*element);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::a, HTML::TagNames::b, HTML::TagNames::big, HTML::TagNames::code, HTML::TagNames::em, HTML::TagNames::font, HTML::TagNames::i, HTML::TagNames::nobr, HTML::TagNames::s, HTML::TagNames::small, HTML::TagNames::strike, HTML::TagNames::strong, HTML::TagNames::tt, HTML::TagNames::u)) {
+ if (run_the_adoption_agency_algorithm(token) == AdoptionAgencyAlgorithmOutcome::RunAnyOtherEndTagSteps)
+ goto AnyOtherEndTag;
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::applet, HTML::TagNames::marquee, HTML::TagNames::object)) {
+ reconstruct_the_active_formatting_elements();
+ insert_html_element(token);
+ m_list_of_active_formatting_elements.add_marker();
+ m_frameset_ok = false;
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::applet, HTML::TagNames::marquee, HTML::TagNames::object)) {
+ if (!m_stack_of_open_elements.has_in_scope(token.tag_name())) {
+ PARSE_ERROR();
+ return;
+ }
+
+ generate_implied_end_tags();
+ if (current_node().local_name() != token.tag_name()) {
+ PARSE_ERROR();
+ }
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(token.tag_name());
+ m_list_of_active_formatting_elements.clear_up_to_the_last_marker();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::table) {
+ if (!document().in_quirks_mode()) {
+ if (m_stack_of_open_elements.has_in_button_scope(HTML::TagNames::p))
+ close_a_p_element();
+ }
+ insert_html_element(token);
+ m_frameset_ok = false;
+ m_insertion_mode = InsertionMode::InTable;
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::br) {
+ token.drop_attributes();
+ goto BRStartTag;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::area, HTML::TagNames::br, HTML::TagNames::embed, HTML::TagNames::img, HTML::TagNames::keygen, HTML::TagNames::wbr)) {
+ BRStartTag:
+ reconstruct_the_active_formatting_elements();
+ insert_html_element(token);
+ m_stack_of_open_elements.pop();
+ token.acknowledge_self_closing_flag_if_set();
+ m_frameset_ok = false;
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::input) {
+ reconstruct_the_active_formatting_elements();
+ insert_html_element(token);
+ m_stack_of_open_elements.pop();
+ token.acknowledge_self_closing_flag_if_set();
+ auto type_attribute = token.attribute(HTML::AttributeNames::type);
+ if (type_attribute.is_null() || !type_attribute.equals_ignoring_case("hidden")) {
+ m_frameset_ok = false;
+ }
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::param, HTML::TagNames::source, HTML::TagNames::track)) {
+ insert_html_element(token);
+ m_stack_of_open_elements.pop();
+ token.acknowledge_self_closing_flag_if_set();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::hr) {
+ if (m_stack_of_open_elements.has_in_button_scope(HTML::TagNames::p))
+ close_a_p_element();
+ insert_html_element(token);
+ m_stack_of_open_elements.pop();
+ token.acknowledge_self_closing_flag_if_set();
+ m_frameset_ok = false;
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::image) {
+ // Parse error. Change the token's tag name to HTML::TagNames::img and reprocess it. (Don't ask.)
+ PARSE_ERROR();
+ token.m_tag.tag_name.clear();
+ token.m_tag.tag_name.append(HTML::TagNames::img);
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::textarea) {
+ insert_html_element(token);
+
+ m_tokenizer.switch_to({}, HTMLTokenizer::State::RCDATA);
+
+ // If the next token is a U+000A LINE FEED (LF) character token,
+ // then ignore that token and move on to the next one.
+ // (Newlines at the start of pre blocks are ignored as an authoring convenience.)
+ auto next_token = m_tokenizer.next_token();
+
+ m_original_insertion_mode = m_insertion_mode;
+ m_frameset_ok = false;
+ m_insertion_mode = InsertionMode::Text;
+
+ if (next_token.has_value() && next_token.value().is_character() && next_token.value().code_point() == '\n') {
+ // Ignore it.
+ } else {
+ process_using_the_rules_for(m_insertion_mode, next_token.value());
+ }
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::xmp) {
+ if (m_stack_of_open_elements.has_in_button_scope(HTML::TagNames::p)) {
+ close_a_p_element();
+ }
+ reconstruct_the_active_formatting_elements();
+ m_frameset_ok = false;
+ parse_generic_raw_text_element(token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::iframe) {
+ m_frameset_ok = false;
+ parse_generic_raw_text_element(token);
+ return;
+ }
+
+ if (token.is_start_tag() && ((token.tag_name() == HTML::TagNames::noembed) || (token.tag_name() == HTML::TagNames::noscript && m_scripting_enabled))) {
+ parse_generic_raw_text_element(token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::select) {
+ reconstruct_the_active_formatting_elements();
+ insert_html_element(token);
+ m_frameset_ok = false;
+ switch (m_insertion_mode) {
+ case InsertionMode::InTable:
+ case InsertionMode::InCaption:
+ case InsertionMode::InTableBody:
+ case InsertionMode::InRow:
+ case InsertionMode::InCell:
+ m_insertion_mode = InsertionMode::InSelectInTable;
+ break;
+ default:
+ m_insertion_mode = InsertionMode::InSelect;
+ break;
+ }
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::optgroup, HTML::TagNames::option)) {
+ if (current_node().local_name() == HTML::TagNames::option)
+ m_stack_of_open_elements.pop();
+ reconstruct_the_active_formatting_elements();
+ insert_html_element(token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::rb, HTML::TagNames::rtc)) {
+ if (m_stack_of_open_elements.has_in_scope(HTML::TagNames::ruby))
+ generate_implied_end_tags();
+
+ if (current_node().local_name() != HTML::TagNames::ruby)
+ PARSE_ERROR();
+
+ insert_html_element(token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::rp, HTML::TagNames::rt)) {
+ if (m_stack_of_open_elements.has_in_scope(HTML::TagNames::ruby))
+ generate_implied_end_tags(HTML::TagNames::rtc);
+
+ if (current_node().local_name() != HTML::TagNames::rtc || current_node().local_name() != HTML::TagNames::ruby)
+ PARSE_ERROR();
+
+ insert_html_element(token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::math) {
+ reconstruct_the_active_formatting_elements();
+ adjust_mathml_attributes(token);
+ adjust_foreign_attributes(token);
+
+ insert_foreign_element(token, Namespace::MathML);
+
+ if (token.is_self_closing()) {
+ m_stack_of_open_elements.pop();
+ token.acknowledge_self_closing_flag_if_set();
+ }
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::svg) {
+ reconstruct_the_active_formatting_elements();
+ adjust_svg_attributes(token);
+ adjust_foreign_attributes(token);
+
+ insert_foreign_element(token, Namespace::SVG);
+
+ if (token.is_self_closing()) {
+ m_stack_of_open_elements.pop();
+ token.acknowledge_self_closing_flag_if_set();
+ }
+ return;
+ }
+
+ if ((token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::caption, HTML::TagNames::col, HTML::TagNames::colgroup, HTML::TagNames::frame, HTML::TagNames::head, HTML::TagNames::tbody, HTML::TagNames::td, HTML::TagNames::tfoot, HTML::TagNames::th, HTML::TagNames::thead, HTML::TagNames::tr))) {
+ PARSE_ERROR();
+ return;
+ }
+
+ // Any other start tag
+ if (token.is_start_tag()) {
+ reconstruct_the_active_formatting_elements();
+ insert_html_element(token);
+ return;
+ }
+
+ if (token.is_end_tag()) {
+ AnyOtherEndTag:
+ RefPtr<DOM::Element> node;
+ for (ssize_t i = m_stack_of_open_elements.elements().size() - 1; i >= 0; --i) {
+ node = m_stack_of_open_elements.elements()[i];
+ if (node->local_name() == token.tag_name()) {
+ generate_implied_end_tags(token.tag_name());
+ if (node != current_node()) {
+ PARSE_ERROR();
+ }
+ while (&current_node() != node) {
+ m_stack_of_open_elements.pop();
+ }
+ m_stack_of_open_elements.pop();
+ break;
+ }
+ if (is_special_tag(node->local_name(), node->namespace_())) {
+ PARSE_ERROR();
+ return;
+ }
+ }
+ return;
+ }
+}
+
+void HTMLDocumentParser::adjust_mathml_attributes(HTMLToken& token)
+{
+ token.adjust_attribute_name("definitionurl", "definitionURL");
+}
+
+void HTMLDocumentParser::adjust_svg_tag_names(HTMLToken& token)
+{
+ token.adjust_tag_name("altglyph", "altGlyph");
+ token.adjust_tag_name("altglyphdef", "altGlyphDef");
+ token.adjust_tag_name("altglyphitem", "altGlyphItem");
+ token.adjust_tag_name("animatecolor", "animateColor");
+ token.adjust_tag_name("animatemotion", "animateMotion");
+ token.adjust_tag_name("animatetransform", "animateTransform");
+ token.adjust_tag_name("clippath", "clipPath");
+ token.adjust_tag_name("feblend", "feBlend");
+ token.adjust_tag_name("fecolormatrix", "feColorMatrix");
+ token.adjust_tag_name("fecomponenttransfer", "feComponentTransfer");
+ token.adjust_tag_name("fecomposite", "feComposite");
+ token.adjust_tag_name("feconvolvematrix", "feConvolveMatrix");
+ token.adjust_tag_name("fediffuselighting", "feDiffuseLighting");
+ token.adjust_tag_name("fedisplacementmap", "feDisplacementMap");
+ token.adjust_tag_name("fedistantlight", "feDistantLight");
+ token.adjust_tag_name("fedropshadow", "feDropShadow");
+ token.adjust_tag_name("feflood", "feFlood");
+ token.adjust_tag_name("fefunca", "feFuncA");
+ token.adjust_tag_name("fefuncb", "feFuncB");
+ token.adjust_tag_name("fefuncg", "feFuncG");
+ token.adjust_tag_name("fefuncr", "feFuncR");
+ token.adjust_tag_name("fegaussianblur", "feGaussianBlur");
+ token.adjust_tag_name("feimage", "feImage");
+ token.adjust_tag_name("femerge", "feMerge");
+ token.adjust_tag_name("femergenode", "feMergeNode");
+ token.adjust_tag_name("femorphology", "feMorphology");
+ token.adjust_tag_name("feoffset", "feOffset");
+ token.adjust_tag_name("fepointlight", "fePointLight");
+ token.adjust_tag_name("fespecularlighting", "feSpecularLighting");
+ token.adjust_tag_name("fespotlight", "feSpotlight");
+ token.adjust_tag_name("glyphref", "glyphRef");
+ token.adjust_tag_name("lineargradient", "linearGradient");
+ token.adjust_tag_name("radialgradient", "radialGradient");
+ token.adjust_tag_name("textpath", "textPath");
+}
+
+void HTMLDocumentParser::adjust_svg_attributes(HTMLToken& token)
+{
+ token.adjust_attribute_name("attributename", "attributeName");
+ token.adjust_attribute_name("attributetype", "attributeType");
+ token.adjust_attribute_name("basefrequency", "baseFrequency");
+ token.adjust_attribute_name("baseprofile", "baseProfile");
+ token.adjust_attribute_name("calcmode", "calcMode");
+ token.adjust_attribute_name("clippathunits", "clipPathUnits");
+ token.adjust_attribute_name("diffuseconstant", "diffuseConstant");
+ token.adjust_attribute_name("edgemode", "edgeMode");
+ token.adjust_attribute_name("filterunits", "filterUnits");
+ token.adjust_attribute_name("glyphref", "glyphRef");
+ token.adjust_attribute_name("gradienttransform", "gradientTransform");
+ token.adjust_attribute_name("gradientunits", "gradientUnits");
+ token.adjust_attribute_name("kernelmatrix", "kernelMatrix");
+ token.adjust_attribute_name("kernelunitlength", "kernelUnitLength");
+ token.adjust_attribute_name("keypoints", "keyPoints");
+ token.adjust_attribute_name("keysplines", "keySplines");
+ token.adjust_attribute_name("keytimes", "keyTimes");
+ token.adjust_attribute_name("lengthadjust", "lengthAdjust");
+ token.adjust_attribute_name("limitingconeangle", "limitingConeAngle");
+ token.adjust_attribute_name("markerheight", "markerHeight");
+ token.adjust_attribute_name("markerunits", "markerUnits");
+ token.adjust_attribute_name("markerwidth", "markerWidth");
+ token.adjust_attribute_name("maskcontentunits", "maskContentUnits");
+ token.adjust_attribute_name("maskunits", "maskUnits");
+ token.adjust_attribute_name("numoctaves", "numOctaves");
+ token.adjust_attribute_name("pathlength", "pathLength");
+ token.adjust_attribute_name("patterncontentunits", "patternContentUnits");
+ token.adjust_attribute_name("patterntransform", "patternTransform");
+ token.adjust_attribute_name("patternunits", "patternUnits");
+ token.adjust_attribute_name("pointsatx", "pointsAtX");
+ token.adjust_attribute_name("pointsaty", "pointsAtY");
+ token.adjust_attribute_name("pointsatz", "pointsAtZ");
+ token.adjust_attribute_name("preservealpha", "preserveAlpha");
+ token.adjust_attribute_name("preserveaspectratio", "preserveAspectRatio");
+ token.adjust_attribute_name("primitiveunits", "primitiveUnits");
+ token.adjust_attribute_name("refx", "refX");
+ token.adjust_attribute_name("refy", "refY");
+ token.adjust_attribute_name("repeatcount", "repeatCount");
+ token.adjust_attribute_name("repeatdur", "repeatDur");
+ token.adjust_attribute_name("requiredextensions", "requiredExtensions");
+ token.adjust_attribute_name("requiredfeatures", "requiredFeatures");
+ token.adjust_attribute_name("specularconstant", "specularConstant");
+ token.adjust_attribute_name("specularexponent", "specularExponent");
+ token.adjust_attribute_name("spreadmethod", "spreadMethod");
+ token.adjust_attribute_name("startoffset", "startOffset");
+ token.adjust_attribute_name("stddeviation", "stdDeviation");
+ token.adjust_attribute_name("stitchtiles", "stitchTiles");
+ token.adjust_attribute_name("surfacescale", "surfaceScale");
+ token.adjust_attribute_name("systemlanguage", "systemLanguage");
+ token.adjust_attribute_name("tablevalues", "tableValues");
+ token.adjust_attribute_name("targetx", "targetX");
+ token.adjust_attribute_name("targety", "targetY");
+ token.adjust_attribute_name("textlength", "textLength");
+ token.adjust_attribute_name("viewbox", "viewBox");
+ token.adjust_attribute_name("viewtarget", "viewTarget");
+ token.adjust_attribute_name("xchannelselector", "xChannelSelector");
+ token.adjust_attribute_name("ychannelselector", "yChannelSelector");
+ token.adjust_attribute_name("zoomandpan", "zoomAndPan");
+}
+
+void HTMLDocumentParser::adjust_foreign_attributes(HTMLToken& token)
+{
+ token.adjust_foreign_attribute("xlink:actuate", "xlink", "actuate", Namespace::XLink);
+ token.adjust_foreign_attribute("xlink:arcrole", "xlink", "arcrole", Namespace::XLink);
+ token.adjust_foreign_attribute("xlink:href", "xlink", "href", Namespace::XLink);
+ token.adjust_foreign_attribute("xlink:role", "xlink", "role", Namespace::XLink);
+ token.adjust_foreign_attribute("xlink:show", "xlink", "show", Namespace::XLink);
+ token.adjust_foreign_attribute("xlink:title", "xlink", "title", Namespace::XLink);
+ token.adjust_foreign_attribute("xlink:type", "xlink", "type", Namespace::XLink);
+
+ token.adjust_foreign_attribute("xml:lang", "xml", "lang", Namespace::XML);
+ token.adjust_foreign_attribute("xml:space", "xml", "space", Namespace::XML);
+
+ token.adjust_foreign_attribute("xmlns", "", "xmlns", Namespace::XMLNS);
+ token.adjust_foreign_attribute("xmlns:xlink", "xmlns", "xlink", Namespace::XMLNS);
+}
+
+void HTMLDocumentParser::increment_script_nesting_level()
+{
+ ++m_script_nesting_level;
+}
+
+void HTMLDocumentParser::decrement_script_nesting_level()
+{
+ ASSERT(m_script_nesting_level);
+ --m_script_nesting_level;
+}
+
+void HTMLDocumentParser::handle_text(HTMLToken& token)
+{
+ if (token.is_character()) {
+ insert_character(token.code_point());
+ return;
+ }
+ if (token.is_end_of_file()) {
+ PARSE_ERROR();
+ if (current_node().local_name() == HTML::TagNames::script)
+ downcast<HTMLScriptElement>(current_node()).set_already_started({}, true);
+ m_stack_of_open_elements.pop();
+ m_insertion_mode = m_original_insertion_mode;
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::script) {
+ // Make sure the <script> element has up-to-date text content before preparing the script.
+ flush_character_insertions();
+
+ NonnullRefPtr<HTMLScriptElement> script = downcast<HTMLScriptElement>(current_node());
+ m_stack_of_open_elements.pop();
+ m_insertion_mode = m_original_insertion_mode;
+ // FIXME: Handle tokenizer insertion point stuff here.
+ increment_script_nesting_level();
+ script->prepare_script({});
+ decrement_script_nesting_level();
+ if (script_nesting_level() == 0)
+ m_parser_pause_flag = false;
+ // FIXME: Handle tokenizer insertion point stuff here too.
+
+ while (document().pending_parsing_blocking_script()) {
+ if (script_nesting_level() != 0) {
+ m_parser_pause_flag = true;
+ // FIXME: Abort the processing of any nested invocations of the tokenizer,
+ // yielding control back to the caller. (Tokenization will resume when
+ // the caller returns to the "outer" tree construction stage.)
+ TODO();
+ } else {
+ auto the_script = document().take_pending_parsing_blocking_script({});
+ m_tokenizer.set_blocked(true);
+
+ // FIXME: If the parser's Document has a style sheet that is blocking scripts
+ // or the script's "ready to be parser-executed" flag is not set:
+ // spin the event loop until the parser's Document has no style sheet
+ // that is blocking scripts and the script's "ready to be parser-executed"
+ // flag is set.
+
+ if (the_script->failed_to_load())
+ return;
+
+ ASSERT(the_script->is_ready_to_be_parser_executed());
+
+ if (m_aborted)
+ return;
+
+ m_tokenizer.set_blocked(false);
+
+ // FIXME: Handle tokenizer insertion point stuff here too.
+
+ ASSERT(script_nesting_level() == 0);
+ increment_script_nesting_level();
+
+ the_script->execute_script();
+
+ decrement_script_nesting_level();
+ ASSERT(script_nesting_level() == 0);
+ m_parser_pause_flag = false;
+
+ // FIXME: Handle tokenizer insertion point stuff here too.
+ }
+ }
+ return;
+ }
+
+ if (token.is_end_tag()) {
+ m_stack_of_open_elements.pop();
+ m_insertion_mode = m_original_insertion_mode;
+ return;
+ }
+ TODO();
+}
+
+void HTMLDocumentParser::clear_the_stack_back_to_a_table_context()
+{
+ while (!current_node().local_name().is_one_of(HTML::TagNames::table, HTML::TagNames::template_, HTML::TagNames::html))
+ m_stack_of_open_elements.pop();
+
+ if (current_node().local_name() == HTML::TagNames::html)
+ ASSERT(m_parsing_fragment);
+}
+
+void HTMLDocumentParser::clear_the_stack_back_to_a_table_row_context()
+{
+ while (!current_node().local_name().is_one_of(HTML::TagNames::tr, HTML::TagNames::template_, HTML::TagNames::html))
+ m_stack_of_open_elements.pop();
+
+ if (current_node().local_name() == HTML::TagNames::html)
+ ASSERT(m_parsing_fragment);
+}
+
+void HTMLDocumentParser::clear_the_stack_back_to_a_table_body_context()
+{
+ while (!current_node().local_name().is_one_of(HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead, HTML::TagNames::template_, HTML::TagNames::html))
+ m_stack_of_open_elements.pop();
+
+ if (current_node().local_name() == HTML::TagNames::html)
+ ASSERT(m_parsing_fragment);
+}
+
+void HTMLDocumentParser::handle_in_row(HTMLToken& token)
+{
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::th, HTML::TagNames::td)) {
+ clear_the_stack_back_to_a_table_row_context();
+ insert_html_element(token);
+ m_insertion_mode = InsertionMode::InCell;
+ m_list_of_active_formatting_elements.add_marker();
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::tr) {
+ if (!m_stack_of_open_elements.has_in_table_scope(HTML::TagNames::tr)) {
+ PARSE_ERROR();
+ return;
+ }
+ clear_the_stack_back_to_a_table_row_context();
+ m_stack_of_open_elements.pop();
+ m_insertion_mode = InsertionMode::InTableBody;
+ return;
+ }
+
+ if ((token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::caption, HTML::TagNames::col, HTML::TagNames::colgroup, HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead, HTML::TagNames::tr))
+ || (token.is_end_tag() && token.tag_name() == HTML::TagNames::table)) {
+ if (!m_stack_of_open_elements.has_in_table_scope(HTML::TagNames::tr)) {
+ PARSE_ERROR();
+ return;
+ }
+ clear_the_stack_back_to_a_table_row_context();
+ m_stack_of_open_elements.pop();
+ m_insertion_mode = InsertionMode::InTableBody;
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead)) {
+ if (!m_stack_of_open_elements.has_in_table_scope(token.tag_name())) {
+ PARSE_ERROR();
+ return;
+ }
+ if (!m_stack_of_open_elements.has_in_table_scope(HTML::TagNames::tr)) {
+ return;
+ }
+ clear_the_stack_back_to_a_table_row_context();
+ m_stack_of_open_elements.pop();
+ m_insertion_mode = InsertionMode::InTableBody;
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::body, HTML::TagNames::caption, HTML::TagNames::col, HTML::TagNames::colgroup, HTML::TagNames::html, HTML::TagNames::td, HTML::TagNames::th)) {
+ PARSE_ERROR();
+ return;
+ }
+
+ process_using_the_rules_for(InsertionMode::InTable, token);
+}
+
+void HTMLDocumentParser::close_the_cell()
+{
+ generate_implied_end_tags();
+ if (!current_node().local_name().is_one_of(HTML::TagNames::td, HTML::TagNames::th)) {
+ PARSE_ERROR();
+ }
+ while (!current_node().local_name().is_one_of(HTML::TagNames::td, HTML::TagNames::th))
+ m_stack_of_open_elements.pop();
+ m_stack_of_open_elements.pop();
+ m_list_of_active_formatting_elements.clear_up_to_the_last_marker();
+ m_insertion_mode = InsertionMode::InRow;
+}
+
+void HTMLDocumentParser::handle_in_cell(HTMLToken& token)
+{
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::td, HTML::TagNames::th)) {
+ if (!m_stack_of_open_elements.has_in_table_scope(token.tag_name())) {
+ PARSE_ERROR();
+ return;
+ }
+ generate_implied_end_tags();
+
+ if (current_node().local_name() != token.tag_name()) {
+ PARSE_ERROR();
+ }
+
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(token.tag_name());
+
+ m_list_of_active_formatting_elements.clear_up_to_the_last_marker();
+
+ m_insertion_mode = InsertionMode::InRow;
+ return;
+ }
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::caption, HTML::TagNames::col, HTML::TagNames::colgroup, HTML::TagNames::tbody, HTML::TagNames::td, HTML::TagNames::tfoot, HTML::TagNames::th, HTML::TagNames::thead, HTML::TagNames::tr)) {
+ if (!m_stack_of_open_elements.has_in_table_scope(HTML::TagNames::td) && !m_stack_of_open_elements.has_in_table_scope(HTML::TagNames::th)) {
+ ASSERT(m_parsing_fragment);
+ PARSE_ERROR();
+ return;
+ }
+ close_the_cell();
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::body, HTML::TagNames::caption, HTML::TagNames::col, HTML::TagNames::colgroup, HTML::TagNames::html)) {
+ PARSE_ERROR();
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::table, HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead, HTML::TagNames::tr)) {
+ if (!m_stack_of_open_elements.has_in_table_scope(token.tag_name())) {
+ PARSE_ERROR();
+ return;
+ }
+ close_the_cell();
+ // Reprocess the token.
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ process_using_the_rules_for(InsertionMode::InBody, token);
+}
+
+void HTMLDocumentParser::handle_in_table_text(HTMLToken& token)
+{
+ if (token.is_character()) {
+ if (token.code_point() == 0) {
+ PARSE_ERROR();
+ return;
+ }
+
+ m_pending_table_character_tokens.append(token);
+ return;
+ }
+
+ for (auto& pending_token : m_pending_table_character_tokens) {
+ ASSERT(pending_token.is_character());
+ if (!pending_token.is_parser_whitespace()) {
+ // If any of the tokens in the pending table character tokens list
+ // are character tokens that are not ASCII whitespace, then this is a parse error:
+ // reprocess the character tokens in the pending table character tokens list using
+ // the rules given in the "anything else" entry in the "in table" insertion mode.
+ PARSE_ERROR();
+ m_foster_parenting = true;
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ m_foster_parenting = false;
+ return;
+ }
+ }
+
+ for (auto& pending_token : m_pending_table_character_tokens) {
+ insert_character(pending_token.code_point());
+ }
+
+ m_insertion_mode = m_original_insertion_mode;
+ process_using_the_rules_for(m_insertion_mode, token);
+}
+
+void HTMLDocumentParser::handle_in_table_body(HTMLToken& token)
+{
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::tr) {
+ clear_the_stack_back_to_a_table_body_context();
+ insert_html_element(token);
+ m_insertion_mode = InsertionMode::InRow;
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::th, HTML::TagNames::td)) {
+ PARSE_ERROR();
+ clear_the_stack_back_to_a_table_body_context();
+ insert_html_element(HTMLToken::make_start_tag(HTML::TagNames::tr));
+ m_insertion_mode = InsertionMode::InRow;
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead)) {
+ if (!m_stack_of_open_elements.has_in_table_scope(token.tag_name())) {
+ PARSE_ERROR();
+ return;
+ }
+ clear_the_stack_back_to_a_table_body_context();
+ m_stack_of_open_elements.pop();
+ m_insertion_mode = InsertionMode::InTable;
+ return;
+ }
+
+ if ((token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::caption, HTML::TagNames::col, HTML::TagNames::colgroup, HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead))
+ || (token.is_end_tag() && token.tag_name() == HTML::TagNames::table)) {
+
+ if (!m_stack_of_open_elements.has_in_table_scope(HTML::TagNames::tbody)
+ && !m_stack_of_open_elements.has_in_table_scope(HTML::TagNames::thead)
+ && !m_stack_of_open_elements.has_in_table_scope(HTML::TagNames::tfoot)) {
+ PARSE_ERROR();
+ return;
+ }
+
+ clear_the_stack_back_to_a_table_body_context();
+ m_stack_of_open_elements.pop();
+ m_insertion_mode = InsertionMode::InTable;
+ process_using_the_rules_for(InsertionMode::InTable, token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::body, HTML::TagNames::caption, HTML::TagNames::col, HTML::TagNames::colgroup, HTML::TagNames::html, HTML::TagNames::td, HTML::TagNames::th, HTML::TagNames::tr)) {
+ PARSE_ERROR();
+ return;
+ }
+
+ process_using_the_rules_for(InsertionMode::InTable, token);
+}
+
+void HTMLDocumentParser::handle_in_table(HTMLToken& token)
+{
+ if (token.is_character() && current_node().local_name().is_one_of(HTML::TagNames::table, HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead, HTML::TagNames::tr)) {
+ m_pending_table_character_tokens.clear();
+ m_original_insertion_mode = m_insertion_mode;
+ m_insertion_mode = InsertionMode::InTableText;
+ process_using_the_rules_for(InsertionMode::InTableText, token);
+ return;
+ }
+ if (token.is_comment()) {
+ insert_comment(token);
+ return;
+ }
+ if (token.is_doctype()) {
+ PARSE_ERROR();
+ return;
+ }
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::caption) {
+ clear_the_stack_back_to_a_table_context();
+ m_list_of_active_formatting_elements.add_marker();
+ insert_html_element(token);
+ m_insertion_mode = InsertionMode::InCaption;
+ return;
+ }
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::colgroup) {
+ clear_the_stack_back_to_a_table_context();
+ insert_html_element(token);
+ m_insertion_mode = InsertionMode::InColumnGroup;
+ return;
+ }
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::col) {
+ clear_the_stack_back_to_a_table_context();
+ insert_html_element(HTMLToken::make_start_tag(HTML::TagNames::colgroup));
+ m_insertion_mode = InsertionMode::InColumnGroup;
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead)) {
+ clear_the_stack_back_to_a_table_context();
+ insert_html_element(token);
+ m_insertion_mode = InsertionMode::InTableBody;
+ return;
+ }
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::td, HTML::TagNames::th, HTML::TagNames::tr)) {
+ clear_the_stack_back_to_a_table_context();
+ insert_html_element(HTMLToken::make_start_tag(HTML::TagNames::tbody));
+ m_insertion_mode = InsertionMode::InTableBody;
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::table) {
+ PARSE_ERROR();
+ if (!m_stack_of_open_elements.has_in_table_scope(HTML::TagNames::table))
+ return;
+
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::table);
+
+ reset_the_insertion_mode_appropriately();
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::table) {
+ if (!m_stack_of_open_elements.has_in_table_scope(HTML::TagNames::table)) {
+ PARSE_ERROR();
+ return;
+ }
+
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::table);
+
+ reset_the_insertion_mode_appropriately();
+ return;
+ }
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::body, HTML::TagNames::caption, HTML::TagNames::col, HTML::TagNames::colgroup, HTML::TagNames::html, HTML::TagNames::tbody, HTML::TagNames::td, HTML::TagNames::tfoot, HTML::TagNames::th, HTML::TagNames::thead, HTML::TagNames::tr)) {
+ PARSE_ERROR();
+ return;
+ }
+ if ((token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::style, HTML::TagNames::script, HTML::TagNames::template_))
+ || (token.is_end_tag() && token.tag_name() == HTML::TagNames::template_)) {
+ process_using_the_rules_for(InsertionMode::InHead, token);
+ return;
+ }
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::input) {
+ auto type_attribute = token.attribute(HTML::AttributeNames::type);
+ if (type_attribute.is_null() || !type_attribute.equals_ignoring_case("hidden")) {
+ goto AnythingElse;
+ }
+
+ PARSE_ERROR();
+ insert_html_element(token);
+
+ // FIXME: Is this the correct interpretation of "Pop that input element off the stack of open elements."?
+ // Because this wording is the first time it's seen in the spec.
+ // Other times it's worded as: "Immediately pop the current node off the stack of open elements."
+ m_stack_of_open_elements.pop();
+ token.acknowledge_self_closing_flag_if_set();
+ return;
+ }
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::form) {
+ PARSE_ERROR();
+ if (m_form_element || m_stack_of_open_elements.contains(HTML::TagNames::template_)) {
+ return;
+ }
+
+ m_form_element = downcast<HTMLFormElement>(*insert_html_element(token));
+
+ // FIXME: See previous FIXME, as this is the same situation but for form.
+ m_stack_of_open_elements.pop();
+ return;
+ }
+ if (token.is_end_of_file()) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+AnythingElse:
+ PARSE_ERROR();
+ m_foster_parenting = true;
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ m_foster_parenting = false;
+}
+
+void HTMLDocumentParser::handle_in_select_in_table(HTMLToken& token)
+{
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::caption, HTML::TagNames::table, HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead, HTML::TagNames::tr, HTML::TagNames::td, HTML::TagNames::th)) {
+ PARSE_ERROR();
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::select);
+ reset_the_insertion_mode_appropriately();
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::caption, HTML::TagNames::table, HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead, HTML::TagNames::tr, HTML::TagNames::td, HTML::TagNames::th)) {
+ PARSE_ERROR();
+
+ if (!m_stack_of_open_elements.has_in_table_scope(token.tag_name()))
+ return;
+
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::select);
+ reset_the_insertion_mode_appropriately();
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ process_using_the_rules_for(InsertionMode::InSelect, token);
+}
+
+void HTMLDocumentParser::handle_in_select(HTMLToken& token)
+{
+ if (token.is_character()) {
+ if (token.code_point() == 0) {
+ PARSE_ERROR();
+ return;
+ }
+ insert_character(token.code_point());
+ return;
+ }
+
+ if (token.is_comment()) {
+ insert_comment(token);
+ return;
+ }
+
+ if (token.is_doctype()) {
+ PARSE_ERROR();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::html) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::option) {
+ if (current_node().local_name() == HTML::TagNames::option) {
+ m_stack_of_open_elements.pop();
+ }
+ insert_html_element(token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::optgroup) {
+ if (current_node().local_name() == HTML::TagNames::option) {
+ m_stack_of_open_elements.pop();
+ }
+ if (current_node().local_name() == HTML::TagNames::optgroup) {
+ m_stack_of_open_elements.pop();
+ }
+ insert_html_element(token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::optgroup) {
+ if (current_node().local_name() == HTML::TagNames::option && node_before_current_node().local_name() == HTML::TagNames::optgroup)
+ m_stack_of_open_elements.pop();
+
+ if (current_node().local_name() == HTML::TagNames::optgroup) {
+ m_stack_of_open_elements.pop();
+ } else {
+ PARSE_ERROR();
+ return;
+ }
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::option) {
+ if (current_node().local_name() == HTML::TagNames::option) {
+ m_stack_of_open_elements.pop();
+ } else {
+ PARSE_ERROR();
+ return;
+ }
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::select) {
+ if (!m_stack_of_open_elements.has_in_select_scope(HTML::TagNames::select)) {
+ ASSERT(m_parsing_fragment);
+ PARSE_ERROR();
+ return;
+ }
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::select);
+ reset_the_insertion_mode_appropriately();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::select) {
+ PARSE_ERROR();
+
+ if (!m_stack_of_open_elements.has_in_select_scope(HTML::TagNames::select)) {
+ ASSERT(m_parsing_fragment);
+ return;
+ }
+
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::select);
+ reset_the_insertion_mode_appropriately();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::input, HTML::TagNames::keygen, HTML::TagNames::textarea)) {
+ PARSE_ERROR();
+
+ if (!m_stack_of_open_elements.has_in_select_scope(HTML::TagNames::select)) {
+ ASSERT(m_parsing_fragment);
+ return;
+ }
+
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::select);
+ reset_the_insertion_mode_appropriately();
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::script, HTML::TagNames::template_)) {
+ process_using_the_rules_for(InsertionMode::InHead, token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::template_) {
+ process_using_the_rules_for(InsertionMode::InHead, token);
+ return;
+ }
+
+ if (token.is_end_of_file()) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+ PARSE_ERROR();
+}
+
+void HTMLDocumentParser::handle_in_caption(HTMLToken& token)
+{
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::caption) {
+ if (!m_stack_of_open_elements.has_in_table_scope(HTML::TagNames::caption)) {
+ ASSERT(m_parsing_fragment);
+ PARSE_ERROR();
+ return;
+ }
+
+ generate_implied_end_tags();
+
+ if (current_node().local_name() != HTML::TagNames::caption)
+ PARSE_ERROR();
+
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::caption);
+ m_list_of_active_formatting_elements.clear_up_to_the_last_marker();
+
+ m_insertion_mode = InsertionMode::InTable;
+ return;
+ }
+
+ if ((token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::caption, HTML::TagNames::col, HTML::TagNames::colgroup, HTML::TagNames::tbody, HTML::TagNames::td, HTML::TagNames::tfoot, HTML::TagNames::th, HTML::TagNames::thead, HTML::TagNames::tr))
+ || (token.is_end_tag() && token.tag_name() == HTML::TagNames::table)) {
+ if (!m_stack_of_open_elements.has_in_table_scope(HTML::TagNames::caption)) {
+ ASSERT(m_parsing_fragment);
+ PARSE_ERROR();
+ return;
+ }
+
+ generate_implied_end_tags();
+
+ if (current_node().local_name() != HTML::TagNames::caption)
+ PARSE_ERROR();
+
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::caption);
+ m_list_of_active_formatting_elements.clear_up_to_the_last_marker();
+
+ m_insertion_mode = InsertionMode::InTable;
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::body, HTML::TagNames::col, HTML::TagNames::colgroup, HTML::TagNames::html, HTML::TagNames::tbody, HTML::TagNames::td, HTML::TagNames::tfoot, HTML::TagNames::th, HTML::TagNames::thead, HTML::TagNames::tr)) {
+ PARSE_ERROR();
+ return;
+ }
+
+ process_using_the_rules_for(InsertionMode::InBody, token);
+}
+
+void HTMLDocumentParser::handle_in_column_group(HTMLToken& token)
+{
+ if (token.is_character() && token.is_parser_whitespace()) {
+ insert_character(token.code_point());
+ return;
+ }
+
+ if (token.is_comment()) {
+ insert_comment(token);
+ return;
+ }
+
+ if (token.is_doctype()) {
+ PARSE_ERROR();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::html) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::col) {
+ insert_html_element(token);
+ m_stack_of_open_elements.pop();
+ token.acknowledge_self_closing_flag_if_set();
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::colgroup) {
+ if (current_node().local_name() != HTML::TagNames::colgroup) {
+ PARSE_ERROR();
+ return;
+ }
+
+ m_stack_of_open_elements.pop();
+ m_insertion_mode = InsertionMode::InTable;
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::col) {
+ PARSE_ERROR();
+ return;
+ }
+
+ if ((token.is_start_tag() || token.is_end_tag()) && token.tag_name() == HTML::TagNames::template_) {
+ process_using_the_rules_for(InsertionMode::InHead, token);
+ return;
+ }
+
+ if (token.is_end_of_file()) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+ if (current_node().local_name() != HTML::TagNames::colgroup) {
+ PARSE_ERROR();
+ return;
+ }
+
+ m_stack_of_open_elements.pop();
+ m_insertion_mode = InsertionMode::InTable;
+ process_using_the_rules_for(m_insertion_mode, token);
+}
+
+void HTMLDocumentParser::handle_in_template(HTMLToken& token)
+{
+ if (token.is_character() || token.is_comment() || token.is_doctype()) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::base, HTML::TagNames::basefont, HTML::TagNames::bgsound, HTML::TagNames::link, HTML::TagNames::meta, HTML::TagNames::noframes, HTML::TagNames::script, HTML::TagNames::style, HTML::TagNames::template_, HTML::TagNames::title)) {
+ process_using_the_rules_for(InsertionMode::InHead, token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::template_) {
+ process_using_the_rules_for(InsertionMode::InHead, token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::caption, HTML::TagNames::colgroup, HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead)) {
+ m_stack_of_template_insertion_modes.take_last();
+ m_stack_of_template_insertion_modes.append(InsertionMode::InTable);
+ m_insertion_mode = InsertionMode::InTable;
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::col) {
+ m_stack_of_template_insertion_modes.take_last();
+ m_stack_of_template_insertion_modes.append(InsertionMode::InColumnGroup);
+ m_insertion_mode = InsertionMode::InColumnGroup;
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::tr) {
+ m_stack_of_template_insertion_modes.take_last();
+ m_stack_of_template_insertion_modes.append(InsertionMode::InTableBody);
+ m_insertion_mode = InsertionMode::InTableBody;
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::td, HTML::TagNames::th)) {
+ m_stack_of_template_insertion_modes.take_last();
+ m_stack_of_template_insertion_modes.append(InsertionMode::InRow);
+ m_insertion_mode = InsertionMode::InRow;
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ if (token.is_start_tag()) {
+ m_stack_of_template_insertion_modes.take_last();
+ m_stack_of_template_insertion_modes.append(InsertionMode::InBody);
+ m_insertion_mode = InsertionMode::InBody;
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+
+ if (token.is_end_tag()) {
+ PARSE_ERROR();
+ return;
+ }
+
+ if (token.is_end_of_file()) {
+ if (!m_stack_of_open_elements.contains(HTML::TagNames::template_)) {
+ ASSERT(m_parsing_fragment);
+ stop_parsing();
+ return;
+ }
+
+ PARSE_ERROR();
+ m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::template_);
+ m_list_of_active_formatting_elements.clear_up_to_the_last_marker();
+ m_stack_of_template_insertion_modes.take_last();
+ reset_the_insertion_mode_appropriately();
+ process_using_the_rules_for(m_insertion_mode, token);
+ }
+}
+
+void HTMLDocumentParser::handle_in_frameset(HTMLToken& token)
+{
+ if (token.is_character() && token.is_parser_whitespace()) {
+ insert_character(token.code_point());
+ return;
+ }
+
+ if (token.is_comment()) {
+ insert_comment(token);
+ return;
+ }
+
+ if (token.is_doctype()) {
+ PARSE_ERROR();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::html) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::frameset) {
+ insert_html_element(token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::frameset) {
+ // FIXME: If the current node is the root html element, then this is a parse error; ignore the token. (fragment case)
+
+ m_stack_of_open_elements.pop();
+
+ if (!m_parsing_fragment && current_node().local_name() != HTML::TagNames::frameset) {
+ m_insertion_mode = InsertionMode::AfterFrameset;
+ }
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::frame) {
+ insert_html_element(token);
+ m_stack_of_open_elements.pop();
+ token.acknowledge_self_closing_flag_if_set();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::noframes) {
+ process_using_the_rules_for(InsertionMode::InHead, token);
+ return;
+ }
+
+ if (token.is_end_of_file()) {
+ //FIXME: If the current node is not the root html element, then this is a parse error.
+
+ stop_parsing();
+ return;
+ }
+
+ PARSE_ERROR();
+}
+
+void HTMLDocumentParser::handle_after_frameset(HTMLToken& token)
+{
+ if (token.is_character() && token.is_parser_whitespace()) {
+ insert_character(token.code_point());
+ return;
+ }
+
+ if (token.is_comment()) {
+ insert_comment(token);
+ return;
+ }
+
+ if (token.is_doctype()) {
+ PARSE_ERROR();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::html) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+ if (token.is_end_tag() && token.tag_name() == HTML::TagNames::html) {
+ m_insertion_mode = InsertionMode::AfterAfterFrameset;
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::noframes) {
+ process_using_the_rules_for(InsertionMode::InHead, token);
+ return;
+ }
+
+ if (token.is_end_of_file()) {
+ stop_parsing();
+ return;
+ }
+
+ PARSE_ERROR();
+}
+
+void HTMLDocumentParser::handle_after_after_frameset(HTMLToken& token)
+{
+ if (token.is_comment()) {
+ auto comment = adopt(*new DOM::Comment(document(), token.m_comment_or_character.data.to_string()));
+ document().append_child(move(comment));
+ return;
+ }
+
+ if (token.is_doctype() || token.is_parser_whitespace() || (token.is_start_tag() && token.tag_name() == HTML::TagNames::html)) {
+ process_using_the_rules_for(InsertionMode::InBody, token);
+ return;
+ }
+
+ if (token.is_end_of_file()) {
+ stop_parsing();
+ return;
+ }
+
+ if (token.is_start_tag() && token.tag_name() == HTML::TagNames::noframes) {
+ process_using_the_rules_for(InsertionMode::InHead, token);
+ return;
+ }
+
+ PARSE_ERROR();
+}
+
+void HTMLDocumentParser::process_using_the_rules_for_foreign_content(HTMLToken& token)
+{
+ if (token.is_character()) {
+ if (token.code_point() == 0) {
+ PARSE_ERROR();
+ insert_character(0xFFFD);
+ return;
+ }
+ if (token.is_parser_whitespace()) {
+ insert_character(token.code_point());
+ return;
+ }
+ insert_character(token.code_point());
+ m_frameset_ok = false;
+ return;
+ }
+
+ if (token.is_comment()) {
+ insert_comment(token);
+ return;
+ }
+
+ if (token.is_doctype()) {
+ PARSE_ERROR();
+ return;
+ }
+
+ if ((token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::b, HTML::TagNames::big, HTML::TagNames::blockquote, HTML::TagNames::body, HTML::TagNames::br, HTML::TagNames::center, HTML::TagNames::code, HTML::TagNames::dd, HTML::TagNames::div, HTML::TagNames::dl, HTML::TagNames::dt, HTML::TagNames::em, HTML::TagNames::embed, HTML::TagNames::h1, HTML::TagNames::h2, HTML::TagNames::h3, HTML::TagNames::h4, HTML::TagNames::h5, HTML::TagNames::h6, HTML::TagNames::head, HTML::TagNames::hr, HTML::TagNames::i, HTML::TagNames::img, HTML::TagNames::li, HTML::TagNames::listing, HTML::TagNames::menu, HTML::TagNames::meta, HTML::TagNames::nobr, HTML::TagNames::ol, HTML::TagNames::p, HTML::TagNames::pre, HTML::TagNames::ruby, HTML::TagNames::s, HTML::TagNames::small, HTML::TagNames::span, HTML::TagNames::strong, HTML::TagNames::strike, HTML::TagNames::sub, HTML::TagNames::sup, HTML::TagNames::table, HTML::TagNames::tt, HTML::TagNames::u, HTML::TagNames::ul, HTML::TagNames::var))
+ || (token.is_start_tag() && token.tag_name() == HTML::TagNames::font && (token.has_attribute(HTML::AttributeNames::color) || token.has_attribute(HTML::AttributeNames::face) || token.has_attribute(HTML::AttributeNames::size)))) {
+ PARSE_ERROR();
+ if (m_parsing_fragment) {
+ goto AnyOtherStartTag;
+ }
+
+ TODO();
+ }
+
+ if (token.is_start_tag()) {
+ AnyOtherStartTag:
+ if (adjusted_current_node().namespace_() == Namespace::MathML) {
+ adjust_mathml_attributes(token);
+ } else if (adjusted_current_node().namespace_() == Namespace::SVG) {
+ adjust_svg_tag_names(token);
+ adjust_svg_attributes(token);
+ }
+
+ adjust_foreign_attributes(token);
+ insert_foreign_element(token, adjusted_current_node().namespace_());
+
+ if (token.is_self_closing()) {
+ if (token.tag_name() == SVG::TagNames::script && current_node().namespace_() == Namespace::SVG) {
+ token.acknowledge_self_closing_flag_if_set();
+ goto ScriptEndTag;
+ }
+
+ m_stack_of_open_elements.pop();
+ token.acknowledge_self_closing_flag_if_set();
+ }
+
+ return;
+ }
+
+ if (token.is_end_tag() && current_node().namespace_() == Namespace::SVG && current_node().tag_name() == SVG::TagNames::script) {
+ ScriptEndTag:
+ m_stack_of_open_elements.pop();
+ TODO();
+ }
+
+ if (token.is_end_tag()) {
+ RefPtr<DOM::Element> node = current_node();
+ // FIXME: Not sure if this is the correct to_lowercase, as the specification says "to ASCII lowercase"
+ if (node->tag_name().to_lowercase() != token.tag_name())
+ PARSE_ERROR();
+ for (ssize_t i = m_stack_of_open_elements.elements().size() - 1; i >= 0; --i) {
+ if (node == m_stack_of_open_elements.first()) {
+ ASSERT(m_parsing_fragment);
+ return;
+ }
+ // FIXME: See the above FIXME
+ if (node->tag_name().to_lowercase() == token.tag_name()) {
+ while (current_node() != node)
+ m_stack_of_open_elements.pop();
+ m_stack_of_open_elements.pop();
+ return;
+ }
+
+ node = m_stack_of_open_elements.elements().at(i - 1);
+
+ if (node->namespace_() != Namespace::HTML)
+ continue;
+
+ process_using_the_rules_for(m_insertion_mode, token);
+ return;
+ }
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+void HTMLDocumentParser::reset_the_insertion_mode_appropriately()
+{
+ for (ssize_t i = m_stack_of_open_elements.elements().size() - 1; i >= 0; --i) {
+ bool last = i == 0;
+ // NOTE: When parsing fragments, we substitute the context element for the root of the stack of open elements.
+ RefPtr<DOM::Element> node;
+ if (last && m_parsing_fragment) {
+ node = m_context_element;
+ } else {
+ node = m_stack_of_open_elements.elements().at(i);
+ }
+
+ if (node->local_name() == HTML::TagNames::select) {
+ TODO();
+ }
+
+ if (!last && node->local_name().is_one_of(HTML::TagNames::td, HTML::TagNames::th)) {
+ m_insertion_mode = InsertionMode::InCell;
+ return;
+ }
+
+ if (node->local_name() == HTML::TagNames::tr) {
+ m_insertion_mode = InsertionMode::InRow;
+ return;
+ }
+
+ if (node->local_name().is_one_of(HTML::TagNames::tbody, HTML::TagNames::thead, HTML::TagNames::tfoot)) {
+ m_insertion_mode = InsertionMode::InTableBody;
+ return;
+ }
+
+ if (node->local_name() == HTML::TagNames::caption) {
+ m_insertion_mode = InsertionMode::InCaption;
+ return;
+ }
+
+ if (node->local_name() == HTML::TagNames::colgroup) {
+ m_insertion_mode = InsertionMode::InColumnGroup;
+ return;
+ }
+
+ if (node->local_name() == HTML::TagNames::table) {
+ m_insertion_mode = InsertionMode::InTable;
+ return;
+ }
+
+ if (node->local_name() == HTML::TagNames::template_) {
+ m_insertion_mode = m_stack_of_template_insertion_modes.last();
+ return;
+ }
+
+ if (!last && node->local_name() == HTML::TagNames::head) {
+ m_insertion_mode = InsertionMode::InHead;
+ return;
+ }
+
+ if (node->local_name() == HTML::TagNames::body) {
+ m_insertion_mode = InsertionMode::InBody;
+ return;
+ }
+
+ if (node->local_name() == HTML::TagNames::frameset) {
+ ASSERT(m_parsing_fragment);
+ m_insertion_mode = InsertionMode::InFrameset;
+ return;
+ }
+
+ if (node->local_name() == HTML::TagNames::html) {
+ if (!m_head_element) {
+ ASSERT(m_parsing_fragment);
+ m_insertion_mode = InsertionMode::BeforeHead;
+ return;
+ }
+
+ m_insertion_mode = InsertionMode::AfterHead;
+ return;
+ }
+ }
+
+ ASSERT(m_parsing_fragment);
+ m_insertion_mode = InsertionMode::InBody;
+}
+
+const char* HTMLDocumentParser::insertion_mode_name() const
+{
+ switch (m_insertion_mode) {
+#define __ENUMERATE_INSERTION_MODE(mode) \
+ case InsertionMode::mode: \
+ return #mode;
+ ENUMERATE_INSERTION_MODES
+#undef __ENUMERATE_INSERTION_MODE
+ }
+ ASSERT_NOT_REACHED();
+}
+
+DOM::Document& HTMLDocumentParser::document()
+{
+ return *m_document;
+}
+
+NonnullRefPtrVector<DOM::Node> HTMLDocumentParser::parse_html_fragment(DOM::Element& context_element, const StringView& markup)
+{
+ auto temp_document = DOM::Document::create();
+ HTMLDocumentParser parser(*temp_document, markup, "utf-8");
+ parser.m_context_element = context_element;
+ parser.m_parsing_fragment = true;
+ parser.document().set_quirks_mode(context_element.document().mode());
+
+ if (context_element.local_name().is_one_of(HTML::TagNames::title, HTML::TagNames::textarea)) {
+ parser.m_tokenizer.switch_to({}, HTMLTokenizer::State::RCDATA);
+ } else if (context_element.local_name().is_one_of(HTML::TagNames::style, HTML::TagNames::xmp, HTML::TagNames::iframe, HTML::TagNames::noembed, HTML::TagNames::noframes)) {
+ parser.m_tokenizer.switch_to({}, HTMLTokenizer::State::RAWTEXT);
+ } else if (context_element.local_name().is_one_of(HTML::TagNames::script)) {
+ parser.m_tokenizer.switch_to({}, HTMLTokenizer::State::ScriptData);
+ } else if (context_element.local_name().is_one_of(HTML::TagNames::noscript)) {
+ if (context_element.document().is_scripting_enabled())
+ parser.m_tokenizer.switch_to({}, HTMLTokenizer::State::RAWTEXT);
+ } else if (context_element.local_name().is_one_of(HTML::TagNames::plaintext)) {
+ parser.m_tokenizer.switch_to({}, HTMLTokenizer::State::PLAINTEXT);
+ }
+
+ auto root = create_element(context_element.document(), HTML::TagNames::html, Namespace::HTML);
+ parser.document().append_child(root);
+ parser.m_stack_of_open_elements.push(root);
+
+ if (context_element.local_name() == HTML::TagNames::template_) {
+ parser.m_stack_of_template_insertion_modes.append(InsertionMode::InTemplate);
+ }
+
+ // FIXME: Create a start tag token whose name is the local name of context and whose attributes are the attributes of context.
+
+ parser.reset_the_insertion_mode_appropriately();
+
+ for (auto* form_candidate = &context_element; form_candidate; form_candidate = form_candidate->parent_element()) {
+ if (is<HTMLFormElement>(*form_candidate)) {
+ parser.m_form_element = downcast<HTMLFormElement>(*form_candidate);
+ break;
+ }
+ }
+
+ parser.run(context_element.document().url());
+
+ NonnullRefPtrVector<DOM::Node> children;
+ while (RefPtr<DOM::Node> child = root->first_child()) {
+ root->remove_child(*child);
+ context_element.document().adopt_node(*child);
+ children.append(*child);
+ }
+ return children;
+}
+}
diff --git a/Userland/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.h b/Userland/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.h
new file mode 100644
index 0000000000..4abbb74b72
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.h
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtrVector.h>
+#include <LibWeb/DOM/Node.h>
+#include <LibWeb/HTML/Parser/HTMLTokenizer.h>
+#include <LibWeb/HTML/Parser/ListOfActiveFormattingElements.h>
+#include <LibWeb/HTML/Parser/StackOfOpenElements.h>
+
+namespace Web::HTML {
+
+#define ENUMERATE_INSERTION_MODES \
+ __ENUMERATE_INSERTION_MODE(Initial) \
+ __ENUMERATE_INSERTION_MODE(BeforeHTML) \
+ __ENUMERATE_INSERTION_MODE(BeforeHead) \
+ __ENUMERATE_INSERTION_MODE(InHead) \
+ __ENUMERATE_INSERTION_MODE(InHeadNoscript) \
+ __ENUMERATE_INSERTION_MODE(AfterHead) \
+ __ENUMERATE_INSERTION_MODE(InBody) \
+ __ENUMERATE_INSERTION_MODE(Text) \
+ __ENUMERATE_INSERTION_MODE(InTable) \
+ __ENUMERATE_INSERTION_MODE(InTableText) \
+ __ENUMERATE_INSERTION_MODE(InCaption) \
+ __ENUMERATE_INSERTION_MODE(InColumnGroup) \
+ __ENUMERATE_INSERTION_MODE(InTableBody) \
+ __ENUMERATE_INSERTION_MODE(InRow) \
+ __ENUMERATE_INSERTION_MODE(InCell) \
+ __ENUMERATE_INSERTION_MODE(InSelect) \
+ __ENUMERATE_INSERTION_MODE(InSelectInTable) \
+ __ENUMERATE_INSERTION_MODE(InTemplate) \
+ __ENUMERATE_INSERTION_MODE(AfterBody) \
+ __ENUMERATE_INSERTION_MODE(InFrameset) \
+ __ENUMERATE_INSERTION_MODE(AfterFrameset) \
+ __ENUMERATE_INSERTION_MODE(AfterAfterBody) \
+ __ENUMERATE_INSERTION_MODE(AfterAfterFrameset)
+
+RefPtr<DOM::Document> parse_html_document(const StringView&, const URL&, const String& encoding);
+
+class HTMLDocumentParser {
+public:
+ HTMLDocumentParser(DOM::Document&, const StringView& input, const String& encoding);
+ ~HTMLDocumentParser();
+
+ void run(const URL&);
+
+ DOM::Document& document();
+
+ static NonnullRefPtrVector<DOM::Node> parse_html_fragment(DOM::Element& context_element, const StringView&);
+
+ enum class InsertionMode {
+#define __ENUMERATE_INSERTION_MODE(mode) mode,
+ ENUMERATE_INSERTION_MODES
+#undef __ENUMERATE_INSERTION_MODE
+ };
+
+ InsertionMode insertion_mode() const { return m_insertion_mode; }
+
+ static bool is_special_tag(const FlyString& tag_name, const FlyString& namespace_);
+
+private:
+ const char* insertion_mode_name() const;
+
+ DOM::QuirksMode which_quirks_mode(const HTMLToken&) const;
+
+ void handle_initial(HTMLToken&);
+ void handle_before_html(HTMLToken&);
+ void handle_before_head(HTMLToken&);
+ void handle_in_head(HTMLToken&);
+ void handle_in_head_noscript(HTMLToken&);
+ void handle_after_head(HTMLToken&);
+ void handle_in_body(HTMLToken&);
+ void handle_after_body(HTMLToken&);
+ void handle_after_after_body(HTMLToken&);
+ void handle_text(HTMLToken&);
+ void handle_in_table(HTMLToken&);
+ void handle_in_table_body(HTMLToken&);
+ void handle_in_row(HTMLToken&);
+ void handle_in_cell(HTMLToken&);
+ void handle_in_table_text(HTMLToken&);
+ void handle_in_select_in_table(HTMLToken&);
+ void handle_in_select(HTMLToken&);
+ void handle_in_caption(HTMLToken&);
+ void handle_in_column_group(HTMLToken&);
+ void handle_in_template(HTMLToken&);
+ void handle_in_frameset(HTMLToken&);
+ void handle_after_frameset(HTMLToken&);
+ void handle_after_after_frameset(HTMLToken&);
+
+ void stop_parsing() { m_stop_parsing = true; }
+
+ void generate_implied_end_tags(const FlyString& exception = {});
+ void generate_all_implied_end_tags_thoroughly();
+ bool stack_of_open_elements_has_element_with_tag_name_in_scope(const FlyString& tag_name);
+ NonnullRefPtr<DOM::Element> create_element_for(const HTMLToken&, const FlyString& namespace_);
+
+ struct AdjustedInsertionLocation {
+ RefPtr<DOM::Node> parent;
+ RefPtr<DOM::Node> insert_before_sibling;
+ };
+
+ AdjustedInsertionLocation find_appropriate_place_for_inserting_node();
+
+ DOM::Text* find_character_insertion_node();
+ void flush_character_insertions();
+ RefPtr<DOM::Element> insert_foreign_element(const HTMLToken&, const FlyString&);
+ RefPtr<DOM::Element> insert_html_element(const HTMLToken&);
+ DOM::Element& current_node();
+ DOM::Element& adjusted_current_node();
+ DOM::Element& node_before_current_node();
+ void insert_character(u32 data);
+ void insert_comment(HTMLToken&);
+ void reconstruct_the_active_formatting_elements();
+ void close_a_p_element();
+ void process_using_the_rules_for(InsertionMode, HTMLToken&);
+ void process_using_the_rules_for_foreign_content(HTMLToken&);
+ void parse_generic_raw_text_element(HTMLToken&);
+ void increment_script_nesting_level();
+ void decrement_script_nesting_level();
+ size_t script_nesting_level() const { return m_script_nesting_level; }
+ void reset_the_insertion_mode_appropriately();
+
+ void adjust_mathml_attributes(HTMLToken&);
+ void adjust_svg_tag_names(HTMLToken&);
+ void adjust_svg_attributes(HTMLToken&);
+ void adjust_foreign_attributes(HTMLToken&);
+
+ enum AdoptionAgencyAlgorithmOutcome {
+ DoNothing,
+ RunAnyOtherEndTagSteps,
+ };
+
+ AdoptionAgencyAlgorithmOutcome run_the_adoption_agency_algorithm(HTMLToken&);
+ void clear_the_stack_back_to_a_table_context();
+ void clear_the_stack_back_to_a_table_body_context();
+ void clear_the_stack_back_to_a_table_row_context();
+ void close_the_cell();
+
+ InsertionMode m_insertion_mode { InsertionMode::Initial };
+ InsertionMode m_original_insertion_mode { InsertionMode::Initial };
+
+ StackOfOpenElements m_stack_of_open_elements;
+ Vector<InsertionMode> m_stack_of_template_insertion_modes;
+ ListOfActiveFormattingElements m_list_of_active_formatting_elements;
+
+ HTMLTokenizer m_tokenizer;
+
+ bool m_foster_parenting { false };
+ bool m_frameset_ok { true };
+ bool m_parsing_fragment { false };
+ bool m_scripting_enabled { true };
+ bool m_invoked_via_document_write { false };
+ bool m_aborted { false };
+ bool m_parser_pause_flag { false };
+ bool m_stop_parsing { false };
+ size_t m_script_nesting_level { 0 };
+
+ NonnullRefPtr<DOM::Document> m_document;
+ RefPtr<HTMLHeadElement> m_head_element;
+ RefPtr<HTMLFormElement> m_form_element;
+ RefPtr<DOM::Element> m_context_element;
+
+ Vector<HTMLToken> m_pending_table_character_tokens;
+
+ RefPtr<DOM::Text> m_character_insertion_node;
+ StringBuilder m_character_insertion_builder;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/Parser/HTMLToken.cpp b/Userland/Libraries/LibWeb/HTML/Parser/HTMLToken.cpp
new file mode 100644
index 0000000000..53a2357be5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Parser/HTMLToken.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/Parser/HTMLToken.h>
+
+namespace Web::HTML {
+
+String HTMLToken::to_string() const
+{
+ StringBuilder builder;
+
+ switch (type()) {
+ case HTMLToken::Type::DOCTYPE:
+ builder.append("DOCTYPE");
+ builder.append(" { name: '");
+ builder.append(m_doctype.name.to_string());
+ builder.append("' }");
+ break;
+ case HTMLToken::Type::StartTag:
+ builder.append("StartTag");
+ break;
+ case HTMLToken::Type::EndTag:
+ builder.append("EndTag");
+ break;
+ case HTMLToken::Type::Comment:
+ builder.append("Comment");
+ break;
+ case HTMLToken::Type::Character:
+ builder.append("Character");
+ break;
+ case HTMLToken::Type::EndOfFile:
+ builder.append("EndOfFile");
+ break;
+ case HTMLToken::Type::Invalid:
+ ASSERT_NOT_REACHED();
+ }
+
+ if (type() == HTMLToken::Type::StartTag || type() == HTMLToken::Type::EndTag) {
+ builder.append(" { name: '");
+ builder.append(m_tag.tag_name.to_string());
+ builder.append("', { ");
+ for (auto& attribute : m_tag.attributes) {
+ builder.append(attribute.local_name_builder.to_string());
+ builder.append("=\"");
+ builder.append(attribute.value_builder.to_string());
+ builder.append("\" ");
+ }
+ builder.append("} }");
+ }
+
+ if (type() == HTMLToken::Type::Comment || type() == HTMLToken::Type::Character) {
+ builder.append(" { data: '");
+ builder.append(m_comment_or_character.data.to_string());
+ builder.append("' }");
+ }
+
+ return builder.to_string();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/Parser/HTMLToken.h b/Userland/Libraries/LibWeb/HTML/Parser/HTMLToken.h
new file mode 100644
index 0000000000..c2246229c6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Parser/HTMLToken.h
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/Types.h>
+#include <AK/Utf8View.h>
+#include <AK/Vector.h>
+
+namespace Web::HTML {
+
+class HTMLToken {
+ friend class HTMLDocumentParser;
+ friend class HTMLTokenizer;
+
+public:
+ enum class Type {
+ Invalid,
+ DOCTYPE,
+ StartTag,
+ EndTag,
+ Comment,
+ Character,
+ EndOfFile,
+ };
+
+ static HTMLToken make_character(u32 code_point)
+ {
+ HTMLToken token;
+ token.m_type = Type::Character;
+ token.m_comment_or_character.data.append(code_point);
+ return token;
+ }
+
+ static HTMLToken make_start_tag(const FlyString& tag_name)
+ {
+ HTMLToken token;
+ token.m_type = Type::StartTag;
+ token.m_tag.tag_name.append(tag_name);
+ return token;
+ }
+
+ bool is_doctype() const { return m_type == Type::DOCTYPE; }
+ bool is_start_tag() const { return m_type == Type::StartTag; }
+ bool is_end_tag() const { return m_type == Type::EndTag; }
+ bool is_comment() const { return m_type == Type::Comment; }
+ bool is_character() const { return m_type == Type::Character; }
+ bool is_end_of_file() const { return m_type == Type::EndOfFile; }
+
+ u32 code_point() const
+ {
+ ASSERT(is_character());
+ Utf8View view(m_comment_or_character.data.string_view());
+ ASSERT(view.length() == 1);
+ return *view.begin();
+ }
+
+ bool is_parser_whitespace() const
+ {
+ // NOTE: The parser considers '\r' to be whitespace, while the tokenizer does not.
+ if (!is_character())
+ return false;
+ switch (code_point()) {
+ case '\t':
+ case '\n':
+ case '\f':
+ case '\r':
+ case ' ':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ String tag_name() const
+ {
+ ASSERT(is_start_tag() || is_end_tag());
+ return m_tag.tag_name.to_string();
+ }
+
+ bool is_self_closing() const
+ {
+ ASSERT(is_start_tag() || is_end_tag());
+ return m_tag.self_closing;
+ }
+
+ bool has_acknowledged_self_closing_flag() const
+ {
+ ASSERT(is_self_closing());
+ return m_tag.self_closing_acknowledged;
+ }
+
+ void acknowledge_self_closing_flag_if_set()
+ {
+ if (is_self_closing())
+ m_tag.self_closing_acknowledged = true;
+ }
+
+ StringView attribute(const FlyString& attribute_name)
+ {
+ ASSERT(is_start_tag() || is_end_tag());
+ for (auto& attribute : m_tag.attributes) {
+ if (attribute_name == attribute.local_name_builder.string_view())
+ return attribute.value_builder.string_view();
+ }
+ return {};
+ }
+
+ bool has_attribute(const FlyString& attribute_name)
+ {
+ return !attribute(attribute_name).is_null();
+ }
+
+ void adjust_tag_name(const FlyString& old_name, const FlyString& new_name)
+ {
+ ASSERT(is_start_tag() || is_end_tag());
+ if (old_name == m_tag.tag_name.string_view()) {
+ m_tag.tag_name.clear();
+ m_tag.tag_name.append(new_name);
+ }
+ }
+
+ void adjust_attribute_name(const FlyString& old_name, const FlyString& new_name)
+ {
+ ASSERT(is_start_tag() || is_end_tag());
+ for (auto& attribute : m_tag.attributes) {
+ if (old_name == attribute.local_name_builder.string_view()) {
+ attribute.local_name_builder.clear();
+ attribute.local_name_builder.append(new_name);
+ }
+ }
+ }
+
+ void adjust_foreign_attribute(const FlyString& old_name, const FlyString& prefix, const FlyString& local_name, const FlyString& namespace_)
+ {
+ ASSERT(is_start_tag() || is_end_tag());
+ for (auto& attribute : m_tag.attributes) {
+ if (old_name == attribute.local_name_builder.string_view()) {
+ attribute.prefix_builder.clear();
+ attribute.prefix_builder.append(prefix);
+
+ attribute.local_name_builder.clear();
+ attribute.local_name_builder.append(local_name);
+
+ attribute.namespace_builder.clear();
+ attribute.namespace_builder.append(namespace_);
+ }
+ }
+ }
+
+ void drop_attributes()
+ {
+ ASSERT(is_start_tag() || is_end_tag());
+ m_tag.attributes.clear();
+ }
+
+ Type type() const { return m_type; }
+
+ String to_string() const;
+
+private:
+ struct AttributeBuilder {
+ StringBuilder prefix_builder;
+ StringBuilder local_name_builder;
+ StringBuilder namespace_builder;
+ StringBuilder value_builder;
+ };
+
+ Type m_type { Type::Invalid };
+
+ // Type::DOCTYPE
+ struct {
+ // NOTE: "Missing" is a distinct state from the empty string.
+
+ StringBuilder name;
+ bool missing_name { true };
+ StringBuilder public_identifier;
+ bool missing_public_identifier { true };
+ StringBuilder system_identifier;
+ bool missing_system_identifier { true };
+ bool force_quirks { false };
+ } m_doctype;
+
+ // Type::StartTag
+ // Type::EndTag
+ struct {
+ StringBuilder tag_name;
+ bool self_closing { false };
+ bool self_closing_acknowledged { false };
+ Vector<AttributeBuilder> attributes;
+ } m_tag;
+
+ // Type::Comment
+ // Type::Character
+ struct {
+ StringBuilder data;
+ } m_comment_or_character;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/Parser/HTMLTokenizer.cpp b/Userland/Libraries/LibWeb/HTML/Parser/HTMLTokenizer.cpp
new file mode 100644
index 0000000000..1bf1dab3c3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Parser/HTMLTokenizer.cpp
@@ -0,0 +1,2663 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibTextCodec/Decoder.h>
+#include <LibWeb/HTML/Parser/Entities.h>
+#include <LibWeb/HTML/Parser/HTMLToken.h>
+#include <LibWeb/HTML/Parser/HTMLTokenizer.h>
+#include <ctype.h>
+#include <string.h>
+
+namespace Web::HTML {
+
+#pragma GCC diagnostic ignored "-Wunused-label"
+
+//#define TOKENIZER_TRACE
+
+#ifdef TOKENIZER_TRACE
+# define PARSE_ERROR() \
+ do { \
+ dbg() << "Parse error (tokenization)" << __PRETTY_FUNCTION__ << " @ " << __LINE__; \
+ } while (0)
+#else
+# define PARSE_ERROR()
+#endif
+
+#define CONSUME_NEXT_INPUT_CHARACTER \
+ current_input_character = next_code_point();
+
+#define SWITCH_TO(new_state) \
+ do { \
+ will_switch_to(State::new_state); \
+ m_state = State::new_state; \
+ CONSUME_NEXT_INPUT_CHARACTER; \
+ goto new_state; \
+ } while (0)
+
+#define RECONSUME_IN(new_state) \
+ do { \
+ will_reconsume_in(State::new_state); \
+ m_state = State::new_state; \
+ goto new_state; \
+ } while (0)
+
+#define SWITCH_TO_RETURN_STATE \
+ do { \
+ will_switch_to(m_return_state); \
+ m_state = m_return_state; \
+ goto _StartOfFunction; \
+ } while (0)
+
+#define RECONSUME_IN_RETURN_STATE \
+ do { \
+ will_reconsume_in(m_return_state); \
+ m_state = m_return_state; \
+ if (current_input_character.has_value()) \
+ m_utf8_iterator = m_prev_utf8_iterator; \
+ goto _StartOfFunction; \
+ } while (0)
+
+#define SWITCH_TO_AND_EMIT_CURRENT_TOKEN(new_state) \
+ do { \
+ will_switch_to(State::new_state); \
+ m_state = State::new_state; \
+ will_emit(m_current_token); \
+ m_queued_tokens.enqueue(m_current_token); \
+ return m_queued_tokens.dequeue(); \
+ } while (0)
+
+#define EMIT_CHARACTER_AND_RECONSUME_IN(code_point, new_state) \
+ do { \
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point)); \
+ will_reconsume_in(State::new_state); \
+ m_state = State::new_state; \
+ goto new_state; \
+ } while (0)
+
+#define FLUSH_CODEPOINTS_CONSUMED_AS_A_CHARACTER_REFERENCE \
+ do { \
+ for (auto code_point : m_temporary_buffer) { \
+ if (consumed_as_part_of_an_attribute()) { \
+ m_current_token.m_tag.attributes.last().value_builder.append_code_point(code_point); \
+ } else { \
+ create_new_token(HTMLToken::Type::Character); \
+ m_current_token.m_comment_or_character.data.append_code_point(code_point); \
+ m_queued_tokens.enqueue(m_current_token); \
+ } \
+ } \
+ } while (0)
+
+#define DONT_CONSUME_NEXT_INPUT_CHARACTER \
+ do { \
+ m_utf8_iterator = m_prev_utf8_iterator; \
+ } while (0)
+
+#define ON(code_point) \
+ if (current_input_character.has_value() && current_input_character.value() == code_point)
+
+#define ON_EOF \
+ if (!current_input_character.has_value())
+
+#define ON_ASCII_ALPHA \
+ if (current_input_character.has_value() && isalpha(current_input_character.value()))
+
+#define ON_ASCII_ALPHANUMERIC \
+ if (current_input_character.has_value() && isalnum(current_input_character.value()))
+
+#define ON_ASCII_UPPER_ALPHA \
+ if (current_input_character.has_value() && current_input_character.value() >= 'A' && current_input_character.value() <= 'Z')
+
+#define ON_ASCII_LOWER_ALPHA \
+ if (current_input_character.has_value() && current_input_character.value() >= 'a' && current_input_character.value() <= 'z')
+
+#define ON_ASCII_DIGIT \
+ if (current_input_character.has_value() && isdigit(current_input_character.value()))
+
+#define ON_ASCII_HEX_DIGIT \
+ if (current_input_character.has_value() && isxdigit(current_input_character.value()))
+
+#define ON_WHITESPACE \
+ if (current_input_character.has_value() && strchr("\t\n\f ", current_input_character.value()))
+
+#define ANYTHING_ELSE if (1)
+
+#define EMIT_EOF \
+ do { \
+ if (m_has_emitted_eof) \
+ return {}; \
+ m_has_emitted_eof = true; \
+ create_new_token(HTMLToken::Type::EndOfFile); \
+ will_emit(m_current_token); \
+ m_queued_tokens.enqueue(m_current_token); \
+ return m_queued_tokens.dequeue(); \
+ } while (0)
+
+#define EMIT_CURRENT_TOKEN \
+ do { \
+ will_emit(m_current_token); \
+ m_queued_tokens.enqueue(m_current_token); \
+ return m_queued_tokens.dequeue(); \
+ } while (0)
+
+#define EMIT_CHARACTER(code_point) \
+ do { \
+ create_new_token(HTMLToken::Type::Character); \
+ m_current_token.m_comment_or_character.data.append_code_point(code_point); \
+ m_queued_tokens.enqueue(m_current_token); \
+ return m_queued_tokens.dequeue(); \
+ } while (0)
+
+#define EMIT_CURRENT_CHARACTER \
+ EMIT_CHARACTER(current_input_character.value());
+
+#define SWITCH_TO_AND_EMIT_CHARACTER(code_point, new_state) \
+ do { \
+ will_switch_to(State::new_state); \
+ m_state = State::new_state; \
+ EMIT_CHARACTER(code_point); \
+ } while (0)
+
+#define SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(new_state) \
+ SWITCH_TO_AND_EMIT_CHARACTER(current_input_character.value(), new_state)
+
+#define BEGIN_STATE(state) \
+ state: \
+ case State::state: { \
+ { \
+ {
+
+#define END_STATE \
+ ASSERT_NOT_REACHED(); \
+ break; \
+ } \
+ } \
+ }
+
+static inline bool is_surrogate(u32 code_point)
+{
+ return (code_point & 0xfffff800) == 0xd800;
+}
+
+static inline bool is_noncharacter(u32 code_point)
+{
+ return code_point >= 0xfdd0 && (code_point <= 0xfdef || (code_point & 0xfffe) == 0xfffe) && code_point <= 0x10ffff;
+}
+
+static inline bool is_c0_control(u32 code_point)
+{
+ return code_point <= 0x1f;
+}
+
+static inline bool is_control(u32 code_point)
+{
+ return is_c0_control(code_point) || (code_point >= 0x7f && code_point <= 0x9f);
+}
+
+Optional<u32> HTMLTokenizer::next_code_point()
+{
+ if (m_utf8_iterator == m_utf8_view.end())
+ return {};
+ m_prev_utf8_iterator = m_utf8_iterator;
+ ++m_utf8_iterator;
+#ifdef TOKENIZER_TRACE
+ dbg() << "(Tokenizer) Next code_point: " << (char)*m_prev_utf8_iterator;
+#endif
+ return *m_prev_utf8_iterator;
+}
+
+Optional<u32> HTMLTokenizer::peek_code_point(size_t offset) const
+{
+ auto it = m_utf8_iterator;
+ for (size_t i = 0; i < offset && it != m_utf8_view.end(); ++i)
+ ++it;
+ if (it == m_utf8_view.end())
+ return {};
+ return *it;
+}
+
+Optional<HTMLToken> HTMLTokenizer::next_token()
+{
+_StartOfFunction:
+ if (!m_queued_tokens.is_empty())
+ return m_queued_tokens.dequeue();
+
+ for (;;) {
+ auto current_input_character = next_code_point();
+ switch (m_state) {
+ BEGIN_STATE(Data)
+ {
+ ON('&')
+ {
+ m_return_state = State::Data;
+ SWITCH_TO(CharacterReference);
+ }
+ ON('<')
+ {
+ SWITCH_TO(TagOpen);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ EMIT_CURRENT_CHARACTER;
+ }
+ ON_EOF
+ {
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ EMIT_CURRENT_CHARACTER;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(TagOpen)
+ {
+ ON('!')
+ {
+ SWITCH_TO(MarkupDeclarationOpen);
+ }
+ ON('/')
+ {
+ SWITCH_TO(EndTagOpen);
+ }
+ ON_ASCII_ALPHA
+ {
+ create_new_token(HTMLToken::Type::StartTag);
+ RECONSUME_IN(TagName);
+ }
+ ON('?')
+ {
+ PARSE_ERROR();
+ create_new_token(HTMLToken::Type::Comment);
+ RECONSUME_IN(BogusComment);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ EMIT_CHARACTER_AND_RECONSUME_IN('<', Data);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(TagName)
+ {
+ ON_WHITESPACE
+ {
+ SWITCH_TO(BeforeAttributeName);
+ }
+ ON('/')
+ {
+ SWITCH_TO(SelfClosingStartTag);
+ }
+ ON('>')
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_ASCII_UPPER_ALPHA
+ {
+ m_current_token.m_tag.tag_name.append(tolower(current_input_character.value()));
+ continue;
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ m_current_token.m_tag.tag_name.append_code_point(0xFFFD);
+ continue;
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_tag.tag_name.append_code_point(current_input_character.value());
+ continue;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(EndTagOpen)
+ {
+ ON_ASCII_ALPHA
+ {
+ create_new_token(HTMLToken::Type::EndTag);
+ RECONSUME_IN(TagName);
+ }
+ ON('>')
+ {
+ PARSE_ERROR();
+ SWITCH_TO(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ create_new_token(HTMLToken::Type::Comment);
+ RECONSUME_IN(BogusComment);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(MarkupDeclarationOpen)
+ {
+ DONT_CONSUME_NEXT_INPUT_CHARACTER;
+ if (consume_next_if_match("--")) {
+ create_new_token(HTMLToken::Type::Comment);
+ SWITCH_TO(CommentStart);
+ }
+ if (consume_next_if_match("DOCTYPE", CaseSensitivity::CaseInsensitive)) {
+ SWITCH_TO(DOCTYPE);
+ }
+ if (consume_next_if_match("[CDATA[")) {
+ TODO();
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ create_new_token(HTMLToken::Type::Comment);
+ SWITCH_TO(BogusComment);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(BogusComment)
+ {
+ ON('>')
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ m_current_token.m_comment_or_character.data.append_code_point(0xFFFD);
+ continue;
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_comment_or_character.data.append_code_point(current_input_character.value());
+ continue;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(DOCTYPE)
+ {
+ ON_WHITESPACE
+ {
+ SWITCH_TO(BeforeDOCTYPEName);
+ }
+ ON('>')
+ {
+ RECONSUME_IN(BeforeDOCTYPEName);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ create_new_token(HTMLToken::Type::DOCTYPE);
+ m_current_token.m_doctype.force_quirks = true;
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ RECONSUME_IN(BeforeDOCTYPEName);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(BeforeDOCTYPEName)
+ {
+ ON_WHITESPACE
+ {
+ continue;
+ }
+ ON_ASCII_UPPER_ALPHA
+ {
+ create_new_token(HTMLToken::Type::DOCTYPE);
+ m_current_token.m_doctype.name.append(tolower(current_input_character.value()));
+ m_current_token.m_doctype.missing_name = false;
+ SWITCH_TO(DOCTYPEName);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ create_new_token(HTMLToken::Type::DOCTYPE);
+ m_current_token.m_doctype.name.append_code_point(0xFFFD);
+ m_current_token.m_doctype.missing_name = false;
+ SWITCH_TO(DOCTYPEName);
+ }
+ ON('>')
+ {
+ PARSE_ERROR();
+ create_new_token(HTMLToken::Type::DOCTYPE);
+ m_current_token.m_doctype.force_quirks = true;
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ create_new_token(HTMLToken::Type::DOCTYPE);
+ m_current_token.m_doctype.force_quirks = true;
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ create_new_token(HTMLToken::Type::DOCTYPE);
+ m_current_token.m_doctype.name.append_code_point(current_input_character.value());
+ m_current_token.m_doctype.missing_name = false;
+ SWITCH_TO(DOCTYPEName);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(DOCTYPEName)
+ {
+ ON_WHITESPACE
+ {
+ SWITCH_TO(AfterDOCTYPEName);
+ }
+ ON('>')
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_ASCII_UPPER_ALPHA
+ {
+ m_current_token.m_doctype.name.append(tolower(current_input_character.value()));
+ continue;
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.name.append_code_point(0xFFFD);
+ continue;
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_doctype.name.append_code_point(current_input_character.value());
+ continue;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(AfterDOCTYPEName)
+ {
+ ON_WHITESPACE
+ {
+ continue;
+ }
+ ON('>')
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ if (toupper(current_input_character.value()) == 'P' && consume_next_if_match("UBLIC", CaseSensitivity::CaseInsensitive)) {
+ SWITCH_TO(AfterDOCTYPEPublicKeyword);
+ }
+ if (toupper(current_input_character.value()) == 'S' && consume_next_if_match("YSTEM", CaseSensitivity::CaseInsensitive)) {
+ SWITCH_TO(AfterDOCTYPESystemKeyword);
+ }
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ RECONSUME_IN(BogusDOCTYPE);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(AfterDOCTYPEPublicKeyword)
+ {
+ ON_WHITESPACE
+ {
+ SWITCH_TO(BeforeDOCTYPEPublicIdentifier);
+ }
+ ON('"')
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.public_identifier.clear();
+ m_current_token.m_doctype.missing_public_identifier = false;
+ SWITCH_TO(DOCTYPEPublicIdentifierDoubleQuoted);
+ }
+ ON('\'')
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.public_identifier.clear();
+ m_current_token.m_doctype.missing_public_identifier = false;
+ SWITCH_TO(DOCTYPEPublicIdentifierSingleQuoted);
+ }
+ ON('>')
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ RECONSUME_IN(BogusDOCTYPE);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(AfterDOCTYPESystemKeyword)
+ {
+ ON_WHITESPACE
+ {
+ SWITCH_TO(BeforeDOCTYPESystemIdentifier);
+ }
+ ON('"')
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.system_identifier.clear();
+ m_current_token.m_doctype.missing_system_identifier = false;
+ SWITCH_TO(DOCTYPESystemIdentifierDoubleQuoted);
+ }
+ ON('\'')
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.system_identifier.clear();
+ m_current_token.m_doctype.missing_system_identifier = false;
+ SWITCH_TO(DOCTYPESystemIdentifierSingleQuoted);
+ }
+ ON('>')
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ RECONSUME_IN(BogusDOCTYPE);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(BeforeDOCTYPEPublicIdentifier)
+ {
+ ON_WHITESPACE
+ {
+ continue;
+ }
+ ON('"')
+ {
+ m_current_token.m_doctype.public_identifier.clear();
+ m_current_token.m_doctype.missing_public_identifier = false;
+ SWITCH_TO(DOCTYPEPublicIdentifierDoubleQuoted);
+ }
+ ON('\'')
+ {
+ m_current_token.m_doctype.public_identifier.clear();
+ m_current_token.m_doctype.missing_public_identifier = false;
+ SWITCH_TO(DOCTYPEPublicIdentifierSingleQuoted);
+ }
+ ON('>')
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ RECONSUME_IN(BogusDOCTYPE);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(BeforeDOCTYPESystemIdentifier)
+ {
+ ON_WHITESPACE
+ {
+ continue;
+ }
+ ON('"')
+ {
+ m_current_token.m_doctype.system_identifier.clear();
+ m_current_token.m_doctype.missing_system_identifier = false;
+ SWITCH_TO(DOCTYPESystemIdentifierDoubleQuoted);
+ }
+ ON('\'')
+ {
+ m_current_token.m_doctype.system_identifier.clear();
+ m_current_token.m_doctype.missing_system_identifier = false;
+ SWITCH_TO(DOCTYPESystemIdentifierSingleQuoted);
+ }
+ ON('>')
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ RECONSUME_IN(BogusDOCTYPE);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(DOCTYPEPublicIdentifierDoubleQuoted)
+ {
+ ON('"')
+ {
+ SWITCH_TO(AfterDOCTYPEPublicIdentifier);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.public_identifier.append_code_point(0xFFFD);
+ continue;
+ }
+ ON('>')
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_doctype.public_identifier.append_code_point(current_input_character.value());
+ continue;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(DOCTYPEPublicIdentifierSingleQuoted)
+ {
+ ON('\'')
+ {
+ SWITCH_TO(AfterDOCTYPEPublicIdentifier);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.public_identifier.append_code_point(0xFFFD);
+ continue;
+ }
+ ON('>')
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_doctype.public_identifier.append_code_point(current_input_character.value());
+ continue;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(DOCTYPESystemIdentifierDoubleQuoted)
+ {
+ ON('"')
+ {
+ SWITCH_TO(AfterDOCTYPESystemIdentifier);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.system_identifier.append_code_point(0xFFFD);
+ continue;
+ }
+ ON('>')
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_doctype.system_identifier.append_code_point(current_input_character.value());
+ continue;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(DOCTYPESystemIdentifierSingleQuoted)
+ {
+ ON('\'')
+ {
+ SWITCH_TO(AfterDOCTYPESystemIdentifier);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.system_identifier.append_code_point(0xFFFD);
+ continue;
+ }
+ ON('>')
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_doctype.system_identifier.append_code_point(current_input_character.value());
+ continue;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(AfterDOCTYPEPublicIdentifier)
+ {
+ ON_WHITESPACE
+ {
+ SWITCH_TO(BetweenDOCTYPEPublicAndSystemIdentifiers);
+ }
+ ON('>')
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON('"')
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.system_identifier.clear();
+ m_current_token.m_doctype.missing_system_identifier = false;
+ SWITCH_TO(DOCTYPESystemIdentifierDoubleQuoted);
+ }
+ ON('\'')
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.system_identifier.clear();
+ m_current_token.m_doctype.missing_system_identifier = false;
+ SWITCH_TO(DOCTYPESystemIdentifierSingleQuoted);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ RECONSUME_IN(BogusDOCTYPE);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(BetweenDOCTYPEPublicAndSystemIdentifiers)
+ {
+ ON_WHITESPACE
+ {
+ continue;
+ }
+ ON('>')
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON('"')
+ {
+ m_current_token.m_doctype.system_identifier.clear();
+ m_current_token.m_doctype.missing_system_identifier = false;
+ SWITCH_TO(DOCTYPESystemIdentifierDoubleQuoted);
+ }
+ ON('\'')
+ {
+ m_current_token.m_doctype.system_identifier.clear();
+ m_current_token.m_doctype.missing_system_identifier = false;
+ SWITCH_TO(DOCTYPESystemIdentifierSingleQuoted);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ RECONSUME_IN(BogusDOCTYPE);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(AfterDOCTYPESystemIdentifier)
+ {
+ ON_WHITESPACE
+ {
+ continue;
+ }
+ ON('>')
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_current_token.m_doctype.force_quirks = true;
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ RECONSUME_IN(BogusDOCTYPE);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(BogusDOCTYPE)
+ {
+ ON('>')
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ continue;
+ }
+ ON_EOF
+ {
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ continue;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(BeforeAttributeName)
+ {
+ ON_WHITESPACE
+ {
+ continue;
+ }
+ ON('/')
+ {
+ RECONSUME_IN(AfterAttributeName);
+ }
+ ON('>')
+ {
+ RECONSUME_IN(AfterAttributeName);
+ }
+ ON_EOF
+ {
+ RECONSUME_IN(AfterAttributeName);
+ }
+ ON('=')
+ {
+ PARSE_ERROR();
+ auto new_attribute = HTMLToken::AttributeBuilder();
+ new_attribute.local_name_builder.append_code_point(current_input_character.value());
+ m_current_token.m_tag.attributes.append(new_attribute);
+ SWITCH_TO(AttributeName);
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_tag.attributes.append(HTMLToken::AttributeBuilder());
+ RECONSUME_IN(AttributeName);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(SelfClosingStartTag)
+ {
+ ON('>')
+ {
+ m_current_token.m_tag.self_closing = true;
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ RECONSUME_IN(BeforeAttributeName);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(AttributeName)
+ {
+ ON_WHITESPACE
+ {
+ RECONSUME_IN(AfterAttributeName);
+ }
+ ON('/')
+ {
+ RECONSUME_IN(AfterAttributeName);
+ }
+ ON('>')
+ {
+ RECONSUME_IN(AfterAttributeName);
+ }
+ ON_EOF
+ {
+ RECONSUME_IN(AfterAttributeName);
+ }
+ ON('=')
+ {
+ SWITCH_TO(BeforeAttributeValue);
+ }
+ ON_ASCII_UPPER_ALPHA
+ {
+ m_current_token.m_tag.attributes.last().local_name_builder.append_code_point(tolower(current_input_character.value()));
+ continue;
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ m_current_token.m_tag.attributes.last().local_name_builder.append_code_point(0xFFFD);
+ continue;
+ }
+ ON('"')
+ {
+ PARSE_ERROR();
+ goto AnythingElseAttributeName;
+ }
+ ON('\'')
+ {
+ PARSE_ERROR();
+ goto AnythingElseAttributeName;
+ }
+ ON('<')
+ {
+ PARSE_ERROR();
+ goto AnythingElseAttributeName;
+ }
+ ANYTHING_ELSE
+ {
+ AnythingElseAttributeName:
+ m_current_token.m_tag.attributes.last().local_name_builder.append_code_point(current_input_character.value());
+ continue;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(AfterAttributeName)
+ {
+ ON_WHITESPACE
+ {
+ continue;
+ }
+ ON('/')
+ {
+ SWITCH_TO(SelfClosingStartTag);
+ }
+ ON('=')
+ {
+ SWITCH_TO(BeforeAttributeValue);
+ }
+ ON('>')
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_tag.attributes.append(HTMLToken::AttributeBuilder());
+ RECONSUME_IN(AttributeName);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(BeforeAttributeValue)
+ {
+ ON_WHITESPACE
+ {
+ continue;
+ }
+ ON('"')
+ {
+ SWITCH_TO(AttributeValueDoubleQuoted);
+ }
+ ON('\'')
+ {
+ SWITCH_TO(AttributeValueSingleQuoted);
+ }
+ ON('>')
+ {
+ PARSE_ERROR();
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ANYTHING_ELSE
+ {
+ RECONSUME_IN(AttributeValueUnquoted);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(AttributeValueDoubleQuoted)
+ {
+ ON('"')
+ {
+ SWITCH_TO(AfterAttributeValueQuoted);
+ }
+ ON('&')
+ {
+ m_return_state = State::AttributeValueDoubleQuoted;
+ SWITCH_TO(CharacterReference);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ m_current_token.m_tag.attributes.last().value_builder.append_code_point(0xFFFD);
+ continue;
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_tag.attributes.last().value_builder.append_code_point(current_input_character.value());
+ continue;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(AttributeValueSingleQuoted)
+ {
+ ON('\'')
+ {
+ SWITCH_TO(AfterAttributeValueQuoted);
+ }
+ ON('&')
+ {
+ m_return_state = State::AttributeValueSingleQuoted;
+ SWITCH_TO(CharacterReference);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ m_current_token.m_tag.attributes.last().value_builder.append_code_point(0xFFFD);
+ continue;
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_tag.attributes.last().value_builder.append_code_point(current_input_character.value());
+ continue;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(AttributeValueUnquoted)
+ {
+ ON_WHITESPACE
+ {
+ SWITCH_TO(BeforeAttributeName);
+ }
+ ON('&')
+ {
+ m_return_state = State::AttributeValueUnquoted;
+ SWITCH_TO(CharacterReference);
+ }
+ ON('>')
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ m_current_token.m_tag.attributes.last().value_builder.append_code_point(0xFFFD);
+ continue;
+ }
+ ON('"')
+ {
+ PARSE_ERROR();
+ goto AnythingElseAttributeValueUnquoted;
+ }
+ ON('\'')
+ {
+ PARSE_ERROR();
+ goto AnythingElseAttributeValueUnquoted;
+ }
+ ON('<')
+ {
+ PARSE_ERROR();
+ goto AnythingElseAttributeValueUnquoted;
+ }
+ ON('=')
+ {
+ PARSE_ERROR();
+ goto AnythingElseAttributeValueUnquoted;
+ }
+ ON('`')
+ {
+ PARSE_ERROR();
+ goto AnythingElseAttributeValueUnquoted;
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ AnythingElseAttributeValueUnquoted:
+ m_current_token.m_tag.attributes.last().value_builder.append_code_point(current_input_character.value());
+ continue;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(AfterAttributeValueQuoted)
+ {
+ ON_WHITESPACE
+ {
+ SWITCH_TO(BeforeAttributeName);
+ }
+ ON('/')
+ {
+ SWITCH_TO(SelfClosingStartTag);
+ }
+ ON('>')
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ RECONSUME_IN(BeforeAttributeName);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(CommentStart)
+ {
+ ON('-')
+ {
+ SWITCH_TO(CommentStartDash);
+ }
+ ON('>')
+ {
+ PARSE_ERROR();
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ANYTHING_ELSE
+ {
+ RECONSUME_IN(Comment);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(CommentStartDash)
+ {
+ ON('-')
+ {
+ SWITCH_TO(CommentEnd);
+ }
+ ON('>')
+ {
+ PARSE_ERROR();
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_comment_or_character.data.append('-');
+ RECONSUME_IN(Comment);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(Comment)
+ {
+ ON('<')
+ {
+ m_current_token.m_comment_or_character.data.append_code_point(current_input_character.value());
+ SWITCH_TO(CommentLessThanSign);
+ }
+ ON('-')
+ {
+ SWITCH_TO(CommentEndDash);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ m_current_token.m_comment_or_character.data.append_code_point(0xFFFD);
+ continue;
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_comment_or_character.data.append_code_point(current_input_character.value());
+ continue;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(CommentEnd)
+ {
+ ON('>')
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON('!')
+ {
+ SWITCH_TO(CommentEndBang);
+ }
+ ON('-')
+ {
+ m_current_token.m_comment_or_character.data.append('-');
+ continue;
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_comment_or_character.data.append('-');
+ RECONSUME_IN(Comment);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(CommentEndBang)
+ {
+ ON('-')
+ {
+ m_current_token.m_comment_or_character.data.append("--!");
+ SWITCH_TO(CommentEndDash);
+ }
+ ON('>')
+ {
+ PARSE_ERROR();
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_comment_or_character.data.append("--!");
+ RECONSUME_IN(Comment);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(CommentEndDash)
+ {
+ ON('-')
+ {
+ SWITCH_TO(CommentEnd);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ m_queued_tokens.enqueue(m_current_token);
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ m_current_token.m_comment_or_character.data.append('-');
+ RECONSUME_IN(Comment);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(CommentLessThanSign)
+ {
+ ON('!')
+ {
+ m_current_token.m_comment_or_character.data.append_code_point(current_input_character.value());
+ SWITCH_TO(CommentLessThanSignBang);
+ }
+ ON('<')
+ {
+ m_current_token.m_comment_or_character.data.append_code_point(current_input_character.value());
+ continue;
+ }
+ ANYTHING_ELSE
+ {
+ RECONSUME_IN(Comment);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(CommentLessThanSignBang)
+ {
+ ON('-')
+ {
+ SWITCH_TO(CommentLessThanSignBangDash);
+ }
+ ANYTHING_ELSE
+ {
+ RECONSUME_IN(Comment);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(CommentLessThanSignBangDash)
+ {
+ ON('-')
+ {
+ SWITCH_TO(CommentLessThanSignBangDashDash);
+ }
+ ANYTHING_ELSE
+ {
+ RECONSUME_IN(CommentEndDash);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(CommentLessThanSignBangDashDash)
+ {
+ ON('>')
+ {
+ RECONSUME_IN(CommentEnd);
+ }
+ ON_EOF
+ {
+ RECONSUME_IN(CommentEnd);
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ RECONSUME_IN(CommentEnd);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(CharacterReference)
+ {
+ m_temporary_buffer.clear();
+ m_temporary_buffer.append('&');
+
+ ON_ASCII_ALPHANUMERIC
+ {
+ RECONSUME_IN(NamedCharacterReference);
+ }
+ ON('#')
+ {
+ m_temporary_buffer.append(current_input_character.value());
+ SWITCH_TO(NumericCharacterReference);
+ }
+ ANYTHING_ELSE
+ {
+ FLUSH_CODEPOINTS_CONSUMED_AS_A_CHARACTER_REFERENCE;
+ RECONSUME_IN_RETURN_STATE;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(NamedCharacterReference)
+ {
+ size_t byte_offset = m_utf8_view.byte_offset_of(m_prev_utf8_iterator);
+
+ auto match = HTML::code_points_from_entity(m_decoded_input.substring_view(byte_offset, m_decoded_input.length() - byte_offset - 1));
+
+ if (match.has_value()) {
+ for (size_t i = 0; i < match.value().entity.length() - 1; ++i) {
+ m_prev_utf8_iterator = m_utf8_iterator;
+ ++m_utf8_iterator;
+ }
+ for (auto ch : match.value().entity)
+ m_temporary_buffer.append(ch);
+
+ if (consumed_as_part_of_an_attribute() && !match.value().entity.ends_with(';')) {
+ auto next_code_point = peek_code_point(0);
+ if (next_code_point.has_value() && (next_code_point.value() == '=' || isalnum(next_code_point.value()))) {
+ FLUSH_CODEPOINTS_CONSUMED_AS_A_CHARACTER_REFERENCE;
+ SWITCH_TO_RETURN_STATE;
+ }
+ }
+
+ if (!match.value().entity.ends_with(';')) {
+ PARSE_ERROR();
+ }
+
+ m_temporary_buffer.clear();
+ m_temporary_buffer.append(match.value().code_points);
+
+ FLUSH_CODEPOINTS_CONSUMED_AS_A_CHARACTER_REFERENCE;
+ SWITCH_TO_RETURN_STATE;
+ } else {
+ FLUSH_CODEPOINTS_CONSUMED_AS_A_CHARACTER_REFERENCE;
+ // FIXME: This should be SWITCH_TO, but we always lose the first character on this path, so just reconsume it.
+ // I can't wrap my head around how to do it as the spec says.
+ RECONSUME_IN(AmbiguousAmpersand);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(AmbiguousAmpersand)
+ {
+ ON_ASCII_ALPHANUMERIC
+ {
+ if (consumed_as_part_of_an_attribute()) {
+ m_current_token.m_tag.attributes.last().value_builder.append_code_point(current_input_character.value());
+ continue;
+ } else {
+ EMIT_CURRENT_CHARACTER;
+ }
+ }
+ ON(';')
+ {
+ PARSE_ERROR();
+ RECONSUME_IN_RETURN_STATE;
+ }
+ ANYTHING_ELSE
+ {
+ RECONSUME_IN_RETURN_STATE;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(NumericCharacterReference)
+ {
+ m_character_reference_code = 0;
+
+ ON('X')
+ {
+ m_temporary_buffer.append(current_input_character.value());
+ SWITCH_TO(HexadecimalCharacterReferenceStart);
+ }
+ ON('x')
+ {
+ m_temporary_buffer.append(current_input_character.value());
+ SWITCH_TO(HexadecimalCharacterReferenceStart);
+ }
+ ANYTHING_ELSE
+ {
+ RECONSUME_IN(DecimalCharacterReferenceStart);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(HexadecimalCharacterReferenceStart)
+ {
+ ON_ASCII_HEX_DIGIT
+ {
+ RECONSUME_IN(HexadecimalCharacterReference);
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ FLUSH_CODEPOINTS_CONSUMED_AS_A_CHARACTER_REFERENCE;
+ RECONSUME_IN_RETURN_STATE;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(DecimalCharacterReferenceStart)
+ {
+ ON_ASCII_DIGIT
+ {
+ RECONSUME_IN(DecimalCharacterReference);
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ FLUSH_CODEPOINTS_CONSUMED_AS_A_CHARACTER_REFERENCE;
+ RECONSUME_IN_RETURN_STATE;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(HexadecimalCharacterReference)
+ {
+ ON_ASCII_DIGIT
+ {
+ m_character_reference_code *= 16;
+ m_character_reference_code += current_input_character.value() - 0x30;
+ continue;
+ }
+ ON_ASCII_UPPER_ALPHA
+ {
+ m_character_reference_code *= 16;
+ m_character_reference_code += current_input_character.value() - 0x37;
+ continue;
+ }
+ ON_ASCII_LOWER_ALPHA
+ {
+ m_character_reference_code *= 16;
+ m_character_reference_code += current_input_character.value() - 0x57;
+ continue;
+ }
+ ON(';')
+ {
+ SWITCH_TO(NumericCharacterReferenceEnd);
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ RECONSUME_IN(NumericCharacterReferenceEnd);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(DecimalCharacterReference)
+ {
+ ON_ASCII_DIGIT
+ {
+ m_character_reference_code *= 10;
+ m_character_reference_code += current_input_character.value() - 0x30;
+ continue;
+ }
+ ON(';')
+ {
+ SWITCH_TO(NumericCharacterReferenceEnd);
+ }
+ ANYTHING_ELSE
+ {
+ PARSE_ERROR();
+ RECONSUME_IN(NumericCharacterReferenceEnd);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(NumericCharacterReferenceEnd)
+ {
+ DONT_CONSUME_NEXT_INPUT_CHARACTER;
+
+ if (m_character_reference_code == 0) {
+ PARSE_ERROR();
+ m_character_reference_code = 0xFFFD;
+ }
+ if (m_character_reference_code > 0x10ffff) {
+ PARSE_ERROR();
+ m_character_reference_code = 0xFFFD;
+ }
+ if (is_surrogate(m_character_reference_code)) {
+ PARSE_ERROR();
+ m_character_reference_code = 0xFFFD;
+ }
+ if (is_noncharacter(m_character_reference_code)) {
+ PARSE_ERROR();
+ }
+ if (m_character_reference_code == 0xd || (is_control(m_character_reference_code) && !isspace(m_character_reference_code))) {
+ PARSE_ERROR();
+ constexpr struct {
+ u32 number;
+ u32 code_point;
+ } conversion_table[] = {
+ { 0x80, 0x20AC },
+ { 0x82, 0x201A },
+ { 0x83, 0x0192 },
+ { 0x84, 0x201E },
+ { 0x85, 0x2026 },
+ { 0x86, 0x2020 },
+ { 0x87, 0x2021 },
+ { 0x88, 0x02C6 },
+ { 0x89, 0x2030 },
+ { 0x8A, 0x0160 },
+ { 0x8B, 0x2039 },
+ { 0x8C, 0x0152 },
+ { 0x8E, 0x017D },
+ { 0x91, 0x2018 },
+ { 0x92, 0x2019 },
+ { 0x93, 0x201C },
+ { 0x94, 0x201D },
+ { 0x95, 0x2022 },
+ { 0x96, 0x2013 },
+ { 0x97, 0x2014 },
+ { 0x98, 0x02DC },
+ { 0x99, 0x2122 },
+ { 0x9A, 0x0161 },
+ { 0x9B, 0x203A },
+ { 0x9C, 0x0153 },
+ { 0x9E, 0x017E },
+ { 0x9F, 0x0178 },
+ };
+ for (auto& entry : conversion_table) {
+ if (m_character_reference_code == entry.number) {
+ m_character_reference_code = entry.code_point;
+ break;
+ }
+ }
+ }
+
+ m_temporary_buffer.clear();
+ m_temporary_buffer.append(m_character_reference_code);
+ FLUSH_CODEPOINTS_CONSUMED_AS_A_CHARACTER_REFERENCE;
+ SWITCH_TO_RETURN_STATE;
+ }
+ END_STATE
+
+ BEGIN_STATE(RCDATA)
+ {
+ ON('&')
+ {
+ m_return_state = State::RCDATA;
+ SWITCH_TO(CharacterReference);
+ }
+ ON('<')
+ {
+ SWITCH_TO(RCDATALessThanSign);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ EMIT_CHARACTER(0xFFFD);
+ }
+ ON_EOF
+ {
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ EMIT_CURRENT_CHARACTER;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(RCDATALessThanSign)
+ {
+ ON('/')
+ {
+ m_temporary_buffer.clear();
+ SWITCH_TO(RCDATAEndTagOpen);
+ }
+ ANYTHING_ELSE
+ {
+ EMIT_CHARACTER_AND_RECONSUME_IN('<', RCDATA);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(RCDATAEndTagOpen)
+ {
+ ON_ASCII_ALPHA
+ {
+ create_new_token(HTMLToken::Type::EndTag);
+ RECONSUME_IN(RCDATAEndTagName);
+ }
+ ANYTHING_ELSE
+ {
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ RECONSUME_IN(RCDATA);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(RCDATAEndTagName)
+ {
+ ON_WHITESPACE
+ {
+ if (!current_end_tag_token_is_appropriate()) {
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer)
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ RECONSUME_IN(RCDATA);
+ }
+ SWITCH_TO(BeforeAttributeName);
+ }
+ ON('/')
+ {
+ if (!current_end_tag_token_is_appropriate()) {
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer)
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ RECONSUME_IN(RCDATA);
+ }
+ SWITCH_TO(SelfClosingStartTag);
+ }
+ ON('>')
+ {
+ if (!current_end_tag_token_is_appropriate()) {
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer)
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ RECONSUME_IN(RCDATA);
+ }
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_ASCII_UPPER_ALPHA
+ {
+ m_current_token.m_tag.tag_name.append(tolower(current_input_character.value()));
+ m_temporary_buffer.append(current_input_character.value());
+ continue;
+ }
+ ON_ASCII_LOWER_ALPHA
+ {
+ m_current_token.m_tag.tag_name.append_code_point(current_input_character.value());
+ m_temporary_buffer.append(current_input_character.value());
+ continue;
+ }
+ ANYTHING_ELSE
+ {
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer)
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ RECONSUME_IN(RCDATA);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(RAWTEXT)
+ {
+ ON('<')
+ {
+ SWITCH_TO(RAWTEXTLessThanSign);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ EMIT_CHARACTER(0xFFFD);
+ }
+ ON_EOF
+ {
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ EMIT_CURRENT_CHARACTER;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(RAWTEXTLessThanSign)
+ {
+ ON('/')
+ {
+ m_temporary_buffer.clear();
+ SWITCH_TO(RAWTEXTEndTagOpen);
+ }
+ ANYTHING_ELSE
+ {
+ EMIT_CHARACTER_AND_RECONSUME_IN('<', RAWTEXT);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(RAWTEXTEndTagOpen)
+ {
+ ON_ASCII_ALPHA
+ {
+ create_new_token(HTMLToken::Type::EndTag);
+ RECONSUME_IN(RAWTEXTEndTagName);
+ }
+ ANYTHING_ELSE
+ {
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ RECONSUME_IN(RAWTEXT);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(RAWTEXTEndTagName)
+ {
+ ON_WHITESPACE
+ {
+ if (!current_end_tag_token_is_appropriate()) {
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer)
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ RECONSUME_IN(RAWTEXT);
+ }
+ SWITCH_TO(BeforeAttributeName);
+ }
+ ON('/')
+ {
+ if (!current_end_tag_token_is_appropriate()) {
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer)
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ RECONSUME_IN(RAWTEXT);
+ }
+ SWITCH_TO(SelfClosingStartTag);
+ }
+ ON('>')
+ {
+ if (!current_end_tag_token_is_appropriate()) {
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer)
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ RECONSUME_IN(RAWTEXT);
+ }
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ }
+ ON_ASCII_UPPER_ALPHA
+ {
+ m_current_token.m_tag.tag_name.append(tolower(current_input_character.value()));
+ m_temporary_buffer.append(current_input_character.value());
+ continue;
+ }
+ ON_ASCII_LOWER_ALPHA
+ {
+ m_current_token.m_tag.tag_name.append(current_input_character.value());
+ m_temporary_buffer.append(current_input_character.value());
+ continue;
+ }
+ ANYTHING_ELSE
+ {
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer)
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ RECONSUME_IN(RAWTEXT);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptData)
+ {
+ ON('<')
+ {
+ SWITCH_TO(ScriptDataLessThanSign);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ EMIT_CHARACTER(0xFFFD);
+ }
+ ON_EOF
+ {
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ EMIT_CURRENT_CHARACTER;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(PLAINTEXT)
+ {
+ ON(0)
+ {
+ PARSE_ERROR();
+ EMIT_CHARACTER(0xFFFD);
+ }
+ ON_EOF
+ {
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ EMIT_CURRENT_CHARACTER;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataLessThanSign)
+ {
+ ON('/')
+ {
+ m_temporary_buffer.clear();
+ SWITCH_TO(ScriptDataEndTagOpen);
+ }
+ ON('!')
+ {
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('!'));
+ SWITCH_TO(ScriptDataEscapeStart);
+ }
+ ANYTHING_ELSE
+ {
+ EMIT_CHARACTER_AND_RECONSUME_IN('<', ScriptData);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataEscapeStart)
+ {
+ ON('-')
+ {
+ SWITCH_TO_AND_EMIT_CHARACTER('-', ScriptDataEscapeStartDash);
+ }
+ ANYTHING_ELSE
+ {
+ RECONSUME_IN(ScriptData);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataEscapeStartDash)
+ {
+ ON('-')
+ {
+ SWITCH_TO_AND_EMIT_CHARACTER('-', ScriptDataEscapedDashDash);
+ }
+ ANYTHING_ELSE
+ {
+ RECONSUME_IN(ScriptData);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataEscapedDashDash)
+ {
+ ON('-')
+ {
+ EMIT_CHARACTER('-');
+ }
+ ON('<')
+ {
+ SWITCH_TO(ScriptDataEscapedLessThanSign);
+ }
+ ON('>')
+ {
+ SWITCH_TO_AND_EMIT_CHARACTER('>', ScriptData);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ SWITCH_TO_AND_EMIT_CHARACTER(0xFFFD, ScriptDataEscaped);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataEscaped);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataEscapedLessThanSign)
+ {
+ ON('/')
+ {
+ m_temporary_buffer.clear();
+ SWITCH_TO(ScriptDataEscapedEndTagOpen);
+ }
+ ON_ASCII_ALPHA
+ {
+ m_temporary_buffer.clear();
+ EMIT_CHARACTER_AND_RECONSUME_IN('<', ScriptDataDoubleEscapeStart);
+ }
+ ANYTHING_ELSE
+ {
+ EMIT_CHARACTER_AND_RECONSUME_IN('<', ScriptDataEscaped);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataEscapedEndTagOpen)
+ {
+ ON_ASCII_ALPHA
+ {
+ create_new_token(HTMLToken::Type::EndTag);
+ RECONSUME_IN(ScriptDataEscapedEndTagName);
+ }
+ ANYTHING_ELSE
+ {
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ RECONSUME_IN(ScriptDataEscaped);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataEscapedEndTagName)
+ {
+ ON_WHITESPACE
+ {
+ if (current_end_tag_token_is_appropriate())
+ SWITCH_TO(BeforeAttributeName);
+
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer) {
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ }
+ RECONSUME_IN(ScriptDataEscaped);
+ }
+ ON('/')
+ {
+ if (current_end_tag_token_is_appropriate())
+ SWITCH_TO(SelfClosingStartTag);
+
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer) {
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ }
+ RECONSUME_IN(ScriptDataEscaped);
+ }
+ ON('>')
+ {
+ if (current_end_tag_token_is_appropriate())
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer) {
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ }
+ RECONSUME_IN(ScriptDataEscaped);
+ }
+ ON_ASCII_UPPER_ALPHA
+ {
+ m_current_token.m_tag.tag_name.append(tolower(current_input_character.value()));
+ m_temporary_buffer.append(current_input_character.value());
+ continue;
+ }
+ ON_ASCII_LOWER_ALPHA
+ {
+ m_current_token.m_tag.tag_name.append(current_input_character.value());
+ m_temporary_buffer.append(current_input_character.value());
+ continue;
+ }
+ ANYTHING_ELSE
+ {
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer) {
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ }
+ RECONSUME_IN(ScriptDataEscaped);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataDoubleEscapeStart)
+ {
+ auto temporary_buffer_equal_to_script = [this]() -> bool {
+ if (m_temporary_buffer.size() != 6)
+ return false;
+
+ // FIXME: Is there a better way of doing this?
+ return m_temporary_buffer[0] == 's' && m_temporary_buffer[1] == 'c' && m_temporary_buffer[2] == 'r' && m_temporary_buffer[3] == 'i' && m_temporary_buffer[4] == 'p' && m_temporary_buffer[5] == 't';
+ };
+ ON_WHITESPACE
+ {
+ if (temporary_buffer_equal_to_script())
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataDoubleEscaped);
+ else
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataEscaped);
+ }
+ ON('/')
+ {
+ if (temporary_buffer_equal_to_script())
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataDoubleEscaped);
+ else
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataEscaped);
+ }
+ ON('>')
+ {
+ if (temporary_buffer_equal_to_script())
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataDoubleEscaped);
+ else
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataEscaped);
+ }
+ ON_ASCII_UPPER_ALPHA
+ {
+ m_temporary_buffer.append(tolower(current_input_character.value()));
+ EMIT_CURRENT_CHARACTER;
+ }
+ ON_ASCII_LOWER_ALPHA
+ {
+ m_temporary_buffer.append(current_input_character.value());
+ EMIT_CURRENT_CHARACTER;
+ }
+ ANYTHING_ELSE
+ {
+ RECONSUME_IN(ScriptDataEscaped);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataDoubleEscaped)
+ {
+ ON('-')
+ {
+ SWITCH_TO_AND_EMIT_CHARACTER('-', ScriptDataDoubleEscapedDash);
+ }
+ ON('<')
+ {
+ SWITCH_TO_AND_EMIT_CHARACTER('<', ScriptDataDoubleEscapedLessThanSign);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ EMIT_CHARACTER(0xFFFD);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ EMIT_CURRENT_CHARACTER;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataDoubleEscapedDash)
+ {
+ ON('-')
+ {
+ SWITCH_TO_AND_EMIT_CHARACTER('-', ScriptDataDoubleEscapedDashDash);
+ }
+ ON('<')
+ {
+ SWITCH_TO_AND_EMIT_CHARACTER('<', ScriptDataDoubleEscapedLessThanSign);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ SWITCH_TO_AND_EMIT_CHARACTER(0xFFFD, ScriptDataDoubleEscaped);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataDoubleEscaped);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataDoubleEscapedDashDash)
+ {
+ ON('-')
+ {
+ EMIT_CHARACTER('-');
+ }
+ ON('<')
+ {
+ SWITCH_TO_AND_EMIT_CHARACTER('<', ScriptDataDoubleEscapedLessThanSign);
+ }
+ ON('>')
+ {
+ SWITCH_TO_AND_EMIT_CHARACTER('>', ScriptData);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ SWITCH_TO_AND_EMIT_CHARACTER(0xFFFD, ScriptDataDoubleEscaped);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataDoubleEscaped);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataDoubleEscapedLessThanSign)
+ {
+ ON('/')
+ {
+ m_temporary_buffer.clear();
+ SWITCH_TO_AND_EMIT_CHARACTER('/', ScriptDataDoubleEscapeEnd);
+ }
+ ANYTHING_ELSE
+ {
+ RECONSUME_IN(ScriptDataDoubleEscaped);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataDoubleEscapeEnd)
+ {
+ auto temporary_buffer_equal_to_script = [this]() -> bool {
+ if (m_temporary_buffer.size() != 6)
+ return false;
+
+ // FIXME: Is there a better way of doing this?
+ return m_temporary_buffer[0] == 's' && m_temporary_buffer[1] == 'c' && m_temporary_buffer[2] == 'r' && m_temporary_buffer[3] == 'i' && m_temporary_buffer[4] == 'p' && m_temporary_buffer[5] == 't';
+ };
+ ON_WHITESPACE
+ {
+ if (temporary_buffer_equal_to_script())
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataEscaped);
+ else
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataDoubleEscaped);
+ }
+ ON('/')
+ {
+ if (temporary_buffer_equal_to_script())
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataEscaped);
+ else
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataDoubleEscaped);
+ }
+ ON('>')
+ {
+ if (temporary_buffer_equal_to_script())
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataEscaped);
+ else
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataDoubleEscaped);
+ }
+ ON_ASCII_UPPER_ALPHA
+ {
+ m_temporary_buffer.append(tolower(current_input_character.value()));
+ EMIT_CURRENT_CHARACTER;
+ }
+ ON_ASCII_LOWER_ALPHA
+ {
+ m_temporary_buffer.append(current_input_character.value());
+ EMIT_CURRENT_CHARACTER;
+ }
+ ANYTHING_ELSE
+ {
+ RECONSUME_IN(ScriptDataDoubleEscaped);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataEscapedDash)
+ {
+ ON('-')
+ {
+ SWITCH_TO_AND_EMIT_CHARACTER('-', ScriptDataEscapedDashDash);
+ }
+ ON('<')
+ {
+ SWITCH_TO(ScriptDataEscapedLessThanSign);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ SWITCH_TO_AND_EMIT_CHARACTER(0xFFFD, ScriptDataEscaped);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ SWITCH_TO_AND_EMIT_CURRENT_CHARACTER(ScriptDataEscaped);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataEscaped)
+ {
+ ON('-')
+ {
+ SWITCH_TO_AND_EMIT_CHARACTER('-', ScriptDataEscapedDash);
+ }
+ ON('<')
+ {
+ SWITCH_TO(ScriptDataEscapedLessThanSign);
+ }
+ ON(0)
+ {
+ PARSE_ERROR();
+ EMIT_CHARACTER(0xFFFD);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ EMIT_CURRENT_CHARACTER;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataEndTagOpen)
+ {
+ ON_ASCII_ALPHA
+ {
+ create_new_token(HTMLToken::Type::EndTag);
+ RECONSUME_IN(ScriptDataEndTagName);
+ }
+ ANYTHING_ELSE
+ {
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ RECONSUME_IN(ScriptData);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(ScriptDataEndTagName)
+ {
+ ON_WHITESPACE
+ {
+ if (current_end_tag_token_is_appropriate())
+ SWITCH_TO(BeforeAttributeName);
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer)
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ RECONSUME_IN(ScriptData);
+ }
+ ON('/')
+ {
+ if (current_end_tag_token_is_appropriate())
+ SWITCH_TO(SelfClosingStartTag);
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer)
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ RECONSUME_IN(ScriptData);
+ }
+ ON('>')
+ {
+ if (current_end_tag_token_is_appropriate())
+ SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data);
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer)
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ RECONSUME_IN(ScriptData);
+ }
+ ON_ASCII_UPPER_ALPHA
+ {
+ m_current_token.m_tag.tag_name.append(tolower(current_input_character.value()));
+ m_temporary_buffer.append(current_input_character.value());
+ continue;
+ }
+ ON_ASCII_LOWER_ALPHA
+ {
+ m_current_token.m_tag.tag_name.append(current_input_character.value());
+ m_temporary_buffer.append(current_input_character.value());
+ continue;
+ }
+ ANYTHING_ELSE
+ {
+ m_queued_tokens.enqueue(HTMLToken::make_character('<'));
+ m_queued_tokens.enqueue(HTMLToken::make_character('/'));
+ for (auto code_point : m_temporary_buffer)
+ m_queued_tokens.enqueue(HTMLToken::make_character(code_point));
+ RECONSUME_IN(ScriptData);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(CDATASection)
+ {
+ ON(']')
+ {
+ SWITCH_TO(CDATASectionBracket);
+ }
+ ON_EOF
+ {
+ PARSE_ERROR();
+ EMIT_EOF;
+ }
+ ANYTHING_ELSE
+ {
+ EMIT_CURRENT_CHARACTER;
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(CDATASectionBracket)
+ {
+ ON(']')
+ {
+ SWITCH_TO(CDATASectionEnd);
+ }
+ ANYTHING_ELSE
+ {
+ EMIT_CHARACTER_AND_RECONSUME_IN(']', CDATASection);
+ }
+ }
+ END_STATE
+
+ BEGIN_STATE(CDATASectionEnd)
+ {
+ ON(']')
+ {
+ EMIT_CHARACTER(']');
+ }
+ ON('>')
+ {
+ SWITCH_TO(Data);
+ }
+ ANYTHING_ELSE
+ {
+ m_queued_tokens.enqueue(HTMLToken::make_character(']'));
+ m_queued_tokens.enqueue(HTMLToken::make_character(']'));
+ RECONSUME_IN(CDATASection);
+ }
+ }
+ END_STATE
+
+ default:
+ TODO();
+ }
+ }
+}
+
+bool HTMLTokenizer::consume_next_if_match(const StringView& string, CaseSensitivity case_sensitivity)
+{
+ for (size_t i = 0; i < string.length(); ++i) {
+ auto code_point = peek_code_point(i);
+ if (!code_point.has_value())
+ return false;
+ // FIXME: This should be more Unicode-aware.
+ if (case_sensitivity == CaseSensitivity::CaseInsensitive) {
+ if (code_point.value() < 0x80) {
+ if (tolower(code_point.value()) != tolower(string[i]))
+ return false;
+ continue;
+ }
+ }
+ if (code_point.value() != (u32)string[i])
+ return false;
+ }
+ for (size_t i = 0; i < string.length(); ++i) {
+ m_prev_utf8_iterator = m_utf8_iterator;
+ ++m_utf8_iterator;
+ }
+ return true;
+}
+
+void HTMLTokenizer::create_new_token(HTMLToken::Type type)
+{
+ m_current_token = {};
+ m_current_token.m_type = type;
+}
+
+HTMLTokenizer::HTMLTokenizer(const StringView& input, const String& encoding)
+{
+ auto* decoder = TextCodec::decoder_for(encoding);
+ ASSERT(decoder);
+ m_decoded_input = decoder->to_utf8(input);
+ m_utf8_view = Utf8View(m_decoded_input);
+ m_utf8_iterator = m_utf8_view.begin();
+}
+
+void HTMLTokenizer::will_switch_to([[maybe_unused]] State new_state)
+{
+#ifdef TOKENIZER_TRACE
+ dbg() << "[" << state_name(m_state) << "] Switch to " << state_name(new_state);
+#endif
+}
+
+void HTMLTokenizer::will_reconsume_in([[maybe_unused]] State new_state)
+{
+#ifdef TOKENIZER_TRACE
+ dbg() << "[" << state_name(m_state) << "] Reconsume in " << state_name(new_state);
+#endif
+}
+
+void HTMLTokenizer::switch_to(Badge<HTMLDocumentParser>, State new_state)
+{
+#ifdef TOKENIZER_TRACE
+ dbg() << "[" << state_name(m_state) << "] Parser switches tokenizer state to " << state_name(new_state);
+#endif
+ m_state = new_state;
+}
+
+void HTMLTokenizer::will_emit(HTMLToken& token)
+{
+ if (token.is_start_tag())
+ m_last_emitted_start_tag = token;
+}
+
+bool HTMLTokenizer::current_end_tag_token_is_appropriate() const
+{
+ ASSERT(m_current_token.is_end_tag());
+ if (!m_last_emitted_start_tag.is_start_tag())
+ return false;
+ return m_current_token.tag_name() == m_last_emitted_start_tag.tag_name();
+}
+
+bool HTMLTokenizer::consumed_as_part_of_an_attribute() const
+{
+ return m_return_state == State::AttributeValueUnquoted || m_return_state == State::AttributeValueSingleQuoted || m_return_state == State::AttributeValueDoubleQuoted;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/Parser/HTMLTokenizer.h b/Userland/Libraries/LibWeb/HTML/Parser/HTMLTokenizer.h
new file mode 100644
index 0000000000..787bc12b46
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Parser/HTMLTokenizer.h
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Queue.h>
+#include <AK/StringView.h>
+#include <AK/Types.h>
+#include <AK/Utf8View.h>
+#include <LibWeb/Forward.h>
+#include <LibWeb/HTML/Parser/HTMLToken.h>
+
+namespace Web::HTML {
+
+#define ENUMERATE_TOKENIZER_STATES \
+ __ENUMERATE_TOKENIZER_STATE(Data) \
+ __ENUMERATE_TOKENIZER_STATE(RCDATA) \
+ __ENUMERATE_TOKENIZER_STATE(RAWTEXT) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptData) \
+ __ENUMERATE_TOKENIZER_STATE(PLAINTEXT) \
+ __ENUMERATE_TOKENIZER_STATE(TagOpen) \
+ __ENUMERATE_TOKENIZER_STATE(EndTagOpen) \
+ __ENUMERATE_TOKENIZER_STATE(TagName) \
+ __ENUMERATE_TOKENIZER_STATE(RCDATALessThanSign) \
+ __ENUMERATE_TOKENIZER_STATE(RCDATAEndTagOpen) \
+ __ENUMERATE_TOKENIZER_STATE(RCDATAEndTagName) \
+ __ENUMERATE_TOKENIZER_STATE(RAWTEXTLessThanSign) \
+ __ENUMERATE_TOKENIZER_STATE(RAWTEXTEndTagOpen) \
+ __ENUMERATE_TOKENIZER_STATE(RAWTEXTEndTagName) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataLessThanSign) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataEndTagOpen) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataEndTagName) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataEscapeStart) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataEscapeStartDash) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataEscaped) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataEscapedDash) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataEscapedDashDash) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataEscapedLessThanSign) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataEscapedEndTagOpen) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataEscapedEndTagName) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataDoubleEscapeStart) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataDoubleEscaped) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataDoubleEscapedDash) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataDoubleEscapedDashDash) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataDoubleEscapedLessThanSign) \
+ __ENUMERATE_TOKENIZER_STATE(ScriptDataDoubleEscapeEnd) \
+ __ENUMERATE_TOKENIZER_STATE(BeforeAttributeName) \
+ __ENUMERATE_TOKENIZER_STATE(AttributeName) \
+ __ENUMERATE_TOKENIZER_STATE(AfterAttributeName) \
+ __ENUMERATE_TOKENIZER_STATE(BeforeAttributeValue) \
+ __ENUMERATE_TOKENIZER_STATE(AttributeValueDoubleQuoted) \
+ __ENUMERATE_TOKENIZER_STATE(AttributeValueSingleQuoted) \
+ __ENUMERATE_TOKENIZER_STATE(AttributeValueUnquoted) \
+ __ENUMERATE_TOKENIZER_STATE(AfterAttributeValueQuoted) \
+ __ENUMERATE_TOKENIZER_STATE(SelfClosingStartTag) \
+ __ENUMERATE_TOKENIZER_STATE(BogusComment) \
+ __ENUMERATE_TOKENIZER_STATE(MarkupDeclarationOpen) \
+ __ENUMERATE_TOKENIZER_STATE(CommentStart) \
+ __ENUMERATE_TOKENIZER_STATE(CommentStartDash) \
+ __ENUMERATE_TOKENIZER_STATE(Comment) \
+ __ENUMERATE_TOKENIZER_STATE(CommentLessThanSign) \
+ __ENUMERATE_TOKENIZER_STATE(CommentLessThanSignBang) \
+ __ENUMERATE_TOKENIZER_STATE(CommentLessThanSignBangDash) \
+ __ENUMERATE_TOKENIZER_STATE(CommentLessThanSignBangDashDash) \
+ __ENUMERATE_TOKENIZER_STATE(CommentEndDash) \
+ __ENUMERATE_TOKENIZER_STATE(CommentEnd) \
+ __ENUMERATE_TOKENIZER_STATE(CommentEndBang) \
+ __ENUMERATE_TOKENIZER_STATE(DOCTYPE) \
+ __ENUMERATE_TOKENIZER_STATE(BeforeDOCTYPEName) \
+ __ENUMERATE_TOKENIZER_STATE(DOCTYPEName) \
+ __ENUMERATE_TOKENIZER_STATE(AfterDOCTYPEName) \
+ __ENUMERATE_TOKENIZER_STATE(AfterDOCTYPEPublicKeyword) \
+ __ENUMERATE_TOKENIZER_STATE(BeforeDOCTYPEPublicIdentifier) \
+ __ENUMERATE_TOKENIZER_STATE(DOCTYPEPublicIdentifierDoubleQuoted) \
+ __ENUMERATE_TOKENIZER_STATE(DOCTYPEPublicIdentifierSingleQuoted) \
+ __ENUMERATE_TOKENIZER_STATE(AfterDOCTYPEPublicIdentifier) \
+ __ENUMERATE_TOKENIZER_STATE(BetweenDOCTYPEPublicAndSystemIdentifiers) \
+ __ENUMERATE_TOKENIZER_STATE(AfterDOCTYPESystemKeyword) \
+ __ENUMERATE_TOKENIZER_STATE(BeforeDOCTYPESystemIdentifier) \
+ __ENUMERATE_TOKENIZER_STATE(DOCTYPESystemIdentifierDoubleQuoted) \
+ __ENUMERATE_TOKENIZER_STATE(DOCTYPESystemIdentifierSingleQuoted) \
+ __ENUMERATE_TOKENIZER_STATE(AfterDOCTYPESystemIdentifier) \
+ __ENUMERATE_TOKENIZER_STATE(BogusDOCTYPE) \
+ __ENUMERATE_TOKENIZER_STATE(CDATASection) \
+ __ENUMERATE_TOKENIZER_STATE(CDATASectionBracket) \
+ __ENUMERATE_TOKENIZER_STATE(CDATASectionEnd) \
+ __ENUMERATE_TOKENIZER_STATE(CharacterReference) \
+ __ENUMERATE_TOKENIZER_STATE(NamedCharacterReference) \
+ __ENUMERATE_TOKENIZER_STATE(AmbiguousAmpersand) \
+ __ENUMERATE_TOKENIZER_STATE(NumericCharacterReference) \
+ __ENUMERATE_TOKENIZER_STATE(HexadecimalCharacterReferenceStart) \
+ __ENUMERATE_TOKENIZER_STATE(DecimalCharacterReferenceStart) \
+ __ENUMERATE_TOKENIZER_STATE(HexadecimalCharacterReference) \
+ __ENUMERATE_TOKENIZER_STATE(DecimalCharacterReference) \
+ __ENUMERATE_TOKENIZER_STATE(NumericCharacterReferenceEnd)
+
+class HTMLTokenizer {
+public:
+ explicit HTMLTokenizer(const StringView& input, const String& encoding);
+
+ enum class State {
+#define __ENUMERATE_TOKENIZER_STATE(state) state,
+ ENUMERATE_TOKENIZER_STATES
+#undef __ENUMERATE_TOKENIZER_STATE
+ };
+
+ Optional<HTMLToken> next_token();
+
+ void switch_to(Badge<HTMLDocumentParser>, State new_state);
+
+ void set_blocked(bool b) { m_blocked = b; }
+ bool is_blocked() const { return m_blocked; }
+
+ String source() const { return m_decoded_input; }
+
+private:
+ Optional<u32> next_code_point();
+ Optional<u32> peek_code_point(size_t offset) const;
+ bool consume_next_if_match(const StringView&, CaseSensitivity = CaseSensitivity::CaseSensitive);
+ void create_new_token(HTMLToken::Type);
+ bool current_end_tag_token_is_appropriate() const;
+
+ static const char* state_name(State state)
+ {
+ switch (state) {
+#define __ENUMERATE_TOKENIZER_STATE(state) \
+ case State::state: \
+ return #state;
+ ENUMERATE_TOKENIZER_STATES
+#undef __ENUMERATE_TOKENIZER_STATE
+ };
+ ASSERT_NOT_REACHED();
+ }
+
+ void will_emit(HTMLToken&);
+ void will_switch_to(State);
+ void will_reconsume_in(State);
+
+ bool consumed_as_part_of_an_attribute() const;
+
+ State m_state { State::Data };
+ State m_return_state { State::Data };
+
+ Vector<u32> m_temporary_buffer;
+
+ String m_decoded_input;
+
+ StringView m_input;
+
+ Utf8View m_utf8_view;
+ AK::Utf8CodepointIterator m_utf8_iterator;
+ AK::Utf8CodepointIterator m_prev_utf8_iterator;
+
+ HTMLToken m_current_token;
+
+ HTMLToken m_last_emitted_start_tag;
+
+ bool m_has_emitted_eof { false };
+
+ Queue<HTMLToken> m_queued_tokens;
+
+ u32 m_character_reference_code { 0 };
+
+ bool m_blocked { false };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/Parser/ListOfActiveFormattingElements.cpp b/Userland/Libraries/LibWeb/HTML/Parser/ListOfActiveFormattingElements.cpp
new file mode 100644
index 0000000000..bad6140632
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Parser/ListOfActiveFormattingElements.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/HTML/Parser/ListOfActiveFormattingElements.h>
+
+namespace Web::HTML {
+
+ListOfActiveFormattingElements::~ListOfActiveFormattingElements()
+{
+}
+
+void ListOfActiveFormattingElements::add(DOM::Element& element)
+{
+ // FIXME: Implement the Noah's Ark clause https://html.spec.whatwg.org/multipage/parsing.html#push-onto-the-list-of-active-formatting-elements
+ m_entries.append({ element });
+}
+
+void ListOfActiveFormattingElements::add_marker()
+{
+ m_entries.append({ nullptr });
+}
+
+bool ListOfActiveFormattingElements::contains(const DOM::Element& element) const
+{
+ for (auto& entry : m_entries) {
+ if (entry.element == &element)
+ return true;
+ }
+ return false;
+}
+
+DOM::Element* ListOfActiveFormattingElements::last_element_with_tag_name_before_marker(const FlyString& tag_name)
+{
+ for (ssize_t i = m_entries.size() - 1; i >= 0; --i) {
+ auto& entry = m_entries[i];
+ if (entry.is_marker())
+ return nullptr;
+ if (entry.element->local_name() == tag_name)
+ return entry.element;
+ }
+ return nullptr;
+}
+
+void ListOfActiveFormattingElements::remove(DOM::Element& element)
+{
+ m_entries.remove_first_matching([&](auto& entry) {
+ return entry.element == &element;
+ });
+}
+
+void ListOfActiveFormattingElements::clear_up_to_the_last_marker()
+{
+ while (!m_entries.is_empty()) {
+ auto entry = m_entries.take_last();
+ if (entry.is_marker())
+ break;
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/Parser/ListOfActiveFormattingElements.h b/Userland/Libraries/LibWeb/HTML/Parser/ListOfActiveFormattingElements.h
new file mode 100644
index 0000000000..65064be7e5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Parser/ListOfActiveFormattingElements.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtrVector.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::HTML {
+
+class ListOfActiveFormattingElements {
+public:
+ ListOfActiveFormattingElements() { }
+ ~ListOfActiveFormattingElements();
+
+ struct Entry {
+ bool is_marker() const { return !element; }
+
+ RefPtr<DOM::Element> element;
+ };
+
+ bool is_empty() const { return m_entries.is_empty(); }
+ bool contains(const DOM::Element&) const;
+
+ void add(DOM::Element& element);
+ void add_marker();
+
+ void remove(DOM::Element&);
+
+ const Vector<Entry>& entries() const { return m_entries; }
+ Vector<Entry>& entries() { return m_entries; }
+
+ DOM::Element* last_element_with_tag_name_before_marker(const FlyString& tag_name);
+
+ void clear_up_to_the_last_marker();
+
+private:
+ Vector<Entry> m_entries;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/Parser/StackOfOpenElements.cpp b/Userland/Libraries/LibWeb/HTML/Parser/StackOfOpenElements.cpp
new file mode 100644
index 0000000000..2406711bff
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Parser/StackOfOpenElements.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/HTML/Parser/HTMLDocumentParser.h>
+#include <LibWeb/HTML/Parser/StackOfOpenElements.h>
+
+namespace Web::HTML {
+
+static Vector<FlyString> s_base_list { "applet", "caption", "html", "table", "td", "th", "marquee", "object", "template" };
+
+StackOfOpenElements::~StackOfOpenElements()
+{
+}
+
+bool StackOfOpenElements::has_in_scope_impl(const FlyString& tag_name, const Vector<FlyString>& list) const
+{
+ for (ssize_t i = m_elements.size() - 1; i >= 0; --i) {
+ auto& node = m_elements.at(i);
+ if (node.local_name() == tag_name)
+ return true;
+ if (list.contains_slow(node.local_name()))
+ return false;
+ }
+ ASSERT_NOT_REACHED();
+}
+
+bool StackOfOpenElements::has_in_scope(const FlyString& tag_name) const
+{
+ return has_in_scope_impl(tag_name, s_base_list);
+}
+
+bool StackOfOpenElements::has_in_scope_impl(const DOM::Element& target_node, const Vector<FlyString>& list) const
+{
+ for (ssize_t i = m_elements.size() - 1; i >= 0; --i) {
+ auto& node = m_elements.at(i);
+ if (&node == &target_node)
+ return true;
+ if (list.contains_slow(node.local_name()))
+ return false;
+ }
+ ASSERT_NOT_REACHED();
+}
+
+bool StackOfOpenElements::has_in_scope(const DOM::Element& target_node) const
+{
+ return has_in_scope_impl(target_node, s_base_list);
+}
+
+bool StackOfOpenElements::has_in_button_scope(const FlyString& tag_name) const
+{
+ auto list = s_base_list;
+ list.append("button");
+ return has_in_scope_impl(tag_name, list);
+}
+
+bool StackOfOpenElements::has_in_table_scope(const FlyString& tag_name) const
+{
+ return has_in_scope_impl(tag_name, { "html", "table", "template" });
+}
+
+bool StackOfOpenElements::has_in_list_item_scope(const FlyString& tag_name) const
+{
+ auto list = s_base_list;
+ list.append("ol");
+ list.append("ul");
+ return has_in_scope_impl(tag_name, list);
+}
+
+bool StackOfOpenElements::has_in_select_scope(const FlyString& tag_name) const
+{
+ return has_in_scope_impl(tag_name, { "option", "optgroup" });
+}
+
+bool StackOfOpenElements::contains(const DOM::Element& element) const
+{
+ for (auto& element_on_stack : m_elements) {
+ if (&element == &element_on_stack)
+ return true;
+ }
+ return false;
+}
+
+bool StackOfOpenElements::contains(const FlyString& tag_name) const
+{
+ for (auto& element_on_stack : m_elements) {
+ if (element_on_stack.local_name() == tag_name)
+ return true;
+ }
+ return false;
+}
+
+void StackOfOpenElements::pop_until_an_element_with_tag_name_has_been_popped(const FlyString& tag_name)
+{
+ while (m_elements.last().local_name() != tag_name)
+ pop();
+ pop();
+}
+
+DOM::Element* StackOfOpenElements::topmost_special_node_below(const DOM::Element& formatting_element)
+{
+ DOM::Element* found_element = nullptr;
+ for (ssize_t i = m_elements.size() - 1; i >= 0; --i) {
+ auto& element = m_elements[i];
+ if (&element == &formatting_element)
+ break;
+ if (HTMLDocumentParser::is_special_tag(element.local_name(), element.namespace_()))
+ found_element = &element;
+ }
+ return found_element;
+}
+
+StackOfOpenElements::LastElementResult StackOfOpenElements::last_element_with_tag_name(const FlyString& tag_name)
+{
+ for (ssize_t i = m_elements.size() - 1; i >= 0; --i) {
+ auto& element = m_elements[i];
+ if (element.local_name() == tag_name)
+ return { &element, i };
+ }
+ return { nullptr, -1 };
+}
+
+DOM::Element* StackOfOpenElements::element_before(const DOM::Element& target)
+{
+ bool found_target = false;
+ for (ssize_t i = m_elements.size() - 1; i >= 0; --i) {
+ auto& element = m_elements[i];
+ if (&element == &target) {
+ found_target = true;
+ } else if (found_target)
+ return &element;
+ }
+ return nullptr;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/Parser/StackOfOpenElements.h b/Userland/Libraries/LibWeb/HTML/Parser/StackOfOpenElements.h
new file mode 100644
index 0000000000..2a62bbf53e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Parser/StackOfOpenElements.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtrVector.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::HTML {
+
+class StackOfOpenElements {
+public:
+ StackOfOpenElements() { }
+ ~StackOfOpenElements();
+
+ DOM::Element& first() { return m_elements.first(); }
+ DOM::Element& last() { return m_elements.last(); }
+
+ bool is_empty() const { return m_elements.is_empty(); }
+ void push(NonnullRefPtr<DOM::Element> element) { m_elements.append(move(element)); }
+ NonnullRefPtr<DOM::Element> pop() { return m_elements.take_last(); }
+
+ const DOM::Element& current_node() const { return m_elements.last(); }
+ DOM::Element& current_node() { return m_elements.last(); }
+
+ bool has_in_scope(const FlyString& tag_name) const;
+ bool has_in_button_scope(const FlyString& tag_name) const;
+ bool has_in_table_scope(const FlyString& tag_name) const;
+ bool has_in_list_item_scope(const FlyString& tag_name) const;
+ bool has_in_select_scope(const FlyString& tag_name) const;
+
+ bool has_in_scope(const DOM::Element&) const;
+
+ bool contains(const DOM::Element&) const;
+ bool contains(const FlyString& tag_name) const;
+
+ const NonnullRefPtrVector<DOM::Element>& elements() const { return m_elements; }
+ NonnullRefPtrVector<DOM::Element>& elements() { return m_elements; }
+
+ void pop_until_an_element_with_tag_name_has_been_popped(const FlyString&);
+
+ DOM::Element* topmost_special_node_below(const DOM::Element&);
+
+ struct LastElementResult {
+ DOM::Element* element;
+ ssize_t index;
+ };
+ LastElementResult last_element_with_tag_name(const FlyString&);
+ DOM::Element* element_before(const DOM::Element&);
+
+private:
+ bool has_in_scope_impl(const FlyString& tag_name, const Vector<FlyString>&) const;
+ bool has_in_scope_impl(const DOM::Element& target_node, const Vector<FlyString>&) const;
+
+ NonnullRefPtrVector<DOM::Element> m_elements;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/SubmitEvent.h b/Userland/Libraries/LibWeb/HTML/SubmitEvent.h
new file mode 100644
index 0000000000..06c4b6fb0b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/SubmitEvent.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/DOM/Event.h>
+
+namespace Web::HTML {
+
+class SubmitEvent final : public DOM::Event {
+public:
+ using WrapperType = Bindings::SubmitEventWrapper;
+
+ static NonnullRefPtr<SubmitEvent> create(const FlyString& event_name, RefPtr<HTMLElement> submitter)
+ {
+ return adopt(*new SubmitEvent(event_name, submitter));
+ }
+
+ const RefPtr<HTMLElement> submitter() const { return m_submitter; }
+
+private:
+ SubmitEvent(const FlyString& event_name, RefPtr<HTMLElement> submitter)
+ : DOM::Event(event_name)
+ , m_submitter(submitter)
+ {
+ }
+
+ RefPtr<HTMLElement> m_submitter;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/SubmitEvent.idl b/Userland/Libraries/LibWeb/HTML/SubmitEvent.idl
new file mode 100644
index 0000000000..c816ae663f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/SubmitEvent.idl
@@ -0,0 +1,5 @@
+interface SubmitEvent : Event {
+
+ readonly attribute HTMLElement? submitter;
+
+};
diff --git a/Userland/Libraries/LibWeb/HTML/TagNames.cpp b/Userland/Libraries/LibWeb/HTML/TagNames.cpp
new file mode 100644
index 0000000000..1ab76b7376
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/TagNames.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/TagNames.h>
+
+namespace Web::HTML::TagNames {
+
+#define __ENUMERATE_HTML_TAG(name) FlyString name;
+ENUMERATE_HTML_TAGS
+#undef __ENUMERATE_HTML_TAG
+
+[[gnu::constructor]] static void initialize()
+{
+ static bool s_initialized = false;
+ if (s_initialized)
+ return;
+
+#define __ENUMERATE_HTML_TAG(name) \
+ name = #name;
+ ENUMERATE_HTML_TAGS
+#undef __ENUMERATE_HTML_TAG
+
+ template_ = "template";
+
+ s_initialized = true;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/TagNames.h b/Userland/Libraries/LibWeb/HTML/TagNames.h
new file mode 100644
index 0000000000..9d7f924e2a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/TagNames.h
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+
+namespace Web::HTML::TagNames {
+
+#define ENUMERATE_HTML_TAGS \
+ __ENUMERATE_HTML_TAG(a) \
+ __ENUMERATE_HTML_TAG(abbr) \
+ __ENUMERATE_HTML_TAG(acronym) \
+ __ENUMERATE_HTML_TAG(address) \
+ __ENUMERATE_HTML_TAG(applet) \
+ __ENUMERATE_HTML_TAG(area) \
+ __ENUMERATE_HTML_TAG(article) \
+ __ENUMERATE_HTML_TAG(aside) \
+ __ENUMERATE_HTML_TAG(audio) \
+ __ENUMERATE_HTML_TAG(b) \
+ __ENUMERATE_HTML_TAG(base) \
+ __ENUMERATE_HTML_TAG(basefont) \
+ __ENUMERATE_HTML_TAG(bdi) \
+ __ENUMERATE_HTML_TAG(bdo) \
+ __ENUMERATE_HTML_TAG(bgsound) \
+ __ENUMERATE_HTML_TAG(big) \
+ __ENUMERATE_HTML_TAG(blink) \
+ __ENUMERATE_HTML_TAG(blockquote) \
+ __ENUMERATE_HTML_TAG(body) \
+ __ENUMERATE_HTML_TAG(br) \
+ __ENUMERATE_HTML_TAG(button) \
+ __ENUMERATE_HTML_TAG(canvas) \
+ __ENUMERATE_HTML_TAG(caption) \
+ __ENUMERATE_HTML_TAG(center) \
+ __ENUMERATE_HTML_TAG(cite) \
+ __ENUMERATE_HTML_TAG(code) \
+ __ENUMERATE_HTML_TAG(col) \
+ __ENUMERATE_HTML_TAG(colgroup) \
+ __ENUMERATE_HTML_TAG(data) \
+ __ENUMERATE_HTML_TAG(datalist) \
+ __ENUMERATE_HTML_TAG(dd) \
+ __ENUMERATE_HTML_TAG(del) \
+ __ENUMERATE_HTML_TAG(details) \
+ __ENUMERATE_HTML_TAG(dfn) \
+ __ENUMERATE_HTML_TAG(dialog) \
+ __ENUMERATE_HTML_TAG(dir) \
+ __ENUMERATE_HTML_TAG(div) \
+ __ENUMERATE_HTML_TAG(dl) \
+ __ENUMERATE_HTML_TAG(dt) \
+ __ENUMERATE_HTML_TAG(em) \
+ __ENUMERATE_HTML_TAG(embed) \
+ __ENUMERATE_HTML_TAG(fieldset) \
+ __ENUMERATE_HTML_TAG(figcaption) \
+ __ENUMERATE_HTML_TAG(figure) \
+ __ENUMERATE_HTML_TAG(font) \
+ __ENUMERATE_HTML_TAG(footer) \
+ __ENUMERATE_HTML_TAG(form) \
+ __ENUMERATE_HTML_TAG(frame) \
+ __ENUMERATE_HTML_TAG(frameset) \
+ __ENUMERATE_HTML_TAG(h1) \
+ __ENUMERATE_HTML_TAG(h2) \
+ __ENUMERATE_HTML_TAG(h3) \
+ __ENUMERATE_HTML_TAG(h4) \
+ __ENUMERATE_HTML_TAG(h5) \
+ __ENUMERATE_HTML_TAG(h6) \
+ __ENUMERATE_HTML_TAG(head) \
+ __ENUMERATE_HTML_TAG(header) \
+ __ENUMERATE_HTML_TAG(hgroup) \
+ __ENUMERATE_HTML_TAG(hr) \
+ __ENUMERATE_HTML_TAG(html) \
+ __ENUMERATE_HTML_TAG(i) \
+ __ENUMERATE_HTML_TAG(iframe) \
+ __ENUMERATE_HTML_TAG(image) \
+ __ENUMERATE_HTML_TAG(img) \
+ __ENUMERATE_HTML_TAG(input) \
+ __ENUMERATE_HTML_TAG(ins) \
+ __ENUMERATE_HTML_TAG(kbd) \
+ __ENUMERATE_HTML_TAG(keygen) \
+ __ENUMERATE_HTML_TAG(label) \
+ __ENUMERATE_HTML_TAG(legend) \
+ __ENUMERATE_HTML_TAG(li) \
+ __ENUMERATE_HTML_TAG(link) \
+ __ENUMERATE_HTML_TAG(listing) \
+ __ENUMERATE_HTML_TAG(main) \
+ __ENUMERATE_HTML_TAG(map) \
+ __ENUMERATE_HTML_TAG(mark) \
+ __ENUMERATE_HTML_TAG(marquee) \
+ __ENUMERATE_HTML_TAG(math) \
+ __ENUMERATE_HTML_TAG(menu) \
+ __ENUMERATE_HTML_TAG(meta) \
+ __ENUMERATE_HTML_TAG(meter) \
+ __ENUMERATE_HTML_TAG(nav) \
+ __ENUMERATE_HTML_TAG(nobr) \
+ __ENUMERATE_HTML_TAG(noembed) \
+ __ENUMERATE_HTML_TAG(noframes) \
+ __ENUMERATE_HTML_TAG(noscript) \
+ __ENUMERATE_HTML_TAG(object) \
+ __ENUMERATE_HTML_TAG(ol) \
+ __ENUMERATE_HTML_TAG(optgroup) \
+ __ENUMERATE_HTML_TAG(option) \
+ __ENUMERATE_HTML_TAG(output) \
+ __ENUMERATE_HTML_TAG(p) \
+ __ENUMERATE_HTML_TAG(param) \
+ __ENUMERATE_HTML_TAG(picture) \
+ __ENUMERATE_HTML_TAG(path) \
+ __ENUMERATE_HTML_TAG(plaintext) \
+ __ENUMERATE_HTML_TAG(pre) \
+ __ENUMERATE_HTML_TAG(progress) \
+ __ENUMERATE_HTML_TAG(q) \
+ __ENUMERATE_HTML_TAG(ruby) \
+ __ENUMERATE_HTML_TAG(rb) \
+ __ENUMERATE_HTML_TAG(rp) \
+ __ENUMERATE_HTML_TAG(rt) \
+ __ENUMERATE_HTML_TAG(rtc) \
+ __ENUMERATE_HTML_TAG(s) \
+ __ENUMERATE_HTML_TAG(samp) \
+ __ENUMERATE_HTML_TAG(script) \
+ __ENUMERATE_HTML_TAG(section) \
+ __ENUMERATE_HTML_TAG(select) \
+ __ENUMERATE_HTML_TAG(slot) \
+ __ENUMERATE_HTML_TAG(small) \
+ __ENUMERATE_HTML_TAG(source) \
+ __ENUMERATE_HTML_TAG(span) \
+ __ENUMERATE_HTML_TAG(strike) \
+ __ENUMERATE_HTML_TAG(strong) \
+ __ENUMERATE_HTML_TAG(style) \
+ __ENUMERATE_HTML_TAG(sub) \
+ __ENUMERATE_HTML_TAG(sup) \
+ __ENUMERATE_HTML_TAG(summary) \
+ __ENUMERATE_HTML_TAG(svg) \
+ __ENUMERATE_HTML_TAG(table) \
+ __ENUMERATE_HTML_TAG(tbody) \
+ __ENUMERATE_HTML_TAG(td) \
+ __ENUMERATE_HTML_TAG(template_) \
+ __ENUMERATE_HTML_TAG(textarea) \
+ __ENUMERATE_HTML_TAG(tfoot) \
+ __ENUMERATE_HTML_TAG(th) \
+ __ENUMERATE_HTML_TAG(thead) \
+ __ENUMERATE_HTML_TAG(time) \
+ __ENUMERATE_HTML_TAG(title) \
+ __ENUMERATE_HTML_TAG(tr) \
+ __ENUMERATE_HTML_TAG(track) \
+ __ENUMERATE_HTML_TAG(tt) \
+ __ENUMERATE_HTML_TAG(u) \
+ __ENUMERATE_HTML_TAG(ul) \
+ __ENUMERATE_HTML_TAG(var) \
+ __ENUMERATE_HTML_TAG(video) \
+ __ENUMERATE_HTML_TAG(wbr) \
+ __ENUMERATE_HTML_TAG(xmp)
+
+#define __ENUMERATE_HTML_TAG(name) extern FlyString name;
+ENUMERATE_HTML_TAGS
+#undef __ENUMERATE_HTML_TAG
+
+}
diff --git a/Userland/Libraries/LibWeb/HighResolutionTime/Performance.cpp b/Userland/Libraries/LibWeb/HighResolutionTime/Performance.cpp
new file mode 100644
index 0000000000..cdefa63eff
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HighResolutionTime/Performance.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Bindings/PerformanceWrapper.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/DOM/EventDispatcher.h>
+#include <LibWeb/DOM/Window.h>
+#include <LibWeb/HighResolutionTime/Performance.h>
+
+namespace Web::HighResolutionTime {
+
+Performance::Performance(DOM::Window& window)
+ : DOM::EventTarget(static_cast<Bindings::ScriptExecutionContext&>(window.document()))
+ , m_window(window)
+{
+ m_timer.start();
+}
+
+Performance::~Performance()
+{
+}
+
+double Performance::time_origin() const
+{
+ auto origin = m_timer.origin_time();
+ return (origin.tv_sec * 1000.0) + (origin.tv_usec / 1000.0);
+}
+
+void Performance::ref_event_target()
+{
+ m_window.ref();
+}
+
+void Performance::unref_event_target()
+{
+ m_window.unref();
+}
+
+bool Performance::dispatch_event(NonnullRefPtr<DOM::Event> event)
+{
+ return DOM::EventDispatcher::dispatch(*this, event);
+}
+
+Bindings::EventTargetWrapper* Performance::create_wrapper(JS::GlobalObject& global_object)
+{
+ return Bindings::wrap(global_object, *this);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HighResolutionTime/Performance.h b/Userland/Libraries/LibWeb/HighResolutionTime/Performance.h
new file mode 100644
index 0000000000..d5a9effbf5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HighResolutionTime/Performance.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/StdLibExtras.h>
+#include <LibCore/ElapsedTimer.h>
+#include <LibWeb/Bindings/Wrappable.h>
+#include <LibWeb/DOM/EventTarget.h>
+
+namespace Web::HighResolutionTime {
+
+class Performance final
+ : public DOM::EventTarget
+ , public Bindings::Wrappable {
+public:
+ using WrapperType = Bindings::PerformanceWrapper;
+ using AllowOwnPtr = AK::TrueType;
+
+ explicit Performance(DOM::Window&);
+ ~Performance();
+
+ double now() const { return m_timer.elapsed(); }
+ double time_origin() const;
+
+ virtual void ref_event_target() override;
+ virtual void unref_event_target() override;
+
+ virtual bool dispatch_event(NonnullRefPtr<DOM::Event>) override;
+ virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
+
+private:
+ DOM::Window& m_window;
+ Core::ElapsedTimer m_timer;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/HighResolutionTime/Performance.idl b/Userland/Libraries/LibWeb/HighResolutionTime/Performance.idl
new file mode 100644
index 0000000000..889760a08a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HighResolutionTime/Performance.idl
@@ -0,0 +1,4 @@
+interface Performance : EventTarget {
+ double now();
+ readonly attribute double timeOrigin;
+};
diff --git a/Userland/Libraries/LibWeb/InProcessWebView.cpp b/Userland/Libraries/LibWeb/InProcessWebView.cpp
new file mode 100644
index 0000000000..84b8dfec07
--- /dev/null
+++ b/Userland/Libraries/LibWeb/InProcessWebView.cpp
@@ -0,0 +1,435 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LexicalPath.h>
+#include <AK/URL.h>
+#include <LibCore/File.h>
+#include <LibCore/MimeData.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/Clipboard.h>
+#include <LibGUI/MessageBox.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/ImageDecoder.h>
+#include <LibGfx/ShareableBitmap.h>
+#include <LibJS/Runtime/Value.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/DOM/ElementFactory.h>
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/Dump.h>
+#include <LibWeb/HTML/HTMLAnchorElement.h>
+#include <LibWeb/HTML/HTMLImageElement.h>
+#include <LibWeb/HTML/Parser/HTMLDocumentParser.h>
+#include <LibWeb/InProcessWebView.h>
+#include <LibWeb/Layout/BreakNode.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+#include <LibWeb/Layout/Node.h>
+#include <LibWeb/Layout/TextNode.h>
+#include <LibWeb/Loader/ResourceLoader.h>
+#include <LibWeb/Page/EventHandler.h>
+#include <LibWeb/Page/Frame.h>
+#include <LibWeb/Painting/PaintContext.h>
+#include <LibWeb/UIEvents/MouseEvent.h>
+#include <stdio.h>
+
+//#define SELECTION_DEBUG
+
+REGISTER_WIDGET(Web, InProcessWebView)
+
+namespace Web {
+
+InProcessWebView::InProcessWebView()
+ : m_page(make<Page>(*this))
+{
+ set_should_hide_unnecessary_scrollbars(true);
+ set_background_role(ColorRole::Base);
+ set_focus_policy(GUI::FocusPolicy::StrongFocus);
+
+ m_copy_action = GUI::CommonActions::make_copy_action([this](auto&) {
+ GUI::Clipboard::the().set_plain_text(selected_text());
+ });
+
+ m_select_all_action = GUI::CommonActions::make_select_all_action([this](auto&) {
+ select_all();
+ });
+}
+
+InProcessWebView::~InProcessWebView()
+{
+}
+
+void InProcessWebView::select_all()
+{
+ auto* layout_root = this->layout_root();
+ if (!layout_root)
+ return;
+
+ const Layout::Node* first_layout_node = layout_root;
+
+ for (;;) {
+ auto* next = first_layout_node->next_in_pre_order();
+ if (!next)
+ break;
+ first_layout_node = next;
+ if (is<Layout::TextNode>(*first_layout_node))
+ break;
+ }
+
+ const Layout::Node* last_layout_node = first_layout_node;
+
+ for (const Layout::Node* layout_node = first_layout_node; layout_node; layout_node = layout_node->next_in_pre_order()) {
+ if (is<Layout::TextNode>(*layout_node))
+ last_layout_node = layout_node;
+ }
+
+ ASSERT(first_layout_node);
+ ASSERT(last_layout_node);
+
+ int last_layout_node_index_in_node = 0;
+ if (is<Layout::TextNode>(*last_layout_node))
+ last_layout_node_index_in_node = downcast<Layout::TextNode>(*last_layout_node).text_for_rendering().length() - 1;
+
+ layout_root->set_selection({ { first_layout_node, 0 }, { last_layout_node, last_layout_node_index_in_node } });
+ update();
+}
+
+String InProcessWebView::selected_text() const
+{
+ return page().focused_frame().selected_text();
+}
+
+void InProcessWebView::page_did_layout()
+{
+ ASSERT(layout_root());
+ set_content_size(layout_root()->size().to_type<int>());
+}
+
+void InProcessWebView::page_did_change_title(const String& title)
+{
+ if (on_title_change)
+ on_title_change(title);
+}
+
+void InProcessWebView::page_did_set_document_in_main_frame(DOM::Document* document)
+{
+ if (on_set_document)
+ on_set_document(document);
+ layout_and_sync_size();
+ scroll_to_top();
+ update();
+}
+
+void InProcessWebView::page_did_start_loading(const URL& url)
+{
+ if (on_load_start)
+ on_load_start(url);
+}
+
+void InProcessWebView::page_did_finish_loading(const URL& url)
+{
+ if (on_load_finish)
+ on_load_finish(url);
+}
+
+void InProcessWebView::page_did_change_selection()
+{
+ update();
+}
+
+void InProcessWebView::page_did_request_cursor_change(Gfx::StandardCursor cursor)
+{
+ set_override_cursor(cursor);
+}
+
+void InProcessWebView::page_did_request_context_menu(const Gfx::IntPoint& content_position)
+{
+ if (on_context_menu_request)
+ on_context_menu_request(screen_relative_rect().location().translated(to_widget_position(content_position)));
+}
+
+void InProcessWebView::page_did_request_link_context_menu(const Gfx::IntPoint& content_position, const URL& url, [[maybe_unused]] const String& target, [[maybe_unused]] unsigned modifiers)
+{
+ if (on_link_context_menu_request)
+ on_link_context_menu_request(url, screen_relative_rect().location().translated(to_widget_position(content_position)));
+}
+
+void InProcessWebView::page_did_request_image_context_menu(const Gfx::IntPoint& content_position, const URL& url, [[maybe_unused]] const String& target, [[maybe_unused]] unsigned modifiers, const Gfx::Bitmap* bitmap)
+{
+ if (!on_image_context_menu_request)
+ return;
+ Gfx::ShareableBitmap shareable_bitmap;
+ if (bitmap)
+ shareable_bitmap = Gfx::ShareableBitmap(*bitmap);
+ on_image_context_menu_request(url, screen_relative_rect().location().translated(to_widget_position(content_position)), shareable_bitmap);
+}
+
+void InProcessWebView::page_did_click_link(const URL& url, const String& target, unsigned modifiers)
+{
+ if (on_link_click)
+ on_link_click(url, target, modifiers);
+}
+
+void InProcessWebView::page_did_middle_click_link(const URL& url, const String& target, unsigned modifiers)
+{
+ if (on_link_middle_click)
+ on_link_middle_click(url, target, modifiers);
+}
+
+void InProcessWebView::page_did_enter_tooltip_area([[maybe_unused]] const Gfx::IntPoint& content_position, const String& title)
+{
+ GUI::Application::the()->show_tooltip(title, nullptr);
+}
+
+void InProcessWebView::page_did_leave_tooltip_area()
+{
+ GUI::Application::the()->hide_tooltip();
+}
+
+void InProcessWebView::page_did_hover_link(const URL& url)
+{
+ if (on_link_hover)
+ on_link_hover(url);
+}
+
+void InProcessWebView::page_did_unhover_link()
+{
+ if (on_link_hover)
+ on_link_hover({});
+}
+
+void InProcessWebView::page_did_invalidate(const Gfx::IntRect&)
+{
+ update();
+}
+
+void InProcessWebView::page_did_change_favicon(const Gfx::Bitmap& bitmap)
+{
+ if (on_favicon_change)
+ on_favicon_change(bitmap);
+}
+
+void InProcessWebView::layout_and_sync_size()
+{
+ if (!document())
+ return;
+
+ bool had_vertical_scrollbar = vertical_scrollbar().is_visible();
+ bool had_horizontal_scrollbar = horizontal_scrollbar().is_visible();
+
+ page().main_frame().set_size(available_size());
+ set_content_size(layout_root()->size().to_type<int>());
+
+ // NOTE: If layout caused us to gain or lose scrollbars, we have to lay out again
+ // since the scrollbars now take up some of the available space.
+ if (had_vertical_scrollbar != vertical_scrollbar().is_visible() || had_horizontal_scrollbar != horizontal_scrollbar().is_visible()) {
+ page().main_frame().set_size(available_size());
+ set_content_size(layout_root()->size().to_type<int>());
+ }
+
+ page().main_frame().set_viewport_scroll_offset({ horizontal_scrollbar().value(), vertical_scrollbar().value() });
+}
+
+void InProcessWebView::resize_event(GUI::ResizeEvent& event)
+{
+ GUI::ScrollableWidget::resize_event(event);
+ layout_and_sync_size();
+}
+
+void InProcessWebView::paint_event(GUI::PaintEvent& event)
+{
+ GUI::Frame::paint_event(event);
+
+ GUI::Painter painter(*this);
+ painter.add_clip_rect(widget_inner_rect());
+ painter.add_clip_rect(event.rect());
+
+ if (!layout_root()) {
+ painter.fill_rect(event.rect(), palette().color(background_role()));
+ return;
+ }
+
+ painter.fill_rect(event.rect(), document()->background_color(palette()));
+
+ if (auto background_bitmap = document()->background_image()) {
+ painter.draw_tiled_bitmap(event.rect(), *background_bitmap);
+ }
+
+ painter.translate(frame_thickness(), frame_thickness());
+ painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
+
+ PaintContext context(painter, palette(), { horizontal_scrollbar().value(), vertical_scrollbar().value() });
+ context.set_should_show_line_box_borders(m_should_show_line_box_borders);
+ context.set_viewport_rect(viewport_rect_in_content_coordinates());
+ context.set_has_focus(is_focused());
+ layout_root()->paint_all_phases(context);
+}
+
+void InProcessWebView::mousemove_event(GUI::MouseEvent& event)
+{
+ page().handle_mousemove(to_content_position(event.position()), event.buttons(), event.modifiers());
+ GUI::ScrollableWidget::mousemove_event(event);
+}
+
+void InProcessWebView::mousedown_event(GUI::MouseEvent& event)
+{
+ page().handle_mousedown(to_content_position(event.position()), event.button(), event.modifiers());
+ GUI::ScrollableWidget::mousedown_event(event);
+}
+
+void InProcessWebView::mouseup_event(GUI::MouseEvent& event)
+{
+ page().handle_mouseup(to_content_position(event.position()), event.button(), event.modifiers());
+ GUI::ScrollableWidget::mouseup_event(event);
+}
+
+void InProcessWebView::keydown_event(GUI::KeyEvent& event)
+{
+ bool page_accepted_event = page().handle_keydown(event.key(), event.modifiers(), event.code_point());
+
+ if (event.modifiers() == 0) {
+ switch (event.key()) {
+ case Key_Home:
+ vertical_scrollbar().set_value(0);
+ break;
+ case Key_End:
+ vertical_scrollbar().set_value(vertical_scrollbar().max());
+ break;
+ case Key_Down:
+ vertical_scrollbar().set_value(vertical_scrollbar().value() + vertical_scrollbar().step());
+ break;
+ case Key_Up:
+ vertical_scrollbar().set_value(vertical_scrollbar().value() - vertical_scrollbar().step());
+ break;
+ case Key_Left:
+ horizontal_scrollbar().set_value(horizontal_scrollbar().value() + horizontal_scrollbar().step());
+ break;
+ case Key_Right:
+ horizontal_scrollbar().set_value(horizontal_scrollbar().value() - horizontal_scrollbar().step());
+ break;
+ case Key_PageDown:
+ vertical_scrollbar().set_value(vertical_scrollbar().value() + frame_inner_rect().height());
+ break;
+ case Key_PageUp:
+ vertical_scrollbar().set_value(vertical_scrollbar().value() - frame_inner_rect().height());
+ break;
+ default:
+ if (!page_accepted_event) {
+ ScrollableWidget::keydown_event(event);
+ return;
+ }
+ break;
+ }
+ }
+
+ event.accept();
+}
+
+URL InProcessWebView::url() const
+{
+ if (!page().main_frame().document())
+ return {};
+ return page().main_frame().document()->url();
+}
+
+void InProcessWebView::reload()
+{
+ load(url());
+}
+
+void InProcessWebView::load_html(const StringView& html, const URL& url)
+{
+ page().main_frame().loader().load_html(html, url);
+}
+
+bool InProcessWebView::load(const URL& url)
+{
+ set_override_cursor(Gfx::StandardCursor::None);
+ return page().main_frame().loader().load(url, FrameLoader::Type::Navigation);
+}
+
+const Layout::InitialContainingBlockBox* InProcessWebView::layout_root() const
+{
+ return document() ? document()->layout_node() : nullptr;
+}
+
+Layout::InitialContainingBlockBox* InProcessWebView::layout_root()
+{
+ if (!document())
+ return nullptr;
+ return const_cast<Layout::InitialContainingBlockBox*>(document()->layout_node());
+}
+
+void InProcessWebView::page_did_request_scroll_into_view(const Gfx::IntRect& rect)
+{
+ scroll_into_view(rect, true, true);
+ set_override_cursor(Gfx::StandardCursor::None);
+}
+
+void InProcessWebView::load_empty_document()
+{
+ page().main_frame().set_document(nullptr);
+}
+
+DOM::Document* InProcessWebView::document()
+{
+ return page().main_frame().document();
+}
+
+const DOM::Document* InProcessWebView::document() const
+{
+ return page().main_frame().document();
+}
+
+void InProcessWebView::set_document(DOM::Document* document)
+{
+ page().main_frame().set_document(document);
+}
+
+void InProcessWebView::did_scroll()
+{
+ page().main_frame().set_viewport_scroll_offset({ horizontal_scrollbar().value(), vertical_scrollbar().value() });
+ page().main_frame().did_scroll({});
+}
+
+void InProcessWebView::drop_event(GUI::DropEvent& event)
+{
+ if (event.mime_data().has_urls()) {
+ if (on_url_drop) {
+ on_url_drop(event.mime_data().urls().first());
+ return;
+ }
+ }
+ ScrollableWidget::drop_event(event);
+}
+
+void InProcessWebView::page_did_request_alert(const String& message)
+{
+ GUI::MessageBox::show(window(), message, "Alert", GUI::MessageBox::Type::Information);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/InProcessWebView.h b/Userland/Libraries/LibWeb/InProcessWebView.h
new file mode 100644
index 0000000000..084c3a01c4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/InProcessWebView.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/URL.h>
+#include <LibGUI/ScrollableWidget.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/Page/Page.h>
+#include <LibWeb/WebViewHooks.h>
+
+namespace Web {
+
+class InProcessWebView final
+ : public GUI::ScrollableWidget
+ , public WebViewHooks
+ , public PageClient {
+ C_OBJECT(InProcessWebView);
+
+public:
+ virtual ~InProcessWebView() override;
+
+ void load_html(const StringView&, const URL&);
+ void load_empty_document();
+
+ DOM::Document* document();
+ const DOM::Document* document() const;
+
+ void set_document(DOM::Document*);
+
+ const Layout::InitialContainingBlockBox* layout_root() const;
+ Layout::InitialContainingBlockBox* layout_root();
+
+ void reload();
+ bool load(const URL&);
+
+ URL url() const;
+
+ void set_should_show_line_box_borders(bool value) { m_should_show_line_box_borders = value; }
+
+ GUI::Action& select_all_action() { return *m_select_all_action; }
+ GUI::Action& copy_action() { return *m_copy_action; }
+
+ String selected_text() const;
+ void select_all();
+
+private:
+ InProcessWebView();
+
+ Page& page() { return *m_page; }
+ const Page& page() const { return *m_page; }
+
+ virtual void resize_event(GUI::ResizeEvent&) override;
+ virtual void paint_event(GUI::PaintEvent&) override;
+ virtual void mousemove_event(GUI::MouseEvent&) override;
+ virtual void mousedown_event(GUI::MouseEvent&) override;
+ virtual void mouseup_event(GUI::MouseEvent&) override;
+ virtual void keydown_event(GUI::KeyEvent&) override;
+ virtual void drop_event(GUI::DropEvent&) override;
+
+ virtual void did_scroll() override;
+
+ // ^Web::PageClient
+ virtual Gfx::Palette palette() const override { return GUI::ScrollableWidget::palette(); }
+ virtual void page_did_change_title(const String&) override;
+ virtual void page_did_set_document_in_main_frame(DOM::Document*) override;
+ virtual void page_did_start_loading(const URL&) override;
+ virtual void page_did_finish_loading(const URL&) override;
+ virtual void page_did_change_selection() override;
+ virtual void page_did_request_cursor_change(Gfx::StandardCursor) override;
+ virtual void page_did_request_context_menu(const Gfx::IntPoint&) override;
+ virtual void page_did_request_link_context_menu(const Gfx::IntPoint&, const URL&, const String& target, unsigned modifiers) override;
+ virtual void page_did_request_image_context_menu(const Gfx::IntPoint&, const URL&, const String& target, unsigned modifiers, const Gfx::Bitmap*) override;
+ virtual void page_did_click_link(const URL&, const String& target, unsigned modifiers) override;
+ virtual void page_did_middle_click_link(const URL&, const String& target, unsigned modifiers) override;
+ virtual void page_did_enter_tooltip_area(const Gfx::IntPoint&, const String&) override;
+ virtual void page_did_leave_tooltip_area() override;
+ virtual void page_did_hover_link(const URL&) override;
+ virtual void page_did_unhover_link() override;
+ virtual void page_did_invalidate(const Gfx::IntRect&) override;
+ virtual void page_did_change_favicon(const Gfx::Bitmap&) override;
+ virtual void page_did_layout() override;
+ virtual void page_did_request_scroll_into_view(const Gfx::IntRect&) override;
+ virtual void page_did_request_alert(const String&) override;
+
+ void layout_and_sync_size();
+
+ bool m_should_show_line_box_borders { false };
+
+ NonnullOwnPtr<Page> m_page;
+
+ RefPtr<GUI::Action> m_copy_action;
+ RefPtr<GUI::Action> m_select_all_action;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/BlockBox.cpp b/Userland/Libraries/LibWeb/Layout/BlockBox.cpp
new file mode 100644
index 0000000000..aab1430afc
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/BlockBox.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Painter.h>
+#include <LibWeb/CSS/StyleResolver.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/Dump.h>
+#include <LibWeb/Layout/BlockBox.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+#include <LibWeb/Layout/InlineFormattingContext.h>
+#include <LibWeb/Layout/InlineNode.h>
+#include <LibWeb/Layout/ReplacedBox.h>
+#include <LibWeb/Layout/TextNode.h>
+#include <LibWeb/Layout/WidgetBox.h>
+#include <math.h>
+
+namespace Web::Layout {
+
+BlockBox::BlockBox(DOM::Document& document, DOM::Node* node, NonnullRefPtr<CSS::StyleProperties> style)
+ : Box(document, node, move(style))
+{
+}
+
+BlockBox::BlockBox(DOM::Document& document, DOM::Node* node, CSS::ComputedValues computed_values)
+ : Box(document, node, move(computed_values))
+{
+}
+
+BlockBox::~BlockBox()
+{
+}
+
+void BlockBox::paint(PaintContext& context, PaintPhase phase)
+{
+ if (!is_visible())
+ return;
+
+ Box::paint(context, phase);
+
+ if (!children_are_inline())
+ return;
+
+ for (auto& line_box : m_line_boxes) {
+ for (auto& fragment : line_box.fragments()) {
+ if (context.should_show_line_box_borders())
+ context.painter().draw_rect(enclosing_int_rect(fragment.absolute_rect()), Color::Green);
+ fragment.paint(context, phase);
+ }
+ }
+
+ // FIXME: Merge this loop with the above somehow..
+ if (phase == PaintPhase::FocusOutline) {
+ for (auto& line_box : m_line_boxes) {
+ for (auto& fragment : line_box.fragments()) {
+ auto* node = fragment.layout_node().dom_node();
+ if (!node)
+ continue;
+ auto* parent = node->parent_element();
+ if (!parent)
+ continue;
+ if (parent->is_focused())
+ context.painter().draw_rect(enclosing_int_rect(fragment.absolute_rect()), context.palette().focus_outline());
+ }
+ }
+ }
+}
+
+HitTestResult BlockBox::hit_test(const Gfx::IntPoint& position, HitTestType type) const
+{
+ if (!children_are_inline())
+ return Box::hit_test(position, type);
+
+ HitTestResult last_good_candidate;
+ for (auto& line_box : m_line_boxes) {
+ for (auto& fragment : line_box.fragments()) {
+ if (is<Box>(fragment.layout_node()) && downcast<Box>(fragment.layout_node()).stacking_context())
+ continue;
+ if (enclosing_int_rect(fragment.absolute_rect()).contains(position)) {
+ if (is<BlockBox>(fragment.layout_node()))
+ return downcast<BlockBox>(fragment.layout_node()).hit_test(position, type);
+ return { fragment.layout_node(), fragment.text_index_at(position.x()) };
+ }
+ if (fragment.absolute_rect().top() <= position.y())
+ last_good_candidate = { fragment.layout_node(), fragment.text_index_at(position.x()) };
+ }
+ }
+
+ if (type == HitTestType::TextCursor && last_good_candidate.layout_node)
+ return last_good_candidate;
+ return { absolute_rect().contains(position.x(), position.y()) ? this : nullptr };
+}
+
+void BlockBox::split_into_lines(InlineFormattingContext& context, LayoutMode layout_mode)
+{
+ auto& containing_block = context.containing_block();
+ auto* line_box = &containing_block.ensure_last_line_box();
+
+ context.dimension_box_on_line(*this, layout_mode);
+
+ float available_width = context.available_width_at_line(containing_block.line_boxes().size() - 1);
+
+ if (layout_mode == LayoutMode::AllPossibleLineBreaks && line_box->width() > 0) {
+ line_box = &containing_block.add_line_box();
+ } else if (layout_mode == LayoutMode::Default && line_box->width() > 0 && line_box->width() + border_box_width() > available_width) {
+ line_box = &containing_block.add_line_box();
+ }
+ line_box->add_fragment(*this, 0, 0, border_box_width(), height());
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/BlockBox.h b/Userland/Libraries/LibWeb/Layout/BlockBox.h
new file mode 100644
index 0000000000..0062841454
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/BlockBox.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Layout/Box.h>
+#include <LibWeb/Layout/LineBox.h>
+
+namespace Web::Layout {
+
+class BlockBox : public Box {
+public:
+ BlockBox(DOM::Document&, DOM::Node*, NonnullRefPtr<CSS::StyleProperties>);
+ BlockBox(DOM::Document&, DOM::Node*, CSS::ComputedValues);
+ virtual ~BlockBox() override;
+
+ virtual void paint(PaintContext&, PaintPhase) override;
+
+ virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const override;
+
+ BlockBox* previous_sibling() { return downcast<BlockBox>(Node::previous_sibling()); }
+ const BlockBox* previous_sibling() const { return downcast<BlockBox>(Node::previous_sibling()); }
+ BlockBox* next_sibling() { return downcast<BlockBox>(Node::next_sibling()); }
+ const BlockBox* next_sibling() const { return downcast<BlockBox>(Node::next_sibling()); }
+
+ template<typename Callback>
+ void for_each_fragment(Callback);
+ template<typename Callback>
+ void for_each_fragment(Callback) const;
+
+ virtual void split_into_lines(InlineFormattingContext&, LayoutMode) override;
+};
+
+template<typename Callback>
+void BlockBox::for_each_fragment(Callback callback)
+{
+ for (auto& line_box : line_boxes()) {
+ for (auto& fragment : line_box.fragments()) {
+ if (callback(fragment) == IterationDecision::Break)
+ return;
+ }
+ }
+}
+
+template<typename Callback>
+void BlockBox::for_each_fragment(Callback callback) const
+{
+ for (auto& line_box : line_boxes()) {
+ for (auto& fragment : line_box.fragments()) {
+ if (callback(fragment) == IterationDecision::Break)
+ return;
+ }
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp
new file mode 100644
index 0000000000..f53ed61c3a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp
@@ -0,0 +1,566 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/Length.h>
+#include <LibWeb/DOM/Node.h>
+#include <LibWeb/Layout/BlockBox.h>
+#include <LibWeb/Layout/BlockFormattingContext.h>
+#include <LibWeb/Layout/Box.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+#include <LibWeb/Layout/InlineFormattingContext.h>
+#include <LibWeb/Layout/ListItemBox.h>
+#include <LibWeb/Layout/WidgetBox.h>
+#include <LibWeb/Page/Frame.h>
+
+namespace Web::Layout {
+
+BlockFormattingContext::BlockFormattingContext(Box& context_box, FormattingContext* parent)
+ : FormattingContext(context_box, parent)
+{
+}
+
+BlockFormattingContext::~BlockFormattingContext()
+{
+}
+
+bool BlockFormattingContext::is_initial() const
+{
+ return is<InitialContainingBlockBox>(context_box());
+}
+
+void BlockFormattingContext::run(Box& box, LayoutMode layout_mode)
+{
+ if (is_initial()) {
+ layout_initial_containing_block(layout_mode);
+ return;
+ }
+
+ // FIXME: BFC currently computes the width+height of the target box.
+ // This is necessary to be able to place absolutely positioned descendants.
+ // The same work is also done by the parent BFC for each of its blocks..
+
+ if (layout_mode == LayoutMode::Default)
+ compute_width(box);
+
+ if (box.children_are_inline()) {
+ layout_inline_children(box, layout_mode);
+ } else {
+ layout_block_level_children(box, layout_mode);
+ }
+
+ if (layout_mode == LayoutMode::Default) {
+ compute_height(box);
+
+ box.for_each_child_of_type<Box>([&](auto& child_box) {
+ if (child_box.is_absolutely_positioned()) {
+ layout_absolutely_positioned_element(child_box);
+ }
+ return IterationDecision::Continue;
+ });
+ }
+}
+
+void BlockFormattingContext::compute_width(Box& box)
+{
+ if (box.is_absolutely_positioned()) {
+ compute_width_for_absolutely_positioned_element(box);
+ return;
+ }
+
+ if (is<ReplacedBox>(box)) {
+ // FIXME: This should not be done *by* ReplacedBox
+ auto& replaced = downcast<ReplacedBox>(box);
+ replaced.prepare_for_replaced_layout();
+ compute_width_for_block_level_replaced_element_in_normal_flow(replaced);
+ return;
+ }
+
+ if (box.is_floating()) {
+ compute_width_for_floating_box(box);
+ return;
+ }
+
+ auto& computed_values = box.computed_values();
+ float width_of_containing_block = box.width_of_logical_containing_block();
+
+ auto zero_value = CSS::Length::make_px(0);
+
+ auto margin_left = CSS::Length::make_auto();
+ auto margin_right = CSS::Length::make_auto();
+ const auto padding_left = computed_values.padding().left.resolved_or_zero(box, width_of_containing_block);
+ const auto padding_right = computed_values.padding().right.resolved_or_zero(box, width_of_containing_block);
+
+ auto try_compute_width = [&](const auto& a_width) {
+ CSS::Length width = a_width;
+ margin_left = computed_values.margin().left.resolved_or_zero(box, width_of_containing_block);
+ margin_right = computed_values.margin().right.resolved_or_zero(box, width_of_containing_block);
+
+ float total_px = computed_values.border_left().width + computed_values.border_right().width;
+ for (auto& value : { margin_left, padding_left, width, padding_right, margin_right }) {
+ total_px += value.to_px(box);
+ }
+
+ if (!box.is_inline()) {
+ // 10.3.3 Block-level, non-replaced elements in normal flow
+ // If 'width' is not 'auto' and 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' (plus any of 'margin-left' or 'margin-right' that are not 'auto') is larger than the width of the containing block, then any 'auto' values for 'margin-left' or 'margin-right' are, for the following rules, treated as zero.
+ if (width.is_auto() && total_px > width_of_containing_block) {
+ if (margin_left.is_auto())
+ margin_left = zero_value;
+ if (margin_right.is_auto())
+ margin_right = zero_value;
+ }
+
+ // 10.3.3 cont'd.
+ auto underflow_px = width_of_containing_block - total_px;
+
+ if (width.is_auto()) {
+ if (margin_left.is_auto())
+ margin_left = zero_value;
+ if (margin_right.is_auto())
+ margin_right = zero_value;
+ if (underflow_px >= 0) {
+ width = CSS::Length(underflow_px, CSS::Length::Type::Px);
+ } else {
+ width = zero_value;
+ margin_right = CSS::Length(margin_right.to_px(box) + underflow_px, CSS::Length::Type::Px);
+ }
+ } else {
+ if (!margin_left.is_auto() && !margin_right.is_auto()) {
+ margin_right = CSS::Length(margin_right.to_px(box) + underflow_px, CSS::Length::Type::Px);
+ } else if (!margin_left.is_auto() && margin_right.is_auto()) {
+ margin_right = CSS::Length(underflow_px, CSS::Length::Type::Px);
+ } else if (margin_left.is_auto() && !margin_right.is_auto()) {
+ margin_left = CSS::Length(underflow_px, CSS::Length::Type::Px);
+ } else { // margin_left.is_auto() && margin_right.is_auto()
+ auto half_of_the_underflow = CSS::Length(underflow_px / 2, CSS::Length::Type::Px);
+ margin_left = half_of_the_underflow;
+ margin_right = half_of_the_underflow;
+ }
+ }
+ } else if (box.is_inline_block()) {
+
+ // 10.3.9 'Inline-block', non-replaced elements in normal flow
+
+ // A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'.
+ if (margin_left.is_auto())
+ margin_left = zero_value;
+ if (margin_right.is_auto())
+ margin_right = zero_value;
+
+ // If 'width' is 'auto', the used value is the shrink-to-fit width as for floating elements.
+ if (width.is_auto()) {
+
+ // Find the available width: in this case, this is the width of the containing
+ // block minus the used values of 'margin-left', 'border-left-width', 'padding-left',
+ // 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars.
+ float available_width = width_of_containing_block
+ - margin_left.to_px(box) - computed_values.border_left().width - padding_left.to_px(box)
+ - padding_right.to_px(box) - computed_values.border_right().width - margin_right.to_px(box);
+
+ auto result = calculate_shrink_to_fit_widths(box);
+
+ // Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width).
+ width = CSS::Length(min(max(result.preferred_minimum_width, available_width), result.preferred_width), CSS::Length::Type::Px);
+ }
+ }
+
+ return width;
+ };
+
+ auto specified_width = computed_values.width().resolved_or_auto(box, width_of_containing_block);
+
+ // 1. The tentative used width is calculated (without 'min-width' and 'max-width')
+ auto used_width = try_compute_width(specified_width);
+
+ // 2. The tentative used width is greater than 'max-width', the rules above are applied again,
+ // but this time using the computed value of 'max-width' as the computed value for 'width'.
+ auto specified_max_width = computed_values.max_width().resolved_or_auto(box, width_of_containing_block);
+ if (!specified_max_width.is_auto()) {
+ if (used_width.to_px(box) > specified_max_width.to_px(box)) {
+ used_width = try_compute_width(specified_max_width);
+ }
+ }
+
+ // 3. If the resulting width is smaller than 'min-width', the rules above are applied again,
+ // but this time using the value of 'min-width' as the computed value for 'width'.
+ auto specified_min_width = computed_values.min_width().resolved_or_auto(box, width_of_containing_block);
+ if (!specified_min_width.is_auto()) {
+ if (used_width.to_px(box) < specified_min_width.to_px(box)) {
+ used_width = try_compute_width(specified_min_width);
+ }
+ }
+
+ box.set_width(used_width.to_px(box));
+ box.box_model().margin.left = margin_left.to_px(box);
+ box.box_model().margin.right = margin_right.to_px(box);
+ box.box_model().border.left = computed_values.border_left().width;
+ box.box_model().border.right = computed_values.border_right().width;
+ box.box_model().padding.left = padding_left.to_px(box);
+ box.box_model().padding.right = padding_right.to_px(box);
+}
+
+void BlockFormattingContext::compute_width_for_floating_box(Box& box)
+{
+ // 10.3.5 Floating, non-replaced elements
+ auto& computed_values = box.computed_values();
+ float width_of_containing_block = box.width_of_logical_containing_block();
+ auto zero_value = CSS::Length::make_px(0);
+
+ auto margin_left = CSS::Length::make_auto();
+ auto margin_right = CSS::Length::make_auto();
+ const auto padding_left = computed_values.padding().left.resolved_or_zero(box, width_of_containing_block);
+ const auto padding_right = computed_values.padding().right.resolved_or_zero(box, width_of_containing_block);
+
+ // If 'margin-left', or 'margin-right' are computed as 'auto', their used value is '0'.
+ if (margin_left.is_auto())
+ margin_left = zero_value;
+ if (margin_right.is_auto())
+ margin_right = zero_value;
+
+ auto width = computed_values.width().resolved_or_auto(box, width_of_containing_block);
+
+ // If 'width' is computed as 'auto', the used value is the "shrink-to-fit" width.
+ if (width.is_auto()) {
+
+ // Find the available width: in this case, this is the width of the containing
+ // block minus the used values of 'margin-left', 'border-left-width', 'padding-left',
+ // 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars.
+ float available_width = width_of_containing_block
+ - margin_left.to_px(box) - computed_values.border_left().width - padding_left.to_px(box)
+ - padding_right.to_px(box) - computed_values.border_right().width - margin_right.to_px(box);
+
+ auto result = calculate_shrink_to_fit_widths(box);
+
+ // Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width).
+ width = CSS::Length(min(max(result.preferred_minimum_width, available_width), result.preferred_width), CSS::Length::Type::Px);
+ }
+
+ float final_width = width.resolved_or_zero(box, width_of_containing_block).to_px(box);
+ box.set_width(final_width);
+}
+
+void BlockFormattingContext::compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox& box)
+{
+ box.set_width(compute_width_for_replaced_element(box));
+}
+
+void BlockFormattingContext::compute_height_for_block_level_replaced_element_in_normal_flow(ReplacedBox& box)
+{
+ box.set_height(compute_height_for_replaced_element(box));
+}
+
+void BlockFormattingContext::compute_height(Box& box)
+{
+ if (is<ReplacedBox>(box)) {
+ compute_height_for_block_level_replaced_element_in_normal_flow(downcast<ReplacedBox>(box));
+ return;
+ }
+
+ auto& computed_values = box.computed_values();
+ auto& containing_block = *box.containing_block();
+
+ CSS::Length specified_height;
+
+ if (computed_values.height().is_percentage() && !containing_block.computed_values().height().is_absolute()) {
+ specified_height = CSS::Length::make_auto();
+ } else {
+ specified_height = computed_values.height().resolved_or_auto(box, containing_block.height());
+ }
+
+ auto specified_max_height = computed_values.max_height().resolved_or_auto(box, containing_block.height());
+
+ box.box_model().margin.top = computed_values.margin().top.resolved_or_zero(box, containing_block.width()).to_px(box);
+ box.box_model().margin.bottom = computed_values.margin().bottom.resolved_or_zero(box, containing_block.width()).to_px(box);
+ box.box_model().border.top = computed_values.border_top().width;
+ box.box_model().border.bottom = computed_values.border_bottom().width;
+ box.box_model().padding.top = computed_values.padding().top.resolved_or_zero(box, containing_block.width()).to_px(box);
+ box.box_model().padding.bottom = computed_values.padding().bottom.resolved_or_zero(box, containing_block.width()).to_px(box);
+
+ if (!specified_height.is_auto()) {
+ float used_height = specified_height.to_px(box);
+ if (!specified_max_height.is_auto())
+ used_height = min(used_height, specified_max_height.to_px(box));
+ box.set_height(used_height);
+ }
+}
+
+void BlockFormattingContext::layout_inline_children(Box& box, LayoutMode layout_mode)
+{
+ InlineFormattingContext context(box, this);
+ context.run(box, layout_mode);
+}
+
+void BlockFormattingContext::layout_block_level_children(Box& box, LayoutMode layout_mode)
+{
+ float content_height = 0;
+ float content_width = 0;
+
+ box.for_each_child_of_type<Box>([&](auto& child_box) {
+ if (child_box.is_absolutely_positioned())
+ return IterationDecision::Continue;
+
+ if (child_box.is_floating()) {
+ layout_floating_child(child_box, box);
+ return IterationDecision::Continue;
+ }
+
+ compute_width(child_box);
+ layout_inside(child_box, layout_mode);
+ compute_height(child_box);
+
+ if (is<ReplacedBox>(child_box))
+ place_block_level_replaced_element_in_normal_flow(child_box, box);
+ else if (is<BlockBox>(child_box))
+ place_block_level_non_replaced_element_in_normal_flow(child_box, box);
+
+ // FIXME: This should be factored differently. It's uncool that we mutate the tree *during* layout!
+ // Instead, we should generate the marker box during the tree build.
+ if (is<ListItemBox>(child_box))
+ downcast<ListItemBox>(child_box).layout_marker();
+
+ content_height = max(content_height, child_box.effective_offset().y() + child_box.height() + child_box.box_model().margin_box().bottom);
+ content_width = max(content_width, downcast<Box>(child_box).width());
+ return IterationDecision::Continue;
+ });
+
+ if (layout_mode != LayoutMode::Default) {
+ if (box.computed_values().width().is_undefined() || box.computed_values().width().is_auto())
+ box.set_width(content_width);
+ }
+
+ // FIXME: It's not right to always shrink-wrap the box to the content here.
+ box.set_height(content_height);
+}
+
+void BlockFormattingContext::place_block_level_replaced_element_in_normal_flow(Box& child_box, Box& containing_block)
+{
+ ASSERT(!containing_block.is_absolutely_positioned());
+ auto& replaced_element_box_model = child_box.box_model();
+
+ replaced_element_box_model.margin.top = child_box.computed_values().margin().top.resolved_or_zero(containing_block, containing_block.width()).to_px(child_box);
+ replaced_element_box_model.margin.bottom = child_box.computed_values().margin().bottom.resolved_or_zero(containing_block, containing_block.width()).to_px(child_box);
+ replaced_element_box_model.border.top = child_box.computed_values().border_top().width;
+ replaced_element_box_model.border.bottom = child_box.computed_values().border_bottom().width;
+ replaced_element_box_model.padding.top = child_box.computed_values().padding().top.resolved_or_zero(containing_block, containing_block.width()).to_px(child_box);
+ replaced_element_box_model.padding.bottom = child_box.computed_values().padding().bottom.resolved_or_zero(containing_block, containing_block.width()).to_px(child_box);
+
+ float x = replaced_element_box_model.margin.left
+ + replaced_element_box_model.border.left
+ + replaced_element_box_model.padding.left
+ + replaced_element_box_model.offset.left;
+
+ float y = replaced_element_box_model.margin_box().top + containing_block.box_model().offset.top;
+
+ child_box.set_offset(x, y);
+}
+
+void BlockFormattingContext::place_block_level_non_replaced_element_in_normal_flow(Box& child_box, Box& containing_block)
+{
+ auto& box_model = child_box.box_model();
+ auto& computed_values = child_box.computed_values();
+
+ box_model.margin.top = computed_values.margin().top.resolved_or_zero(containing_block, containing_block.width()).to_px(child_box);
+ box_model.margin.bottom = computed_values.margin().bottom.resolved_or_zero(containing_block, containing_block.width()).to_px(child_box);
+ box_model.border.top = computed_values.border_top().width;
+ box_model.border.bottom = computed_values.border_bottom().width;
+ box_model.padding.top = computed_values.padding().top.resolved_or_zero(containing_block, containing_block.width()).to_px(child_box);
+ box_model.padding.bottom = computed_values.padding().bottom.resolved_or_zero(containing_block, containing_block.width()).to_px(child_box);
+
+ float x = box_model.margin.left
+ + box_model.border.left
+ + box_model.padding.left
+ + box_model.offset.left;
+
+ if (containing_block.computed_values().text_align() == CSS::TextAlign::LibwebCenter) {
+ x = (containing_block.width() / 2) - child_box.width() / 2;
+ }
+
+ float y = box_model.margin_box().top
+ + box_model.offset.top;
+
+ // NOTE: Empty (0-height) preceding siblings have their margins collapsed with *their* preceding sibling, etc.
+ float collapsed_bottom_margin_of_preceding_siblings = 0;
+
+ auto* relevant_sibling = child_box.previous_sibling_of_type<Layout::BlockBox>();
+ while (relevant_sibling != nullptr) {
+ if (!relevant_sibling->is_absolutely_positioned() && !relevant_sibling->is_floating()) {
+ collapsed_bottom_margin_of_preceding_siblings = max(collapsed_bottom_margin_of_preceding_siblings, relevant_sibling->box_model().margin.bottom);
+ if (relevant_sibling->border_box_height() > 0)
+ break;
+ }
+ relevant_sibling = relevant_sibling->previous_sibling();
+ }
+
+ if (relevant_sibling) {
+ y += relevant_sibling->effective_offset().y()
+ + relevant_sibling->height()
+ + relevant_sibling->box_model().border_box().bottom;
+
+ // Collapse top margin with bottom margin of preceding siblings if needed
+ float my_margin_top = box_model.margin.top;
+
+ if (my_margin_top < 0 || collapsed_bottom_margin_of_preceding_siblings < 0) {
+ // Negative margins present.
+ float largest_negative_margin = -min(my_margin_top, collapsed_bottom_margin_of_preceding_siblings);
+ float largest_positive_margin = (my_margin_top < 0 && collapsed_bottom_margin_of_preceding_siblings < 0) ? 0 : max(my_margin_top, collapsed_bottom_margin_of_preceding_siblings);
+ float final_margin = largest_positive_margin - largest_negative_margin;
+ y += final_margin - my_margin_top;
+ } else if (collapsed_bottom_margin_of_preceding_siblings > my_margin_top) {
+ // Sibling's margin is larger than mine, adjust so we use sibling's.
+ y += collapsed_bottom_margin_of_preceding_siblings - my_margin_top;
+ }
+ }
+
+ if (child_box.computed_values().clear() == CSS::Clear::Left || child_box.computed_values().clear() == CSS::Clear::Both) {
+ if (!m_left_floating_boxes.is_empty()) {
+ float clearance_y = 0;
+ for (auto* floating_box : m_left_floating_boxes) {
+ clearance_y = max(clearance_y, floating_box->effective_offset().y() + floating_box->box_model().margin_box().bottom);
+ }
+ y = max(y, clearance_y);
+ m_left_floating_boxes.clear();
+ }
+ }
+
+ if (child_box.computed_values().clear() == CSS::Clear::Right || child_box.computed_values().clear() == CSS::Clear::Both) {
+ if (!m_right_floating_boxes.is_empty()) {
+ float clearance_y = 0;
+ for (auto* floating_box : m_right_floating_boxes) {
+ clearance_y = max(clearance_y, floating_box->effective_offset().y() + floating_box->box_model().margin_box().bottom);
+ }
+ y = max(y, clearance_y);
+ m_right_floating_boxes.clear();
+ }
+ }
+
+ child_box.set_offset(x, y);
+}
+
+void BlockFormattingContext::layout_initial_containing_block(LayoutMode layout_mode)
+{
+ auto viewport_rect = context_box().frame().viewport_rect();
+
+ auto& icb = downcast<Layout::InitialContainingBlockBox>(context_box());
+ icb.build_stacking_context_tree();
+
+ icb.set_width(viewport_rect.width());
+
+ layout_block_level_children(context_box(), layout_mode);
+
+ ASSERT(!icb.children_are_inline());
+
+ // FIXME: The ICB should have the height of the viewport.
+ // Instead of auto-sizing the ICB, we should spill into overflow.
+ float lowest_bottom = 0;
+ icb.for_each_child_of_type<Box>([&](auto& child) {
+ lowest_bottom = max(lowest_bottom, child.absolute_rect().bottom());
+ });
+
+ // FIXME: This is a hack and should be managed by an overflow mechanism.
+ icb.set_height(max(static_cast<float>(viewport_rect.height()), lowest_bottom));
+
+ // FIXME: This is a total hack. Make sure any GUI::Widgets are moved into place after layout.
+ // We should stop embedding GUI::Widgets entirely, since that won't work out-of-process.
+ icb.for_each_in_subtree_of_type<Layout::WidgetBox>([&](auto& widget) {
+ widget.update_widget();
+ return IterationDecision::Continue;
+ });
+}
+
+static Gfx::FloatRect rect_in_coordinate_space(const Box& box, const Box& context_box)
+{
+ Gfx::FloatRect rect { box.effective_offset(), box.size() };
+ for (auto* ancestor = box.parent(); ancestor; ancestor = ancestor->parent()) {
+ if (is<Box>(*ancestor)) {
+ auto offset = downcast<Box>(*ancestor).effective_offset();
+ rect.move_by(offset);
+ }
+ if (ancestor == &context_box)
+ break;
+ }
+ return rect;
+}
+
+void BlockFormattingContext::layout_floating_child(Box& box, Box& containing_block)
+{
+ ASSERT(box.is_floating());
+
+ compute_width(box);
+ layout_inside(box, LayoutMode::Default);
+ compute_height(box);
+
+ // First we place the box normally (to get the right y coordinate.)
+ place_block_level_non_replaced_element_in_normal_flow(box, containing_block);
+
+ // Then we float it to the left or right.
+ float x = box.effective_offset().x();
+
+ auto box_in_context_rect = rect_in_coordinate_space(box, context_box());
+ float y_in_context_box = box_in_context_rect.y();
+
+ // Next, float to the left and/or right
+ if (box.computed_values().float_() == CSS::Float::Left) {
+ if (!m_left_floating_boxes.is_empty()) {
+ auto& previous_floating_box = *m_left_floating_boxes.last();
+ auto previous_rect = rect_in_coordinate_space(previous_floating_box, context_box());
+ if (previous_rect.contains_vertically(y_in_context_box)) {
+ // This box touches another already floating box. Stack to the right.
+ x = previous_floating_box.effective_offset().x() + previous_floating_box.width();
+ } else {
+ // This box does not touch another floating box, go all the way to the left.
+ x = 0;
+ // Also, forget all previous left-floating boxes while we're here since they're no longer relevant.
+ m_left_floating_boxes.clear();
+ }
+ } else {
+ // This is the first left-floating box. Go all the way to the left.
+ x = 0;
+ }
+ m_left_floating_boxes.append(&box);
+ } else if (box.computed_values().float_() == CSS::Float::Right) {
+ if (!m_right_floating_boxes.is_empty()) {
+ auto& previous_floating_box = *m_right_floating_boxes.last();
+ auto previous_rect = rect_in_coordinate_space(previous_floating_box, context_box());
+ if (previous_rect.contains_vertically(y_in_context_box)) {
+ // This box touches another already floating box. Stack to the left.
+ x = previous_floating_box.effective_offset().x() - box.width();
+ } else {
+ // This box does not touch another floating box, go all the way to the right.
+ x = containing_block.width() - box.width();
+ // Also, forget all previous right-floating boxes while we're here since they're no longer relevant.
+ m_right_floating_boxes.clear();
+ }
+ } else {
+ // This is the first right-floating box. Go all the way to the right.
+ x = containing_block.width() - box.width();
+ }
+ m_right_floating_boxes.append(&box);
+ }
+
+ box.set_offset(x, box.effective_offset().y());
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h
new file mode 100644
index 0000000000..efaa9ab0b7
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Vector.h>
+#include <LibWeb/Forward.h>
+#include <LibWeb/Layout/FormattingContext.h>
+
+namespace Web::Layout {
+
+class BlockFormattingContext : public FormattingContext {
+public:
+ explicit BlockFormattingContext(Box&, FormattingContext* parent);
+ ~BlockFormattingContext();
+
+ virtual void run(Box&, LayoutMode) override;
+
+ bool is_initial() const;
+
+ const Vector<Box*>& left_floating_boxes() const { return m_left_floating_boxes; }
+ const Vector<Box*>& right_floating_boxes() const { return m_right_floating_boxes; }
+
+protected:
+ void compute_width(Box&);
+ void compute_height(Box&);
+
+private:
+ virtual bool is_block_formatting_context() const final { return true; }
+
+ void compute_width_for_floating_box(Box&);
+
+ void compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox&);
+ void compute_height_for_block_level_replaced_element_in_normal_flow(ReplacedBox&);
+
+ void layout_initial_containing_block(LayoutMode);
+
+ void layout_block_level_children(Box&, LayoutMode);
+ void layout_inline_children(Box&, LayoutMode);
+
+ void place_block_level_replaced_element_in_normal_flow(Box& child, Box& container);
+ void place_block_level_non_replaced_element_in_normal_flow(Box& child, Box& container);
+
+ void layout_floating_child(Box&, Box& containing_block);
+
+ Vector<Box*> m_left_floating_boxes;
+ Vector<Box*> m_right_floating_boxes;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/Box.cpp b/Userland/Libraries/LibWeb/Layout/Box.cpp
new file mode 100644
index 0000000000..9c468252a8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/Box.cpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Painter.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/HTML/HTMLBodyElement.h>
+#include <LibWeb/Layout/BlockBox.h>
+#include <LibWeb/Layout/Box.h>
+#include <LibWeb/Page/Frame.h>
+#include <LibWeb/Painting/BorderPainting.h>
+
+namespace Web::Layout {
+
+void Box::paint(PaintContext& context, PaintPhase phase)
+{
+ if (!is_visible())
+ return;
+
+ Gfx::PainterStateSaver saver(context.painter());
+ if (is_fixed_position())
+ context.painter().translate(context.scroll_offset());
+
+ Gfx::FloatRect padded_rect;
+ padded_rect.set_x(absolute_x() - box_model().padding.left);
+ padded_rect.set_width(width() + box_model().padding.left + box_model().padding.right);
+ padded_rect.set_y(absolute_y() - box_model().padding.top);
+ padded_rect.set_height(height() + box_model().padding.top + box_model().padding.bottom);
+
+ if (phase == PaintPhase::Background && !is_body()) {
+ context.painter().fill_rect(enclosing_int_rect(padded_rect), computed_values().background_color());
+
+ if (background_image() && background_image()->bitmap())
+ context.painter().draw_tiled_bitmap(enclosing_int_rect(padded_rect), *background_image()->bitmap());
+ }
+
+ if (phase == PaintPhase::Border) {
+ Gfx::FloatRect bordered_rect;
+ bordered_rect.set_x(padded_rect.x() - box_model().border.left);
+ bordered_rect.set_width(padded_rect.width() + box_model().border.left + box_model().border.right);
+ bordered_rect.set_y(padded_rect.y() - box_model().border.top);
+ bordered_rect.set_height(padded_rect.height() + box_model().border.top + box_model().border.bottom);
+
+ Painting::paint_border(context, Painting::BorderEdge::Left, bordered_rect, computed_values());
+ Painting::paint_border(context, Painting::BorderEdge::Right, bordered_rect, computed_values());
+ Painting::paint_border(context, Painting::BorderEdge::Top, bordered_rect, computed_values());
+ Painting::paint_border(context, Painting::BorderEdge::Bottom, bordered_rect, computed_values());
+ }
+
+ Layout::NodeWithStyleAndBoxModelMetrics::paint(context, phase);
+
+ if (phase == PaintPhase::Overlay && dom_node() && document().inspected_node() == dom_node()) {
+ auto content_rect = absolute_rect();
+
+ auto margin_box = box_model().margin_box();
+ Gfx::FloatRect margin_rect;
+ margin_rect.set_x(absolute_x() - margin_box.left);
+ margin_rect.set_width(width() + margin_box.left + margin_box.right);
+ margin_rect.set_y(absolute_y() - margin_box.top);
+ margin_rect.set_height(height() + margin_box.top + margin_box.bottom);
+
+ context.painter().draw_rect(enclosing_int_rect(margin_rect), Color::Yellow);
+ context.painter().draw_rect(enclosing_int_rect(padded_rect), Color::Cyan);
+ context.painter().draw_rect(enclosing_int_rect(content_rect), Color::Magenta);
+ }
+
+ if (phase == PaintPhase::FocusOutline && dom_node() && dom_node()->is_element() && downcast<DOM::Element>(*dom_node()).is_focused()) {
+ context.painter().draw_rect(enclosing_int_rect(absolute_rect()), context.palette().focus_outline());
+ }
+}
+
+HitTestResult Box::hit_test(const Gfx::IntPoint& position, HitTestType type) const
+{
+ // FIXME: It would be nice if we could confidently skip over hit testing
+ // parts of the layout tree, but currently we can't just check
+ // m_rect.contains() since inline text rects can't be trusted..
+ HitTestResult result { absolute_rect().contains(position.x(), position.y()) ? this : nullptr };
+ for_each_child_in_paint_order([&](auto& child) {
+ auto child_result = child.hit_test(position, type);
+ if (child_result.layout_node)
+ result = child_result;
+ });
+ return result;
+}
+
+void Box::set_needs_display()
+{
+ if (!is_inline()) {
+ frame().set_needs_display(enclosing_int_rect(absolute_rect()));
+ return;
+ }
+
+ Node::set_needs_display();
+}
+
+bool Box::is_body() const
+{
+ return dom_node() && dom_node() == document().body();
+}
+
+void Box::set_offset(const Gfx::FloatPoint& offset)
+{
+ if (m_offset == offset)
+ return;
+ m_offset = offset;
+ did_set_rect();
+}
+
+void Box::set_size(const Gfx::FloatSize& size)
+{
+ if (m_size == size)
+ return;
+ m_size = size;
+ did_set_rect();
+}
+
+Gfx::FloatPoint Box::effective_offset() const
+{
+ if (m_containing_line_box_fragment)
+ return m_containing_line_box_fragment->offset();
+ return m_offset;
+}
+
+const Gfx::FloatRect Box::absolute_rect() const
+{
+ Gfx::FloatRect rect { effective_offset(), size() };
+ for (auto* block = containing_block(); block; block = block->containing_block()) {
+ rect.move_by(block->effective_offset());
+ }
+ return rect;
+}
+
+void Box::set_containing_line_box_fragment(LineBoxFragment& fragment)
+{
+ m_containing_line_box_fragment = fragment.make_weak_ptr();
+}
+
+StackingContext* Box::enclosing_stacking_context()
+{
+ for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
+ if (!is<Box>(ancestor))
+ continue;
+ auto& ancestor_box = downcast<Box>(*ancestor);
+ if (!ancestor_box.establishes_stacking_context())
+ continue;
+ ASSERT(ancestor_box.stacking_context());
+ return ancestor_box.stacking_context();
+ }
+ // We should always reach the Layout::InitialContainingBlockBox stacking context.
+ ASSERT_NOT_REACHED();
+}
+
+bool Box::establishes_stacking_context() const
+{
+ if (!has_style())
+ return false;
+ if (dom_node() == document().root())
+ return true;
+ auto position = computed_values().position();
+ auto z_index = computed_values().z_index();
+ if (position == CSS::Position::Absolute || position == CSS::Position::Relative) {
+ if (z_index.has_value())
+ return true;
+ }
+ if (position == CSS::Position::Fixed || position == CSS::Position::Sticky)
+ return true;
+ return false;
+}
+
+LineBox& Box::ensure_last_line_box()
+{
+ if (m_line_boxes.is_empty())
+ return add_line_box();
+ return m_line_boxes.last();
+}
+
+LineBox& Box::add_line_box()
+{
+ m_line_boxes.append(LineBox());
+ return m_line_boxes.last();
+}
+
+float Box::width_of_logical_containing_block() const
+{
+ auto* containing_block = this->containing_block();
+ ASSERT(containing_block);
+ return containing_block->width();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/Box.h b/Userland/Libraries/LibWeb/Layout/Box.h
new file mode 100644
index 0000000000..7b05b381e3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/Box.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/OwnPtr.h>
+#include <LibGfx/Rect.h>
+#include <LibWeb/Layout/LineBox.h>
+#include <LibWeb/Layout/Node.h>
+#include <LibWeb/Painting/StackingContext.h>
+
+namespace Web::Layout {
+
+class Box : public NodeWithStyleAndBoxModelMetrics {
+public:
+ const Gfx::FloatRect absolute_rect() const;
+
+ Gfx::FloatPoint effective_offset() const;
+
+ void set_offset(const Gfx::FloatPoint& offset);
+ void set_offset(float x, float y) { set_offset({ x, y }); }
+
+ const Gfx::FloatSize& size() const { return m_size; }
+ void set_size(const Gfx::FloatSize&);
+ void set_size(float width, float height) { set_size({ width, height }); }
+
+ void set_width(float width) { set_size(width, height()); }
+ void set_height(float height) { set_size(width(), height); }
+ float width() const { return m_size.width(); }
+ float height() const { return m_size.height(); }
+
+ float border_box_width() const
+ {
+ auto border_box = box_model().border_box();
+ return width() + border_box.left + border_box.right;
+ }
+
+ float border_box_height() const
+ {
+ auto border_box = box_model().border_box();
+ return height() + border_box.top + border_box.bottom;
+ }
+
+ Gfx::FloatRect content_box_as_relative_rect() const
+ {
+ return { m_offset, m_size };
+ }
+
+ Gfx::FloatRect margin_box_as_relative_rect() const
+ {
+ auto rect = content_box_as_relative_rect();
+ auto margin_box = box_model().margin_box();
+ rect.set_x(rect.x() - margin_box.left);
+ rect.set_width(rect.width() + margin_box.left + margin_box.right);
+ rect.set_y(rect.y() - margin_box.top);
+ rect.set_height(rect.height() + margin_box.top + margin_box.bottom);
+ return rect;
+ }
+
+ float absolute_x() const { return absolute_rect().x(); }
+ float absolute_y() const { return absolute_rect().y(); }
+ Gfx::FloatPoint absolute_position() const { return absolute_rect().location(); }
+
+ virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const override;
+ virtual void set_needs_display() override;
+
+ bool is_body() const;
+
+ void set_containing_line_box_fragment(LineBoxFragment&);
+
+ bool establishes_stacking_context() const;
+ StackingContext* stacking_context() { return m_stacking_context; }
+ const StackingContext* stacking_context() const { return m_stacking_context; }
+ void set_stacking_context(NonnullOwnPtr<StackingContext> context) { m_stacking_context = move(context); }
+ StackingContext* enclosing_stacking_context();
+
+ virtual void paint(PaintContext&, PaintPhase) override;
+
+ Vector<LineBox>& line_boxes() { return m_line_boxes; }
+ const Vector<LineBox>& line_boxes() const { return m_line_boxes; }
+
+ LineBox& ensure_last_line_box();
+ LineBox& add_line_box();
+
+ virtual float width_of_logical_containing_block() const;
+
+protected:
+ Box(DOM::Document& document, DOM::Node* node, NonnullRefPtr<CSS::StyleProperties> style)
+ : NodeWithStyleAndBoxModelMetrics(document, node, move(style))
+ {
+ }
+
+ Box(DOM::Document& document, DOM::Node* node, CSS::ComputedValues computed_values)
+ : NodeWithStyleAndBoxModelMetrics(document, node, move(computed_values))
+ {
+ }
+
+ virtual void did_set_rect() { }
+
+ Vector<LineBox> m_line_boxes;
+
+private:
+ virtual bool is_box() const final { return true; }
+
+ Gfx::FloatPoint m_offset;
+ Gfx::FloatSize m_size;
+
+ // Some boxes hang off of line box fragments. (inline-block, inline-table, replaced, etc)
+ WeakPtr<LineBoxFragment> m_containing_line_box_fragment;
+
+ OwnPtr<StackingContext> m_stacking_context;
+};
+
+}
+
+namespace AK {
+template<>
+inline bool is<Web::Layout::Box>(const Web::Layout::Node& input)
+{
+ return input.is_box();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/BoxModelMetrics.cpp b/Userland/Libraries/LibWeb/Layout/BoxModelMetrics.cpp
new file mode 100644
index 0000000000..d18e5ef531
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/BoxModelMetrics.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Layout/BoxModelMetrics.h>
+
+namespace Web::Layout {
+
+PixelBox BoxModelMetrics::margin_box() const
+{
+ return {
+ margin.top + border.top + padding.top,
+ margin.right + border.right + padding.right,
+ margin.bottom + border.bottom + padding.bottom,
+ margin.left + border.left + padding.left,
+ };
+}
+
+PixelBox BoxModelMetrics::padding_box() const
+{
+ return {
+ padding.top,
+ padding.right,
+ padding.bottom,
+ padding.left,
+ };
+}
+
+PixelBox BoxModelMetrics::border_box() const
+{
+ return {
+ border.top + padding.top,
+ border.right + padding.right,
+ border.bottom + padding.bottom,
+ border.left + padding.left,
+ };
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/BoxModelMetrics.h b/Userland/Libraries/LibWeb/Layout/BoxModelMetrics.h
new file mode 100644
index 0000000000..4b36062b7b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/BoxModelMetrics.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Size.h>
+
+namespace Web::Layout {
+
+struct PixelBox {
+ float top { 0 };
+ float right { 0 };
+ float bottom { 0 };
+ float left { 0 };
+};
+
+struct BoxModelMetrics {
+public:
+ PixelBox margin;
+ PixelBox padding;
+ PixelBox border;
+ PixelBox offset;
+
+ PixelBox margin_box() const;
+ PixelBox padding_box() const;
+ PixelBox border_box() const;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/BreakNode.cpp b/Userland/Libraries/LibWeb/Layout/BreakNode.cpp
new file mode 100644
index 0000000000..9951ae4fe0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/BreakNode.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Layout/BlockBox.h>
+#include <LibWeb/Layout/BreakNode.h>
+#include <LibWeb/Layout/InlineFormattingContext.h>
+
+namespace Web::Layout {
+
+BreakNode::BreakNode(DOM::Document& document, HTML::HTMLBRElement& element)
+ : Layout::NodeWithStyleAndBoxModelMetrics(document, &element, CSS::StyleProperties::create())
+{
+ set_inline(true);
+}
+
+BreakNode::~BreakNode()
+{
+}
+
+void BreakNode::split_into_lines(InlineFormattingContext& context, LayoutMode)
+{
+ context.containing_block().add_line_box();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/BreakNode.h b/Userland/Libraries/LibWeb/Layout/BreakNode.h
new file mode 100644
index 0000000000..58ebbe3322
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/BreakNode.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLBRElement.h>
+#include <LibWeb/Layout/Node.h>
+
+namespace Web::Layout {
+
+class BreakNode final : public NodeWithStyleAndBoxModelMetrics {
+public:
+ BreakNode(DOM::Document&, HTML::HTMLBRElement&);
+ virtual ~BreakNode() override;
+
+ const HTML::HTMLBRElement& dom_node() const { return downcast<HTML::HTMLBRElement>(*Node::dom_node()); }
+
+private:
+ virtual void split_into_lines(InlineFormattingContext&, LayoutMode) override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/ButtonBox.cpp b/Userland/Libraries/LibWeb/Layout/ButtonBox.cpp
new file mode 100644
index 0000000000..c0b3706e28
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/ButtonBox.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Event.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/StylePainter.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/Layout/ButtonBox.h>
+#include <LibWeb/Page/Frame.h>
+
+namespace Web::Layout {
+
+ButtonBox::ButtonBox(DOM::Document& document, HTML::HTMLInputElement& element, NonnullRefPtr<CSS::StyleProperties> style)
+ : ReplacedBox(document, element, move(style))
+{
+}
+
+ButtonBox::~ButtonBox()
+{
+}
+
+void ButtonBox::prepare_for_replaced_layout()
+{
+ set_intrinsic_width(font().width(dom_node().value()) + 20);
+ set_has_intrinsic_width(true);
+
+ set_intrinsic_height(20);
+ set_has_intrinsic_height(true);
+}
+
+void ButtonBox::paint(PaintContext& context, PaintPhase phase)
+{
+ if (!is_visible())
+ return;
+
+ ReplacedBox::paint(context, phase);
+
+ if (phase == PaintPhase::Foreground) {
+ bool hovered = document().hovered_node() == &dom_node();
+ Gfx::StylePainter::paint_button(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), Gfx::ButtonStyle::Normal, m_being_pressed, hovered, dom_node().checked(), dom_node().enabled());
+
+ auto text_rect = enclosing_int_rect(absolute_rect());
+ if (m_being_pressed)
+ text_rect.move_by(1, 1);
+ context.painter().draw_text(text_rect, dom_node().value(), font(), Gfx::TextAlignment::Center, context.palette().button_text());
+ }
+}
+
+void ButtonBox::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned)
+{
+ if (button != GUI::MouseButton::Left || !dom_node().enabled())
+ return;
+
+ m_being_pressed = true;
+ set_needs_display();
+
+ m_tracking_mouse = true;
+ frame().event_handler().set_mouse_event_tracking_layout_node(this);
+}
+
+void ButtonBox::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
+{
+ if (!m_tracking_mouse || button != GUI::MouseButton::Left || !dom_node().enabled())
+ return;
+
+ // NOTE: Handling the click may run arbitrary JS, which could disappear this node.
+ NonnullRefPtr protected_this = *this;
+ NonnullRefPtr protected_frame = frame();
+
+ bool is_inside = enclosing_int_rect(absolute_rect()).contains(position);
+ if (is_inside)
+ dom_node().did_click_button({});
+
+ m_being_pressed = false;
+ m_tracking_mouse = false;
+
+ protected_frame->event_handler().set_mouse_event_tracking_layout_node(nullptr);
+}
+
+void ButtonBox::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned, unsigned)
+{
+ if (!m_tracking_mouse || !dom_node().enabled())
+ return;
+
+ bool is_inside = enclosing_int_rect(absolute_rect()).contains(position);
+ if (m_being_pressed == is_inside)
+ return;
+
+ m_being_pressed = is_inside;
+ set_needs_display();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/ButtonBox.h b/Userland/Libraries/LibWeb/Layout/ButtonBox.h
new file mode 100644
index 0000000000..f26c7ed8d8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/ButtonBox.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLInputElement.h>
+#include <LibWeb/Layout/ReplacedBox.h>
+
+namespace Web::Layout {
+
+class ButtonBox : public ReplacedBox {
+public:
+ ButtonBox(DOM::Document&, HTML::HTMLInputElement&, NonnullRefPtr<CSS::StyleProperties>);
+ virtual ~ButtonBox() override;
+
+ virtual void prepare_for_replaced_layout() override;
+ virtual void paint(PaintContext&, PaintPhase) override;
+
+ const HTML::HTMLInputElement& dom_node() const { return static_cast<const HTML::HTMLInputElement&>(ReplacedBox::dom_node()); }
+ HTML::HTMLInputElement& dom_node() { return static_cast<HTML::HTMLInputElement&>(ReplacedBox::dom_node()); }
+
+private:
+ virtual bool wants_mouse_events() const override { return true; }
+ virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
+ virtual void handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
+ virtual void handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers) override;
+
+ bool m_being_pressed { false };
+ bool m_tracking_mouse { false };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/CanvasBox.cpp b/Userland/Libraries/LibWeb/Layout/CanvasBox.cpp
new file mode 100644
index 0000000000..c1eb048ace
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/CanvasBox.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Painter.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/StylePainter.h>
+#include <LibWeb/Layout/CanvasBox.h>
+
+namespace Web::Layout {
+
+CanvasBox::CanvasBox(DOM::Document& document, HTML::HTMLCanvasElement& element, NonnullRefPtr<CSS::StyleProperties> style)
+ : ReplacedBox(document, element, move(style))
+{
+}
+
+CanvasBox::~CanvasBox()
+{
+}
+
+void CanvasBox::prepare_for_replaced_layout()
+{
+ set_has_intrinsic_width(true);
+ set_has_intrinsic_height(true);
+ set_intrinsic_width(dom_node().width());
+ set_intrinsic_height(dom_node().height());
+}
+
+void CanvasBox::paint(PaintContext& context, PaintPhase phase)
+{
+ if (!is_visible())
+ return;
+
+ ReplacedBox::paint(context, phase);
+
+ if (phase == PaintPhase::Foreground) {
+ // FIXME: This should be done at a different level. Also rect() does not include padding etc!
+ if (!context.viewport_rect().intersects(enclosing_int_rect(absolute_rect())))
+ return;
+
+ if (dom_node().bitmap())
+ context.painter().draw_scaled_bitmap(enclosing_int_rect(absolute_rect()), *dom_node().bitmap(), dom_node().bitmap()->rect());
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/CanvasBox.h b/Userland/Libraries/LibWeb/Layout/CanvasBox.h
new file mode 100644
index 0000000000..150e28ae21
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/CanvasBox.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLCanvasElement.h>
+#include <LibWeb/Layout/ReplacedBox.h>
+
+namespace Web::Layout {
+
+class CanvasBox : public ReplacedBox {
+public:
+ CanvasBox(DOM::Document&, HTML::HTMLCanvasElement&, NonnullRefPtr<CSS::StyleProperties>);
+ virtual ~CanvasBox() override;
+
+ virtual void prepare_for_replaced_layout() override;
+ virtual void paint(PaintContext&, PaintPhase) override;
+
+ const HTML::HTMLCanvasElement& dom_node() const { return static_cast<const HTML::HTMLCanvasElement&>(ReplacedBox::dom_node()); }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/CheckBox.cpp b/Userland/Libraries/LibWeb/Layout/CheckBox.cpp
new file mode 100644
index 0000000000..a259e4f2d5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/CheckBox.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Event.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/StylePainter.h>
+#include <LibWeb/Layout/CheckBox.h>
+#include <LibWeb/Page/Frame.h>
+
+namespace Web::Layout {
+
+CheckBox::CheckBox(DOM::Document& document, HTML::HTMLInputElement& element, NonnullRefPtr<CSS::StyleProperties> style)
+ : ReplacedBox(document, element, move(style))
+{
+ set_has_intrinsic_width(true);
+ set_has_intrinsic_height(true);
+ set_intrinsic_width(13);
+ set_intrinsic_height(13);
+}
+
+CheckBox::~CheckBox()
+{
+}
+
+void CheckBox::paint(PaintContext& context, PaintPhase phase)
+{
+ if (!is_visible())
+ return;
+
+ ReplacedBox::paint(context, phase);
+
+ if (phase == PaintPhase::Foreground) {
+ Gfx::StylePainter::paint_check_box(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), dom_node().enabled(), dom_node().checked(), m_being_pressed);
+ }
+}
+
+void CheckBox::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned)
+{
+ if (button != GUI::MouseButton::Left || !dom_node().enabled())
+ return;
+
+ m_being_pressed = true;
+ set_needs_display();
+
+ m_tracking_mouse = true;
+ frame().event_handler().set_mouse_event_tracking_layout_node(this);
+}
+
+void CheckBox::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
+{
+ if (!m_tracking_mouse || button != GUI::MouseButton::Left || !dom_node().enabled())
+ return;
+
+ // NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node.
+ NonnullRefPtr protect = *this;
+
+ bool is_inside = enclosing_int_rect(absolute_rect()).contains(position);
+ if (is_inside)
+ dom_node().set_checked(!dom_node().checked());
+
+ m_being_pressed = false;
+ m_tracking_mouse = false;
+ frame().event_handler().set_mouse_event_tracking_layout_node(nullptr);
+}
+
+void CheckBox::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned, unsigned)
+{
+ if (!m_tracking_mouse || !dom_node().enabled())
+ return;
+
+ bool is_inside = enclosing_int_rect(absolute_rect()).contains(position);
+ if (m_being_pressed == is_inside)
+ return;
+
+ m_being_pressed = is_inside;
+ set_needs_display();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/CheckBox.h b/Userland/Libraries/LibWeb/Layout/CheckBox.h
new file mode 100644
index 0000000000..47809c07f9
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/CheckBox.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLInputElement.h>
+#include <LibWeb/Layout/ReplacedBox.h>
+
+namespace Web::Layout {
+
+class CheckBox : public ReplacedBox {
+public:
+ CheckBox(DOM::Document&, HTML::HTMLInputElement&, NonnullRefPtr<CSS::StyleProperties>);
+ virtual ~CheckBox() override;
+
+ virtual void paint(PaintContext&, PaintPhase) override;
+
+ const HTML::HTMLInputElement& dom_node() const { return static_cast<const HTML::HTMLInputElement&>(ReplacedBox::dom_node()); }
+ HTML::HTMLInputElement& dom_node() { return static_cast<HTML::HTMLInputElement&>(ReplacedBox::dom_node()); }
+
+private:
+ virtual bool wants_mouse_events() const override { return true; }
+ virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
+ virtual void handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
+ virtual void handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers) override;
+
+ bool m_being_pressed { false };
+ bool m_tracking_mouse { false };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp
new file mode 100644
index 0000000000..e0a802e002
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp
@@ -0,0 +1,548 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Dump.h>
+#include <LibWeb/Layout/BlockBox.h>
+#include <LibWeb/Layout/BlockFormattingContext.h>
+#include <LibWeb/Layout/Box.h>
+#include <LibWeb/Layout/FormattingContext.h>
+#include <LibWeb/Layout/InlineFormattingContext.h>
+#include <LibWeb/Layout/ReplacedBox.h>
+#include <LibWeb/Layout/TableBox.h>
+#include <LibWeb/Layout/TableCellBox.h>
+#include <LibWeb/Layout/TableFormattingContext.h>
+#include <LibWeb/Layout/TableRowBox.h>
+
+namespace Web::Layout {
+
+FormattingContext::FormattingContext(Box& context_box, FormattingContext* parent)
+ : m_parent(parent)
+ , m_context_box(&context_box)
+{
+}
+
+FormattingContext::~FormattingContext()
+{
+}
+
+bool FormattingContext::creates_block_formatting_context(const Box& box)
+{
+ if (box.is_root_element())
+ return true;
+ if (box.is_floating())
+ return true;
+ if (box.is_absolutely_positioned())
+ return true;
+ if (box.is_inline_block())
+ return true;
+ if (is<TableCellBox>(box))
+ return true;
+ // FIXME: table-caption
+ // FIXME: anonymous table cells
+ // FIXME: Block elements where overflow has a value other than visible and clip.
+ // FIXME: display: flow-root
+ // FIXME: Elements with contain: layout, content, or paint.
+ // FIXME: flex
+ // FIXME: grid
+ // FIXME: multicol
+ // FIXME: column-span: all
+ return false;
+}
+
+void FormattingContext::layout_inside(Box& box, LayoutMode layout_mode)
+{
+ if (creates_block_formatting_context(box)) {
+ BlockFormattingContext context(box, this);
+ context.run(box, layout_mode);
+ return;
+ }
+ if (is<TableBox>(box)) {
+ TableFormattingContext context(box, this);
+ context.run(box, layout_mode);
+ } else if (box.children_are_inline()) {
+ InlineFormattingContext context(box, this);
+ context.run(box, layout_mode);
+ } else {
+ // FIXME: This needs refactoring!
+ ASSERT(is_block_formatting_context());
+ run(box, layout_mode);
+ }
+}
+
+static float greatest_child_width(const Box& box)
+{
+ float max_width = 0;
+ if (box.children_are_inline()) {
+ for (auto& child : box.line_boxes()) {
+ max_width = max(max_width, child.width());
+ }
+ } else {
+ box.for_each_child_of_type<Box>([&](auto& child) {
+ max_width = max(max_width, child.border_box_width());
+ });
+ }
+ return max_width;
+}
+
+FormattingContext::ShrinkToFitResult FormattingContext::calculate_shrink_to_fit_widths(Box& box)
+{
+ // Calculate the preferred width by formatting the content without breaking lines
+ // other than where explicit line breaks occur.
+ layout_inside(box, LayoutMode::OnlyRequiredLineBreaks);
+ float preferred_width = greatest_child_width(box);
+
+ // Also calculate the preferred minimum width, e.g., by trying all possible line breaks.
+ // CSS 2.2 does not define the exact algorithm.
+
+ layout_inside(box, LayoutMode::AllPossibleLineBreaks);
+ float preferred_minimum_width = greatest_child_width(box);
+
+ return { preferred_width, preferred_minimum_width };
+}
+
+static Gfx::FloatSize solve_replaced_size_constraint(float w, float h, const ReplacedBox& box)
+{
+ // 10.4 Minimum and maximum widths: 'min-width' and 'max-width'
+
+ auto& containing_block = *box.containing_block();
+ auto specified_min_width = box.computed_values().min_width().resolved_or_zero(box, containing_block.width()).to_px(box);
+ auto specified_max_width = box.computed_values().max_width().resolved(CSS::Length::make_px(w), box, containing_block.width()).to_px(box);
+ auto specified_min_height = box.computed_values().min_height().resolved_or_auto(box, containing_block.height()).to_px(box);
+ auto specified_max_height = box.computed_values().max_height().resolved(CSS::Length::make_px(h), box, containing_block.height()).to_px(box);
+
+ auto min_width = min(specified_min_width, specified_max_width);
+ auto max_width = max(specified_min_width, specified_max_width);
+ auto min_height = min(specified_min_height, specified_max_height);
+ auto max_height = max(specified_min_height, specified_max_height);
+
+ if (w > max_width)
+ return { w, max(max_width * h / w, min_height) };
+ if (w < min_width)
+ return { max_width, min(min_width * h / w, max_height) };
+ if (h > max_height)
+ return { max(max_height * w / h, min_width), max_height };
+ if (h < min_height)
+ return { min(min_height * w / h, max_width), min_height };
+ if ((w > max_width && h > max_height) && (max_width / w < max_height / h))
+ return { max_width, max(min_height, max_width * h / w) };
+ if ((w > max_width && h > max_height) && (max_width / w > max_height / h))
+ return { max(min_width, max_height * w / h), max_height };
+ if ((w < min_width && h < min_height) && (min_width / w < min_height / h))
+ return { min(max_width, min_height * w / h), min_height };
+ if ((w < min_width && h < min_height) && (min_width / w > min_height / h))
+ return { min_width, min(max_height, min_width * h / w) };
+ if (w < min_width && h > max_height)
+ return { min_width, max_height };
+ if (w > max_width && h < min_height)
+ return { max_width, min_height };
+ return { w, h };
+}
+
+float FormattingContext::tentative_width_for_replaced_element(const ReplacedBox& box, const CSS::Length& width)
+{
+ auto& containing_block = *box.containing_block();
+ auto specified_height = box.computed_values().height().resolved_or_auto(box, containing_block.height());
+
+ float used_width = width.to_px(box);
+
+ // If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic width,
+ // then that intrinsic width is the used value of 'width'.
+ if (specified_height.is_auto() && width.is_auto() && box.has_intrinsic_width()) {
+ used_width = box.intrinsic_width();
+ }
+
+ // If 'height' and 'width' both have computed values of 'auto' and the element has no intrinsic width,
+ // but does have an intrinsic height and intrinsic ratio;
+ // or if 'width' has a computed value of 'auto',
+ // 'height' has some other computed value, and the element does have an intrinsic ratio; then the used value of 'width' is:
+ //
+ // (used height) * (intrinsic ratio)
+ else if ((specified_height.is_auto() && width.is_auto() && !box.has_intrinsic_width() && box.has_intrinsic_height() && box.has_intrinsic_ratio()) || (width.is_auto() && box.has_intrinsic_ratio())) {
+ used_width = compute_height_for_replaced_element(box) * box.intrinsic_ratio();
+ }
+
+ else if (width.is_auto() && box.has_intrinsic_width()) {
+ used_width = box.intrinsic_width();
+ }
+
+ else if (width.is_auto()) {
+ used_width = 300;
+ }
+
+ return used_width;
+}
+
+void FormattingContext::compute_width_for_absolutely_positioned_element(Box& box)
+{
+ if (is<ReplacedBox>(box))
+ compute_width_for_absolutely_positioned_replaced_element(downcast<ReplacedBox>(box));
+ else
+ compute_width_for_absolutely_positioned_non_replaced_element(box);
+}
+
+void FormattingContext::compute_height_for_absolutely_positioned_element(Box& box)
+{
+ if (is<ReplacedBox>(box))
+ compute_height_for_absolutely_positioned_replaced_element(downcast<ReplacedBox>(box));
+ else
+ compute_height_for_absolutely_positioned_non_replaced_element(box);
+}
+
+float FormattingContext::compute_width_for_replaced_element(const ReplacedBox& box)
+{
+ // 10.3.4 Block-level, replaced elements in normal flow...
+ // 10.3.2 Inline, replaced elements
+
+ auto zero_value = CSS::Length::make_px(0);
+ auto& containing_block = *box.containing_block();
+
+ auto margin_left = box.computed_values().margin().left.resolved_or_zero(box, containing_block.width());
+ auto margin_right = box.computed_values().margin().right.resolved_or_zero(box, containing_block.width());
+
+ // A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'.
+ if (margin_left.is_auto())
+ margin_left = zero_value;
+ if (margin_right.is_auto())
+ margin_right = zero_value;
+
+ auto specified_width = box.computed_values().width().resolved_or_auto(box, containing_block.width());
+
+ // 1. The tentative used width is calculated (without 'min-width' and 'max-width')
+ auto used_width = tentative_width_for_replaced_element(box, specified_width);
+
+ // 2. The tentative used width is greater than 'max-width', the rules above are applied again,
+ // but this time using the computed value of 'max-width' as the computed value for 'width'.
+ auto specified_max_width = box.computed_values().max_width().resolved_or_auto(box, containing_block.width());
+ if (!specified_max_width.is_auto()) {
+ if (used_width > specified_max_width.to_px(box)) {
+ used_width = tentative_width_for_replaced_element(box, specified_max_width);
+ }
+ }
+
+ // 3. If the resulting width is smaller than 'min-width', the rules above are applied again,
+ // but this time using the value of 'min-width' as the computed value for 'width'.
+ auto specified_min_width = box.computed_values().min_width().resolved_or_auto(box, containing_block.width());
+ if (!specified_min_width.is_auto()) {
+ if (used_width < specified_min_width.to_px(box)) {
+ used_width = tentative_width_for_replaced_element(box, specified_min_width);
+ }
+ }
+
+ return used_width;
+}
+
+float FormattingContext::tentative_height_for_replaced_element(const ReplacedBox& box, const CSS::Length& height)
+{
+ auto& containing_block = *box.containing_block();
+ auto specified_width = box.computed_values().width().resolved_or_auto(box, containing_block.width());
+
+ float used_height = height.to_px(box);
+
+ // If 'height' and 'width' both have computed values of 'auto' and the element also has
+ // an intrinsic height, then that intrinsic height is the used value of 'height'.
+ if (specified_width.is_auto() && height.is_auto() && box.has_intrinsic_height())
+ used_height = box.intrinsic_height();
+ else if (height.is_auto() && box.has_intrinsic_ratio())
+ used_height = compute_width_for_replaced_element(box) / box.intrinsic_ratio();
+ else if (height.is_auto() && box.has_intrinsic_height())
+ used_height = box.intrinsic_height();
+ else if (height.is_auto())
+ used_height = 150;
+
+ return used_height;
+}
+
+float FormattingContext::compute_height_for_replaced_element(const ReplacedBox& box)
+{
+ // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow,
+ // 'inline-block' replaced elements in normal flow and floating replaced elements
+
+ auto& containing_block = *box.containing_block();
+ auto specified_width = box.computed_values().width().resolved_or_auto(box, containing_block.width());
+ auto specified_height = box.computed_values().height().resolved_or_auto(box, containing_block.height());
+
+ float used_height = tentative_height_for_replaced_element(box, specified_height);
+
+ if (specified_width.is_auto() && specified_height.is_auto() && box.has_intrinsic_ratio()) {
+ float w = tentative_width_for_replaced_element(box, specified_width);
+ float h = used_height;
+ used_height = solve_replaced_size_constraint(w, h, box).height();
+ }
+
+ return used_height;
+}
+
+void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_element(Box& box)
+{
+ auto& containing_block = *box.containing_block();
+ auto& computed_values = box.computed_values();
+ auto zero_value = CSS::Length::make_px(0);
+
+ auto margin_left = CSS::Length::make_auto();
+ auto margin_right = CSS::Length::make_auto();
+ const auto border_left = computed_values.border_left().width;
+ const auto border_right = computed_values.border_right().width;
+ const auto padding_left = computed_values.padding().left.resolved_or_zero(box, containing_block.width());
+ const auto padding_right = computed_values.padding().right.resolved_or_zero(box, containing_block.width());
+
+ auto try_compute_width = [&](const auto& a_width) {
+ margin_left = computed_values.margin().left.resolved_or_zero(box, containing_block.width());
+ margin_right = computed_values.margin().right.resolved_or_zero(box, containing_block.width());
+
+ auto left = computed_values.offset().left.resolved_or_auto(box, containing_block.width());
+ auto right = computed_values.offset().right.resolved_or_auto(box, containing_block.width());
+ auto width = a_width;
+
+ auto solve_for_left = [&] {
+ return CSS::Length(containing_block.width() - margin_left.to_px(box) - border_left - padding_left.to_px(box) - width.to_px(box) - padding_right.to_px(box) - border_right - margin_right.to_px(box) - right.to_px(box), CSS::Length::Type::Px);
+ };
+
+ auto solve_for_width = [&] {
+ return CSS::Length(containing_block.width() - left.to_px(box) - margin_left.to_px(box) - border_left - padding_left.to_px(box) - padding_right.to_px(box) - border_right - margin_right.to_px(box) - right.to_px(box), CSS::Length::Type::Px);
+ };
+
+ auto solve_for_right = [&] {
+ return CSS::Length(containing_block.width() - left.to_px(box) - margin_left.to_px(box) - border_left - padding_left.to_px(box) - width.to_px(box) - padding_right.to_px(box) - border_right - margin_right.to_px(box), CSS::Length::Type::Px);
+ };
+
+ // If all three of 'left', 'width', and 'right' are 'auto':
+ if (left.is_auto() && width.is_auto() && right.is_auto()) {
+ // First set any 'auto' values for 'margin-left' and 'margin-right' to 0.
+ if (margin_left.is_auto())
+ margin_left = CSS::Length::make_px(0);
+ if (margin_right.is_auto())
+ margin_right = CSS::Length::make_px(0);
+ // Then, if the 'direction' property of the element establishing the static-position containing block
+ // is 'ltr' set 'left' to the static position and apply rule number three below;
+ // otherwise, set 'right' to the static position and apply rule number one below.
+ // FIXME: This is very hackish.
+ left = CSS::Length::make_px(0);
+ goto Rule3;
+ }
+
+ if (!left.is_auto() && !width.is_auto() && !right.is_auto()) {
+ // FIXME: This should be solved in a more complicated way.
+ return width;
+ }
+
+ if (margin_left.is_auto())
+ margin_left = CSS::Length::make_px(0);
+ if (margin_right.is_auto())
+ margin_right = CSS::Length::make_px(0);
+
+ // 1. 'left' and 'width' are 'auto' and 'right' is not 'auto',
+ // then the width is shrink-to-fit. Then solve for 'left'
+ if (left.is_auto() && width.is_auto() && !right.is_auto()) {
+ auto result = calculate_shrink_to_fit_widths(box);
+ solve_for_left();
+ auto available_width = solve_for_width();
+ width = CSS::Length(min(max(result.preferred_minimum_width, available_width.to_px(box)), result.preferred_width), CSS::Length::Type::Px);
+ }
+
+ // 2. 'left' and 'right' are 'auto' and 'width' is not 'auto',
+ // then if the 'direction' property of the element establishing
+ // the static-position containing block is 'ltr' set 'left'
+ // to the static position, otherwise set 'right' to the static position.
+ // Then solve for 'left' (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr').
+ else if (left.is_auto() && right.is_auto() && !width.is_auto()) {
+ // FIXME: Check direction
+ // FIXME: Use the static-position containing block
+ left = zero_value;
+ right = solve_for_right();
+ }
+
+ // 3. 'width' and 'right' are 'auto' and 'left' is not 'auto',
+ // then the width is shrink-to-fit. Then solve for 'right'
+ else if (width.is_auto() && right.is_auto() && !left.is_auto()) {
+ Rule3:
+ auto result = calculate_shrink_to_fit_widths(box);
+ auto available_width = solve_for_width();
+ width = CSS::Length(min(max(result.preferred_minimum_width, available_width.to_px(box)), result.preferred_width), CSS::Length::Type::Px);
+ right = solve_for_right();
+ }
+
+ // 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve for 'left'
+ else if (left.is_auto() && !width.is_auto() && !right.is_auto()) {
+ left = solve_for_left();
+ }
+
+ // 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve for 'width'
+ else if (width.is_auto() && !left.is_auto() && !right.is_auto()) {
+ width = solve_for_width();
+ }
+
+ // 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve for 'right'
+ else if (right.is_auto() && !left.is_auto() && !width.is_auto()) {
+ right = solve_for_right();
+ }
+
+ return width;
+ };
+
+ auto specified_width = computed_values.width().resolved_or_auto(box, containing_block.width());
+
+ // 1. The tentative used width is calculated (without 'min-width' and 'max-width')
+ auto used_width = try_compute_width(specified_width);
+
+ // 2. The tentative used width is greater than 'max-width', the rules above are applied again,
+ // but this time using the computed value of 'max-width' as the computed value for 'width'.
+ auto specified_max_width = computed_values.max_width().resolved_or_auto(box, containing_block.width());
+ if (!specified_max_width.is_auto()) {
+ if (used_width.to_px(box) > specified_max_width.to_px(box)) {
+ used_width = try_compute_width(specified_max_width);
+ }
+ }
+
+ // 3. If the resulting width is smaller than 'min-width', the rules above are applied again,
+ // but this time using the value of 'min-width' as the computed value for 'width'.
+ auto specified_min_width = computed_values.min_width().resolved_or_auto(box, containing_block.width());
+ if (!specified_min_width.is_auto()) {
+ if (used_width.to_px(box) < specified_min_width.to_px(box)) {
+ used_width = try_compute_width(specified_min_width);
+ }
+ }
+
+ box.set_width(used_width.to_px(box));
+
+ box.box_model().margin.left = margin_left.to_px(box);
+ box.box_model().margin.right = margin_right.to_px(box);
+ box.box_model().border.left = border_left;
+ box.box_model().border.right = border_right;
+ box.box_model().padding.left = padding_left.to_px(box);
+ box.box_model().padding.right = padding_right.to_px(box);
+}
+
+void FormattingContext::compute_width_for_absolutely_positioned_replaced_element(ReplacedBox& box)
+{
+ // 10.3.8 Absolutely positioned, replaced elements
+ // The used value of 'width' is determined as for inline replaced elements.
+ box.prepare_for_replaced_layout();
+ box.set_width(compute_width_for_replaced_element(box));
+}
+
+void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_element(Box& box)
+{
+ auto& computed_values = box.computed_values();
+ auto& containing_block = *box.containing_block();
+
+ CSS::Length specified_height;
+
+ if (computed_values.height().is_percentage() && !containing_block.computed_values().height().is_absolute()) {
+ specified_height = CSS::Length::make_auto();
+ } else {
+ specified_height = computed_values.height().resolved_or_auto(box, containing_block.height());
+ }
+
+ auto specified_max_height = computed_values.max_height().resolved_or_auto(box, containing_block.height());
+
+ box.box_model().margin.top = computed_values.margin().top.resolved_or_zero(box, containing_block.width()).to_px(box);
+ box.box_model().margin.bottom = computed_values.margin().bottom.resolved_or_zero(box, containing_block.width()).to_px(box);
+ box.box_model().border.top = computed_values.border_top().width;
+ box.box_model().border.bottom = computed_values.border_bottom().width;
+ box.box_model().padding.top = computed_values.padding().top.resolved_or_zero(box, containing_block.width()).to_px(box);
+ box.box_model().padding.bottom = computed_values.padding().bottom.resolved_or_zero(box, containing_block.width()).to_px(box);
+
+ if (!specified_height.is_auto()) {
+ float used_height = specified_height.to_px(box);
+ if (!specified_max_height.is_auto())
+ used_height = min(used_height, specified_max_height.to_px(box));
+ box.set_height(used_height);
+ }
+}
+
+void FormattingContext::layout_absolutely_positioned_element(Box& box)
+{
+ auto& containing_block = context_box();
+ auto& box_model = box.box_model();
+
+ auto specified_width = box.computed_values().width().resolved_or_auto(box, containing_block.width());
+
+ compute_width_for_absolutely_positioned_element(box);
+ layout_inside(box, LayoutMode::Default);
+ compute_height_for_absolutely_positioned_element(box);
+
+ box_model.margin.left = box.computed_values().margin().left.resolved_or_auto(box, containing_block.width()).to_px(box);
+ box_model.margin.top = box.computed_values().margin().top.resolved_or_auto(box, containing_block.height()).to_px(box);
+ box_model.margin.right = box.computed_values().margin().right.resolved_or_auto(box, containing_block.width()).to_px(box);
+ box_model.margin.bottom = box.computed_values().margin().bottom.resolved_or_auto(box, containing_block.height()).to_px(box);
+
+ box_model.border.left = box.computed_values().border_left().width;
+ box_model.border.right = box.computed_values().border_right().width;
+ box_model.border.top = box.computed_values().border_top().width;
+ box_model.border.bottom = box.computed_values().border_bottom().width;
+
+ box_model.offset.left = box.computed_values().offset().left.resolved_or_auto(box, containing_block.width()).to_px(box);
+ box_model.offset.top = box.computed_values().offset().top.resolved_or_auto(box, containing_block.height()).to_px(box);
+ box_model.offset.right = box.computed_values().offset().right.resolved_or_auto(box, containing_block.width()).to_px(box);
+ box_model.offset.bottom = box.computed_values().offset().bottom.resolved_or_auto(box, containing_block.height()).to_px(box);
+
+ if (box.computed_values().offset().left.is_auto() && specified_width.is_auto() && box.computed_values().offset().right.is_auto()) {
+ if (box.computed_values().margin().left.is_auto())
+ box_model.margin.left = 0;
+ if (box.computed_values().margin().right.is_auto())
+ box_model.margin.right = 0;
+ }
+
+ Gfx::FloatPoint used_offset;
+
+ if (!box.computed_values().offset().left.is_auto()) {
+ float x_offset = box_model.offset.left
+ + box_model.border_box().left;
+ used_offset.set_x(x_offset + box_model.margin.left);
+ } else if (!box.computed_values().offset().right.is_auto()) {
+ float x_offset = 0
+ - box_model.offset.right
+ - box_model.border_box().right;
+ used_offset.set_x(containing_block.width() + x_offset - box.width() - box_model.margin.right);
+ } else {
+ float x_offset = box_model.margin_box().left;
+ used_offset.set_x(x_offset);
+ }
+
+ if (!box.computed_values().offset().top.is_auto()) {
+ float y_offset = box_model.offset.top
+ + box_model.border_box().top;
+ used_offset.set_y(y_offset + box_model.margin.top);
+ } else if (!box.computed_values().offset().bottom.is_auto()) {
+ float y_offset = 0
+ - box_model.offset.bottom
+ - box_model.border_box().bottom;
+ used_offset.set_y(containing_block.height() + y_offset - box.height() - box_model.margin.bottom);
+ } else {
+ float y_offset = box_model.margin_box().top;
+ used_offset.set_y(y_offset);
+ }
+
+ box.set_offset(used_offset);
+}
+
+void FormattingContext::compute_height_for_absolutely_positioned_replaced_element(ReplacedBox& box)
+{
+ // FIXME: Implement this.
+ return compute_height_for_absolutely_positioned_non_replaced_element(box);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.h b/Userland/Libraries/LibWeb/Layout/FormattingContext.h
new file mode 100644
index 0000000000..aad024a04e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Forward.h>
+
+namespace Web::Layout {
+
+class FormattingContext {
+public:
+ virtual void run(Box&, LayoutMode) = 0;
+
+ Box& context_box() { return *m_context_box; }
+ const Box& context_box() const { return *m_context_box; }
+
+ FormattingContext* parent() { return m_parent; }
+ const FormattingContext* parent() const { return m_parent; }
+
+ virtual bool is_block_formatting_context() const { return false; }
+
+ static bool creates_block_formatting_context(const Box&);
+
+ static float compute_width_for_replaced_element(const ReplacedBox&);
+ static float compute_height_for_replaced_element(const ReplacedBox&);
+
+protected:
+ FormattingContext(Box&, FormattingContext* parent = nullptr);
+ virtual ~FormattingContext();
+
+ void layout_inside(Box&, LayoutMode);
+
+ struct ShrinkToFitResult {
+ float preferred_width { 0 };
+ float preferred_minimum_width { 0 };
+ };
+
+ static float tentative_width_for_replaced_element(const ReplacedBox&, const CSS::Length& width);
+ static float tentative_height_for_replaced_element(const ReplacedBox&, const CSS::Length& width);
+
+ ShrinkToFitResult calculate_shrink_to_fit_widths(Box&);
+
+ void layout_absolutely_positioned_element(Box&);
+ void compute_width_for_absolutely_positioned_element(Box&);
+ void compute_width_for_absolutely_positioned_non_replaced_element(Box&);
+ void compute_width_for_absolutely_positioned_replaced_element(ReplacedBox&);
+ void compute_height_for_absolutely_positioned_element(Box&);
+ void compute_height_for_absolutely_positioned_non_replaced_element(Box&);
+ void compute_height_for_absolutely_positioned_replaced_element(ReplacedBox&);
+
+ FormattingContext* m_parent { nullptr };
+ Box* m_context_box { nullptr };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/FrameBox.cpp b/Userland/Libraries/LibWeb/Layout/FrameBox.cpp
new file mode 100644
index 0000000000..79ad0e1815
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/FrameBox.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGUI/Widget.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/StylePainter.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/InProcessWebView.h>
+#include <LibWeb/Layout/FrameBox.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+#include <LibWeb/Page/Frame.h>
+
+//#define DEBUG_HIGHLIGHT_FOCUSED_FRAME
+
+namespace Web::Layout {
+
+FrameBox::FrameBox(DOM::Document& document, DOM::Element& element, NonnullRefPtr<CSS::StyleProperties> style)
+ : ReplacedBox(document, element, move(style))
+{
+}
+
+FrameBox::~FrameBox()
+{
+}
+
+void FrameBox::prepare_for_replaced_layout()
+{
+ ASSERT(dom_node().content_frame());
+
+ set_has_intrinsic_width(true);
+ set_has_intrinsic_height(true);
+ // FIXME: Do proper error checking, etc.
+ set_intrinsic_width(dom_node().attribute(HTML::AttributeNames::width).to_int().value_or(300));
+ set_intrinsic_height(dom_node().attribute(HTML::AttributeNames::height).to_int().value_or(150));
+}
+
+void FrameBox::paint(PaintContext& context, PaintPhase phase)
+{
+ ReplacedBox::paint(context, phase);
+
+ if (phase == PaintPhase::Foreground) {
+ auto* hosted_document = dom_node().content_document();
+ if (!hosted_document)
+ return;
+ auto* hosted_layout_tree = hosted_document->layout_node();
+ if (!hosted_layout_tree)
+ return;
+
+ context.painter().save();
+ auto old_viewport_rect = context.viewport_rect();
+
+ context.painter().add_clip_rect(enclosing_int_rect(absolute_rect()));
+ context.painter().translate(absolute_x(), absolute_y());
+
+ context.set_viewport_rect({ {}, dom_node().content_frame()->size() });
+ const_cast<Layout::InitialContainingBlockBox*>(hosted_layout_tree)->paint_all_phases(context);
+
+ context.set_viewport_rect(old_viewport_rect);
+ context.painter().restore();
+
+#ifdef DEBUG_HIGHLIGHT_FOCUSED_FRAME
+ if (dom_node().content_frame()->is_focused_frame()) {
+ context.painter().draw_rect(absolute_rect().to<int>(), Color::Cyan);
+ }
+#endif
+ }
+}
+
+void FrameBox::did_set_rect()
+{
+ ReplacedBox::did_set_rect();
+
+ ASSERT(dom_node().content_frame());
+ dom_node().content_frame()->set_size(size().to_type<int>());
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/FrameBox.h b/Userland/Libraries/LibWeb/Layout/FrameBox.h
new file mode 100644
index 0000000000..93a5216f3f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/FrameBox.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLIFrameElement.h>
+#include <LibWeb/Layout/ReplacedBox.h>
+
+namespace Web::Layout {
+
+class FrameBox final : public ReplacedBox {
+public:
+ FrameBox(DOM::Document&, DOM::Element&, NonnullRefPtr<CSS::StyleProperties>);
+ virtual ~FrameBox() override;
+
+ virtual void paint(PaintContext&, PaintPhase) override;
+ virtual void prepare_for_replaced_layout() override;
+
+ const HTML::HTMLIFrameElement& dom_node() const { return downcast<HTML::HTMLIFrameElement>(ReplacedBox::dom_node()); }
+ HTML::HTMLIFrameElement& dom_node() { return downcast<HTML::HTMLIFrameElement>(ReplacedBox::dom_node()); }
+
+private:
+ virtual void did_set_rect() override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/ImageBox.cpp b/Userland/Libraries/LibWeb/Layout/ImageBox.cpp
new file mode 100644
index 0000000000..a00c551c7a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/ImageBox.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Painter.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibGfx/ImageDecoder.h>
+#include <LibGfx/StylePainter.h>
+#include <LibWeb/Layout/ImageBox.h>
+
+namespace Web::Layout {
+
+ImageBox::ImageBox(DOM::Document& document, DOM::Element& element, NonnullRefPtr<CSS::StyleProperties> style, const ImageLoader& image_loader)
+ : ReplacedBox(document, element, move(style))
+ , m_image_loader(image_loader)
+{
+}
+
+ImageBox::~ImageBox()
+{
+}
+
+int ImageBox::preferred_width() const
+{
+ return dom_node().attribute(HTML::AttributeNames::width).to_int().value_or(m_image_loader.width());
+}
+
+int ImageBox::preferred_height() const
+{
+ return dom_node().attribute(HTML::AttributeNames::height).to_int().value_or(m_image_loader.height());
+}
+
+void ImageBox::prepare_for_replaced_layout()
+{
+ if (!m_image_loader.has_loaded_or_failed()) {
+ set_has_intrinsic_width(true);
+ set_has_intrinsic_height(true);
+ set_intrinsic_width(0);
+ set_intrinsic_height(0);
+ } else {
+ if (m_image_loader.width()) {
+ set_has_intrinsic_width(true);
+ set_intrinsic_width(m_image_loader.width());
+ }
+ if (m_image_loader.height()) {
+ set_has_intrinsic_height(true);
+ set_intrinsic_height(m_image_loader.height());
+ }
+
+ if (m_image_loader.width() && m_image_loader.height()) {
+ set_has_intrinsic_ratio(true);
+ set_intrinsic_ratio((float)m_image_loader.width() / (float)m_image_loader.height());
+ } else {
+ set_has_intrinsic_ratio(false);
+ }
+ }
+
+ if (renders_as_alt_text()) {
+ auto& image_element = downcast<HTML::HTMLImageElement>(dom_node());
+ auto& font = Gfx::FontDatabase::default_font();
+ auto alt = image_element.alt();
+ if (alt.is_empty())
+ alt = image_element.src();
+ set_width(font.width(alt) + 16);
+ set_height(font.glyph_height() + 16);
+ }
+
+ if (!has_intrinsic_width() && !has_intrinsic_height()) {
+ set_width(16);
+ set_height(16);
+ }
+}
+
+void ImageBox::paint(PaintContext& context, PaintPhase phase)
+{
+ if (!is_visible())
+ return;
+
+ // FIXME: This should be done at a different level. Also rect() does not include padding etc!
+ if (!context.viewport_rect().intersects(enclosing_int_rect(absolute_rect())))
+ return;
+
+ ReplacedBox::paint(context, phase);
+
+ if (phase == PaintPhase::Foreground) {
+ if (renders_as_alt_text()) {
+ auto& image_element = downcast<HTML::HTMLImageElement>(dom_node());
+ context.painter().set_font(Gfx::FontDatabase::default_font());
+ Gfx::StylePainter::paint_frame(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2);
+ auto alt = image_element.alt();
+ if (alt.is_empty())
+ alt = image_element.src();
+ context.painter().draw_text(enclosing_int_rect(absolute_rect()), alt, Gfx::TextAlignment::Center, computed_values().color(), Gfx::TextElision::Right);
+ } else if (auto bitmap = m_image_loader.bitmap()) {
+ context.painter().draw_scaled_bitmap(enclosing_int_rect(absolute_rect()), *bitmap, bitmap->rect());
+ }
+ }
+}
+
+bool ImageBox::renders_as_alt_text() const
+{
+ if (is<HTML::HTMLImageElement>(dom_node()))
+ return !m_image_loader.has_image();
+ return false;
+}
+
+void ImageBox::set_visible_in_viewport(Badge<Layout::InitialContainingBlockBox>, bool visible_in_viewport)
+{
+ m_image_loader.set_visible_in_viewport(visible_in_viewport);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/ImageBox.h b/Userland/Libraries/LibWeb/Layout/ImageBox.h
new file mode 100644
index 0000000000..a404ca1ca0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/ImageBox.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/HTML/HTMLImageElement.h>
+#include <LibWeb/Layout/ReplacedBox.h>
+
+namespace Web::Layout {
+
+class ImageBox : public ReplacedBox {
+public:
+ ImageBox(DOM::Document&, DOM::Element&, NonnullRefPtr<CSS::StyleProperties>, const ImageLoader&);
+ virtual ~ImageBox() override;
+
+ virtual void prepare_for_replaced_layout() override;
+ virtual void paint(PaintContext&, PaintPhase) override;
+
+ const DOM::Element& dom_node() const { return static_cast<const DOM::Element&>(ReplacedBox::dom_node()); }
+
+ bool renders_as_alt_text() const;
+
+ void set_visible_in_viewport(Badge<InitialContainingBlockBox>, bool);
+
+private:
+ int preferred_width() const;
+ int preferred_height() const;
+
+ const ImageLoader& m_image_loader;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/InitialContainingBlockBox.cpp b/Userland/Libraries/LibWeb/Layout/InitialContainingBlockBox.cpp
new file mode 100644
index 0000000000..320f0f770c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/InitialContainingBlockBox.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Dump.h>
+#include <LibWeb/Layout/ImageBox.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+#include <LibWeb/Layout/WidgetBox.h>
+#include <LibWeb/Page/Frame.h>
+#include <LibWeb/Painting/StackingContext.h>
+
+namespace Web::Layout {
+
+InitialContainingBlockBox::InitialContainingBlockBox(DOM::Document& document, NonnullRefPtr<CSS::StyleProperties> style)
+ : BlockBox(document, &document, move(style))
+{
+}
+
+InitialContainingBlockBox::~InitialContainingBlockBox()
+{
+}
+
+void InitialContainingBlockBox::build_stacking_context_tree()
+{
+ if (stacking_context())
+ return;
+
+ set_stacking_context(make<StackingContext>(*this, nullptr));
+
+ for_each_in_subtree_of_type<Box>([&](Box& box) {
+ if (&box == this)
+ return IterationDecision::Continue;
+ if (!box.establishes_stacking_context()) {
+ ASSERT(!box.stacking_context());
+ return IterationDecision::Continue;
+ }
+ auto* parent_context = box.enclosing_stacking_context();
+ ASSERT(parent_context);
+ box.set_stacking_context(make<StackingContext>(box, parent_context));
+ return IterationDecision::Continue;
+ });
+}
+
+void InitialContainingBlockBox::did_set_viewport_rect(Badge<Frame>, const Gfx::IntRect& a_viewport_rect)
+{
+ Gfx::FloatRect viewport_rect(a_viewport_rect.x(), a_viewport_rect.y(), a_viewport_rect.width(), a_viewport_rect.height());
+ for_each_in_subtree_of_type<ImageBox>([&](auto& layout_image) {
+ const_cast<ImageBox&>(layout_image).set_visible_in_viewport({}, viewport_rect.intersects(layout_image.absolute_rect()));
+ return IterationDecision::Continue;
+ });
+}
+
+void InitialContainingBlockBox::paint_all_phases(PaintContext& context)
+{
+ paint(context, PaintPhase::Background);
+ paint(context, PaintPhase::Border);
+ paint(context, PaintPhase::Foreground);
+ if (context.has_focus())
+ paint(context, PaintPhase::FocusOutline);
+ paint(context, PaintPhase::Overlay);
+}
+
+void InitialContainingBlockBox::paint(PaintContext& context, PaintPhase phase)
+{
+ stacking_context()->paint(context, phase);
+}
+
+HitTestResult InitialContainingBlockBox::hit_test(const Gfx::IntPoint& position, HitTestType type) const
+{
+ return stacking_context()->hit_test(position, type);
+}
+
+void InitialContainingBlockBox::recompute_selection_states()
+{
+ SelectionState state = SelectionState::None;
+
+ auto selection = this->selection().normalized();
+
+ for_each_in_subtree([&](auto& layout_node) {
+ if (!selection.is_valid()) {
+ // Everything gets SelectionState::None.
+ } else if (&layout_node == selection.start().layout_node && &layout_node == selection.end().layout_node) {
+ state = SelectionState::StartAndEnd;
+ } else if (&layout_node == selection.start().layout_node) {
+ state = SelectionState::Start;
+ } else if (&layout_node == selection.end().layout_node) {
+ state = SelectionState::End;
+ } else {
+ if (state == SelectionState::Start)
+ state = SelectionState::Full;
+ else if (state == SelectionState::End || state == SelectionState::StartAndEnd)
+ state = SelectionState::None;
+ }
+ layout_node.set_selection_state(state);
+ return IterationDecision::Continue;
+ });
+}
+
+void InitialContainingBlockBox::set_selection(const LayoutRange& selection)
+{
+ m_selection = selection;
+ recompute_selection_states();
+}
+
+void InitialContainingBlockBox::set_selection_end(const LayoutPosition& position)
+{
+ m_selection.set_end(position);
+ recompute_selection_states();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/InitialContainingBlockBox.h b/Userland/Libraries/LibWeb/Layout/InitialContainingBlockBox.h
new file mode 100644
index 0000000000..b7b4810364
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/InitialContainingBlockBox.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/Layout/BlockBox.h>
+
+namespace Web::Layout {
+
+class InitialContainingBlockBox final : public BlockBox {
+public:
+ explicit InitialContainingBlockBox(DOM::Document&, NonnullRefPtr<CSS::StyleProperties>);
+ virtual ~InitialContainingBlockBox() override;
+
+ const DOM::Document& dom_node() const { return static_cast<const DOM::Document&>(*Node::dom_node()); }
+
+ void paint_all_phases(PaintContext&);
+ virtual void paint(PaintContext&, PaintPhase) override;
+
+ virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const override;
+
+ const LayoutRange& selection() const { return m_selection; }
+ void set_selection(const LayoutRange&);
+ void set_selection_end(const LayoutPosition&);
+
+ void did_set_viewport_rect(Badge<Frame>, const Gfx::IntRect&);
+
+ void build_stacking_context_tree();
+
+ void recompute_selection_states();
+
+private:
+ LayoutRange m_selection;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp
new file mode 100644
index 0000000000..b309e02e04
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/Length.h>
+#include <LibWeb/DOM/Node.h>
+#include <LibWeb/Dump.h>
+#include <LibWeb/Layout/BlockBox.h>
+#include <LibWeb/Layout/BlockFormattingContext.h>
+#include <LibWeb/Layout/Box.h>
+#include <LibWeb/Layout/InlineFormattingContext.h>
+#include <LibWeb/Layout/InlineNode.h>
+#include <LibWeb/Layout/ReplacedBox.h>
+
+namespace Web::Layout {
+
+InlineFormattingContext::InlineFormattingContext(Box& containing_block, FormattingContext* parent)
+ : FormattingContext(containing_block, parent)
+{
+}
+
+InlineFormattingContext::~InlineFormattingContext()
+{
+}
+
+struct AvailableSpaceForLineInfo {
+ float left { 0 };
+ float right { 0 };
+};
+
+static AvailableSpaceForLineInfo available_space_for_line(const InlineFormattingContext& context, size_t line_index)
+{
+ AvailableSpaceForLineInfo info;
+
+ // FIXME: This is a total hack guess since we don't actually know the final y position of lines here!
+ float line_height = context.containing_block().line_height();
+ float y = (line_index * line_height);
+
+ auto& bfc = static_cast<const BlockFormattingContext&>(*context.parent());
+
+ for (ssize_t i = bfc.left_floating_boxes().size() - 1; i >= 0; --i) {
+ auto& floating_box = *bfc.left_floating_boxes().at(i);
+ auto rect = floating_box.margin_box_as_relative_rect();
+ if (rect.contains_vertically(y)) {
+ info.left = rect.right() + 1;
+ break;
+ }
+ }
+
+ info.right = context.containing_block().width();
+
+ for (ssize_t i = bfc.right_floating_boxes().size() - 1; i >= 0; --i) {
+ auto& floating_box = *bfc.right_floating_boxes().at(i);
+ auto rect = floating_box.margin_box_as_relative_rect();
+ if (rect.contains_vertically(y)) {
+ info.right = rect.left() - 1;
+ break;
+ }
+ }
+
+ return info;
+}
+
+float InlineFormattingContext::available_width_at_line(size_t line_index) const
+{
+ auto info = available_space_for_line(*this, line_index);
+ return info.right - info.left;
+}
+
+void InlineFormattingContext::run(Box&, LayoutMode layout_mode)
+{
+ ASSERT(containing_block().children_are_inline());
+ containing_block().line_boxes().clear();
+ containing_block().for_each_child([&](auto& child) {
+ ASSERT(child.is_inline());
+ if (is<Box>(child) && child.is_absolutely_positioned()) {
+ layout_absolutely_positioned_element(downcast<Box>(child));
+ return;
+ }
+
+ child.split_into_lines(*this, layout_mode);
+ });
+
+ for (auto& line_box : containing_block().line_boxes()) {
+ line_box.trim_trailing_whitespace();
+ }
+
+ // If there's an empty line box at the bottom, just remove it instead of giving it height.
+ if (!containing_block().line_boxes().is_empty() && containing_block().line_boxes().last().fragments().is_empty())
+ containing_block().line_boxes().take_last();
+
+ auto text_align = containing_block().computed_values().text_align();
+ float min_line_height = containing_block().line_height();
+ float content_height = 0;
+ float max_linebox_width = 0;
+
+ for (size_t line_index = 0; line_index < containing_block().line_boxes().size(); ++line_index) {
+ auto& line_box = containing_block().line_boxes()[line_index];
+ float max_height = min_line_height;
+ for (auto& fragment : line_box.fragments()) {
+ max_height = max(max_height, fragment.height());
+ }
+
+ float x_offset = available_space_for_line(*this, line_index).left;
+
+ float excess_horizontal_space = (float)containing_block().width() - line_box.width();
+
+ switch (text_align) {
+ case CSS::TextAlign::Center:
+ case CSS::TextAlign::LibwebCenter:
+ x_offset += excess_horizontal_space / 2;
+ break;
+ case CSS::TextAlign::Right:
+ x_offset += excess_horizontal_space;
+ break;
+ case CSS::TextAlign::Left:
+ case CSS::TextAlign::Justify:
+ default:
+ break;
+ }
+
+ float excess_horizontal_space_including_whitespace = excess_horizontal_space;
+ int whitespace_count = 0;
+ if (text_align == CSS::TextAlign::Justify) {
+ for (auto& fragment : line_box.fragments()) {
+ if (fragment.is_justifiable_whitespace()) {
+ ++whitespace_count;
+ excess_horizontal_space_including_whitespace += fragment.width();
+ }
+ }
+ }
+
+ float justified_space_width = whitespace_count ? (excess_horizontal_space_including_whitespace / (float)whitespace_count) : 0;
+
+ for (size_t i = 0; i < line_box.fragments().size(); ++i) {
+ auto& fragment = line_box.fragments()[i];
+
+ if (fragment.type() == LineBoxFragment::Type::Leading || fragment.type() == LineBoxFragment::Type::Trailing) {
+ fragment.set_height(max_height);
+ } else {
+ fragment.set_height(max(min_line_height, fragment.height()));
+ }
+
+ // Vertically align everyone's bottom to the line.
+ // FIXME: Support other kinds of vertical alignment.
+ fragment.set_offset({ roundf(x_offset + fragment.offset().x()), content_height + (max_height - fragment.height()) });
+
+ if (text_align == CSS::TextAlign::Justify
+ && fragment.is_justifiable_whitespace()
+ && fragment.width() != justified_space_width) {
+ float diff = justified_space_width - fragment.width();
+ fragment.set_width(justified_space_width);
+ // Shift subsequent sibling fragments to the right to adjust for change in width.
+ for (size_t j = i + 1; j < line_box.fragments().size(); ++j) {
+ auto offset = line_box.fragments()[j].offset();
+ offset.move_by(diff, 0);
+ line_box.fragments()[j].set_offset(offset);
+ }
+ }
+ }
+
+ if (!line_box.fragments().is_empty()) {
+ float left_edge = line_box.fragments().first().offset().x();
+ float right_edge = line_box.fragments().last().offset().x() + line_box.fragments().last().width();
+ float final_line_box_width = right_edge - left_edge;
+ line_box.m_width = final_line_box_width;
+ max_linebox_width = max(max_linebox_width, final_line_box_width);
+ }
+
+ content_height += max_height;
+ }
+
+ if (layout_mode != LayoutMode::Default) {
+ containing_block().set_width(max_linebox_width);
+ }
+
+ containing_block().set_height(content_height);
+}
+
+void InlineFormattingContext::dimension_box_on_line(Box& box, LayoutMode layout_mode)
+{
+ if (is<ReplacedBox>(box)) {
+ auto& replaced = downcast<ReplacedBox>(box);
+ replaced.set_width(compute_width_for_replaced_element(replaced));
+ replaced.set_height(compute_height_for_replaced_element(replaced));
+ return;
+ }
+
+ if (box.is_inline_block()) {
+ auto& inline_block = const_cast<BlockBox&>(downcast<BlockBox>(box));
+
+ if (inline_block.computed_values().width().is_undefined_or_auto()) {
+ auto result = calculate_shrink_to_fit_widths(inline_block);
+
+ auto margin_left = inline_block.computed_values().margin().left.resolved_or_zero(inline_block, containing_block().width()).to_px(inline_block);
+ auto border_left_width = inline_block.computed_values().border_left().width;
+ auto padding_left = inline_block.computed_values().padding().left.resolved_or_zero(inline_block, containing_block().width()).to_px(inline_block);
+
+ auto margin_right = inline_block.computed_values().margin().right.resolved_or_zero(inline_block, containing_block().width()).to_px(inline_block);
+ auto border_right_width = inline_block.computed_values().border_right().width;
+ auto padding_right = inline_block.computed_values().padding().right.resolved_or_zero(inline_block, containing_block().width()).to_px(inline_block);
+
+ auto available_width = containing_block().width()
+ - margin_left
+ - border_left_width
+ - padding_left
+ - padding_right
+ - border_right_width
+ - margin_right;
+
+ auto width = min(max(result.preferred_minimum_width, available_width), result.preferred_width);
+ inline_block.set_width(width);
+ } else {
+ inline_block.set_width(inline_block.computed_values().width().resolved_or_zero(inline_block, containing_block().width()).to_px(inline_block));
+ }
+ layout_inside(inline_block, layout_mode);
+
+ if (inline_block.computed_values().height().is_undefined_or_auto()) {
+ // FIXME: (10.6.6) If 'height' is 'auto', the height depends on the element's descendants per 10.6.7.
+ } else {
+ inline_block.set_height(inline_block.computed_values().height().resolved_or_zero(inline_block, containing_block().height()).to_px(inline_block));
+ }
+ return;
+ }
+
+ // Non-replaced, non-inline-block, box on a line!?
+ // I don't think we should be here. Dump the box tree so we can take a look at it.
+ dbgln("FIXME: I've been asked to dimension a non-replaced, non-inline-block box on a line:");
+ dump_tree(box);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h
new file mode 100644
index 0000000000..38af898d21
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+#include <LibWeb/Forward.h>
+#include <LibWeb/Layout/FormattingContext.h>
+
+namespace Web::Layout {
+
+class InlineFormattingContext final : public FormattingContext {
+public:
+ InlineFormattingContext(Box& containing_block, FormattingContext* parent);
+ ~InlineFormattingContext();
+
+ Box& containing_block() { return context_box(); }
+ const Box& containing_block() const { return context_box(); }
+
+ virtual void run(Box&, LayoutMode) override;
+
+ float available_width_at_line(size_t line_index) const;
+
+ void dimension_box_on_line(Box&, LayoutMode);
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/InlineNode.cpp b/Userland/Libraries/LibWeb/Layout/InlineNode.cpp
new file mode 100644
index 0000000000..ab3974d54f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/InlineNode.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/Painter.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/Layout/BlockBox.h>
+#include <LibWeb/Layout/InlineFormattingContext.h>
+#include <LibWeb/Layout/InlineNode.h>
+
+namespace Web::Layout {
+
+InlineNode::InlineNode(DOM::Document& document, DOM::Element& element, NonnullRefPtr<CSS::StyleProperties> style)
+ : Layout::NodeWithStyleAndBoxModelMetrics(document, &element, move(style))
+{
+ set_inline(true);
+}
+
+InlineNode::~InlineNode()
+{
+}
+
+void InlineNode::split_into_lines(InlineFormattingContext& context, LayoutMode layout_mode)
+{
+ auto& containing_block = context.context_box();
+
+ if (!computed_values().padding().left.is_undefined_or_auto()) {
+ float padding_left = computed_values().padding().left.resolved(CSS::Length::make_px(0), *this, containing_block.width()).to_px(*this);
+ containing_block.ensure_last_line_box().add_fragment(*this, 0, 0, padding_left, 0, LineBoxFragment::Type::Leading);
+ }
+
+ NodeWithStyleAndBoxModelMetrics::split_into_lines(context, layout_mode);
+
+ if (!computed_values().padding().right.is_undefined_or_auto()) {
+ float padding_right = computed_values().padding().right.resolved(CSS::Length::make_px(0), *this, containing_block.width()).to_px(*this);
+ containing_block.ensure_last_line_box().add_fragment(*this, 0, 0, padding_right, 0, LineBoxFragment::Type::Trailing);
+ }
+}
+
+void InlineNode::paint_fragment(PaintContext& context, const LineBoxFragment& fragment, PaintPhase phase) const
+{
+ auto& painter = context.painter();
+
+ if (phase == PaintPhase::Background) {
+ painter.fill_rect(enclosing_int_rect(fragment.absolute_rect()), computed_values().background_color());
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/InlineNode.h b/Userland/Libraries/LibWeb/Layout/InlineNode.h
new file mode 100644
index 0000000000..8f47b24f34
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/InlineNode.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Layout/Box.h>
+
+namespace Web::Layout {
+
+class InlineNode : public NodeWithStyleAndBoxModelMetrics {
+public:
+ InlineNode(DOM::Document&, DOM::Element&, NonnullRefPtr<CSS::StyleProperties>);
+ virtual ~InlineNode() override;
+
+ virtual void paint_fragment(PaintContext&, const LineBoxFragment&, PaintPhase) const override;
+
+ virtual void split_into_lines(InlineFormattingContext&, LayoutMode) override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/LayoutPosition.cpp b/Userland/Libraries/LibWeb/Layout/LayoutPosition.cpp
new file mode 100644
index 0000000000..307c81f9f7
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/LayoutPosition.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Position.h>
+#include <LibWeb/DOM/Range.h>
+#include <LibWeb/Layout/LayoutPosition.h>
+#include <LibWeb/Layout/Node.h>
+
+namespace Web::Layout {
+
+DOM::Position LayoutPosition::to_dom_position() const
+{
+ if (!layout_node)
+ return {};
+
+ // FIXME: Verify that there are no shenanigans going on.
+ return { const_cast<DOM::Node&>(*layout_node->dom_node()), (unsigned)index_in_node };
+}
+
+LayoutRange LayoutRange::normalized() const
+{
+ if (!is_valid())
+ return {};
+ if (m_start.layout_node == m_end.layout_node) {
+ if (m_start.index_in_node < m_end.index_in_node)
+ return *this;
+ return { m_end, m_start };
+ }
+ if (m_start.layout_node->is_before(*m_end.layout_node))
+ return *this;
+ return { m_end, m_start };
+}
+
+NonnullRefPtr<DOM::Range> LayoutRange::to_dom_range() const
+{
+ ASSERT(is_valid());
+
+ auto start = m_start.to_dom_position();
+ auto end = m_end.to_dom_position();
+
+ return DOM::Range::create(*start.node(), start.offset(), *end.node(), end.offset());
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/LayoutPosition.h b/Userland/Libraries/LibWeb/Layout/LayoutPosition.h
new file mode 100644
index 0000000000..840a9c388b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/LayoutPosition.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/RefPtr.h>
+#include <LibWeb/Forward.h>
+#include <LibWeb/Layout/Node.h>
+
+namespace Web::Layout {
+
+class Node;
+
+struct LayoutPosition {
+ RefPtr<Node> layout_node;
+ int index_in_node { 0 };
+
+ DOM::Position to_dom_position() const;
+};
+
+class LayoutRange {
+public:
+ LayoutRange() { }
+ LayoutRange(const LayoutPosition& start, const LayoutPosition& end)
+ : m_start(start)
+ , m_end(end)
+ {
+ }
+
+ bool is_valid() const { return m_start.layout_node && m_end.layout_node; }
+
+ void set(const LayoutPosition& start, const LayoutPosition& end)
+ {
+ m_start = start;
+ m_end = end;
+ }
+
+ void set_start(const LayoutPosition& start) { m_start = start; }
+ void set_end(const LayoutPosition& end) { m_end = end; }
+
+ const LayoutPosition& start() const { return m_start; }
+ LayoutPosition& start() { return m_start; }
+ const LayoutPosition& end() const { return m_end; }
+ LayoutPosition& end() { return m_end; }
+
+ LayoutRange normalized() const;
+
+ NonnullRefPtr<DOM::Range> to_dom_range() const;
+
+private:
+ LayoutPosition m_start;
+ LayoutPosition m_end;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/LineBox.cpp b/Userland/Libraries/LibWeb/Layout/LineBox.cpp
new file mode 100644
index 0000000000..df08bfe9eb
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/LineBox.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Utf8View.h>
+#include <LibWeb/Layout/Box.h>
+#include <LibWeb/Layout/LineBox.h>
+#include <LibWeb/Layout/Node.h>
+#include <LibWeb/Layout/TextNode.h>
+#include <ctype.h>
+
+namespace Web::Layout {
+
+void LineBox::add_fragment(Node& layout_node, int start, int length, float width, float height, LineBoxFragment::Type fragment_type)
+{
+ bool text_align_is_justify = layout_node.computed_values().text_align() == CSS::TextAlign::Justify;
+ if (!text_align_is_justify && !m_fragments.is_empty() && &m_fragments.last().layout_node() == &layout_node) {
+ // The fragment we're adding is from the last Layout::Node on the line.
+ // Expand the last fragment instead of adding a new one with the same Layout::Node.
+ m_fragments.last().m_length = (start - m_fragments.last().m_start) + length;
+ m_fragments.last().set_width(m_fragments.last().width() + width);
+ } else {
+ m_fragments.append(make<LineBoxFragment>(layout_node, start, length, Gfx::FloatPoint(m_width, 0.0f), Gfx::FloatSize(width, height), fragment_type));
+ }
+ m_width += width;
+
+ if (is<Box>(layout_node))
+ downcast<Box>(layout_node).set_containing_line_box_fragment(m_fragments.last());
+}
+
+void LineBox::trim_trailing_whitespace()
+{
+ while (!m_fragments.is_empty() && m_fragments.last().is_justifiable_whitespace()) {
+ auto fragment = m_fragments.take_last();
+ m_width -= fragment->width();
+ }
+
+ if (m_fragments.is_empty())
+ return;
+
+ auto last_text = m_fragments.last().text();
+ if (last_text.is_null())
+ return;
+ auto& last_fragment = m_fragments.last();
+
+ int space_width = last_fragment.layout_node().font().glyph_width(' ');
+ while (last_fragment.length() && isspace(last_text[last_fragment.length() - 1])) {
+ last_fragment.m_length -= 1;
+ last_fragment.set_width(last_fragment.width() - space_width);
+ m_width -= space_width;
+ }
+}
+
+bool LineBox::is_empty_or_ends_in_whitespace() const
+{
+ if (m_fragments.is_empty())
+ return true;
+ return m_fragments.last().ends_in_whitespace();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/LineBox.h b/Userland/Libraries/LibWeb/Layout/LineBox.h
new file mode 100644
index 0000000000..dcf1efd081
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/LineBox.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullOwnPtrVector.h>
+#include <AK/Vector.h>
+#include <LibWeb/Layout/LineBoxFragment.h>
+
+namespace Web::Layout {
+
+class LineBox {
+public:
+ LineBox() { }
+
+ float width() const { return m_width; }
+
+ void add_fragment(Node& layout_node, int start, int length, float width, float height, LineBoxFragment::Type = LineBoxFragment::Type::Normal);
+
+ const NonnullOwnPtrVector<LineBoxFragment>& fragments() const { return m_fragments; }
+ NonnullOwnPtrVector<LineBoxFragment>& fragments() { return m_fragments; }
+
+ void trim_trailing_whitespace();
+
+ bool is_empty_or_ends_in_whitespace() const;
+
+private:
+ friend class BlockBox;
+ friend class InlineFormattingContext;
+ NonnullOwnPtrVector<LineBoxFragment> m_fragments;
+ float m_width { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/LineBoxFragment.cpp b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.cpp
new file mode 100644
index 0000000000..ba49f93d18
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Utf8View.h>
+#include <LibGUI/Painter.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+#include <LibWeb/Layout/LineBoxFragment.h>
+#include <LibWeb/Layout/TextNode.h>
+#include <LibWeb/Painting/BorderPainting.h>
+#include <LibWeb/Painting/PaintContext.h>
+#include <ctype.h>
+
+namespace Web::Layout {
+
+void LineBoxFragment::paint(PaintContext& context, PaintPhase phase)
+{
+ for (auto* ancestor = layout_node().parent(); ancestor; ancestor = ancestor->parent()) {
+ if (!ancestor->is_visible())
+ return;
+ }
+
+ layout_node().paint_fragment(context, *this, phase);
+}
+
+bool LineBoxFragment::ends_in_whitespace() const
+{
+ auto text = this->text();
+ if (text.is_empty())
+ return false;
+ return isspace(text[text.length() - 1]);
+}
+
+bool LineBoxFragment::is_justifiable_whitespace() const
+{
+ return text() == " ";
+}
+
+StringView LineBoxFragment::text() const
+{
+ if (!is<TextNode>(layout_node()))
+ return {};
+ return downcast<TextNode>(layout_node()).text_for_rendering().substring_view(m_start, m_length);
+}
+
+const Gfx::FloatRect LineBoxFragment::absolute_rect() const
+{
+ Gfx::FloatRect rect { {}, size() };
+ rect.set_location(m_layout_node.containing_block()->absolute_position());
+ rect.move_by(offset());
+ return rect;
+}
+
+int LineBoxFragment::text_index_at(float x) const
+{
+ if (!is<TextNode>(layout_node()))
+ return 0;
+ auto& layout_text = downcast<TextNode>(layout_node());
+ auto& font = layout_text.font();
+ Utf8View view(text());
+
+ float relative_x = x - absolute_x();
+ float glyph_spacing = font.glyph_spacing();
+
+ if (relative_x < 0)
+ return 0;
+
+ float width_so_far = 0;
+ for (auto it = view.begin(); it != view.end(); ++it) {
+ float glyph_width = font.glyph_or_emoji_width(*it);
+ if ((width_so_far + (glyph_width + glyph_spacing) / 2) > relative_x)
+ return m_start + view.byte_offset_of(it);
+ width_so_far += glyph_width + glyph_spacing;
+ }
+ return m_start + m_length;
+}
+
+Gfx::FloatRect LineBoxFragment::selection_rect(const Gfx::Font& font) const
+{
+ if (layout_node().selection_state() == Node::SelectionState::None)
+ return {};
+
+ if (layout_node().selection_state() == Node::SelectionState::Full)
+ return absolute_rect();
+
+ auto selection = layout_node().root().selection().normalized();
+ if (!selection.is_valid())
+ return {};
+ if (!is<TextNode>(layout_node()))
+ return {};
+
+ const auto start_index = m_start;
+ const auto end_index = m_start + m_length;
+ auto text = this->text();
+
+ if (layout_node().selection_state() == Node::SelectionState::StartAndEnd) {
+ // we are in the start/end node (both the same)
+ if (start_index > selection.end().index_in_node)
+ return {};
+ if (end_index < selection.start().index_in_node)
+ return {};
+
+ if (selection.start().index_in_node == selection.end().index_in_node)
+ return {};
+
+ auto selection_start_in_this_fragment = max(0, selection.start().index_in_node - m_start);
+ auto selection_end_in_this_fragment = min(m_length, selection.end().index_in_node - m_start);
+ auto pixel_distance_to_first_selected_character = font.width(text.substring_view(0, selection_start_in_this_fragment));
+ auto pixel_width_of_selection = font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment)) + 1;
+
+ auto rect = absolute_rect();
+ rect.set_x(rect.x() + pixel_distance_to_first_selected_character);
+ rect.set_width(pixel_width_of_selection);
+
+ return rect;
+ }
+ if (layout_node().selection_state() == Node::SelectionState::Start) {
+ // we are in the start node
+ if (end_index < selection.start().index_in_node)
+ return {};
+
+ auto selection_start_in_this_fragment = max(0, selection.start().index_in_node - m_start);
+ auto selection_end_in_this_fragment = m_length;
+ auto pixel_distance_to_first_selected_character = font.width(text.substring_view(0, selection_start_in_this_fragment));
+ auto pixel_width_of_selection = font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment)) + 1;
+
+ auto rect = absolute_rect();
+ rect.set_x(rect.x() + pixel_distance_to_first_selected_character);
+ rect.set_width(pixel_width_of_selection);
+
+ return rect;
+ }
+ if (layout_node().selection_state() == Node::SelectionState::End) {
+ // we are in the end node
+ if (start_index > selection.end().index_in_node)
+ return {};
+
+ auto selection_start_in_this_fragment = 0;
+ auto selection_end_in_this_fragment = min(selection.end().index_in_node, m_length);
+ auto pixel_distance_to_first_selected_character = font.width(text.substring_view(0, selection_start_in_this_fragment));
+ auto pixel_width_of_selection = font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment)) + 1;
+
+ auto rect = absolute_rect();
+ rect.set_x(rect.x() + pixel_distance_to_first_selected_character);
+ rect.set_width(pixel_width_of_selection);
+
+ return rect;
+ }
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h
new file mode 100644
index 0000000000..03819f0fb3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Weakable.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/Rect.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::Layout {
+
+class LineBoxFragment : public Weakable<LineBoxFragment> {
+ friend class LineBox;
+
+public:
+ enum class Type {
+ Normal,
+ Leading,
+ Trailing,
+ };
+
+ LineBoxFragment(Node& layout_node, int start, int length, const Gfx::FloatPoint& offset, const Gfx::FloatSize& size, Type type)
+ : m_layout_node(layout_node)
+ , m_start(start)
+ , m_length(length)
+ , m_offset(offset)
+ , m_size(size)
+ , m_type(type)
+ {
+ }
+
+ Node& layout_node() const { return m_layout_node; }
+ int start() const { return m_start; }
+ int length() const { return m_length; }
+ const Gfx::FloatRect absolute_rect() const;
+ Type type() const { return m_type; }
+
+ const Gfx::FloatPoint& offset() const { return m_offset; }
+ void set_offset(const Gfx::FloatPoint& offset) { m_offset = offset; }
+
+ const Gfx::FloatSize& size() const { return m_size; }
+ void set_width(float width) { m_size.set_width(width); }
+ void set_height(float height) { m_size.set_height(height); }
+ float width() const { return m_size.width(); }
+ float height() const { return m_size.height(); }
+
+ float absolute_x() const { return absolute_rect().x(); }
+
+ void paint(PaintContext&, PaintPhase);
+
+ bool ends_in_whitespace() const;
+ bool is_justifiable_whitespace() const;
+ StringView text() const;
+
+ int text_index_at(float x) const;
+
+ Gfx::FloatRect selection_rect(const Gfx::Font&) const;
+
+private:
+ Node& m_layout_node;
+ int m_start { 0 };
+ int m_length { 0 };
+ Gfx::FloatPoint m_offset;
+ Gfx::FloatSize m_size;
+ Type m_type { Type::Normal };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/ListItemBox.cpp b/Userland/Libraries/LibWeb/Layout/ListItemBox.cpp
new file mode 100644
index 0000000000..06ed92533e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/ListItemBox.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Layout/ListItemBox.h>
+#include <LibWeb/Layout/ListItemMarkerBox.h>
+
+namespace Web::Layout {
+
+ListItemBox::ListItemBox(DOM::Document& document, DOM::Element& element, NonnullRefPtr<CSS::StyleProperties> style)
+ : Layout::BlockBox(document, &element, move(style))
+{
+}
+
+ListItemBox::~ListItemBox()
+{
+}
+
+void ListItemBox::layout_marker()
+{
+ if (m_marker) {
+ remove_child(*m_marker);
+ m_marker = nullptr;
+ }
+
+ if (computed_values().list_style_type() == CSS::ListStyleType::None)
+ return;
+
+ if (!m_marker) {
+ m_marker = adopt(*new ListItemMarkerBox(document()));
+ if (first_child())
+ m_marker->set_inline(first_child()->is_inline());
+ append_child(*m_marker);
+ }
+
+ m_marker->set_offset(-8, 0);
+ m_marker->set_size(4, height());
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/ListItemBox.h b/Userland/Libraries/LibWeb/Layout/ListItemBox.h
new file mode 100644
index 0000000000..39b5af84e3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/ListItemBox.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/Layout/BlockBox.h>
+
+namespace Web::Layout {
+
+class ListItemMarkerBox;
+
+class ListItemBox final : public BlockBox {
+public:
+ ListItemBox(DOM::Document&, DOM::Element&, NonnullRefPtr<CSS::StyleProperties>);
+ virtual ~ListItemBox() override;
+
+ void layout_marker();
+
+private:
+ RefPtr<ListItemMarkerBox> m_marker;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.cpp b/Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.cpp
new file mode 100644
index 0000000000..2b387112e6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Painter.h>
+#include <LibWeb/Layout/ListItemMarkerBox.h>
+
+namespace Web::Layout {
+
+ListItemMarkerBox::ListItemMarkerBox(DOM::Document& document)
+ : Box(document, nullptr, CSS::StyleProperties::create())
+{
+}
+
+ListItemMarkerBox::~ListItemMarkerBox()
+{
+}
+
+void ListItemMarkerBox::paint(PaintContext& context, PaintPhase phase)
+{
+ if (phase != PaintPhase::Foreground)
+ return;
+ Gfx::IntRect bullet_rect { 0, 0, 4, 4 };
+ bullet_rect.center_within(enclosing_int_rect(absolute_rect()));
+ // FIXME: It would be nicer to not have to go via the parent here to get our inherited style.
+ auto color = parent()->computed_values().color();
+ context.painter().fill_rect(bullet_rect, color);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.h b/Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.h
new file mode 100644
index 0000000000..e570fd21df
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/ListItemMarkerBox.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Layout/Box.h>
+
+namespace Web::Layout {
+
+class ListItemMarkerBox final : public Box {
+public:
+ explicit ListItemMarkerBox(DOM::Document&);
+ virtual ~ListItemMarkerBox() override;
+
+ virtual void paint(PaintContext&, PaintPhase) override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/Node.cpp b/Userland/Libraries/LibWeb/Layout/Node.cpp
new file mode 100644
index 0000000000..7af494a0fb
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/Node.cpp
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Demangle.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/Dump.h>
+#include <LibWeb/HTML/HTMLHtmlElement.h>
+#include <LibWeb/Layout/BlockBox.h>
+#include <LibWeb/Layout/FormattingContext.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+#include <LibWeb/Layout/Node.h>
+#include <LibWeb/Layout/ReplacedBox.h>
+#include <LibWeb/Layout/TextNode.h>
+#include <LibWeb/Page/Frame.h>
+#include <typeinfo>
+
+namespace Web::Layout {
+
+Node::Node(DOM::Document& document, DOM::Node* node)
+ : m_document(document)
+ , m_dom_node(node)
+{
+ if (m_dom_node)
+ m_dom_node->set_layout_node({}, this);
+}
+
+Node::~Node()
+{
+ if (m_dom_node && m_dom_node->layout_node() == this)
+ m_dom_node->set_layout_node({}, nullptr);
+}
+
+bool Node::can_contain_boxes_with_position_absolute() const
+{
+ return computed_values().position() != CSS::Position::Static || is<InitialContainingBlockBox>(*this);
+}
+
+const BlockBox* Node::containing_block() const
+{
+ auto nearest_block_ancestor = [this] {
+ auto* ancestor = parent();
+ while (ancestor && !is<BlockBox>(*ancestor))
+ ancestor = ancestor->parent();
+ return downcast<BlockBox>(ancestor);
+ };
+
+ if (is<TextNode>(*this))
+ return nearest_block_ancestor();
+
+ auto position = computed_values().position();
+
+ if (position == CSS::Position::Absolute) {
+ auto* ancestor = parent();
+ while (ancestor && !ancestor->can_contain_boxes_with_position_absolute())
+ ancestor = ancestor->parent();
+ while (ancestor && (!is<BlockBox>(ancestor) || ancestor->is_anonymous()))
+ ancestor = ancestor->containing_block();
+ return downcast<BlockBox>(ancestor);
+ }
+
+ if (position == CSS::Position::Fixed)
+ return &root();
+
+ return nearest_block_ancestor();
+}
+
+void Node::paint(PaintContext& context, PaintPhase phase)
+{
+ if (!is_visible())
+ return;
+
+ before_children_paint(context, phase);
+
+ for_each_child_in_paint_order([&](auto& child) {
+ child.paint(context, phase);
+ });
+
+ after_children_paint(context, phase);
+}
+
+HitTestResult Node::hit_test(const Gfx::IntPoint& position, HitTestType type) const
+{
+ HitTestResult result;
+ for_each_child_in_paint_order([&](auto& child) {
+ auto child_result = child.hit_test(position, type);
+ if (child_result.layout_node)
+ result = child_result;
+ });
+ return result;
+}
+
+const Frame& Node::frame() const
+{
+ ASSERT(document().frame());
+ return *document().frame();
+}
+
+Frame& Node::frame()
+{
+ ASSERT(document().frame());
+ return *document().frame();
+}
+
+const InitialContainingBlockBox& Node::root() const
+{
+ ASSERT(document().layout_node());
+ return *document().layout_node();
+}
+
+InitialContainingBlockBox& Node::root()
+{
+ ASSERT(document().layout_node());
+ return *document().layout_node();
+}
+
+void Node::split_into_lines(InlineFormattingContext& context, LayoutMode layout_mode)
+{
+ for_each_child([&](auto& child) {
+ child.split_into_lines(context, layout_mode);
+ });
+}
+
+void Node::set_needs_display()
+{
+ if (auto* block = containing_block()) {
+ block->for_each_fragment([&](auto& fragment) {
+ if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) {
+ frame().set_needs_display(enclosing_int_rect(fragment.absolute_rect()));
+ }
+ return IterationDecision::Continue;
+ });
+ }
+}
+
+Gfx::FloatPoint Node::box_type_agnostic_position() const
+{
+ if (is<Box>(*this))
+ return downcast<Box>(*this).absolute_position();
+ ASSERT(is_inline());
+ Gfx::FloatPoint position;
+ if (auto* block = containing_block()) {
+ block->for_each_fragment([&](auto& fragment) {
+ if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) {
+ position = fragment.absolute_rect().location();
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+ }
+ return position;
+}
+
+bool Node::is_floating() const
+{
+ if (!has_style())
+ return false;
+ return computed_values().float_() != CSS::Float::None;
+}
+
+bool Node::is_positioned() const
+{
+ return has_style() && computed_values().position() != CSS::Position::Static;
+}
+
+bool Node::is_absolutely_positioned() const
+{
+ if (!has_style())
+ return false;
+ auto position = computed_values().position();
+ return position == CSS::Position::Absolute || position == CSS::Position::Fixed;
+}
+
+bool Node::is_fixed_position() const
+{
+ if (!has_style())
+ return false;
+ auto position = computed_values().position();
+ return position == CSS::Position::Fixed;
+}
+
+NodeWithStyle::NodeWithStyle(DOM::Document& document, DOM::Node* node, NonnullRefPtr<CSS::StyleProperties> specified_style)
+ : Node(document, node)
+{
+ m_has_style = true;
+ apply_style(*specified_style);
+}
+
+NodeWithStyle::NodeWithStyle(DOM::Document& document, DOM::Node* node, CSS::ComputedValues computed_values)
+ : Node(document, node)
+ , m_computed_values(move(computed_values))
+{
+ m_has_style = true;
+ m_font = Gfx::FontDatabase::default_font();
+}
+
+void NodeWithStyle::apply_style(const CSS::StyleProperties& specified_style)
+{
+ auto& computed_values = static_cast<CSS::MutableComputedValues&>(m_computed_values);
+
+ m_font = specified_style.font();
+ m_line_height = specified_style.line_height(*this);
+
+ {
+ // FIXME: This doesn't work right for relative font-sizes
+ auto length = specified_style.length_or_fallback(CSS::PropertyID::FontSize, CSS::Length(10, CSS::Length::Type::Px));
+ m_font_size = length.raw_value();
+ }
+
+ auto bgimage = specified_style.property(CSS::PropertyID::BackgroundImage);
+ if (bgimage.has_value() && bgimage.value()->is_image()) {
+ m_background_image = static_ptr_cast<CSS::ImageStyleValue>(bgimage.value());
+ }
+
+ computed_values.set_display(specified_style.display());
+
+ auto position = specified_style.position();
+ if (position.has_value())
+ computed_values.set_position(position.value());
+
+ auto text_align = specified_style.text_align();
+ if (text_align.has_value())
+ computed_values.set_text_align(text_align.value());
+
+ auto white_space = specified_style.white_space();
+ if (white_space.has_value())
+ computed_values.set_white_space(white_space.value());
+
+ auto float_ = specified_style.float_();
+ if (float_.has_value())
+ computed_values.set_float(float_.value());
+
+ auto clear = specified_style.clear();
+ if (clear.has_value())
+ computed_values.set_clear(clear.value());
+
+ auto text_decoration_line = specified_style.text_decoration_line();
+ if (text_decoration_line.has_value())
+ computed_values.set_text_decoration_line(text_decoration_line.value());
+
+ auto text_transform = specified_style.text_transform();
+ if (text_transform.has_value())
+ computed_values.set_text_transform(text_transform.value());
+
+ if (auto list_style_type = specified_style.list_style_type(); list_style_type.has_value())
+ computed_values.set_list_style_type(list_style_type.value());
+
+ computed_values.set_color(specified_style.color_or_fallback(CSS::PropertyID::Color, document(), Color::Black));
+ computed_values.set_background_color(specified_style.color_or_fallback(CSS::PropertyID::BackgroundColor, document(), Color::Transparent));
+
+ computed_values.set_z_index(specified_style.z_index());
+ computed_values.set_width(specified_style.length_or_fallback(CSS::PropertyID::Width, {}));
+ computed_values.set_min_width(specified_style.length_or_fallback(CSS::PropertyID::MinWidth, {}));
+ computed_values.set_max_width(specified_style.length_or_fallback(CSS::PropertyID::MaxWidth, {}));
+ computed_values.set_height(specified_style.length_or_fallback(CSS::PropertyID::Height, {}));
+ computed_values.set_min_height(specified_style.length_or_fallback(CSS::PropertyID::MinHeight, {}));
+ computed_values.set_max_height(specified_style.length_or_fallback(CSS::PropertyID::MaxHeight, {}));
+
+ computed_values.set_offset(specified_style.length_box(CSS::PropertyID::Left, CSS::PropertyID::Top, CSS::PropertyID::Right, CSS::PropertyID::Bottom, CSS::Length::make_auto()));
+ computed_values.set_margin(specified_style.length_box(CSS::PropertyID::MarginLeft, CSS::PropertyID::MarginTop, CSS::PropertyID::MarginRight, CSS::PropertyID::MarginBottom, CSS::Length::make_px(0)));
+ computed_values.set_padding(specified_style.length_box(CSS::PropertyID::PaddingLeft, CSS::PropertyID::PaddingTop, CSS::PropertyID::PaddingRight, CSS::PropertyID::PaddingBottom, CSS::Length::make_px(0)));
+
+ auto do_border_style = [&](CSS::BorderData& border, CSS::PropertyID width_property, CSS::PropertyID color_property, CSS::PropertyID style_property) {
+ border.width = specified_style.length_or_fallback(width_property, {}).resolved_or_zero(*this, 0).to_px(*this);
+ border.color = specified_style.color_or_fallback(color_property, document(), Color::Transparent);
+ border.line_style = specified_style.line_style(style_property).value_or(CSS::LineStyle::None);
+ };
+
+ do_border_style(computed_values.border_left(), CSS::PropertyID::BorderLeftWidth, CSS::PropertyID::BorderLeftColor, CSS::PropertyID::BorderLeftStyle);
+ do_border_style(computed_values.border_top(), CSS::PropertyID::BorderTopWidth, CSS::PropertyID::BorderTopColor, CSS::PropertyID::BorderTopStyle);
+ do_border_style(computed_values.border_right(), CSS::PropertyID::BorderRightWidth, CSS::PropertyID::BorderRightColor, CSS::PropertyID::BorderRightStyle);
+ do_border_style(computed_values.border_bottom(), CSS::PropertyID::BorderBottomWidth, CSS::PropertyID::BorderBottomColor, CSS::PropertyID::BorderBottomStyle);
+}
+
+void Node::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned, unsigned)
+{
+}
+
+void Node::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned, unsigned)
+{
+}
+
+void Node::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned, unsigned)
+{
+}
+
+bool Node::is_root_element() const
+{
+ if (is_anonymous())
+ return false;
+ return is<HTML::HTMLHtmlElement>(*dom_node());
+}
+
+String Node::class_name() const
+{
+ return demangle(typeid(*this).name());
+}
+
+bool Node::is_inline_block() const
+{
+ return is_inline() && is<BlockBox>(*this);
+}
+
+NonnullRefPtr<NodeWithStyle> NodeWithStyle::create_anonymous_wrapper() const
+{
+ auto wrapper = adopt(*new BlockBox(const_cast<DOM::Document&>(document()), nullptr, m_computed_values.clone_inherited_values()));
+ wrapper->m_font = m_font;
+ wrapper->m_font_size = m_font_size;
+ wrapper->m_line_height = m_line_height;
+ wrapper->m_background_image = m_background_image;
+ return wrapper;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/Node.h b/Userland/Libraries/LibWeb/Layout/Node.h
new file mode 100644
index 0000000000..c1699924e3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/Node.h
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtr.h>
+#include <AK/TypeCasts.h>
+#include <AK/Vector.h>
+#include <LibGfx/Rect.h>
+#include <LibWeb/CSS/ComputedValues.h>
+#include <LibWeb/CSS/StyleProperties.h>
+#include <LibWeb/Forward.h>
+#include <LibWeb/Layout/BoxModelMetrics.h>
+#include <LibWeb/Layout/LayoutPosition.h>
+#include <LibWeb/Painting/PaintContext.h>
+#include <LibWeb/TreeNode.h>
+
+namespace Web::Layout {
+
+enum class LayoutMode {
+ Default,
+ AllPossibleLineBreaks,
+ OnlyRequiredLineBreaks,
+};
+
+enum class PaintPhase {
+ Background,
+ Border,
+ Foreground,
+ FocusOutline,
+ Overlay,
+};
+
+struct HitTestResult {
+ RefPtr<Node> layout_node;
+ int index_in_node { 0 };
+
+ enum InternalPosition {
+ None,
+ Before,
+ Inside,
+ After,
+ };
+ InternalPosition internal_position { None };
+};
+
+enum class HitTestType {
+ Exact, // Exact matches only
+ TextCursor, // Clicking past the right/bottom edge of text will still hit the text
+};
+
+class Node : public TreeNode<Node> {
+public:
+ virtual ~Node();
+
+ virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const;
+
+ bool is_anonymous() const { return !m_dom_node; }
+ const DOM::Node* dom_node() const { return m_dom_node; }
+ DOM::Node* dom_node() { return m_dom_node; }
+
+ DOM::Document& document() { return m_document; }
+ const DOM::Document& document() const { return m_document; }
+
+ const Frame& frame() const;
+ Frame& frame();
+
+ const InitialContainingBlockBox& root() const;
+ InitialContainingBlockBox& root();
+
+ bool is_root_element() const;
+
+ String class_name() const;
+
+ bool has_style() const { return m_has_style; }
+
+ virtual bool can_have_children() const { return true; }
+
+ bool is_inline() const { return m_inline; }
+ void set_inline(bool b) { m_inline = b; }
+
+ bool is_inline_block() const;
+
+ virtual bool wants_mouse_events() const { return false; }
+
+ virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers);
+ virtual void handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers);
+ virtual void handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers);
+
+ virtual void before_children_paint(PaintContext&, PaintPhase) {};
+ virtual void paint(PaintContext&, PaintPhase);
+ virtual void paint_fragment(PaintContext&, const LineBoxFragment&, PaintPhase) const { }
+ virtual void after_children_paint(PaintContext&, PaintPhase) {};
+
+ virtual bool is_box() const { return false; }
+
+ bool is_floating() const;
+ bool is_positioned() const;
+ bool is_absolutely_positioned() const;
+ bool is_fixed_position() const;
+
+ const BlockBox* containing_block() const;
+ BlockBox* containing_block() { return const_cast<BlockBox*>(const_cast<const Node*>(this)->containing_block()); }
+
+ bool can_contain_boxes_with_position_absolute() const;
+
+ const Gfx::Font& font() const;
+ const CSS::ImmutableComputedValues& computed_values() const;
+
+ NodeWithStyle* parent();
+ const NodeWithStyle* parent() const;
+
+ void inserted_into(Node&) { }
+ void removed_from(Node&) { }
+ void children_changed() { }
+
+ virtual void split_into_lines(InlineFormattingContext&, LayoutMode);
+
+ bool is_visible() const { return m_visible; }
+ void set_visible(bool visible) { m_visible = visible; }
+
+ virtual void set_needs_display();
+
+ bool children_are_inline() const { return m_children_are_inline; }
+ void set_children_are_inline(bool value) { m_children_are_inline = value; }
+
+ Gfx::FloatPoint box_type_agnostic_position() const;
+
+ float font_size() const;
+
+ enum class SelectionState {
+ None, // No selection
+ Start, // Selection starts in this Node
+ End, // Selection ends in this Node
+ StartAndEnd, // Selection starts and ends in this Node
+ Full, // Selection starts before and ends after this Node
+ };
+
+ SelectionState selection_state() const { return m_selection_state; }
+ void set_selection_state(SelectionState state) { m_selection_state = state; }
+
+ template<typename Callback>
+ void for_each_child_in_paint_order(Callback callback) const
+ {
+ for_each_child([&](auto& child) {
+ if (is<Box>(child) && downcast<Box>(child).stacking_context())
+ return;
+ if (!child.is_positioned())
+ callback(child);
+ });
+ for_each_child([&](auto& child) {
+ if (is<Box>(child) && downcast<Box>(child).stacking_context())
+ return;
+ if (child.is_positioned())
+ callback(child);
+ });
+ }
+
+protected:
+ Node(DOM::Document&, DOM::Node*);
+
+private:
+ friend class NodeWithStyle;
+
+ NonnullRefPtr<DOM::Document> m_document;
+ RefPtr<DOM::Node> m_dom_node;
+
+ bool m_inline { false };
+ bool m_has_style { false };
+ bool m_visible { true };
+ bool m_children_are_inline { false };
+ SelectionState m_selection_state { SelectionState::None };
+};
+
+class NodeWithStyle : public Node {
+public:
+ virtual ~NodeWithStyle() override { }
+
+ const CSS::ImmutableComputedValues& computed_values() const { return static_cast<const CSS::ImmutableComputedValues&>(m_computed_values); }
+
+ void apply_style(const CSS::StyleProperties&);
+
+ const Gfx::Font& font() const { return *m_font; }
+ float line_height() const { return m_line_height; }
+ float font_size() const { return m_font_size; }
+ const CSS::ImageStyleValue* background_image() const { return m_background_image; }
+
+ NonnullRefPtr<NodeWithStyle> create_anonymous_wrapper() const;
+
+protected:
+ NodeWithStyle(DOM::Document&, DOM::Node*, NonnullRefPtr<CSS::StyleProperties>);
+ NodeWithStyle(DOM::Document&, DOM::Node*, CSS::ComputedValues);
+
+private:
+ CSS::ComputedValues m_computed_values;
+ RefPtr<Gfx::Font> m_font;
+ float m_line_height { 0 };
+ float m_font_size { 0 };
+ RefPtr<CSS::ImageStyleValue> m_background_image;
+
+ CSS::Position m_position;
+};
+
+class NodeWithStyleAndBoxModelMetrics : public NodeWithStyle {
+public:
+ BoxModelMetrics& box_model() { return m_box_model; }
+ const BoxModelMetrics& box_model() const { return m_box_model; }
+
+protected:
+ NodeWithStyleAndBoxModelMetrics(DOM::Document& document, DOM::Node* node, NonnullRefPtr<CSS::StyleProperties> style)
+ : NodeWithStyle(document, node, move(style))
+ {
+ }
+
+ NodeWithStyleAndBoxModelMetrics(DOM::Document& document, DOM::Node* node, CSS::ComputedValues computed_values)
+ : NodeWithStyle(document, node, move(computed_values))
+ {
+ }
+
+private:
+ BoxModelMetrics m_box_model;
+};
+
+inline const Gfx::Font& Node::font() const
+{
+ if (m_has_style)
+ return static_cast<const NodeWithStyle*>(this)->font();
+ return parent()->font();
+}
+
+inline float Node::font_size() const
+{
+ if (m_has_style)
+ return static_cast<const NodeWithStyle*>(this)->font_size();
+ return parent()->font_size();
+}
+
+inline const CSS::ImmutableComputedValues& Node::computed_values() const
+{
+ if (m_has_style)
+ return static_cast<const NodeWithStyle*>(this)->computed_values();
+ return parent()->computed_values();
+}
+
+inline const NodeWithStyle* Node::parent() const
+{
+ return static_cast<const NodeWithStyle*>(TreeNode<Node>::parent());
+}
+
+inline NodeWithStyle* Node::parent()
+{
+ return static_cast<NodeWithStyle*>(TreeNode<Node>::parent());
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/ReplacedBox.cpp b/Userland/Libraries/LibWeb/Layout/ReplacedBox.cpp
new file mode 100644
index 0000000000..2ea63bb233
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/ReplacedBox.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/Layout/BlockBox.h>
+#include <LibWeb/Layout/InlineFormattingContext.h>
+#include <LibWeb/Layout/ReplacedBox.h>
+
+namespace Web::Layout {
+
+ReplacedBox::ReplacedBox(DOM::Document& document, DOM::Element& element, NonnullRefPtr<CSS::StyleProperties> style)
+ : Box(document, &element, move(style))
+{
+ // FIXME: Allow non-inline replaced elements.
+ set_inline(true);
+}
+
+ReplacedBox::~ReplacedBox()
+{
+}
+
+void ReplacedBox::split_into_lines(InlineFormattingContext& context, LayoutMode layout_mode)
+{
+ auto& containing_block = context.containing_block();
+
+ prepare_for_replaced_layout();
+ context.dimension_box_on_line(*this, layout_mode);
+
+ auto* line_box = &containing_block.ensure_last_line_box();
+ if (line_box->width() > 0 && line_box->width() + width() > context.available_width_at_line(containing_block.line_boxes().size() - 1))
+ line_box = &containing_block.add_line_box();
+ line_box->add_fragment(*this, 0, 0, width(), height());
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/ReplacedBox.h b/Userland/Libraries/LibWeb/Layout/ReplacedBox.h
new file mode 100644
index 0000000000..f1da91a197
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/ReplacedBox.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/Layout/Box.h>
+
+namespace Web::Layout {
+
+class ReplacedBox : public Box {
+public:
+ ReplacedBox(DOM::Document&, DOM::Element&, NonnullRefPtr<CSS::StyleProperties>);
+ virtual ~ReplacedBox() override;
+
+ const DOM::Element& dom_node() const { return downcast<DOM::Element>(*Node::dom_node()); }
+ DOM::Element& dom_node() { return downcast<DOM::Element>(*Node::dom_node()); }
+
+ bool has_intrinsic_width() const { return m_has_intrinsic_width; }
+ bool has_intrinsic_height() const { return m_has_intrinsic_height; }
+ bool has_intrinsic_ratio() const { return m_has_intrinsic_ratio; }
+
+ float intrinsic_width() const { return m_intrinsic_width; }
+ float intrinsic_height() const { return m_intrinsic_height; }
+ float intrinsic_ratio() const { return m_intrinsic_ratio; }
+
+ void set_has_intrinsic_width(bool has) { m_has_intrinsic_width = has; }
+ void set_has_intrinsic_height(bool has) { m_has_intrinsic_height = has; }
+ void set_has_intrinsic_ratio(bool has) { m_has_intrinsic_ratio = has; }
+
+ void set_intrinsic_width(float width) { m_intrinsic_width = width; }
+ void set_intrinsic_height(float height) { m_intrinsic_height = height; }
+ void set_intrinsic_ratio(float ratio) { m_intrinsic_ratio = ratio; }
+
+ virtual void prepare_for_replaced_layout() { }
+
+ virtual bool can_have_children() const override { return false; }
+
+protected:
+ virtual void split_into_lines(InlineFormattingContext&, LayoutMode) override;
+
+private:
+ bool m_has_intrinsic_width { false };
+ bool m_has_intrinsic_height { false };
+ bool m_has_intrinsic_ratio { false };
+ float m_intrinsic_width { 0 };
+ float m_intrinsic_height { 0 };
+ float m_intrinsic_ratio { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/SVGBox.cpp b/Userland/Libraries/LibWeb/Layout/SVGBox.cpp
new file mode 100644
index 0000000000..8370d4b9bd
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/SVGBox.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Painter.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/StylePainter.h>
+#include <LibWeb/Layout/SVGBox.h>
+
+namespace Web::Layout {
+
+SVGBox::SVGBox(DOM::Document& document, SVG::SVGElement& element, NonnullRefPtr<CSS::StyleProperties> style)
+ : ReplacedBox(document, element, move(style))
+{
+}
+
+void SVGBox::before_children_paint(PaintContext& context, PaintPhase phase)
+{
+ Node::before_children_paint(context, phase);
+ if (phase != PaintPhase::Foreground)
+ return;
+ context.svg_context().save();
+}
+
+void SVGBox::after_children_paint(PaintContext& context, PaintPhase phase)
+{
+ Node::after_children_paint(context, phase);
+ if (phase != PaintPhase::Foreground)
+ return;
+ context.svg_context().restore();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/SVGBox.h b/Userland/Libraries/LibWeb/Layout/SVGBox.h
new file mode 100644
index 0000000000..dce2ee6f64
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/SVGBox.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Layout/ReplacedBox.h>
+#include <LibWeb/SVG/SVGElement.h>
+#include <LibWeb/SVG/SVGGraphicsElement.h>
+
+namespace Web::Layout {
+
+class SVGBox : public ReplacedBox {
+public:
+ SVGBox(DOM::Document&, SVG::SVGElement&, NonnullRefPtr<CSS::StyleProperties>);
+ virtual ~SVGBox() override = default;
+
+ virtual void before_children_paint(PaintContext& context, PaintPhase phase) override;
+ virtual void after_children_paint(PaintContext& context, PaintPhase phase) override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/SVGGraphicsBox.cpp b/Userland/Libraries/LibWeb/Layout/SVGGraphicsBox.cpp
new file mode 100644
index 0000000000..be1f9541d4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/SVGGraphicsBox.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Layout/SVGGraphicsBox.h>
+
+namespace Web::Layout {
+
+SVGGraphicsBox::SVGGraphicsBox(DOM::Document& document, SVG::SVGGraphicsElement& element, NonnullRefPtr<CSS::StyleProperties> properties)
+ : SVGBox(document, element, properties)
+{
+}
+
+void SVGGraphicsBox::before_children_paint(PaintContext& context, PaintPhase phase)
+{
+ SVGBox::before_children_paint(context, phase);
+ if (phase != PaintPhase::Foreground)
+ return;
+
+ auto& graphics_element = downcast<SVG::SVGGraphicsElement>(dom_node());
+
+ if (graphics_element.fill_color().has_value())
+ context.svg_context().set_fill_color(graphics_element.fill_color().value());
+ if (graphics_element.stroke_color().has_value())
+ context.svg_context().set_stroke_color(graphics_element.stroke_color().value());
+ if (graphics_element.stroke_width().has_value())
+ context.svg_context().set_stroke_width(graphics_element.stroke_width().value());
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/SVGGraphicsBox.h b/Userland/Libraries/LibWeb/Layout/SVGGraphicsBox.h
new file mode 100644
index 0000000000..10dbadef07
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/SVGGraphicsBox.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Layout/SVGBox.h>
+#include <LibWeb/SVG/SVGElement.h>
+#include <LibWeb/SVG/SVGGraphicsElement.h>
+
+namespace Web::Layout {
+
+class SVGGraphicsBox : public SVGBox {
+public:
+ SVGGraphicsBox(DOM::Document&, SVG::SVGGraphicsElement&, NonnullRefPtr<CSS::StyleProperties>);
+ virtual ~SVGGraphicsBox() override = default;
+
+ virtual void before_children_paint(PaintContext& context, PaintPhase phase) override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/SVGPathBox.cpp b/Userland/Libraries/LibWeb/Layout/SVGPathBox.cpp
new file mode 100644
index 0000000000..87879043eb
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/SVGPathBox.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/Painter.h>
+#include <LibWeb/Layout/SVGPathBox.h>
+#include <LibWeb/SVG/SVGPathElement.h>
+
+namespace Web::Layout {
+
+SVGPathBox::SVGPathBox(DOM::Document& document, SVG::SVGPathElement& element, NonnullRefPtr<CSS::StyleProperties> properties)
+ : SVGGraphicsBox(document, element, properties)
+{
+}
+
+void SVGPathBox::prepare_for_replaced_layout()
+{
+ auto& bounding_box = dom_node().get_path().bounding_box();
+ set_has_intrinsic_width(true);
+ set_has_intrinsic_height(true);
+ set_intrinsic_width(bounding_box.width());
+ set_intrinsic_height(bounding_box.height());
+
+ // FIXME: This does not belong here! Someone at a higher level should place this box.
+ set_offset(bounding_box.top_left());
+}
+
+void SVGPathBox::paint(PaintContext& context, PaintPhase phase)
+{
+ if (!is_visible())
+ return;
+
+ SVGGraphicsBox::paint(context, phase);
+
+ if (phase != PaintPhase::Foreground)
+ return;
+
+ auto& path_element = dom_node();
+ auto& path = path_element.get_path();
+
+ // We need to fill the path before applying the stroke, however the filled
+ // path must be closed, whereas the stroke path may not necessary be closed.
+ // Copy the path and close it for filling, but use the previous path for stroke
+ auto closed_path = path;
+ closed_path.close();
+
+ // Fills are computed as though all paths are closed (https://svgwg.org/svg2-draft/painting.html#FillProperties)
+ auto& painter = context.painter();
+ auto& svg_context = context.svg_context();
+
+ auto offset = (absolute_position() - effective_offset()).to_type<int>();
+
+ painter.translate(offset);
+
+ painter.fill_path(
+ closed_path,
+ path_element.fill_color().value_or(svg_context.fill_color()),
+ Gfx::Painter::WindingRule::EvenOdd);
+ painter.stroke_path(
+ path,
+ path_element.stroke_color().value_or(svg_context.stroke_color()),
+ path_element.stroke_width().value_or(svg_context.stroke_width()));
+
+ painter.translate(-offset);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/SVGPathBox.h b/Userland/Libraries/LibWeb/Layout/SVGPathBox.h
new file mode 100644
index 0000000000..cbfe94b4ec
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/SVGPathBox.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Layout/SVGGraphicsBox.h>
+
+namespace Web::Layout {
+
+class SVGPathBox final : public SVGGraphicsBox {
+public:
+ SVGPathBox(DOM::Document&, SVG::SVGPathElement&, NonnullRefPtr<CSS::StyleProperties>);
+ virtual ~SVGPathBox() override = default;
+
+ SVG::SVGPathElement& dom_node() { return downcast<SVG::SVGPathElement>(SVGGraphicsBox::dom_node()); }
+
+ virtual void prepare_for_replaced_layout() override;
+ virtual void paint(PaintContext& context, PaintPhase phase) override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/SVGSVGBox.cpp b/Userland/Libraries/LibWeb/Layout/SVGSVGBox.cpp
new file mode 100644
index 0000000000..af4a038dbc
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/SVGSVGBox.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Layout/SVGSVGBox.h>
+
+namespace Web::Layout {
+
+SVGSVGBox::SVGSVGBox(DOM::Document& document, SVG::SVGSVGElement& element, NonnullRefPtr<CSS::StyleProperties> properties)
+ : SVGGraphicsBox(document, element, properties)
+{
+}
+
+void SVGSVGBox::prepare_for_replaced_layout()
+{
+ set_has_intrinsic_width(true);
+ set_has_intrinsic_height(true);
+ set_intrinsic_width(dom_node().width());
+ set_intrinsic_height(dom_node().height());
+}
+
+void SVGSVGBox::before_children_paint(PaintContext& context, PaintPhase phase)
+{
+ if (phase != PaintPhase::Foreground)
+ return;
+
+ if (!context.has_svg_context())
+ context.set_svg_context(SVGContext());
+
+ SVGGraphicsBox::before_children_paint(context, phase);
+}
+
+void SVGSVGBox::after_children_paint(PaintContext& context, PaintPhase phase)
+{
+ SVGGraphicsBox::after_children_paint(context, phase);
+ if (phase != PaintPhase::Foreground)
+ return;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/SVGSVGBox.h b/Userland/Libraries/LibWeb/Layout/SVGSVGBox.h
new file mode 100644
index 0000000000..a95bf11f69
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/SVGSVGBox.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Layout/SVGGraphicsBox.h>
+#include <LibWeb/SVG/SVGSVGElement.h>
+
+namespace Web::Layout {
+
+class SVGSVGBox final : public SVGGraphicsBox {
+public:
+ SVGSVGBox(DOM::Document&, SVG::SVGSVGElement&, NonnullRefPtr<CSS::StyleProperties>);
+ virtual ~SVGSVGBox() override = default;
+
+ SVG::SVGSVGElement& dom_node() { return downcast<SVG::SVGSVGElement>(SVGGraphicsBox::dom_node()); }
+
+ virtual void prepare_for_replaced_layout() override;
+
+ virtual void before_children_paint(PaintContext& context, PaintPhase phase) override;
+ virtual void after_children_paint(PaintContext& context, PaintPhase phase) override;
+
+ virtual bool can_have_children() const override { return true; }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/TableBox.cpp b/Userland/Libraries/LibWeb/Layout/TableBox.cpp
new file mode 100644
index 0000000000..66fdfb8bd3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/TableBox.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/Layout/TableBox.h>
+
+namespace Web::Layout {
+
+TableBox::TableBox(DOM::Document& document, DOM::Element* element, NonnullRefPtr<CSS::StyleProperties> style)
+ : Layout::BlockBox(document, element, move(style))
+{
+}
+
+TableBox::TableBox(DOM::Document& document, DOM::Element* element, CSS::ComputedValues computed_values)
+ : Layout::BlockBox(document, element, move(computed_values))
+{
+}
+
+TableBox::~TableBox()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/TableBox.h b/Userland/Libraries/LibWeb/Layout/TableBox.h
new file mode 100644
index 0000000000..cff6af9222
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/TableBox.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Layout/BlockBox.h>
+
+namespace Web::Layout {
+
+class TableBox final : public Layout::BlockBox {
+public:
+ TableBox(DOM::Document&, DOM::Element*, NonnullRefPtr<CSS::StyleProperties>);
+ TableBox(DOM::Document&, DOM::Element*, CSS::ComputedValues);
+ virtual ~TableBox() override;
+
+ static CSS::Display static_display() { return CSS::Display::Table; }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/TableCellBox.cpp b/Userland/Libraries/LibWeb/Layout/TableCellBox.cpp
new file mode 100644
index 0000000000..197fe59b87
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/TableCellBox.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/Layout/TableCellBox.h>
+#include <LibWeb/Layout/TableRowBox.h>
+
+namespace Web::Layout {
+
+TableCellBox::TableCellBox(DOM::Document& document, DOM::Element* element, NonnullRefPtr<CSS::StyleProperties> style)
+ : Layout::BlockBox(document, element, move(style))
+{
+}
+
+TableCellBox::TableCellBox(DOM::Document& document, DOM::Element* element, CSS::ComputedValues computed_values)
+ : Layout::BlockBox(document, element, move(computed_values))
+{
+}
+
+TableCellBox::~TableCellBox()
+{
+}
+
+size_t TableCellBox::colspan() const
+{
+ if (!dom_node())
+ return 0;
+ return downcast<DOM::Element>(*dom_node()).attribute(HTML::AttributeNames::colspan).to_uint().value_or(1);
+}
+
+float TableCellBox::width_of_logical_containing_block() const
+{
+ if (auto* row = first_ancestor_of_type<TableRowBox>())
+ return row->width();
+ return 0;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/TableCellBox.h b/Userland/Libraries/LibWeb/Layout/TableCellBox.h
new file mode 100644
index 0000000000..091a4ae6d6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/TableCellBox.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Layout/BlockBox.h>
+
+namespace Web::Layout {
+
+class TableCellBox final : public BlockBox {
+public:
+ TableCellBox(DOM::Document&, DOM::Element*, NonnullRefPtr<CSS::StyleProperties>);
+ TableCellBox(DOM::Document&, DOM::Element*, CSS::ComputedValues);
+ virtual ~TableCellBox() override;
+
+ TableCellBox* next_cell() { return next_sibling_of_type<TableCellBox>(); }
+ const TableCellBox* next_cell() const { return next_sibling_of_type<TableCellBox>(); }
+
+ size_t colspan() const;
+
+ static CSS::Display static_display() { return CSS::Display::TableCell; }
+
+private:
+ virtual float width_of_logical_containing_block() const override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp
new file mode 100644
index 0000000000..865cef64e6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/CSS/Length.h>
+#include <LibWeb/DOM/Node.h>
+#include <LibWeb/Layout/BlockBox.h>
+#include <LibWeb/Layout/Box.h>
+#include <LibWeb/Layout/InlineFormattingContext.h>
+#include <LibWeb/Layout/TableBox.h>
+#include <LibWeb/Layout/TableCellBox.h>
+#include <LibWeb/Layout/TableFormattingContext.h>
+#include <LibWeb/Layout/TableRowBox.h>
+#include <LibWeb/Layout/TableRowGroupBox.h>
+#include <LibWeb/Page/Frame.h>
+
+namespace Web::Layout {
+
+TableFormattingContext::TableFormattingContext(Box& context_box, FormattingContext* parent)
+ : BlockFormattingContext(context_box, parent)
+{
+}
+
+TableFormattingContext::~TableFormattingContext()
+{
+}
+
+void TableFormattingContext::run(Box& box, LayoutMode)
+{
+ compute_width(box);
+
+ float total_content_height = 0;
+
+ box.for_each_child_of_type<TableRowGroupBox>([&](auto& row_group_box) {
+ compute_width(row_group_box);
+ auto column_count = row_group_box.column_count();
+ Vector<float> column_widths;
+ column_widths.resize(column_count);
+
+ row_group_box.template for_each_child_of_type<TableRowBox>([&](auto& row) {
+ calculate_column_widths(row, column_widths);
+ });
+
+ float content_height = 0;
+
+ row_group_box.template for_each_child_of_type<TableRowBox>([&](auto& row) {
+ row.set_offset(0, content_height);
+ layout_row(row, column_widths);
+ content_height += row.height();
+ });
+
+ row_group_box.set_height(content_height);
+
+ total_content_height += content_height;
+ });
+
+ // FIXME: This is a total hack, we should respect the 'height' property.
+ box.set_height(total_content_height);
+}
+
+void TableFormattingContext::calculate_column_widths(Box& row, Vector<float>& column_widths)
+{
+ size_t column_index = 0;
+ auto* table = row.first_ancestor_of_type<TableBox>();
+ bool use_auto_layout = !table || table->computed_values().width().is_undefined_or_auto();
+ row.for_each_child_of_type<TableCellBox>([&](auto& cell) {
+ compute_width(cell);
+ if (use_auto_layout) {
+ layout_inside(cell, LayoutMode::OnlyRequiredLineBreaks);
+ } else {
+ layout_inside(cell, LayoutMode::Default);
+ }
+ column_widths[column_index] = max(column_widths[column_index], cell.width());
+ column_index += cell.colspan();
+ });
+}
+
+void TableFormattingContext::layout_row(Box& row, Vector<float>& column_widths)
+{
+ size_t column_index = 0;
+ float tallest_cell_height = 0;
+ float content_width = 0;
+ auto* table = row.first_ancestor_of_type<TableBox>();
+ bool use_auto_layout = !table || table->computed_values().width().is_undefined_or_auto();
+
+ row.for_each_child_of_type<TableCellBox>([&](auto& cell) {
+ cell.set_offset(row.effective_offset().translated(content_width, 0));
+
+ // Layout the cell contents a second time, now that we know its final width.
+ if (use_auto_layout) {
+ layout_inside(cell, LayoutMode::OnlyRequiredLineBreaks);
+ } else {
+ layout_inside(cell, LayoutMode::Default);
+ }
+
+ size_t cell_colspan = cell.colspan();
+ for (size_t i = 0; i < cell_colspan; ++i)
+ content_width += column_widths[column_index++];
+ tallest_cell_height = max(tallest_cell_height, cell.height());
+ });
+
+ if (use_auto_layout) {
+ row.set_width(content_width);
+ } else {
+ row.set_width(table->width());
+ }
+
+ row.set_height(tallest_cell_height);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h
new file mode 100644
index 0000000000..7bb973a5d2
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibWeb/Layout/BlockFormattingContext.h>
+
+namespace Web::Layout {
+
+class TableFormattingContext final : public BlockFormattingContext {
+public:
+ explicit TableFormattingContext(Box&, FormattingContext* parent);
+ ~TableFormattingContext();
+
+ virtual void run(Box&, LayoutMode) override;
+
+private:
+ void calculate_column_widths(Box& row, Vector<float>& column_widths);
+ void layout_row(Box& row, Vector<float>& column_widths);
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/TableRowBox.cpp b/Userland/Libraries/LibWeb/Layout/TableRowBox.cpp
new file mode 100644
index 0000000000..b3033adb1c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/TableRowBox.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/Layout/TableRowBox.h>
+
+namespace Web::Layout {
+
+TableRowBox::TableRowBox(DOM::Document& document, DOM::Element* element, NonnullRefPtr<CSS::StyleProperties> style)
+ : Box(document, element, move(style))
+{
+}
+
+TableRowBox::TableRowBox(DOM::Document& document, DOM::Element* element, CSS::ComputedValues computed_values)
+ : Box(document, element, move(computed_values))
+{
+}
+
+TableRowBox::~TableRowBox()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/TableRowBox.h b/Userland/Libraries/LibWeb/Layout/TableRowBox.h
new file mode 100644
index 0000000000..eab602c156
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/TableRowBox.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Layout/Box.h>
+
+namespace Web::Layout {
+
+class TableRowBox final : public Box {
+public:
+ TableRowBox(DOM::Document&, DOM::Element*, NonnullRefPtr<CSS::StyleProperties>);
+ TableRowBox(DOM::Document&, DOM::Element*, CSS::ComputedValues);
+ virtual ~TableRowBox() override;
+
+ static CSS::Display static_display() { return CSS::Display::TableRow; }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/TableRowGroupBox.cpp b/Userland/Libraries/LibWeb/Layout/TableRowGroupBox.cpp
new file mode 100644
index 0000000000..f6ced13a33
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/TableRowGroupBox.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/Layout/TableCellBox.h>
+#include <LibWeb/Layout/TableRowBox.h>
+#include <LibWeb/Layout/TableRowGroupBox.h>
+
+namespace Web::Layout {
+
+TableRowGroupBox::TableRowGroupBox(DOM::Document& document, DOM::Element& element, NonnullRefPtr<CSS::StyleProperties> style)
+ : Layout::BlockBox(document, &element, move(style))
+{
+}
+
+TableRowGroupBox::~TableRowGroupBox()
+{
+}
+
+size_t TableRowGroupBox::column_count() const
+{
+ size_t table_column_count = 0;
+ for_each_child_of_type<TableRowBox>([&](auto& row) {
+ size_t row_column_count = 0;
+ row.template for_each_child_of_type<TableCellBox>([&](auto& cell) {
+ row_column_count += cell.colspan();
+ });
+ table_column_count = max(table_column_count, row_column_count);
+ });
+ return table_column_count;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/TableRowGroupBox.h b/Userland/Libraries/LibWeb/Layout/TableRowGroupBox.h
new file mode 100644
index 0000000000..6a9fd6956c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/TableRowGroupBox.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Layout/BlockBox.h>
+
+namespace Web::Layout {
+
+class TableRowGroupBox final : public BlockBox {
+public:
+ TableRowGroupBox(DOM::Document&, DOM::Element&, NonnullRefPtr<CSS::StyleProperties>);
+ virtual ~TableRowGroupBox() override;
+
+ size_t column_count() const;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/TextNode.cpp b/Userland/Libraries/LibWeb/Layout/TextNode.cpp
new file mode 100644
index 0000000000..bf027b9cb7
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/TextNode.cpp
@@ -0,0 +1,311 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <AK/Utf8View.h>
+#include <LibCore/DirIterator.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/Font.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/Layout/BlockBox.h>
+#include <LibWeb/Layout/InlineFormattingContext.h>
+#include <LibWeb/Layout/TextNode.h>
+#include <LibWeb/Page/Frame.h>
+#include <ctype.h>
+
+namespace Web::Layout {
+
+TextNode::TextNode(DOM::Document& document, DOM::Text& text)
+ : Node(document, &text)
+{
+ set_inline(true);
+}
+
+TextNode::~TextNode()
+{
+}
+
+static bool is_all_whitespace(const StringView& string)
+{
+ for (size_t i = 0; i < string.length(); ++i) {
+ if (!isspace(string[i]))
+ return false;
+ }
+ return true;
+}
+
+void TextNode::paint_fragment(PaintContext& context, const LineBoxFragment& fragment, PaintPhase phase) const
+{
+ auto& painter = context.painter();
+
+ if (phase == PaintPhase::Background) {
+ painter.fill_rect(enclosing_int_rect(fragment.absolute_rect()), computed_values().background_color());
+ }
+
+ if (phase == PaintPhase::Foreground) {
+ painter.set_font(font());
+
+ if (document().inspected_node() == &dom_node())
+ context.painter().draw_rect(enclosing_int_rect(fragment.absolute_rect()), Color::Magenta);
+
+ if (computed_values().text_decoration_line() == CSS::TextDecorationLine::Underline)
+ painter.draw_line(enclosing_int_rect(fragment.absolute_rect()).bottom_left().translated(0, 1), enclosing_int_rect(fragment.absolute_rect()).bottom_right().translated(0, 1), computed_values().color());
+
+ // FIXME: text-transform should be done already in layout, since uppercase glyphs may be wider than lowercase, etc.
+ auto text = m_text_for_rendering;
+ auto text_transform = computed_values().text_transform();
+ if (text_transform == CSS::TextTransform::Uppercase)
+ text = m_text_for_rendering.to_uppercase();
+ if (text_transform == CSS::TextTransform::Lowercase)
+ text = m_text_for_rendering.to_lowercase();
+
+ painter.draw_text(enclosing_int_rect(fragment.absolute_rect()), text.substring_view(fragment.start(), fragment.length()), Gfx::TextAlignment::CenterLeft, computed_values().color());
+
+ auto selection_rect = fragment.selection_rect(font());
+ if (!selection_rect.is_empty()) {
+ painter.fill_rect(enclosing_int_rect(selection_rect), context.palette().selection());
+ Gfx::PainterStateSaver saver(painter);
+ painter.add_clip_rect(enclosing_int_rect(selection_rect));
+ painter.draw_text(enclosing_int_rect(fragment.absolute_rect()), text.substring_view(fragment.start(), fragment.length()), Gfx::TextAlignment::CenterLeft, context.palette().selection_text());
+ }
+
+ paint_cursor_if_needed(context, fragment);
+ }
+}
+
+void TextNode::paint_cursor_if_needed(PaintContext& context, const LineBoxFragment& fragment) const
+{
+ if (!frame().is_focused_frame())
+ return;
+
+ if (!frame().cursor_blink_state())
+ return;
+
+ if (frame().cursor_position().node() != &dom_node())
+ return;
+
+ if (!(frame().cursor_position().offset() >= (unsigned)fragment.start() && frame().cursor_position().offset() < (unsigned)(fragment.start() + fragment.length())))
+ return;
+
+ if (!fragment.layout_node().dom_node() || !fragment.layout_node().dom_node()->is_editable())
+ return;
+
+ auto fragment_rect = fragment.absolute_rect();
+
+ float cursor_x = fragment_rect.x() + font().width(fragment.text().substring_view(0, frame().cursor_position().offset() - fragment.start()));
+ float cursor_top = fragment_rect.top();
+ float cursor_height = fragment_rect.height();
+ Gfx::IntRect cursor_rect(cursor_x, cursor_top, 1, cursor_height);
+
+ context.painter().draw_rect(cursor_rect, computed_values().color());
+}
+
+template<typename Callback>
+void TextNode::for_each_chunk(Callback callback, LayoutMode layout_mode, bool do_wrap_lines, bool do_wrap_breaks) const
+{
+ Utf8View view(m_text_for_rendering);
+ if (view.is_empty())
+ return;
+
+ auto start_of_chunk = view.begin();
+
+ auto commit_chunk = [&](auto it, bool has_breaking_newline, bool must_commit = false) {
+ if (layout_mode == LayoutMode::OnlyRequiredLineBreaks && !must_commit)
+ return;
+
+ int start = view.byte_offset_of(start_of_chunk);
+ int length = view.byte_offset_of(it) - view.byte_offset_of(start_of_chunk);
+
+ if (has_breaking_newline || length > 0) {
+ auto chunk_view = view.substring_view(start, length);
+ callback(chunk_view, start, length, has_breaking_newline, is_all_whitespace(chunk_view.as_string()));
+ }
+
+ start_of_chunk = it;
+ };
+
+ bool last_was_space = isspace(*view.begin());
+ bool last_was_newline = false;
+ for (auto it = view.begin(); it != view.end();) {
+ if (layout_mode == LayoutMode::AllPossibleLineBreaks) {
+ commit_chunk(it, false);
+ }
+ if (last_was_newline) {
+ last_was_newline = false;
+ commit_chunk(it, true);
+ }
+ if (do_wrap_breaks && *it == '\n') {
+ last_was_newline = true;
+ commit_chunk(it, false);
+ }
+ if (do_wrap_lines) {
+ bool is_space = isspace(*it);
+ if (is_space != last_was_space) {
+ last_was_space = is_space;
+ commit_chunk(it, false);
+ }
+ }
+ ++it;
+ }
+ if (last_was_newline)
+ commit_chunk(view.end(), true);
+ if (start_of_chunk != view.end())
+ commit_chunk(view.end(), false, true);
+}
+
+void TextNode::split_into_lines_by_rules(InlineFormattingContext& context, LayoutMode layout_mode, bool do_collapse, bool do_wrap_lines, bool do_wrap_breaks)
+{
+ auto& containing_block = context.containing_block();
+
+ auto& font = this->font();
+
+ auto& line_boxes = containing_block.line_boxes();
+ containing_block.ensure_last_line_box();
+ float available_width = context.available_width_at_line(line_boxes.size() - 1) - line_boxes.last().width();
+
+ // Collapse whitespace into single spaces
+ if (do_collapse) {
+ auto utf8_view = Utf8View(dom_node().data());
+ StringBuilder builder(dom_node().data().length());
+ auto it = utf8_view.begin();
+ auto skip_over_whitespace = [&] {
+ auto prev = it;
+ while (it != utf8_view.end() && isspace(*it)) {
+ prev = it;
+ ++it;
+ }
+ it = prev;
+ };
+ if (line_boxes.last().is_empty_or_ends_in_whitespace())
+ skip_over_whitespace();
+ for (; it != utf8_view.end(); ++it) {
+ if (!isspace(*it)) {
+ builder.append(utf8_view.as_string().characters_without_null_termination() + utf8_view.byte_offset_of(it), it.code_point_length_in_bytes());
+ } else {
+ builder.append(' ');
+ skip_over_whitespace();
+ }
+ }
+ m_text_for_rendering = builder.to_string();
+ } else {
+ m_text_for_rendering = dom_node().data();
+ }
+
+ // do_wrap_lines => chunks_are_words
+ // !do_wrap_lines => chunks_are_lines
+ struct Chunk {
+ Utf8View view;
+ int start { 0 };
+ int length { 0 };
+ bool is_break { false };
+ bool is_all_whitespace { false };
+ };
+ Vector<Chunk, 128> chunks;
+
+ for_each_chunk(
+ [&](const Utf8View& view, int start, int length, bool is_break, bool is_all_whitespace) {
+ chunks.append({ Utf8View(view), start, length, is_break, is_all_whitespace });
+ },
+ layout_mode, do_wrap_lines, do_wrap_breaks);
+
+ for (size_t i = 0; i < chunks.size(); ++i) {
+ auto& chunk = chunks[i];
+
+ // Collapse entire fragment into non-existence if previous fragment on line ended in whitespace.
+ if (do_collapse && line_boxes.last().is_empty_or_ends_in_whitespace() && chunk.is_all_whitespace)
+ continue;
+
+ float chunk_width;
+ if (do_wrap_lines) {
+ if (do_collapse && isspace(*chunk.view.begin()) && line_boxes.last().is_empty_or_ends_in_whitespace()) {
+ // This is a non-empty chunk that starts with collapsible whitespace.
+ // We are at either at the start of a new line, or after something that ended in whitespace,
+ // so we don't need to contribute our own whitespace to the line. Skip over it instead!
+ ++chunk.start;
+ --chunk.length;
+ chunk.view = chunk.view.substring_view(1, chunk.view.byte_length() - 1);
+ }
+
+ chunk_width = font.width(chunk.view) + font.glyph_spacing();
+
+ if (line_boxes.last().width() > 0 && chunk_width > available_width) {
+ containing_block.add_line_box();
+ available_width = context.available_width_at_line(line_boxes.size() - 1);
+
+ if (do_collapse && chunk.is_all_whitespace)
+ continue;
+ }
+ } else {
+ chunk_width = font.width(chunk.view);
+ }
+
+ line_boxes.last().add_fragment(*this, chunk.start, chunk.length, chunk_width, font.glyph_height());
+ available_width -= chunk_width;
+
+ if (do_wrap_lines) {
+ if (available_width < 0) {
+ containing_block.add_line_box();
+ available_width = context.available_width_at_line(line_boxes.size() - 1);
+ }
+ }
+
+ if (do_wrap_breaks) {
+ if (chunk.is_break) {
+ containing_block.add_line_box();
+ available_width = context.available_width_at_line(line_boxes.size() - 1);
+ }
+ }
+ }
+}
+
+void TextNode::split_into_lines(InlineFormattingContext& context, LayoutMode layout_mode)
+{
+ bool do_collapse = true;
+ bool do_wrap_lines = true;
+ bool do_wrap_breaks = false;
+
+ if (computed_values().white_space() == CSS::WhiteSpace::Nowrap) {
+ do_collapse = true;
+ do_wrap_lines = false;
+ do_wrap_breaks = false;
+ } else if (computed_values().white_space() == CSS::WhiteSpace::Pre) {
+ do_collapse = false;
+ do_wrap_lines = false;
+ do_wrap_breaks = true;
+ } else if (computed_values().white_space() == CSS::WhiteSpace::PreLine) {
+ do_collapse = true;
+ do_wrap_lines = true;
+ do_wrap_breaks = true;
+ } else if (computed_values().white_space() == CSS::WhiteSpace::PreWrap) {
+ do_collapse = false;
+ do_wrap_lines = true;
+ do_wrap_breaks = true;
+ }
+
+ split_into_lines_by_rules(context, layout_mode, do_collapse, do_wrap_lines, do_wrap_breaks);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/TextNode.h b/Userland/Libraries/LibWeb/Layout/TextNode.h
new file mode 100644
index 0000000000..03781a22b2
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/TextNode.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/Layout/Node.h>
+
+namespace Web::Layout {
+
+class LineBoxFragment;
+
+class TextNode : public Node {
+public:
+ TextNode(DOM::Document&, DOM::Text&);
+ virtual ~TextNode() override;
+
+ const DOM::Text& dom_node() const { return static_cast<const DOM::Text&>(*Node::dom_node()); }
+
+ const String& text_for_rendering() const { return m_text_for_rendering; }
+
+ virtual void paint_fragment(PaintContext&, const LineBoxFragment&, PaintPhase) const override;
+
+ virtual void split_into_lines(InlineFormattingContext&, LayoutMode) override;
+
+private:
+ void split_into_lines_by_rules(InlineFormattingContext&, LayoutMode, bool do_collapse, bool do_wrap_lines, bool do_wrap_breaks);
+ void paint_cursor_if_needed(PaintContext&, const LineBoxFragment&) const;
+
+ template<typename Callback>
+ void for_each_chunk(Callback, LayoutMode, bool do_wrap_lines, bool do_wrap_breaks) const;
+
+ String m_text_for_rendering;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp b/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp
new file mode 100644
index 0000000000..6eeff109ea
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/DOM/ParentNode.h>
+#include <LibWeb/Dump.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+#include <LibWeb/Layout/Node.h>
+#include <LibWeb/Layout/TableBox.h>
+#include <LibWeb/Layout/TableCellBox.h>
+#include <LibWeb/Layout/TableRowBox.h>
+#include <LibWeb/Layout/TextNode.h>
+#include <LibWeb/Layout/TreeBuilder.h>
+
+namespace Web::Layout {
+
+TreeBuilder::TreeBuilder()
+{
+}
+
+// The insertion_parent_for_*() functions maintain the invariant that block-level boxes must have either
+// only block-level children or only inline-level children.
+
+static Layout::Node& insertion_parent_for_inline_node(Layout::NodeWithStyle& layout_parent)
+{
+ if (layout_parent.is_inline() && !layout_parent.is_inline_block())
+ return layout_parent;
+
+ if (!layout_parent.has_children() || layout_parent.children_are_inline())
+ return layout_parent;
+
+ // Parent has block-level children, insert into an anonymous wrapper block (and create it first if needed)
+ if (!layout_parent.last_child()->is_anonymous() || !layout_parent.last_child()->children_are_inline()) {
+ layout_parent.append_child(layout_parent.create_anonymous_wrapper());
+ }
+ return *layout_parent.last_child();
+}
+
+static Layout::Node& insertion_parent_for_block_node(Layout::Node& layout_parent, Layout::Node& layout_node)
+{
+ if (!layout_parent.has_children()) {
+ // Parent block has no children, insert this block into parent.
+ return layout_parent;
+ }
+
+ if (!layout_parent.children_are_inline()) {
+ // Parent block has block-level children, insert this block into parent.
+ return layout_parent;
+ }
+
+ // Parent block has inline-level children (our siblings).
+ // First move these siblings into an anonymous wrapper block.
+ NonnullRefPtrVector<Layout::Node> children;
+ while (RefPtr<Layout::Node> child = layout_parent.first_child()) {
+ layout_parent.remove_child(*child);
+ children.append(child.release_nonnull());
+ }
+ layout_parent.append_child(adopt(*new BlockBox(layout_node.document(), nullptr, layout_parent.computed_values().clone_inherited_values())));
+ layout_parent.set_children_are_inline(false);
+ for (auto& child : children) {
+ layout_parent.last_child()->append_child(child);
+ }
+ layout_parent.last_child()->set_children_are_inline(true);
+ // Then it's safe to insert this block into parent.
+ return layout_parent;
+}
+
+void TreeBuilder::create_layout_tree(DOM::Node& dom_node)
+{
+ // If the parent doesn't have a layout node, we don't need one either.
+ if (dom_node.parent() && !dom_node.parent()->layout_node())
+ return;
+
+ auto layout_node = dom_node.create_layout_node();
+ if (!layout_node)
+ return;
+
+ if (!dom_node.parent()) {
+ m_layout_root = layout_node;
+ } else {
+ if (layout_node->is_inline()) {
+ // Inlines can be inserted into the nearest ancestor.
+ auto& insertion_point = insertion_parent_for_inline_node(*m_parent_stack.last());
+ insertion_point.append_child(*layout_node);
+ insertion_point.set_children_are_inline(true);
+ } else {
+ // Non-inlines can't be inserted into an inline parent, so find the nearest non-inline ancestor.
+ auto& nearest_non_inline_ancestor = [&]() -> Layout::Node& {
+ for (ssize_t i = m_parent_stack.size() - 1; i >= 0; --i) {
+ if (!m_parent_stack[i]->is_inline() || m_parent_stack[i]->is_inline_block())
+ return *m_parent_stack[i];
+ }
+ ASSERT_NOT_REACHED();
+ }();
+ auto& insertion_point = insertion_parent_for_block_node(nearest_non_inline_ancestor, *layout_node);
+ insertion_point.append_child(*layout_node);
+ insertion_point.set_children_are_inline(false);
+ }
+ }
+
+ if (dom_node.has_children() && layout_node->can_have_children()) {
+ push_parent(downcast<NodeWithStyle>(*layout_node));
+ downcast<DOM::ParentNode>(dom_node).for_each_child([&](auto& dom_child) {
+ create_layout_tree(dom_child);
+ });
+ pop_parent();
+ }
+}
+
+RefPtr<Node> TreeBuilder::build(DOM::Node& dom_node)
+{
+ if (dom_node.parent()) {
+ // We're building a partial layout tree, so start by building up the stack of parent layout nodes.
+ for (auto* ancestor = dom_node.parent()->layout_node(); ancestor; ancestor = ancestor->parent())
+ m_parent_stack.prepend(downcast<NodeWithStyle>(ancestor));
+ }
+
+ create_layout_tree(dom_node);
+
+ if (auto* root = dom_node.document().layout_node())
+ fixup_tables(*root);
+
+ return move(m_layout_root);
+}
+
+template<CSS::Display display, typename Callback>
+void TreeBuilder::for_each_in_tree_with_display(NodeWithStyle& root, Callback callback)
+{
+ root.for_each_in_subtree_of_type<Box>([&](auto& box) {
+ if (box.computed_values().display() == display)
+ callback(box);
+ return IterationDecision::Continue;
+ });
+}
+
+void TreeBuilder::fixup_tables(NodeWithStyle& root)
+{
+ // NOTE: Even if we only do a partial build, we always do fixup from the root.
+
+ remove_irrelevant_boxes(root);
+ generate_missing_child_wrappers(root);
+ generate_missing_parents(root);
+}
+
+void TreeBuilder::remove_irrelevant_boxes(NodeWithStyle& root)
+{
+ // The following boxes are discarded as if they were display:none:
+
+ NonnullRefPtrVector<Box> to_remove;
+
+ // Children of a table-column.
+ for_each_in_tree_with_display<CSS::Display::TableColumn>(root, [&](Box& table_column) {
+ table_column.for_each_child([&](auto& child) {
+ to_remove.append(child);
+ });
+ });
+
+ // Children of a table-column-group which are not a table-column.
+ for_each_in_tree_with_display<CSS::Display::TableColumnGroup>(root, [&](Box& table_column_group) {
+ table_column_group.for_each_child([&](auto& child) {
+ if (child.computed_values().display() != CSS::Display::TableColumn)
+ to_remove.append(child);
+ });
+ });
+
+ // FIXME:
+ // Anonymous inline boxes which contain only white space and are between two immediate siblings each of which is a table-non-root box.
+ // Anonymous inline boxes which meet all of the following criteria:
+ // - they contain only white space
+ // - they are the first and/or last child of a tabular container
+ // - whose immediate sibling, if any, is a table-non-root box
+
+ for (auto& box : to_remove)
+ box.parent()->remove_child(box);
+}
+
+static bool is_table_track(CSS::Display display)
+{
+ return display == CSS::Display::TableRow || display == CSS::Display::TableColumn;
+}
+
+static bool is_table_track_group(CSS::Display display)
+{
+ return display == CSS::Display::TableRowGroup || display == CSS::Display::TableColumnGroup;
+}
+
+static bool is_not_proper_table_child(const Node& node)
+{
+ if (!node.has_style())
+ return true;
+ auto display = node.computed_values().display();
+ return !is_table_track_group(display) && !is_table_track(display) && display != CSS::Display::TableCaption;
+}
+
+static bool is_not_table_row(const Node& node)
+{
+ if (!node.has_style())
+ return true;
+ auto display = node.computed_values().display();
+ return display != CSS::Display::TableRow;
+}
+
+static bool is_not_table_cell(const Node& node)
+{
+ if (!node.has_style())
+ return true;
+ auto display = node.computed_values().display();
+ return display != CSS::Display::TableCell;
+}
+
+template<typename Matcher, typename Callback>
+static void for_each_sequence_of_consecutive_children_matching(NodeWithStyle& parent, Matcher matcher, Callback callback)
+{
+ NonnullRefPtrVector<Node> sequence;
+ Node* next_sibling = nullptr;
+ for (auto* child = parent.first_child(); child; child = next_sibling) {
+ next_sibling = child->next_sibling();
+ if (matcher(*child)) {
+ sequence.append(*child);
+ } else {
+ if (!sequence.is_empty()) {
+ callback(sequence, next_sibling);
+ sequence.clear();
+ }
+ }
+ }
+ if (!sequence.is_empty())
+ callback(sequence, nullptr);
+}
+
+template<typename WrapperBoxType>
+static void wrap_in_anonymous(NonnullRefPtrVector<Node>& sequence, Node* nearest_sibling)
+{
+ ASSERT(!sequence.is_empty());
+ auto& parent = *sequence.first().parent();
+ auto computed_values = parent.computed_values().clone_inherited_values();
+ static_cast<CSS::MutableComputedValues&>(computed_values).set_display(WrapperBoxType::static_display());
+ auto wrapper = adopt(*new WrapperBoxType(parent.document(), nullptr, move(computed_values)));
+ for (auto& child : sequence) {
+ parent.remove_child(child);
+ wrapper->append_child(child);
+ }
+ if (nearest_sibling)
+ parent.insert_before(move(wrapper), *nearest_sibling);
+ else
+ parent.append_child(move(wrapper));
+}
+
+void TreeBuilder::generate_missing_child_wrappers(NodeWithStyle& root)
+{
+ // An anonymous table-row box must be generated around each sequence of consecutive children of a table-root box which are not proper table child boxes.
+ for_each_in_tree_with_display<CSS::Display::Table>(root, [&](auto& parent) {
+ for_each_sequence_of_consecutive_children_matching(parent, is_not_proper_table_child, [&](auto sequence, auto nearest_sibling) {
+ wrap_in_anonymous<TableRowBox>(sequence, nearest_sibling);
+ });
+ });
+
+ // An anonymous table-row box must be generated around each sequence of consecutive children of a table-row-group box which are not table-row boxes.
+ for_each_in_tree_with_display<CSS::Display::TableRowGroup>(root, [&](auto& parent) {
+ for_each_sequence_of_consecutive_children_matching(parent, is_not_table_row, [&](auto& sequence, auto nearest_sibling) {
+ wrap_in_anonymous<TableRowBox>(sequence, nearest_sibling);
+ });
+ });
+
+ // An anonymous table-cell box must be generated around each sequence of consecutive children of a table-row box which are not table-cell boxes. !Testcase
+ for_each_in_tree_with_display<CSS::Display::TableRow>(root, [&](auto& parent) {
+ for_each_sequence_of_consecutive_children_matching(parent, is_not_table_cell, [&](auto& sequence, auto nearest_sibling) {
+ wrap_in_anonymous<TableCellBox>(sequence, nearest_sibling);
+ });
+ });
+}
+
+void TreeBuilder::generate_missing_parents(NodeWithStyle&)
+{
+ // FIXME: Implement.
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/TreeBuilder.h b/Userland/Libraries/LibWeb/Layout/TreeBuilder.h
new file mode 100644
index 0000000000..03a87cc287
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/TreeBuilder.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtrVector.h>
+#include <AK/RefPtr.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::Layout {
+
+class TreeBuilder {
+public:
+ TreeBuilder();
+
+ RefPtr<Layout::Node> build(DOM::Node&);
+
+private:
+ void create_layout_tree(DOM::Node&);
+
+ void push_parent(Layout::NodeWithStyle& node) { m_parent_stack.append(&node); }
+ void pop_parent() { m_parent_stack.take_last(); }
+
+ template<CSS::Display, typename Callback>
+ void for_each_in_tree_with_display(NodeWithStyle& root, Callback);
+
+ void fixup_tables(NodeWithStyle& root);
+ void remove_irrelevant_boxes(NodeWithStyle& root);
+ void generate_missing_child_wrappers(NodeWithStyle& root);
+ void generate_missing_parents(NodeWithStyle& root);
+
+ RefPtr<Layout::Node> m_layout_root;
+ Vector<Layout::NodeWithStyle*> m_parent_stack;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/WidgetBox.cpp b/Userland/Libraries/LibWeb/Layout/WidgetBox.cpp
new file mode 100644
index 0000000000..c38aa50765
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/WidgetBox.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGUI/Widget.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/StylePainter.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/InProcessWebView.h>
+#include <LibWeb/Layout/WidgetBox.h>
+#include <LibWeb/Page/Frame.h>
+
+namespace Web::Layout {
+
+WidgetBox::WidgetBox(DOM::Document& document, DOM::Element& element, GUI::Widget& widget)
+ : ReplacedBox(document, element, CSS::StyleProperties::create())
+ , m_widget(widget)
+{
+ set_has_intrinsic_width(true);
+ set_has_intrinsic_height(true);
+ set_intrinsic_width(widget.width());
+ set_intrinsic_height(widget.height());
+}
+
+WidgetBox::~WidgetBox()
+{
+ widget().remove_from_parent();
+}
+
+void WidgetBox::did_set_rect()
+{
+ ReplacedBox::did_set_rect();
+ update_widget();
+}
+
+void WidgetBox::update_widget()
+{
+ auto adjusted_widget_position = absolute_rect().location().to_type<int>();
+ auto& page_view = static_cast<const InProcessWebView&>(frame().page()->client());
+ adjusted_widget_position.move_by(-page_view.horizontal_scrollbar().value(), -page_view.vertical_scrollbar().value());
+ widget().move_to(adjusted_widget_position);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Layout/WidgetBox.h b/Userland/Libraries/LibWeb/Layout/WidgetBox.h
new file mode 100644
index 0000000000..8c7445a1fc
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Layout/WidgetBox.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Layout/ReplacedBox.h>
+
+namespace Web::Layout {
+
+class WidgetBox final : public ReplacedBox {
+public:
+ WidgetBox(DOM::Document&, DOM::Element&, GUI::Widget&);
+ virtual ~WidgetBox() override;
+
+ GUI::Widget& widget() { return m_widget; }
+ const GUI::Widget& widget() const { return m_widget; }
+
+ void update_widget();
+
+private:
+ virtual void did_set_rect() override;
+
+ NonnullRefPtr<GUI::Widget> m_widget;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/LayoutTreeModel.cpp b/Userland/Libraries/LibWeb/LayoutTreeModel.cpp
new file mode 100644
index 0000000000..440b2fce52
--- /dev/null
+++ b/Userland/Libraries/LibWeb/LayoutTreeModel.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "LayoutTreeModel.h"
+#include <AK/StringBuilder.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+#include <LibWeb/Layout/TextNode.h>
+#include <ctype.h>
+#include <stdio.h>
+
+namespace Web {
+
+LayoutTreeModel::LayoutTreeModel(DOM::Document& document)
+ : m_document(document)
+{
+ m_document_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-html.png"));
+ m_element_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/inspector-object.png"));
+ m_text_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-unknown.png"));
+}
+
+LayoutTreeModel::~LayoutTreeModel()
+{
+}
+
+GUI::ModelIndex LayoutTreeModel::index(int row, int column, const GUI::ModelIndex& parent) const
+{
+ if (!parent.is_valid())
+ return create_index(row, column, m_document->layout_node());
+ auto& parent_node = *static_cast<Layout::Node*>(parent.internal_data());
+ return create_index(row, column, parent_node.child_at_index(row));
+}
+
+GUI::ModelIndex LayoutTreeModel::parent_index(const GUI::ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return {};
+ auto& node = *static_cast<Layout::Node*>(index.internal_data());
+ if (!node.parent())
+ return {};
+
+ // No grandparent? Parent is the document!
+ if (!node.parent()->parent()) {
+ return create_index(0, 0, m_document->layout_node());
+ }
+
+ // Walk the grandparent's children to find the index of node's parent in its parent.
+ // (This is needed to produce the row number of the GUI::ModelIndex corresponding to node's parent.)
+ int grandparent_child_index = 0;
+ for (auto* grandparent_child = node.parent()->parent()->first_child(); grandparent_child; grandparent_child = grandparent_child->next_sibling()) {
+ if (grandparent_child == node.parent())
+ return create_index(grandparent_child_index, 0, node.parent());
+ ++grandparent_child_index;
+ }
+
+ ASSERT_NOT_REACHED();
+ return {};
+}
+
+int LayoutTreeModel::row_count(const GUI::ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return 1;
+ auto& node = *static_cast<Layout::Node*>(index.internal_data());
+ return node.child_count();
+}
+
+int LayoutTreeModel::column_count(const GUI::ModelIndex&) const
+{
+ return 1;
+}
+
+static String with_whitespace_collapsed(const StringView& string)
+{
+ StringBuilder builder;
+ for (size_t i = 0; i < string.length(); ++i) {
+ if (isspace(string[i])) {
+ builder.append(' ');
+ while (i < string.length()) {
+ if (isspace(string[i])) {
+ ++i;
+ continue;
+ }
+ builder.append(string[i]);
+ break;
+ }
+ continue;
+ }
+ builder.append(string[i]);
+ }
+ return builder.to_string();
+}
+
+GUI::Variant LayoutTreeModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
+{
+ auto& node = *static_cast<Layout::Node*>(index.internal_data());
+ if (role == GUI::ModelRole::Icon) {
+ if (is<Layout::InitialContainingBlockBox>(node))
+ return m_document_icon;
+ if (is<Layout::TextNode>(node))
+ return m_text_icon;
+ return m_element_icon;
+ }
+ if (role == GUI::ModelRole::Display) {
+ if (is<Layout::TextNode>(node))
+ return String::formatted("TextNode: {}", with_whitespace_collapsed(downcast<Layout::TextNode>(node).text_for_rendering()));
+ StringBuilder builder;
+ builder.append(node.class_name());
+ builder.append(' ');
+ if (node.is_anonymous()) {
+ builder.append("[anonymous]");
+ } else if (!node.dom_node()->is_element()) {
+ builder.append(node.dom_node()->node_name());
+ } else {
+ auto& element = downcast<DOM::Element>(*node.dom_node());
+ builder.append('<');
+ builder.append(element.local_name());
+ element.for_each_attribute([&](auto& name, auto& value) {
+ builder.append(' ');
+ builder.append(name);
+ builder.append('=');
+ builder.append('"');
+ builder.append(value);
+ builder.append('"');
+ });
+ builder.append('>');
+ }
+ return builder.to_string();
+ }
+ return {};
+}
+
+void LayoutTreeModel::update()
+{
+ did_update();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/LayoutTreeModel.h b/Userland/Libraries/LibWeb/LayoutTreeModel.h
new file mode 100644
index 0000000000..f1deedcce1
--- /dev/null
+++ b/Userland/Libraries/LibWeb/LayoutTreeModel.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Model.h>
+#include <LibWeb/Forward.h>
+
+namespace Web {
+
+class LayoutTreeModel final : public GUI::Model {
+public:
+ static NonnullRefPtr<LayoutTreeModel> create(DOM::Document& document)
+ {
+ return adopt(*new LayoutTreeModel(document));
+ }
+
+ virtual ~LayoutTreeModel() override;
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
+ virtual GUI::ModelIndex index(int row, int column, const GUI::ModelIndex& parent = GUI::ModelIndex()) const override;
+ virtual GUI::ModelIndex parent_index(const GUI::ModelIndex&) const override;
+ virtual void update() override;
+
+private:
+ explicit LayoutTreeModel(DOM::Document&);
+
+ NonnullRefPtr<DOM::Document> m_document;
+
+ GUI::Icon m_document_icon;
+ GUI::Icon m_element_icon;
+ GUI::Icon m_text_icon;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Loader/ContentFilter.cpp b/Userland/Libraries/LibWeb/Loader/ContentFilter.cpp
new file mode 100644
index 0000000000..b4633b5d01
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Loader/ContentFilter.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibWeb/Loader/ContentFilter.h>
+
+namespace Web {
+
+ContentFilter& ContentFilter::the()
+{
+ static ContentFilter* filter = new ContentFilter;
+ return *filter;
+}
+
+ContentFilter::ContentFilter()
+{
+}
+
+ContentFilter::~ContentFilter()
+{
+}
+
+bool ContentFilter::is_filtered(const URL& url) const
+{
+ auto url_string = url.to_string();
+
+ for (auto& pattern : m_patterns) {
+ if (url_string.matches(pattern.text, CaseSensitivity::CaseSensitive))
+ return true;
+ }
+ return false;
+}
+
+void ContentFilter::add_pattern(const String& pattern)
+{
+ StringBuilder builder;
+ if (!pattern.starts_with('*'))
+ builder.append('*');
+ builder.append(pattern);
+ if (!pattern.ends_with('*'))
+ builder.append('*');
+ m_patterns.empend(builder.to_string());
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Loader/ContentFilter.h b/Userland/Libraries/LibWeb/Loader/ContentFilter.h
new file mode 100644
index 0000000000..964c3b3fa2
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Loader/ContentFilter.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/URL.h>
+#include <AK/Vector.h>
+
+namespace Web {
+
+class ContentFilter {
+public:
+ static ContentFilter& the();
+
+ bool is_filtered(const URL&) const;
+ void add_pattern(const String&);
+
+private:
+ ContentFilter();
+ ~ContentFilter();
+
+ struct Pattern {
+ String text;
+ };
+ Vector<Pattern> m_patterns;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Loader/FrameLoader.cpp b/Userland/Libraries/LibWeb/Loader/FrameLoader.cpp
new file mode 100644
index 0000000000..172a9e1b2a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Loader/FrameLoader.cpp
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LexicalPath.h>
+#include <LibGemini/Document.h>
+#include <LibGfx/ImageDecoder.h>
+#include <LibMarkdown/Document.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/ElementFactory.h>
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/HTML/HTMLIFrameElement.h>
+#include <LibWeb/HTML/Parser/HTMLDocumentParser.h>
+#include <LibWeb/Loader/FrameLoader.h>
+#include <LibWeb/Loader/ResourceLoader.h>
+#include <LibWeb/Namespace.h>
+#include <LibWeb/Page/Frame.h>
+#include <LibWeb/Page/Page.h>
+
+//#define GEMINI_DEBUG 1
+
+namespace Web {
+
+FrameLoader::FrameLoader(Frame& frame)
+ : m_frame(frame)
+{
+}
+
+FrameLoader::~FrameLoader()
+{
+}
+
+static bool build_markdown_document(DOM::Document& document, const ByteBuffer& data)
+{
+ auto markdown_document = Markdown::Document::parse(data);
+ if (!markdown_document)
+ return false;
+
+ HTML::HTMLDocumentParser parser(document, markdown_document->render_to_html(), "utf-8");
+ parser.run(document.url());
+ return true;
+}
+
+static bool build_text_document(DOM::Document& document, const ByteBuffer& data)
+{
+ auto html_element = document.create_element("html");
+ document.append_child(html_element);
+
+ auto head_element = document.create_element("head");
+ html_element->append_child(head_element);
+ auto title_element = document.create_element("title");
+ head_element->append_child(title_element);
+
+ auto title_text = document.create_text_node(document.url().basename());
+ title_element->append_child(title_text);
+
+ auto body_element = document.create_element("body");
+ html_element->append_child(body_element);
+
+ auto pre_element = document.create_element("pre");
+ body_element->append_child(pre_element);
+
+ pre_element->append_child(document.create_text_node(String::copy(data)));
+ return true;
+}
+
+static bool build_image_document(DOM::Document& document, const ByteBuffer& data)
+{
+ auto image_decoder = Gfx::ImageDecoder::create(data.data(), data.size());
+ auto bitmap = image_decoder->bitmap();
+ if (!bitmap)
+ return false;
+
+ auto html_element = document.create_element("html");
+ document.append_child(html_element);
+
+ auto head_element = document.create_element("head");
+ html_element->append_child(head_element);
+ auto title_element = document.create_element("title");
+ head_element->append_child(title_element);
+
+ auto basename = LexicalPath(document.url().path()).basename();
+ auto title_text = adopt(*new DOM::Text(document, String::formatted("{} [{}x{}]", basename, bitmap->width(), bitmap->height())));
+ title_element->append_child(title_text);
+
+ auto body_element = document.create_element("body");
+ html_element->append_child(body_element);
+
+ auto image_element = document.create_element("img");
+ image_element->set_attribute(HTML::AttributeNames::src, document.url().to_string());
+ body_element->append_child(image_element);
+
+ return true;
+}
+
+static bool build_gemini_document(DOM::Document& document, const ByteBuffer& data)
+{
+ StringView gemini_data { data };
+ auto gemini_document = Gemini::Document::parse(gemini_data, document.url());
+ String html_data = gemini_document->render_to_html();
+
+#ifdef GEMINI_DEBUG
+ dbgln("Gemini data:\n\"\"\"{}\"\"\"", gemini_data);
+ dbgln("Converted to HTML:\n\"\"\"{}\"\"\"", html_data);
+#endif
+
+ HTML::HTMLDocumentParser parser(document, html_data, "utf-8");
+ parser.run(document.url());
+ return true;
+}
+
+bool FrameLoader::parse_document(DOM::Document& document, const ByteBuffer& data)
+{
+ auto& mime_type = document.content_type();
+ if (mime_type == "text/html" || mime_type == "image/svg+xml") {
+ HTML::HTMLDocumentParser parser(document, data, document.encoding());
+ parser.run(document.url());
+ return true;
+ }
+ if (mime_type.starts_with("image/"))
+ return build_image_document(document, data);
+ if (mime_type == "text/plain")
+ return build_text_document(document, data);
+ if (mime_type == "text/markdown")
+ return build_markdown_document(document, data);
+ if (mime_type == "text/gemini")
+ return build_gemini_document(document, data);
+
+ return false;
+}
+
+bool FrameLoader::load(const LoadRequest& request, Type type)
+{
+ if (!request.is_valid()) {
+ load_error_page(request.url(), "Invalid request");
+ return false;
+ }
+
+ auto& url = request.url();
+
+ set_resource(ResourceLoader::the().load_resource(Resource::Type::Generic, request));
+
+ if (type == Type::Navigation) {
+ if (auto* page = frame().page())
+ page->client().page_did_start_loading(url);
+ }
+
+ if (type == Type::IFrame)
+ return true;
+
+ if (url.protocol() == "http" || url.protocol() == "https") {
+ URL favicon_url;
+ favicon_url.set_protocol(url.protocol());
+ favicon_url.set_host(url.host());
+ favicon_url.set_port(url.port());
+ favicon_url.set_path("/favicon.ico");
+
+ ResourceLoader::the().load(
+ favicon_url,
+ [this, favicon_url](auto data, auto&) {
+ dbg() << "Favicon downloaded, " << data.size() << " bytes from " << favicon_url;
+ auto decoder = Gfx::ImageDecoder::create(data.data(), data.size());
+ auto bitmap = decoder->bitmap();
+ if (!bitmap) {
+ dbg() << "Could not decode favicon " << favicon_url;
+ return;
+ }
+ dbg() << "Decoded favicon, " << bitmap->size();
+ if (auto* page = frame().page())
+ page->client().page_did_change_favicon(*bitmap);
+ });
+ }
+
+ return true;
+}
+
+bool FrameLoader::load(const URL& url, Type type)
+{
+ dbg() << "FrameLoader::load: " << url;
+
+ if (!url.is_valid()) {
+ load_error_page(url, "Invalid URL");
+ return false;
+ }
+
+ LoadRequest request;
+ request.set_url(url);
+
+ return load(request, type);
+}
+
+void FrameLoader::load_html(const StringView& html, const URL& url)
+{
+ auto document = DOM::Document::create(url);
+ HTML::HTMLDocumentParser parser(document, html, "utf-8");
+ parser.run(url);
+ frame().set_document(&parser.document());
+}
+
+// FIXME: Use an actual templating engine (our own one when it's built, preferably
+// with a way to check these usages at compile time)
+
+void FrameLoader::load_error_page(const URL& failed_url, const String& error)
+{
+ auto error_page_url = "file:///res/html/error.html";
+ ResourceLoader::the().load(
+ error_page_url,
+ [this, failed_url, error](auto data, auto&) {
+ ASSERT(!data.is_null());
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ auto html = String::format(
+ String::copy(data).characters(),
+ escape_html_entities(failed_url.to_string()).characters(),
+ escape_html_entities(error).characters());
+#pragma GCC diagnostic pop
+ auto document = HTML::parse_html_document(html, failed_url, "utf-8");
+ ASSERT(document);
+ frame().set_document(document);
+ },
+ [](auto error) {
+ dbg() << "Failed to load error page: " << error;
+ ASSERT_NOT_REACHED();
+ });
+}
+
+void FrameLoader::resource_did_load()
+{
+ auto url = resource()->url();
+
+ if (!resource()->has_encoded_data()) {
+ load_error_page(url, "No data");
+ return;
+ }
+
+ // FIXME: Also check HTTP status code before redirecting
+ auto location = resource()->response_headers().get("Location");
+ if (location.has_value()) {
+ load(url.complete_url(location.value()), FrameLoader::Type::Navigation);
+ return;
+ }
+
+ dbgln("I believe this content has MIME type '{}', , encoding '{}'", resource()->mime_type(), resource()->encoding());
+
+ auto document = DOM::Document::create();
+ document->set_url(url);
+ document->set_encoding(resource()->encoding());
+ document->set_content_type(resource()->mime_type());
+
+ frame().set_document(document);
+
+ if (!parse_document(*document, resource()->encoded_data())) {
+ load_error_page(url, "Failed to parse content.");
+ return;
+ }
+
+ if (!url.fragment().is_empty())
+ frame().scroll_to_anchor(url.fragment());
+
+ if (auto* host_element = frame().host_element()) {
+ // FIXME: Perhaps in the future we'll have a better common base class for <frame> and <iframe>
+ ASSERT(is<HTML::HTMLIFrameElement>(*host_element));
+ downcast<HTML::HTMLIFrameElement>(*host_element).content_frame_did_load({});
+ }
+
+ if (auto* page = frame().page())
+ page->client().page_did_finish_loading(url);
+}
+
+void FrameLoader::resource_did_fail()
+{
+ load_error_page(resource()->url(), resource()->error());
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Loader/FrameLoader.h b/Userland/Libraries/LibWeb/Loader/FrameLoader.h
new file mode 100644
index 0000000000..f0c3d0bf56
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Loader/FrameLoader.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibWeb/Forward.h>
+#include <LibWeb/Loader/Resource.h>
+
+namespace Web {
+
+class FrameLoader final
+ : public ResourceClient {
+public:
+ enum class Type {
+ Navigation,
+ Reload,
+ IFrame,
+ };
+
+ explicit FrameLoader(Frame&);
+ ~FrameLoader();
+
+ bool load(const URL&, Type);
+ bool load(const LoadRequest&, Type);
+
+ void load_html(const StringView&, const URL&);
+
+ Frame& frame() { return m_frame; }
+ const Frame& frame() const { return m_frame; }
+
+private:
+ // ^ResourceClient
+ virtual void resource_did_load() override;
+ virtual void resource_did_fail() override;
+
+ void load_error_page(const URL& failed_url, const String& error_message);
+ bool parse_document(DOM::Document&, const ByteBuffer& data);
+
+ Frame& m_frame;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Loader/ImageLoader.cpp b/Userland/Libraries/LibWeb/Loader/ImageLoader.cpp
new file mode 100644
index 0000000000..b56ab98abd
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Loader/ImageLoader.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/Timer.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+#include <LibWeb/Loader/ImageLoader.h>
+#include <LibWeb/Loader/ResourceLoader.h>
+
+namespace Web {
+
+ImageLoader::ImageLoader()
+ : m_timer(Core::Timer::construct())
+{
+}
+
+void ImageLoader::load(const URL& url)
+{
+ m_loading_state = LoadingState::Loading;
+ LoadRequest request;
+ request.set_url(url);
+ set_resource(ResourceLoader::the().load_resource(Resource::Type::Image, request));
+}
+
+void ImageLoader::set_visible_in_viewport(bool visible_in_viewport) const
+{
+ if (m_visible_in_viewport == visible_in_viewport)
+ return;
+ m_visible_in_viewport = visible_in_viewport;
+
+ // FIXME: Don't update volatility every time. If we're here, we're probably scanning through
+ // the whole document, updating "is visible in viewport" flags, and this could lead
+ // to the same bitmap being marked volatile back and forth unnecessarily.
+ if (resource())
+ const_cast<ImageResource*>(resource())->update_volatility();
+}
+
+void ImageLoader::resource_did_load()
+{
+ ASSERT(resource());
+
+ if (!resource()->mime_type().starts_with("image/")) {
+ m_loading_state = LoadingState::Failed;
+ if (on_fail)
+ on_fail();
+ return;
+ }
+
+ m_loading_state = LoadingState::Loaded;
+
+#ifdef IMAGE_LOADER_DEBUG
+ if (!resource()->has_encoded_data()) {
+ dbg() << "ImageLoader: Resource did load, no encoded data. URL: " << resource()->url();
+ } else {
+ dbg() << "ImageLoader: Resource did load, has encoded data. URL: " << resource()->url();
+ }
+#endif
+
+ if (resource()->should_decode_in_process()) {
+ auto& decoder = resource()->ensure_decoder();
+
+ if (decoder.is_animated() && decoder.frame_count() > 1) {
+ const auto& first_frame = decoder.frame(0);
+ m_timer->set_interval(first_frame.duration);
+ m_timer->on_timeout = [this] { animate(); };
+ m_timer->start();
+ }
+ }
+
+ if (on_load)
+ on_load();
+}
+
+void ImageLoader::animate()
+{
+ if (!m_visible_in_viewport)
+ return;
+
+ auto& decoder = resource()->ensure_decoder();
+
+ m_current_frame_index = (m_current_frame_index + 1) % decoder.frame_count();
+ const auto& current_frame = decoder.frame(m_current_frame_index);
+
+ if (current_frame.duration != m_timer->interval()) {
+ m_timer->restart(current_frame.duration);
+ }
+
+ if (m_current_frame_index == decoder.frame_count() - 1) {
+ ++m_loops_completed;
+ if (m_loops_completed > 0 && m_loops_completed == decoder.loop_count()) {
+ m_timer->stop();
+ }
+ }
+
+ if (on_animate)
+ on_animate();
+}
+
+void ImageLoader::resource_did_fail()
+{
+ dbg() << "ImageLoader: Resource did fail. URL: " << resource()->url();
+ m_loading_state = LoadingState::Failed;
+ if (on_fail)
+ on_fail();
+}
+
+bool ImageLoader::has_image() const
+{
+ if (!resource())
+ return false;
+ if (resource()->should_decode_in_process())
+ return const_cast<ImageResource*>(resource())->ensure_decoder().bitmap();
+ return true;
+}
+
+unsigned ImageLoader::width() const
+{
+ if (!resource())
+ return 0;
+ if (resource()->should_decode_in_process())
+ return const_cast<ImageResource*>(resource())->ensure_decoder().width();
+ return bitmap() ? bitmap()->width() : 0;
+}
+
+unsigned ImageLoader::height() const
+{
+ if (!resource())
+ return 0;
+ if (resource()->should_decode_in_process())
+ return const_cast<ImageResource*>(resource())->ensure_decoder().height();
+ return bitmap() ? bitmap()->height() : 0;
+}
+
+const Gfx::Bitmap* ImageLoader::bitmap() const
+{
+ if (!resource())
+ return nullptr;
+ return resource()->bitmap(m_current_frame_index);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Loader/ImageLoader.h b/Userland/Libraries/LibWeb/Loader/ImageLoader.h
new file mode 100644
index 0000000000..f07b6e3055
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Loader/ImageLoader.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <LibCore/Timer.h>
+#include <LibWeb/Loader/ImageResource.h>
+
+namespace Web {
+
+class ImageLoader : public ImageResourceClient {
+public:
+ ImageLoader();
+
+ void load(const URL&);
+
+ const Gfx::Bitmap* bitmap() const;
+
+ bool has_image() const;
+
+ bool has_loaded_or_failed() const { return m_loading_state != LoadingState::Loading; }
+
+ void set_visible_in_viewport(bool) const;
+
+ unsigned width() const;
+ unsigned height() const;
+
+ Function<void()> on_load;
+ Function<void()> on_fail;
+ Function<void()> on_animate;
+
+private:
+ // ^ImageResourceClient
+ virtual void resource_did_load() override;
+ virtual void resource_did_fail() override;
+ virtual bool is_visible_in_viewport() const override { return m_visible_in_viewport; }
+
+ void animate();
+
+ enum class LoadingState {
+ None,
+ Loading,
+ Loaded,
+ Failed,
+ };
+
+ mutable bool m_visible_in_viewport { false };
+
+ size_t m_current_frame_index { 0 };
+ size_t m_loops_completed { 0 };
+ LoadingState m_loading_state { LoadingState::Loading };
+ NonnullRefPtr<Core::Timer> m_timer;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Loader/ImageResource.cpp b/Userland/Libraries/LibWeb/Loader/ImageResource.cpp
new file mode 100644
index 0000000000..3daa284deb
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Loader/ImageResource.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+#include <LibImageDecoderClient/Client.h>
+#include <LibWeb/Loader/ImageResource.h>
+
+namespace Web {
+
+ImageResource::ImageResource(const LoadRequest& request)
+ : Resource(Type::Image, request)
+{
+}
+
+ImageResource::~ImageResource()
+{
+}
+
+bool ImageResource::should_decode_in_process() const
+{
+ return mime_type() == "image/gif";
+}
+
+Gfx::ImageDecoder& ImageResource::ensure_decoder()
+{
+ if (!m_decoder)
+ m_decoder = Gfx::ImageDecoder::create(encoded_data());
+ return *m_decoder;
+}
+
+const Gfx::Bitmap* ImageResource::bitmap(size_t frame_index) const
+{
+ if (!has_encoded_data())
+ return nullptr;
+
+ if (should_decode_in_process()) {
+ if (!m_decoder)
+ return nullptr;
+ if (m_decoder->is_animated())
+ m_decoded_image = m_decoder->frame(frame_index).image;
+ else
+ m_decoded_image = m_decoder->bitmap();
+ } else if (!m_decoded_image && !m_has_attempted_decode) {
+ auto image_decoder_client = ImageDecoderClient::Client::construct();
+ m_decoded_image = image_decoder_client->decode_image(encoded_data());
+ m_has_attempted_decode = true;
+ }
+ return m_decoded_image;
+}
+
+void ImageResource::update_volatility()
+{
+ if (!m_decoder)
+ return;
+
+ bool visible_in_viewport = false;
+ for_each_client([&](auto& client) {
+ if (static_cast<const ImageResourceClient&>(client).is_visible_in_viewport())
+ visible_in_viewport = true;
+ });
+
+ if (!visible_in_viewport) {
+ m_decoder->set_volatile();
+ return;
+ }
+
+ bool still_has_decoded_image = m_decoder->set_nonvolatile();
+ if (still_has_decoded_image)
+ return;
+
+ m_decoder = nullptr;
+}
+
+ImageResourceClient::~ImageResourceClient()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Loader/ImageResource.h b/Userland/Libraries/LibWeb/Loader/ImageResource.h
new file mode 100644
index 0000000000..0e66785a70
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Loader/ImageResource.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Loader/Resource.h>
+
+namespace Web {
+
+class ImageResource final : public Resource {
+ friend class Resource;
+
+public:
+ virtual ~ImageResource() override;
+ Gfx::ImageDecoder& ensure_decoder();
+ const Gfx::Bitmap* bitmap(size_t frame_index = 0) const;
+
+ bool should_decode_in_process() const;
+
+ void update_volatility();
+
+private:
+ explicit ImageResource(const LoadRequest&);
+ RefPtr<Gfx::ImageDecoder> m_decoder;
+ mutable RefPtr<Gfx::Bitmap> m_decoded_image;
+ mutable bool m_has_attempted_decode { false };
+};
+
+class ImageResourceClient : public ResourceClient {
+public:
+ virtual ~ImageResourceClient();
+
+ virtual bool is_visible_in_viewport() const { return false; }
+
+protected:
+ ImageResource* resource() { return static_cast<ImageResource*>(ResourceClient::resource()); }
+ const ImageResource* resource() const { return static_cast<const ImageResource*>(ResourceClient::resource()); }
+
+private:
+ virtual Resource::Type client_type() const override { return Resource::Type::Image; }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Loader/LoadRequest.h b/Userland/Libraries/LibWeb/Loader/LoadRequest.h
new file mode 100644
index 0000000000..149ce2537c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Loader/LoadRequest.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/HashMap.h>
+#include <AK/URL.h>
+#include <LibWeb/Forward.h>
+
+namespace Web {
+
+class LoadRequest {
+public:
+ LoadRequest()
+ {
+ }
+
+ bool is_valid() const { return m_url.is_valid(); }
+
+ const URL& url() const { return m_url; }
+ void set_url(const URL& url) { m_url = url; }
+
+ const String& method() const { return m_method; }
+ void set_method(const String& method) { m_method = method; }
+
+ const ByteBuffer& body() const { return m_body; }
+ void set_body(const ByteBuffer& body) { m_body = body; }
+
+ unsigned hash() const
+ {
+ // FIXME: Include headers in the hash as well
+ return pair_int_hash(pair_int_hash(m_url.to_string().hash(), m_method.hash()), string_hash((const char*)m_body.data(), m_body.size()));
+ }
+
+ bool operator==(const LoadRequest& other) const
+ {
+ if (m_headers.size() != other.m_headers.size())
+ return false;
+ for (auto& it : m_headers) {
+ auto jt = other.m_headers.find(it.key);
+ if (jt == other.m_headers.end())
+ return false;
+ if (it.value != jt->value)
+ return false;
+ }
+ return m_url == other.m_url && m_method == other.m_method && m_body == other.m_body;
+ }
+
+ void set_header(const String& name, const String& value) { m_headers.set(name, value); }
+ String header(const String& name) const { return m_headers.get(name).value_or({}); }
+
+ const HashMap<String, String>& headers() const { return m_headers; }
+
+private:
+ URL m_url;
+ String m_method { "GET" };
+ HashMap<String, String> m_headers;
+ ByteBuffer m_body;
+};
+
+}
+
+namespace AK {
+
+template<>
+struct Traits<Web::LoadRequest> : public GenericTraits<Web::LoadRequest> {
+ static unsigned hash(const Web::LoadRequest& request) { return request.hash(); }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Loader/Resource.cpp b/Userland/Libraries/LibWeb/Loader/Resource.cpp
new file mode 100644
index 0000000000..2cb6ebc52d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Loader/Resource.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <LibCore/MimeData.h>
+#include <LibWeb/HTML/HTMLImageElement.h>
+#include <LibWeb/Loader/Resource.h>
+
+namespace Web {
+
+NonnullRefPtr<Resource> Resource::create(Badge<ResourceLoader>, Type type, const LoadRequest& request)
+{
+ if (type == Type::Image)
+ return adopt(*new ImageResource(request));
+ return adopt(*new Resource(type, request));
+}
+
+Resource::Resource(Type type, const LoadRequest& request)
+ : m_request(request)
+ , m_type(type)
+{
+}
+
+Resource::~Resource()
+{
+}
+
+void Resource::for_each_client(Function<void(ResourceClient&)> callback)
+{
+ Vector<WeakPtr<ResourceClient>, 16> clients_copy;
+ clients_copy.ensure_capacity(m_clients.size());
+ for (auto* client : m_clients)
+ clients_copy.append(client->make_weak_ptr());
+ for (auto client : clients_copy) {
+ if (client)
+ callback(*client);
+ }
+}
+
+static String encoding_from_content_type(const String& content_type)
+{
+ auto offset = content_type.index_of("charset=");
+ if (offset.has_value()) {
+ auto encoding = content_type.substring(offset.value() + 8, content_type.length() - offset.value() - 8).to_lowercase();
+ if (encoding.length() >= 2 && encoding.starts_with('"') && encoding.ends_with('"'))
+ return encoding.substring(1, encoding.length() - 2);
+ if (encoding.length() >= 2 && encoding.starts_with('\'') && encoding.ends_with('\''))
+ return encoding.substring(1, encoding.length() - 2);
+ return encoding;
+ }
+
+ return "utf-8";
+}
+
+static String mime_type_from_content_type(const String& content_type)
+{
+ auto offset = content_type.index_of(";");
+ if (offset.has_value())
+ return content_type.substring(0, offset.value()).to_lowercase();
+
+ return content_type;
+}
+
+void Resource::did_load(Badge<ResourceLoader>, ReadonlyBytes data, const HashMap<String, String, CaseInsensitiveStringTraits>& headers)
+{
+ ASSERT(!m_loaded);
+ m_encoded_data = ByteBuffer::copy(data);
+ m_response_headers = headers;
+ m_loaded = true;
+
+ auto content_type = headers.get("Content-Type");
+ if (content_type.has_value()) {
+#ifdef RESOURCE_DEBUG
+ dbgln("Content-Type header: '{}'", content_type.value());
+#endif
+ m_encoding = encoding_from_content_type(content_type.value());
+ m_mime_type = mime_type_from_content_type(content_type.value());
+ } else if (url().protocol() == "data" && !url().data_mime_type().is_empty()) {
+#ifdef RESOURCE_DEBUG
+ dbg() << "This is a data URL with mime-type _" << url().data_mime_type() << "_";
+#endif
+ m_encoding = "utf-8"; // FIXME: This doesn't seem nice.
+ m_mime_type = url().data_mime_type();
+ } else {
+#ifdef RESOURCE_DEBUG
+ dbgln("No Content-Type header to go on! Guessing based on filename...");
+#endif
+ m_encoding = "utf-8"; // FIXME: This doesn't seem nice.
+ m_mime_type = Core::guess_mime_type_based_on_filename(url().path());
+ }
+
+ for_each_client([](auto& client) {
+ client.resource_did_load();
+ });
+}
+
+void Resource::did_fail(Badge<ResourceLoader>, const String& error)
+{
+ m_error = error;
+ m_failed = true;
+
+ for_each_client([](auto& client) {
+ client.resource_did_fail();
+ });
+}
+
+void Resource::register_client(Badge<ResourceClient>, ResourceClient& client)
+{
+ ASSERT(!m_clients.contains(&client));
+ m_clients.set(&client);
+}
+
+void Resource::unregister_client(Badge<ResourceClient>, ResourceClient& client)
+{
+ ASSERT(m_clients.contains(&client));
+ m_clients.remove(&client);
+}
+
+void ResourceClient::set_resource(Resource* resource)
+{
+ if (m_resource)
+ m_resource->unregister_client({}, *this);
+ m_resource = resource;
+ if (m_resource) {
+ ASSERT(resource->type() == client_type());
+
+ m_resource->register_client({}, *this);
+
+ // Make sure that reused resources also have their load callback fired.
+ if (resource->is_loaded())
+ resource_did_load();
+
+ // Make sure that reused resources also have their fail callback fired.
+ if (resource->is_failed())
+ resource_did_fail();
+ }
+}
+
+ResourceClient::~ResourceClient()
+{
+ if (m_resource)
+ m_resource->unregister_client({}, *this);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Loader/Resource.h b/Userland/Libraries/LibWeb/Loader/Resource.h
new file mode 100644
index 0000000000..9838071c65
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Loader/Resource.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/HashMap.h>
+#include <AK/HashTable.h>
+#include <AK/Noncopyable.h>
+#include <AK/RefCounted.h>
+#include <AK/URL.h>
+#include <AK/WeakPtr.h>
+#include <AK/Weakable.h>
+#include <LibGfx/Forward.h>
+#include <LibWeb/Forward.h>
+#include <LibWeb/Loader/LoadRequest.h>
+
+namespace Web {
+
+class ResourceClient;
+
+class Resource : public RefCounted<Resource> {
+ AK_MAKE_NONCOPYABLE(Resource);
+ AK_MAKE_NONMOVABLE(Resource);
+
+public:
+ enum class Type {
+ Generic,
+ Image,
+ };
+
+ static NonnullRefPtr<Resource> create(Badge<ResourceLoader>, Type, const LoadRequest&);
+ virtual ~Resource();
+
+ Type type() const { return m_type; }
+
+ bool is_loaded() const { return m_loaded; }
+
+ bool is_failed() const { return m_failed; }
+ const String& error() const { return m_error; }
+
+ bool has_encoded_data() const { return !m_encoded_data.is_null(); }
+
+ const URL& url() const { return m_request.url(); }
+ const ByteBuffer& encoded_data() const { return m_encoded_data; }
+
+ const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers() const { return m_response_headers; }
+
+ void register_client(Badge<ResourceClient>, ResourceClient&);
+ void unregister_client(Badge<ResourceClient>, ResourceClient&);
+
+ const String& encoding() const { return m_encoding; }
+ const String& mime_type() const { return m_mime_type; }
+
+ void for_each_client(Function<void(ResourceClient&)>);
+
+ void did_load(Badge<ResourceLoader>, ReadonlyBytes data, const HashMap<String, String, CaseInsensitiveStringTraits>& headers);
+ void did_fail(Badge<ResourceLoader>, const String& error);
+
+protected:
+ explicit Resource(Type, const LoadRequest&);
+
+private:
+ LoadRequest m_request;
+ ByteBuffer m_encoded_data;
+ Type m_type { Type::Generic };
+ bool m_loaded { false };
+ bool m_failed { false };
+ String m_error;
+ String m_encoding;
+ String m_mime_type;
+ HashMap<String, String, CaseInsensitiveStringTraits> m_response_headers;
+ HashTable<ResourceClient*> m_clients;
+};
+
+class ResourceClient : public Weakable<ResourceClient> {
+public:
+ virtual ~ResourceClient();
+
+ virtual void resource_did_load() { }
+ virtual void resource_did_fail() { }
+
+protected:
+ virtual Resource::Type client_type() const { return Resource::Type::Generic; }
+
+ Resource* resource() { return m_resource; }
+ const Resource* resource() const { return m_resource; }
+ void set_resource(Resource*);
+
+private:
+ RefPtr<Resource> m_resource;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Loader/ResourceLoader.cpp b/Userland/Libraries/LibWeb/Loader/ResourceLoader.cpp
new file mode 100644
index 0000000000..74ec1194d4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Loader/ResourceLoader.cpp
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Base64.h>
+#include <AK/JsonObject.h>
+#include <AK/SharedBuffer.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/File.h>
+#include <LibProtocol/Client.h>
+#include <LibProtocol/Download.h>
+#include <LibWeb/Loader/ContentFilter.h>
+#include <LibWeb/Loader/LoadRequest.h>
+#include <LibWeb/Loader/Resource.h>
+#include <LibWeb/Loader/ResourceLoader.h>
+
+//#define CACHE_DEBUG
+
+namespace Web {
+
+ResourceLoader& ResourceLoader::the()
+{
+ static ResourceLoader* s_the;
+ if (!s_the)
+ s_the = &ResourceLoader::construct().leak_ref();
+ return *s_the;
+}
+
+ResourceLoader::ResourceLoader()
+ : m_protocol_client(Protocol::Client::construct())
+ , m_user_agent("Mozilla/4.0 (SerenityOS; x86) LibWeb+LibJS (Not KHTML, nor Gecko) LibWeb")
+{
+}
+
+void ResourceLoader::load_sync(const URL& url, Function<void(ReadonlyBytes, const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers)> success_callback, Function<void(const String&)> error_callback)
+{
+ Core::EventLoop loop;
+
+ load(
+ url,
+ [&](auto data, auto& response_headers) {
+ success_callback(data, response_headers);
+ loop.quit(0);
+ },
+ [&](auto& string) {
+ if (error_callback)
+ error_callback(string);
+ loop.quit(0);
+ });
+
+ loop.exec();
+}
+
+static HashMap<LoadRequest, NonnullRefPtr<Resource>> s_resource_cache;
+
+RefPtr<Resource> ResourceLoader::load_resource(Resource::Type type, const LoadRequest& request)
+{
+ if (!request.is_valid())
+ return nullptr;
+
+ auto it = s_resource_cache.find(request);
+ if (it != s_resource_cache.end()) {
+ if (it->value->type() != type) {
+ dbg() << "FIXME: Not using cached resource for " << request.url() << " since there's a type mismatch.";
+ } else {
+#ifdef CACHE_DEBUG
+ dbg() << "Reusing cached resource for: " << request.url();
+#endif
+ return it->value;
+ }
+ }
+
+ auto resource = Resource::create({}, type, request);
+
+ s_resource_cache.set(request, resource);
+
+ load(
+ request,
+ [=](auto data, auto& headers) {
+ const_cast<Resource&>(*resource).did_load({}, data, headers);
+ },
+ [=](auto& error) {
+ const_cast<Resource&>(*resource).did_fail({}, error);
+ });
+
+ return resource;
+}
+
+void ResourceLoader::load(const LoadRequest& request, Function<void(ReadonlyBytes, const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers)> success_callback, Function<void(const String&)> error_callback)
+{
+ auto& url = request.url();
+
+ if (is_port_blocked(url.port())) {
+ dbg() << "ResourceLoader::load: Error: blocked port " << url.port() << " for URL: " << url;
+ return;
+ }
+
+ if (ContentFilter::the().is_filtered(url)) {
+ dbgln("\033[32;1mResourceLoader::load: URL was filtered! {}\033[0m", url);
+ error_callback("URL was filtered");
+ return;
+ }
+
+ if (url.protocol() == "about") {
+ dbg() << "Loading about: URL " << url;
+ deferred_invoke([success_callback = move(success_callback)](auto&) {
+ success_callback(String::empty().to_byte_buffer(), {});
+ });
+ return;
+ }
+
+ if (url.protocol() == "data") {
+ dbg() << "ResourceLoader loading a data URL with mime-type: '" << url.data_mime_type() << "', base64=" << url.data_payload_is_base64() << ", payload='" << url.data_payload() << "'";
+
+ ByteBuffer data;
+ if (url.data_payload_is_base64())
+ data = decode_base64(url.data_payload());
+ else
+ data = url.data_payload().to_byte_buffer();
+
+ deferred_invoke([data = move(data), success_callback = move(success_callback)](auto&) {
+ success_callback(data, {});
+ });
+ return;
+ }
+
+ if (url.protocol() == "file") {
+ auto f = Core::File::construct();
+ f->set_filename(url.path());
+ if (!f->open(Core::IODevice::OpenMode::ReadOnly)) {
+ dbg() << "ResourceLoader::load: Error: " << f->error_string();
+ if (error_callback)
+ error_callback(f->error_string());
+ return;
+ }
+
+ auto data = f->read_all();
+ deferred_invoke([data = move(data), success_callback = move(success_callback)](auto&) {
+ success_callback(data, {});
+ });
+ return;
+ }
+
+ if (url.protocol() == "http" || url.protocol() == "https" || url.protocol() == "gemini") {
+ HashMap<String, String> headers;
+ headers.set("User-Agent", m_user_agent);
+ headers.set("Accept-Encoding", "gzip");
+
+ for (auto& it : request.headers()) {
+ headers.set(it.key, it.value);
+ }
+
+ auto download = protocol_client().start_download(request.method(), url.to_string(), headers, request.body());
+ if (!download) {
+ if (error_callback)
+ error_callback("Failed to initiate load");
+ return;
+ }
+ download->on_buffered_download_finish = [this, success_callback = move(success_callback), error_callback = move(error_callback), download](bool success, auto, auto& response_headers, auto status_code, ReadonlyBytes payload) {
+ if (status_code.has_value() && status_code.value() >= 400 && status_code.value() <= 499) {
+ if (error_callback)
+ error_callback(String::formatted("HTTP error ({})", status_code.value()));
+ return;
+ }
+ --m_pending_loads;
+ if (on_load_counter_change)
+ on_load_counter_change();
+ if (!success) {
+ if (error_callback)
+ error_callback("HTTP load failed");
+ return;
+ }
+ deferred_invoke([download](auto&) {
+ // Clear circular reference of `download` captured by copy
+ const_cast<Protocol::Download&>(*download).on_buffered_download_finish = nullptr;
+ });
+ success_callback(payload, response_headers);
+ };
+ download->set_should_buffer_all_input(true);
+ download->on_certificate_requested = []() -> Protocol::Download::CertificateAndKey {
+ return {};
+ };
+ ++m_pending_loads;
+ if (on_load_counter_change)
+ on_load_counter_change();
+ return;
+ }
+
+ if (error_callback)
+ error_callback(String::formatted("Protocol not implemented: {}", url.protocol()));
+}
+
+void ResourceLoader::load(const URL& url, Function<void(ReadonlyBytes, const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers)> success_callback, Function<void(const String&)> error_callback)
+{
+ LoadRequest request;
+ request.set_url(url);
+ load(request, move(success_callback), move(error_callback));
+}
+
+bool ResourceLoader::is_port_blocked(int port)
+{
+ int ports[] { 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42,
+ 43, 53, 77, 79, 87, 95, 101, 102, 103, 104, 109, 110, 111, 113,
+ 115, 117, 119, 123, 135, 139, 143, 179, 389, 465, 512, 513, 514,
+ 515, 526, 530, 531, 532, 540, 556, 563, 587, 601, 636, 993, 995,
+ 2049, 3659, 4045, 6000, 6379, 6665, 6666, 6667, 6668, 6669, 9000 };
+ for (auto blocked_port : ports)
+ if (port == blocked_port)
+ return true;
+ return false;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Loader/ResourceLoader.h b/Userland/Libraries/LibWeb/Loader/ResourceLoader.h
new file mode 100644
index 0000000000..5fe23adc8a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Loader/ResourceLoader.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/URL.h>
+#include <LibCore/Object.h>
+#include <LibWeb/Loader/Resource.h>
+
+namespace Protocol {
+class Client;
+}
+
+namespace Web {
+
+class ResourceLoader : public Core::Object {
+ C_OBJECT(ResourceLoader)
+public:
+ static ResourceLoader& the();
+
+ RefPtr<Resource> load_resource(Resource::Type, const LoadRequest&);
+
+ void load(const LoadRequest&, Function<void(ReadonlyBytes, const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers)> success_callback, Function<void(const String&)> error_callback = nullptr);
+ void load(const URL&, Function<void(ReadonlyBytes, const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers)> success_callback, Function<void(const String&)> error_callback = nullptr);
+ void load_sync(const URL&, Function<void(ReadonlyBytes, const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers)> success_callback, Function<void(const String&)> error_callback = nullptr);
+
+ Function<void()> on_load_counter_change;
+
+ int pending_loads() const { return m_pending_loads; }
+
+ Protocol::Client& protocol_client() { return *m_protocol_client; }
+
+ const String& user_agent() const { return m_user_agent; }
+
+private:
+ ResourceLoader();
+ static bool is_port_blocked(int port);
+
+ int m_pending_loads { 0 };
+
+ RefPtr<Protocol::Client> m_protocol_client;
+ String m_user_agent;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Namespace.cpp b/Userland/Libraries/LibWeb/Namespace.cpp
new file mode 100644
index 0000000000..b0a1292804
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Namespace.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/Namespace.h>
+
+namespace Web::Namespace {
+
+#define __ENUMERATE_NAMESPACE(name, namespace_) FlyString name;
+ENUMERATE_NAMESPACES
+#undef __ENUMERATE_NAMESPACE
+
+[[gnu::constructor]] static void initialize()
+{
+ static bool s_initialized = false;
+ if (s_initialized)
+ return;
+
+#define __ENUMERATE_NAMESPACE(name, namespace_) \
+ name = namespace_;
+ ENUMERATE_NAMESPACES
+#undef __ENUMERATE_NAMESPACE
+
+ s_initialized = true;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Namespace.h b/Userland/Libraries/LibWeb/Namespace.h
new file mode 100644
index 0000000000..e7fe6ff0f7
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Namespace.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+
+namespace Web::Namespace {
+
+#define ENUMERATE_NAMESPACES \
+ __ENUMERATE_NAMESPACE(HTML, "http://www.w3.org/1999/xhtml") \
+ __ENUMERATE_NAMESPACE(MathML, "http://www.w3.org/1998/Math/MathML") \
+ __ENUMERATE_NAMESPACE(SVG, "http://www.w3.org/2000/svg") \
+ __ENUMERATE_NAMESPACE(XLink, "http://www.w3.org/1999/xlink") \
+ __ENUMERATE_NAMESPACE(XML, "http://www.w3.org/XML/1998/namespace") \
+ __ENUMERATE_NAMESPACE(XMLNS, "http://www.w3.org/2000/xmlns/")
+
+#define __ENUMERATE_NAMESPACE(name, namespace_) extern FlyString name;
+ENUMERATE_NAMESPACES
+#undef __ENUMERATE_NAMESPACE
+
+}
diff --git a/Userland/Libraries/LibWeb/Origin.h b/Userland/Libraries/LibWeb/Origin.h
new file mode 100644
index 0000000000..5a4b061b33
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Origin.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+
+namespace Web {
+
+class Origin {
+public:
+ Origin() { }
+ Origin(const String& protocol, const String& host, u16 port)
+ : m_protocol(protocol)
+ , m_host(host)
+ , m_port(port)
+ {
+ }
+
+ bool is_null() const { return m_protocol.is_null() && m_host.is_null() && !m_port; }
+
+ const String& protocol() const { return m_protocol; }
+ const String& host() const { return m_host; }
+ u16 port() const { return m_port; }
+
+ bool is_same(const Origin& other) const
+ {
+ return protocol() == other.protocol()
+ && host() == other.host()
+ && port() == other.port();
+ }
+
+private:
+ String m_protocol;
+ String m_host;
+ u16 m_port { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/OutOfProcessWebView.cpp b/Userland/Libraries/LibWeb/OutOfProcessWebView.cpp
new file mode 100644
index 0000000000..d30025d678
--- /dev/null
+++ b/Userland/Libraries/LibWeb/OutOfProcessWebView.cpp
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "OutOfProcessWebView.h"
+#include "WebContentClient.h"
+#include <AK/SharedBuffer.h>
+#include <LibGUI/MessageBox.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/SystemTheme.h>
+
+REGISTER_WIDGET(Web, OutOfProcessWebView)
+
+namespace Web {
+
+OutOfProcessWebView::OutOfProcessWebView()
+{
+ set_should_hide_unnecessary_scrollbars(true);
+ set_focus_policy(GUI::FocusPolicy::StrongFocus);
+ m_client = WebContentClient::construct(*this);
+ client().post_message(Messages::WebContentServer::UpdateSystemTheme(Gfx::current_system_theme_buffer_id()));
+}
+
+OutOfProcessWebView::~OutOfProcessWebView()
+{
+}
+
+void OutOfProcessWebView::load(const URL& url)
+{
+ m_url = url;
+ client().post_message(Messages::WebContentServer::LoadURL(url));
+}
+
+void OutOfProcessWebView::load_html(const StringView& html, const URL& url)
+{
+ m_url = url;
+ client().post_message(Messages::WebContentServer::LoadHTML(html, url));
+}
+
+void OutOfProcessWebView::load_empty_document()
+{
+ m_url = {};
+ client().post_message(Messages::WebContentServer::LoadHTML("", {}));
+}
+
+void OutOfProcessWebView::paint_event(GUI::PaintEvent& event)
+{
+ GUI::ScrollableWidget::paint_event(event);
+
+ // If the available size is empty, we don't have a front or back bitmap to draw.
+ if (available_size().is_empty())
+ return;
+
+ GUI::Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ if (!m_has_usable_bitmap) {
+ painter.fill_rect(frame_inner_rect(), palette().base());
+ return;
+ }
+
+ painter.add_clip_rect(frame_inner_rect());
+ painter.translate(frame_thickness(), frame_thickness());
+
+ ASSERT(m_front_bitmap);
+ painter.blit({ 0, 0 }, *m_front_bitmap, m_front_bitmap->rect());
+}
+
+void OutOfProcessWebView::resize_event(GUI::ResizeEvent& event)
+{
+ GUI::ScrollableWidget::resize_event(event);
+
+ client().post_message(Messages::WebContentServer::SetViewportRect(Gfx::IntRect({ horizontal_scrollbar().value(), vertical_scrollbar().value() }, available_size())));
+
+ m_front_bitmap = nullptr;
+ m_back_bitmap = nullptr;
+ m_has_usable_bitmap = false;
+
+ if (available_size().is_empty())
+ return;
+
+ if (auto new_bitmap = Gfx::Bitmap::create_shareable(Gfx::BitmapFormat::RGB32, available_size())) {
+ new_bitmap->shared_buffer()->share_with(client().server_pid());
+ m_front_bitmap = move(new_bitmap);
+ }
+
+ if (auto new_bitmap = Gfx::Bitmap::create_shareable(Gfx::BitmapFormat::RGB32, available_size())) {
+ new_bitmap->shared_buffer()->share_with(client().server_pid());
+ m_back_bitmap = move(new_bitmap);
+ }
+
+ request_repaint();
+}
+
+void OutOfProcessWebView::keydown_event(GUI::KeyEvent& event)
+{
+ client().post_message(Messages::WebContentServer::KeyDown(event.key(), event.modifiers(), event.code_point()));
+}
+
+void OutOfProcessWebView::mousedown_event(GUI::MouseEvent& event)
+{
+ client().post_message(Messages::WebContentServer::MouseDown(to_content_position(event.position()), event.button(), event.buttons(), event.modifiers()));
+}
+
+void OutOfProcessWebView::mouseup_event(GUI::MouseEvent& event)
+{
+ client().post_message(Messages::WebContentServer::MouseUp(to_content_position(event.position()), event.button(), event.buttons(), event.modifiers()));
+}
+
+void OutOfProcessWebView::mousemove_event(GUI::MouseEvent& event)
+{
+ client().post_message(Messages::WebContentServer::MouseMove(to_content_position(event.position()), event.button(), event.buttons(), event.modifiers()));
+}
+
+void OutOfProcessWebView::theme_change_event(GUI::ThemeChangeEvent& event)
+{
+ GUI::ScrollableWidget::theme_change_event(event);
+ client().post_message(Messages::WebContentServer::UpdateSystemTheme(Gfx::current_system_theme_buffer_id()));
+ request_repaint();
+}
+
+void OutOfProcessWebView::notify_server_did_paint(Badge<WebContentClient>, i32 shbuf_id)
+{
+ if (m_back_bitmap->shbuf_id() == shbuf_id) {
+ m_has_usable_bitmap = true;
+ swap(m_back_bitmap, m_front_bitmap);
+ update();
+ }
+}
+
+void OutOfProcessWebView::notify_server_did_invalidate_content_rect(Badge<WebContentClient>, [[maybe_unused]] const Gfx::IntRect& content_rect)
+{
+ request_repaint();
+}
+
+void OutOfProcessWebView::notify_server_did_change_selection(Badge<WebContentClient>)
+{
+ request_repaint();
+}
+
+void OutOfProcessWebView::notify_server_did_layout(Badge<WebContentClient>, const Gfx::IntSize& content_size)
+{
+ set_content_size(content_size);
+}
+
+void OutOfProcessWebView::notify_server_did_change_title(Badge<WebContentClient>, const String& title)
+{
+ if (on_title_change)
+ on_title_change(title);
+}
+
+void OutOfProcessWebView::notify_server_did_request_scroll_into_view(Badge<WebContentClient>, const Gfx::IntRect& rect)
+{
+ scroll_into_view(rect, true, true);
+}
+
+void OutOfProcessWebView::notify_server_did_hover_link(Badge<WebContentClient>, const URL& url)
+{
+ set_override_cursor(Gfx::StandardCursor::Hand);
+ if (on_link_hover)
+ on_link_hover(url);
+}
+
+void OutOfProcessWebView::notify_server_did_unhover_link(Badge<WebContentClient>)
+{
+ set_override_cursor(Gfx::StandardCursor::None);
+ if (on_link_hover)
+ on_link_hover({});
+}
+
+void OutOfProcessWebView::notify_server_did_click_link(Badge<WebContentClient>, const URL& url, const String& target, unsigned int modifiers)
+{
+ if (on_link_click)
+ on_link_click(url, target, modifiers);
+}
+
+void OutOfProcessWebView::notify_server_did_middle_click_link(Badge<WebContentClient>, const URL& url, const String& target, unsigned int modifiers)
+{
+ if (on_link_middle_click)
+ on_link_middle_click(url, target, modifiers);
+}
+
+void OutOfProcessWebView::notify_server_did_start_loading(Badge<WebContentClient>, const URL& url)
+{
+ if (on_load_start)
+ on_load_start(url);
+}
+
+void OutOfProcessWebView::notify_server_did_finish_loading(Badge<WebContentClient>, const URL& url)
+{
+ if (on_load_finish)
+ on_load_finish(url);
+}
+
+void OutOfProcessWebView::notify_server_did_request_context_menu(Badge<WebContentClient>, const Gfx::IntPoint& content_position)
+{
+ if (on_context_menu_request)
+ on_context_menu_request(screen_relative_rect().location().translated(to_widget_position(content_position)));
+}
+
+void OutOfProcessWebView::notify_server_did_request_link_context_menu(Badge<WebContentClient>, const Gfx::IntPoint& content_position, const URL& url, const String&, unsigned)
+{
+ if (on_link_context_menu_request)
+ on_link_context_menu_request(url, screen_relative_rect().location().translated(to_widget_position(content_position)));
+}
+
+void OutOfProcessWebView::notify_server_did_request_alert(Badge<WebContentClient>, const String& message)
+{
+ GUI::MessageBox::show(window(), message, "Alert", GUI::MessageBox::Type::Information);
+}
+
+void OutOfProcessWebView::did_scroll()
+{
+ client().post_message(Messages::WebContentServer::SetViewportRect(visible_content_rect()));
+ request_repaint();
+}
+
+void OutOfProcessWebView::request_repaint()
+{
+ // If this widget was instantiated but not yet added to a window,
+ // it won't have a back bitmap yet, so we can just skip repaint requests.
+ if (!m_back_bitmap)
+ return;
+ client().post_message(Messages::WebContentServer::Paint(m_back_bitmap->rect().translated(horizontal_scrollbar().value(), vertical_scrollbar().value()), m_back_bitmap->shbuf_id()));
+}
+
+WebContentClient& OutOfProcessWebView::client()
+{
+ return *m_client;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/OutOfProcessWebView.h b/Userland/Libraries/LibWeb/OutOfProcessWebView.h
new file mode 100644
index 0000000000..a742a01af8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/OutOfProcessWebView.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/URL.h>
+#include <LibGUI/ScrollableWidget.h>
+#include <LibGUI/Widget.h>
+#include <LibWeb/WebViewHooks.h>
+
+namespace Web {
+
+class WebContentClient;
+
+class OutOfProcessWebView final
+ : public GUI::ScrollableWidget
+ , public Web::WebViewHooks {
+ C_OBJECT(OutOfProcessWebView);
+
+public:
+ virtual ~OutOfProcessWebView() override;
+
+ URL url() const { return m_url; }
+ void load(const URL&);
+
+ void load_html(const StringView&, const URL&);
+ void load_empty_document();
+
+ void notify_server_did_layout(Badge<WebContentClient>, const Gfx::IntSize& content_size);
+ void notify_server_did_paint(Badge<WebContentClient>, i32 shbuf_id);
+ void notify_server_did_invalidate_content_rect(Badge<WebContentClient>, const Gfx::IntRect&);
+ void notify_server_did_change_selection(Badge<WebContentClient>);
+ void notify_server_did_change_title(Badge<WebContentClient>, const String&);
+ void notify_server_did_request_scroll_into_view(Badge<WebContentClient>, const Gfx::IntRect&);
+ void notify_server_did_hover_link(Badge<WebContentClient>, const URL&);
+ void notify_server_did_unhover_link(Badge<WebContentClient>);
+ void notify_server_did_click_link(Badge<WebContentClient>, const URL&, const String& target, unsigned modifiers);
+ void notify_server_did_middle_click_link(Badge<WebContentClient>, const URL&, const String& target, unsigned modifiers);
+ void notify_server_did_start_loading(Badge<WebContentClient>, const URL&);
+ void notify_server_did_finish_loading(Badge<WebContentClient>, const URL&);
+ void notify_server_did_request_context_menu(Badge<WebContentClient>, const Gfx::IntPoint&);
+ void notify_server_did_request_link_context_menu(Badge<WebContentClient>, const Gfx::IntPoint&, const URL&, const String& target, unsigned modifiers);
+ void notify_server_did_request_alert(Badge<WebContentClient>, const String& message);
+
+private:
+ OutOfProcessWebView();
+
+ // ^Widget
+ virtual void paint_event(GUI::PaintEvent&) override;
+ virtual void resize_event(GUI::ResizeEvent&) override;
+ virtual void mousedown_event(GUI::MouseEvent&) override;
+ virtual void mouseup_event(GUI::MouseEvent&) override;
+ virtual void mousemove_event(GUI::MouseEvent&) override;
+ virtual void keydown_event(GUI::KeyEvent&) override;
+ virtual void theme_change_event(GUI::ThemeChangeEvent&) override;
+
+ // ^ScrollableWidget
+ virtual void did_scroll() override;
+
+ void request_repaint();
+
+ WebContentClient& client();
+
+ URL m_url;
+
+ RefPtr<WebContentClient> m_client;
+ RefPtr<Gfx::Bitmap> m_front_bitmap;
+ RefPtr<Gfx::Bitmap> m_back_bitmap;
+
+ bool m_has_usable_bitmap { false };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Page/EditEventHandler.cpp b/Userland/Libraries/LibWeb/Page/EditEventHandler.cpp
new file mode 100644
index 0000000000..5d27efe2cf
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Page/EditEventHandler.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibWeb/DOM/Position.h>
+#include <LibWeb/DOM/Range.h>
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/Layout/LayoutPosition.h>
+#include <LibWeb/Page/Frame.h>
+
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/Dump.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+
+#include "EditEventHandler.h"
+
+namespace Web {
+
+// This method is quite convoluted but this is necessary to make editing feel intuitive.
+void EditEventHandler::handle_delete(DOM::Range& range)
+{
+ auto* start = downcast<DOM::Text>(range.start_container());
+ auto* end = downcast<DOM::Text>(range.end_container());
+
+ if (start == end) {
+ StringBuilder builder;
+ builder.append(start->data().substring_view(0, range.start_offset()));
+ builder.append(end->data().substring_view(range.end_offset()));
+
+ start->set_data(builder.to_string());
+ } else {
+ // Remove all the nodes that are fully enclosed in the range.
+ HashTable<DOM::Node*> queued_for_deletion;
+ for (auto* node = start->next_in_pre_order(); node; node = node->next_in_pre_order()) {
+ if (node == end)
+ break;
+
+ queued_for_deletion.set(node);
+ }
+ for (auto* parent = start->parent(); parent; parent = parent->parent())
+ queued_for_deletion.remove(parent);
+ for (auto* parent = end->parent(); parent; parent = parent->parent())
+ queued_for_deletion.remove(parent);
+ for (auto* node : queued_for_deletion)
+ node->parent()->remove_child(*node);
+
+ // Join the parent nodes of start and end.
+ DOM::Node *insert_after = start, *remove_from = end, *parent_of_end = end->parent();
+ while (remove_from) {
+ auto* next_sibling = remove_from->next_sibling();
+
+ remove_from->parent()->remove_child(*remove_from);
+ insert_after->parent()->insert_before(*remove_from, *insert_after);
+
+ insert_after = remove_from;
+ remove_from = next_sibling;
+ }
+ if (!parent_of_end->has_children()) {
+ if (parent_of_end->parent())
+ parent_of_end->parent()->remove_child(*parent_of_end);
+ }
+
+ // Join the start and end nodes.
+ StringBuilder builder;
+ builder.append(start->data().substring_view(0, range.start_offset()));
+ builder.append(end->data().substring_view(range.end_offset()));
+
+ start->set_data(builder.to_string());
+ start->parent()->remove_child(*end);
+ }
+
+ // FIXME: When nodes are removed from the DOM, the associated layout nodes become stale and still
+ // remain in the layout tree. This has to be fixed, this just causes everything to be recomputed
+ // which really hurts performance.
+ m_frame.document()->force_layout();
+
+ m_frame.did_edit({});
+}
+
+void EditEventHandler::handle_insert(DOM::Position position, u32 code_point)
+{
+ if (is<DOM::Text>(*position.node())) {
+ auto& node = downcast<DOM::Text>(*position.node());
+
+ StringBuilder builder;
+ builder.append(node.data().substring_view(0, position.offset()));
+ builder.append_code_point(code_point);
+ builder.append(node.data().substring_view(position.offset()));
+ node.set_data(builder.to_string());
+
+ node.invalidate_style();
+ }
+
+ // FIXME: When nodes are removed from the DOM, the associated layout nodes become stale and still
+ // remain in the layout tree. This has to be fixed, this just causes everything to be recomputed
+ // which really hurts performance.
+ m_frame.document()->force_layout();
+
+ m_frame.did_edit({});
+}
+}
diff --git a/Userland/Libraries/LibWeb/Page/EditEventHandler.h b/Userland/Libraries/LibWeb/Page/EditEventHandler.h
new file mode 100644
index 0000000000..67edb9eff0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Page/EditEventHandler.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/Forward.h>
+
+namespace Web {
+
+class EditEventHandler {
+public:
+ explicit EditEventHandler(Frame& frame)
+ : m_frame(frame)
+ {
+ }
+
+ virtual ~EditEventHandler() = default;
+
+ virtual void handle_delete(DOM::Range&);
+ virtual void handle_insert(DOM::Position, u32 code_point);
+
+private:
+ Frame& m_frame;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Page/EventHandler.cpp b/Userland/Libraries/LibWeb/Page/EventHandler.cpp
new file mode 100644
index 0000000000..7a69085aa4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Page/EventHandler.cpp
@@ -0,0 +1,414 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGUI/Event.h>
+#include <LibGUI/Window.h>
+#include <LibJS/Runtime/Value.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Range.h>
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/HTML/HTMLAnchorElement.h>
+#include <LibWeb/HTML/HTMLIFrameElement.h>
+#include <LibWeb/HTML/HTMLImageElement.h>
+#include <LibWeb/InProcessWebView.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+#include <LibWeb/Page/EventHandler.h>
+#include <LibWeb/Page/Frame.h>
+#include <LibWeb/UIEvents/EventNames.h>
+#include <LibWeb/UIEvents/MouseEvent.h>
+
+namespace Web {
+
+static Gfx::IntPoint compute_mouse_event_offset(const Gfx::IntPoint& position, const Layout::Node& layout_node)
+{
+ auto top_left_of_layout_node = layout_node.box_type_agnostic_position();
+ return {
+ position.x() - static_cast<int>(top_left_of_layout_node.x()),
+ position.y() - static_cast<int>(top_left_of_layout_node.y())
+ };
+}
+
+EventHandler::EventHandler(Badge<Frame>, Frame& frame)
+ : m_frame(frame)
+ , m_edit_event_handler(make<EditEventHandler>(frame))
+{
+}
+
+EventHandler::~EventHandler()
+{
+}
+
+const Layout::InitialContainingBlockBox* EventHandler::layout_root() const
+{
+ if (!m_frame.document())
+ return nullptr;
+ return m_frame.document()->layout_node();
+}
+
+Layout::InitialContainingBlockBox* EventHandler::layout_root()
+{
+ if (!m_frame.document())
+ return nullptr;
+ return m_frame.document()->layout_node();
+}
+
+bool EventHandler::handle_mouseup(const Gfx::IntPoint& position, unsigned button, unsigned modifiers)
+{
+ if (!layout_root())
+ return false;
+
+ if (m_mouse_event_tracking_layout_node) {
+ m_mouse_event_tracking_layout_node->handle_mouseup({}, position, button, modifiers);
+ return true;
+ }
+
+ bool handled_event = false;
+
+ auto result = layout_root()->hit_test(position, Layout::HitTestType::Exact);
+
+ if (result.layout_node && result.layout_node->wants_mouse_events()) {
+ result.layout_node->handle_mouseup({}, position, button, modifiers);
+
+ // Things may have changed as a consequence of Layout::Node::handle_mouseup(). Hit test again.
+ if (!layout_root())
+ return true;
+ result = layout_root()->hit_test(position, Layout::HitTestType::Exact);
+ }
+
+ if (result.layout_node && result.layout_node->dom_node()) {
+ RefPtr<DOM::Node> node = result.layout_node->dom_node();
+ if (is<HTML::HTMLIFrameElement>(*node)) {
+ if (auto* subframe = downcast<HTML::HTMLIFrameElement>(*node).content_frame())
+ return subframe->event_handler().handle_mouseup(position.translated(compute_mouse_event_offset({}, *result.layout_node)), button, modifiers);
+ return false;
+ }
+ auto offset = compute_mouse_event_offset(position, *result.layout_node);
+ node->dispatch_event(UIEvents::MouseEvent::create(UIEvents::EventNames::mouseup, offset.x(), offset.y()));
+ handled_event = true;
+ }
+
+ if (button == GUI::MouseButton::Left)
+ m_in_mouse_selection = false;
+ return handled_event;
+}
+
+bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned button, unsigned modifiers)
+{
+ if (!layout_root())
+ return false;
+
+ if (m_mouse_event_tracking_layout_node) {
+ m_mouse_event_tracking_layout_node->handle_mousedown({}, position, button, modifiers);
+ return true;
+ }
+
+ NonnullRefPtr document = *m_frame.document();
+ RefPtr<DOM::Node> node;
+
+ {
+ auto result = layout_root()->hit_test(position, Layout::HitTestType::Exact);
+ if (!result.layout_node)
+ return false;
+
+ node = result.layout_node->dom_node();
+ document->set_hovered_node(node);
+
+ if (result.layout_node->wants_mouse_events()) {
+ result.layout_node->handle_mousedown({}, position, button, modifiers);
+ return true;
+ }
+
+ if (!node)
+ return false;
+
+ if (is<HTML::HTMLIFrameElement>(*node)) {
+ if (auto* subframe = downcast<HTML::HTMLIFrameElement>(*node).content_frame())
+ return subframe->event_handler().handle_mousedown(position.translated(compute_mouse_event_offset({}, *result.layout_node)), button, modifiers);
+ return false;
+ }
+
+ if (auto* page = m_frame.page())
+ page->set_focused_frame({}, m_frame);
+
+ auto offset = compute_mouse_event_offset(position, *result.layout_node);
+ node->dispatch_event(UIEvents::MouseEvent::create(UIEvents::EventNames::mousedown, offset.x(), offset.y()));
+ }
+
+ // NOTE: Dispatching an event may have disturbed the world.
+ if (!layout_root() || layout_root() != node->document().layout_node())
+ return true;
+
+ if (button == GUI::MouseButton::Right && is<HTML::HTMLImageElement>(*node)) {
+ auto& image_element = downcast<HTML::HTMLImageElement>(*node);
+ auto image_url = image_element.document().complete_url(image_element.src());
+ if (auto* page = m_frame.page())
+ page->client().page_did_request_image_context_menu(m_frame.to_main_frame_position(position), image_url, "", modifiers, image_element.bitmap());
+ return true;
+ }
+
+ if (RefPtr<HTML::HTMLAnchorElement> link = node->enclosing_link_element()) {
+ auto href = link->href();
+ auto url = document->complete_url(href);
+ dbgln("Web::EventHandler: Clicking on a link to {}", url);
+ if (button == GUI::MouseButton::Left) {
+ auto href = link->href();
+ auto url = document->complete_url(href);
+ if (href.starts_with("javascript:")) {
+ document->run_javascript(href.substring_view(11, href.length() - 11));
+ } else if (href.starts_with('#')) {
+ auto anchor = href.substring_view(1, href.length() - 1);
+ m_frame.scroll_to_anchor(anchor);
+ } else {
+ if (m_frame.is_main_frame()) {
+ if (auto* page = m_frame.page())
+ page->client().page_did_click_link(url, link->target(), modifiers);
+ } else {
+ // FIXME: Handle different targets!
+ m_frame.loader().load(url, FrameLoader::Type::Navigation);
+ }
+ }
+ } else if (button == GUI::MouseButton::Right) {
+ if (auto* page = m_frame.page())
+ page->client().page_did_request_link_context_menu(m_frame.to_main_frame_position(position), url, link->target(), modifiers);
+ } else if (button == GUI::MouseButton::Middle) {
+ if (auto* page = m_frame.page())
+ page->client().page_did_middle_click_link(url, link->target(), modifiers);
+ }
+ } else {
+ if (button == GUI::MouseButton::Left) {
+ auto result = layout_root()->hit_test(position, Layout::HitTestType::TextCursor);
+ if (result.layout_node && result.layout_node->dom_node()) {
+ m_frame.set_cursor_position(DOM::Position(*node, result.index_in_node));
+ layout_root()->set_selection({ { result.layout_node, result.index_in_node }, {} });
+ m_in_mouse_selection = true;
+ }
+ } else if (button == GUI::MouseButton::Right) {
+ if (auto* page = m_frame.page())
+ page->client().page_did_request_context_menu(m_frame.to_main_frame_position(position));
+ }
+ }
+ return true;
+}
+
+bool EventHandler::handle_mousemove(const Gfx::IntPoint& position, unsigned buttons, unsigned modifiers)
+{
+ if (!layout_root())
+ return false;
+
+ if (m_mouse_event_tracking_layout_node) {
+ m_mouse_event_tracking_layout_node->handle_mousemove({}, position, buttons, modifiers);
+ return true;
+ }
+
+ auto& document = *m_frame.document();
+
+ bool hovered_node_changed = false;
+ bool is_hovering_link = false;
+ bool is_hovering_text = false;
+ auto result = layout_root()->hit_test(position, Layout::HitTestType::Exact);
+ const HTML::HTMLAnchorElement* hovered_link_element = nullptr;
+ if (result.layout_node) {
+
+ if (result.layout_node->wants_mouse_events()) {
+ document.set_hovered_node(result.layout_node->dom_node());
+ result.layout_node->handle_mousemove({}, position, buttons, modifiers);
+ // FIXME: It feels a bit aggressive to always update the cursor like this.
+ if (auto* page = m_frame.page())
+ page->client().page_did_request_cursor_change(Gfx::StandardCursor::None);
+ return true;
+ }
+
+ RefPtr<DOM::Node> node = result.layout_node->dom_node();
+
+ if (node && is<HTML::HTMLIFrameElement>(*node)) {
+ if (auto* subframe = downcast<HTML::HTMLIFrameElement>(*node).content_frame())
+ return subframe->event_handler().handle_mousemove(position.translated(compute_mouse_event_offset({}, *result.layout_node)), buttons, modifiers);
+ return false;
+ }
+
+ hovered_node_changed = node != document.hovered_node();
+ document.set_hovered_node(node);
+ if (node) {
+ if (node->is_text())
+ is_hovering_text = true;
+ hovered_link_element = node->enclosing_link_element();
+ if (hovered_link_element)
+ is_hovering_link = true;
+ auto offset = compute_mouse_event_offset(position, *result.layout_node);
+ node->dispatch_event(UIEvents::MouseEvent::create(UIEvents::EventNames::mousemove, offset.x(), offset.y()));
+ // NOTE: Dispatching an event may have disturbed the world.
+ if (!layout_root() || layout_root() != node->document().layout_node())
+ return true;
+ }
+ if (m_in_mouse_selection) {
+ auto hit = layout_root()->hit_test(position, Layout::HitTestType::TextCursor);
+ if (hit.layout_node && hit.layout_node->dom_node()) {
+ layout_root()->set_selection_end({ hit.layout_node, hit.index_in_node });
+ }
+ if (auto* page = m_frame.page())
+ page->client().page_did_change_selection();
+ }
+ }
+
+ if (auto* page = m_frame.page()) {
+ if (is_hovering_link)
+ page->client().page_did_request_cursor_change(Gfx::StandardCursor::Hand);
+ else if (is_hovering_text)
+ page->client().page_did_request_cursor_change(Gfx::StandardCursor::IBeam);
+ else
+ page->client().page_did_request_cursor_change(Gfx::StandardCursor::None);
+
+ if (hovered_node_changed) {
+ RefPtr<HTML::HTMLElement> hovered_html_element = document.hovered_node() ? document.hovered_node()->enclosing_html_element() : nullptr;
+ if (hovered_html_element && !hovered_html_element->title().is_null()) {
+ page->client().page_did_enter_tooltip_area(m_frame.to_main_frame_position(position), hovered_html_element->title());
+ } else {
+ page->client().page_did_leave_tooltip_area();
+ }
+ if (is_hovering_link)
+ page->client().page_did_hover_link(document.complete_url(hovered_link_element->href()));
+ else
+ page->client().page_did_unhover_link();
+ }
+ }
+ return true;
+}
+
+bool EventHandler::focus_next_element()
+{
+ if (!m_frame.document())
+ return false;
+ auto* element = m_frame.document()->focused_element();
+ if (!element) {
+ element = m_frame.document()->first_child_of_type<DOM::Element>();
+ if (element && element->is_focusable()) {
+ m_frame.document()->set_focused_element(element);
+ return true;
+ }
+ }
+
+ for (element = element->next_element_in_pre_order(); element && !element->is_focusable(); element = element->next_element_in_pre_order())
+ ;
+
+ m_frame.document()->set_focused_element(element);
+ return element;
+}
+
+bool EventHandler::focus_previous_element()
+{
+ // FIXME: Implement Shift-Tab cycling backwards through focusable elements!
+ return false;
+}
+
+bool EventHandler::handle_keydown(KeyCode key, unsigned modifiers, u32 code_point)
+{
+ if (key == KeyCode::Key_Tab) {
+ if (modifiers & KeyModifier::Mod_Shift)
+ return focus_previous_element();
+ else
+ return focus_next_element();
+ }
+
+ if (layout_root()->selection().is_valid()) {
+ auto range = layout_root()->selection().to_dom_range()->normalized();
+ if (range->start_container()->is_editable()) {
+ m_frame.document()->layout_node()->set_selection({});
+
+ // FIXME: This doesn't work for some reason?
+ m_frame.set_cursor_position({ *range->start_container(), range->start_offset() });
+
+ if (key == KeyCode::Key_Backspace || key == KeyCode::Key_Delete) {
+
+ m_edit_event_handler->handle_delete(range);
+ return true;
+ } else {
+
+ m_edit_event_handler->handle_delete(range);
+
+ m_edit_event_handler->handle_insert(m_frame.cursor_position(), code_point);
+ m_frame.cursor_position().set_offset(m_frame.cursor_position().offset() + 1);
+ return true;
+ }
+ }
+ }
+
+ if (m_frame.cursor_position().is_valid() && m_frame.cursor_position().node()->is_editable()) {
+ if (key == KeyCode::Key_Backspace) {
+ auto position = m_frame.cursor_position();
+
+ if (position.offset() == 0)
+ TODO();
+
+ m_frame.cursor_position().set_offset(position.offset() - 1);
+ m_edit_event_handler->handle_delete(DOM::Range::create(*position.node(), position.offset() - 1, *position.node(), position.offset()));
+
+ return true;
+ } else if (key == KeyCode::Key_Delete) {
+ auto position = m_frame.cursor_position();
+
+ if (position.offset() >= downcast<DOM::Text>(position.node())->data().length())
+ TODO();
+
+ m_edit_event_handler->handle_delete(DOM::Range::create(*position.node(), position.offset(), *position.node(), position.offset() + 1));
+
+ return true;
+ } else if (key == KeyCode::Key_Right) {
+ auto position = m_frame.cursor_position();
+
+ if (position.offset() >= downcast<DOM::Text>(position.node())->data().length())
+ TODO();
+
+ m_frame.cursor_position().set_offset(position.offset() + 1);
+
+ return true;
+ } else if (key == KeyCode::Key_Left) {
+ auto position = m_frame.cursor_position();
+
+ if (position.offset() == 0)
+ TODO();
+
+ m_frame.cursor_position().set_offset(position.offset() - 1);
+
+ return true;
+ } else {
+ m_edit_event_handler->handle_insert(m_frame.cursor_position(), code_point);
+ m_frame.cursor_position().set_offset(m_frame.cursor_position().offset() + 1);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void EventHandler::set_mouse_event_tracking_layout_node(Layout::Node* layout_node)
+{
+ if (layout_node)
+ m_mouse_event_tracking_layout_node = layout_node->make_weak_ptr();
+ else
+ m_mouse_event_tracking_layout_node = nullptr;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Page/EventHandler.h b/Userland/Libraries/LibWeb/Page/EventHandler.h
new file mode 100644
index 0000000000..7ea12d6461
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Page/EventHandler.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/WeakPtr.h>
+#include <Kernel/API/KeyCode.h>
+#include <LibGUI/Forward.h>
+#include <LibGfx/Forward.h>
+#include <LibWeb/Forward.h>
+#include <LibWeb/Page/EditEventHandler.h>
+
+namespace Web {
+
+class Frame;
+
+class EventHandler {
+public:
+ explicit EventHandler(Badge<Frame>, Frame&);
+ ~EventHandler();
+
+ bool handle_mouseup(const Gfx::IntPoint&, unsigned button, unsigned modifiers);
+ bool handle_mousedown(const Gfx::IntPoint&, unsigned button, unsigned modifiers);
+ bool handle_mousemove(const Gfx::IntPoint&, unsigned buttons, unsigned modifiers);
+
+ bool handle_keydown(KeyCode, unsigned modifiers, u32 code_point);
+
+ void set_mouse_event_tracking_layout_node(Layout::Node*);
+
+ void set_edit_event_handler(NonnullOwnPtr<EditEventHandler> value) { m_edit_event_handler = move(value); }
+
+private:
+ bool focus_next_element();
+ bool focus_previous_element();
+
+ Layout::InitialContainingBlockBox* layout_root();
+ const Layout::InitialContainingBlockBox* layout_root() const;
+
+ Frame& m_frame;
+
+ bool m_in_mouse_selection { false };
+
+ WeakPtr<Layout::Node> m_mouse_event_tracking_layout_node;
+
+ NonnullOwnPtr<EditEventHandler> m_edit_event_handler;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Page/Frame.cpp b/Userland/Libraries/LibWeb/Page/Frame.cpp
new file mode 100644
index 0000000000..8866fea7ef
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Page/Frame.cpp
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/HTML/HTMLAnchorElement.h>
+#include <LibWeb/InProcessWebView.h>
+#include <LibWeb/Layout/BreakNode.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+#include <LibWeb/Layout/TextNode.h>
+#include <LibWeb/Layout/WidgetBox.h>
+#include <LibWeb/Page/Frame.h>
+
+namespace Web {
+
+Frame::Frame(DOM::Element& host_element, Frame& main_frame)
+ : m_page(*main_frame.page())
+ , m_main_frame(main_frame)
+ , m_loader(*this)
+ , m_event_handler({}, *this)
+ , m_host_element(host_element)
+{
+ setup();
+}
+
+Frame::Frame(Page& page)
+ : m_page(page)
+ , m_main_frame(*this)
+ , m_loader(*this)
+ , m_event_handler({}, *this)
+{
+ setup();
+}
+
+Frame::~Frame()
+{
+}
+
+void Frame::setup()
+{
+ m_cursor_blink_timer = Core::Timer::construct(500, [this] {
+ if (!is_focused_frame())
+ return;
+ if (m_cursor_position.node() && m_cursor_position.node()->layout_node()) {
+ m_cursor_blink_state = !m_cursor_blink_state;
+ m_cursor_position.node()->layout_node()->set_needs_display();
+ }
+ });
+}
+
+void Frame::did_edit(Badge<EditEventHandler>)
+{
+ // The user has edited the content, restart the cursor blink cycle so that
+ // the cursor doesn't disappear during rapid continuous editing.
+ m_cursor_blink_state = true;
+ m_cursor_blink_timer->restart();
+}
+
+bool Frame::is_focused_frame() const
+{
+ return m_page && &m_page->focused_frame() == this;
+}
+
+void Frame::set_document(DOM::Document* document)
+{
+ if (m_document == document)
+ return;
+
+ m_cursor_position = {};
+
+ if (m_document)
+ m_document->detach_from_frame({}, *this);
+
+ m_document = document;
+
+ if (m_document) {
+ m_document->attach_to_frame({}, *this);
+ if (m_page)
+ m_page->client().page_did_change_title(m_document->title());
+ }
+
+ if (m_page)
+ m_page->client().page_did_set_document_in_main_frame(m_document);
+}
+
+void Frame::set_size(const Gfx::IntSize& size)
+{
+ if (m_size == size)
+ return;
+ m_size = size;
+ if (m_document)
+ m_document->update_layout();
+}
+
+void Frame::set_viewport_scroll_offset(const Gfx::IntPoint& offset)
+{
+ if (m_viewport_scroll_offset == offset)
+ return;
+ m_viewport_scroll_offset = offset;
+
+ if (m_document && m_document->layout_node())
+ m_document->layout_node()->did_set_viewport_rect({}, viewport_rect());
+}
+
+void Frame::set_needs_display(const Gfx::IntRect& rect)
+{
+ if (!viewport_rect().intersects(rect))
+ return;
+
+ if (is_main_frame()) {
+ if (m_page)
+ m_page->client().page_did_invalidate(to_main_frame_rect(rect));
+ return;
+ }
+
+ if (host_element() && host_element()->layout_node())
+ host_element()->layout_node()->set_needs_display();
+}
+
+void Frame::did_scroll(Badge<InProcessWebView>)
+{
+ if (!m_document)
+ return;
+ if (!m_document->layout_node())
+ return;
+ m_document->layout_node()->for_each_in_subtree_of_type<Layout::WidgetBox>([&](auto& layout_widget) {
+ layout_widget.update_widget();
+ return IterationDecision::Continue;
+ });
+}
+
+void Frame::scroll_to_anchor(const String& fragment)
+{
+ if (!document())
+ return;
+
+ auto element = document()->get_element_by_id(fragment);
+ if (!element) {
+ auto candidates = document()->get_elements_by_name(fragment);
+ for (auto& candidate : candidates) {
+ if (is<HTML::HTMLAnchorElement>(candidate)) {
+ element = downcast<HTML::HTMLAnchorElement>(candidate);
+ break;
+ }
+ }
+ }
+
+ // FIXME: This is overly aggressive and should be something more like a "update_layout_if_needed()"
+ document()->force_layout();
+
+ if (!element || !element->layout_node())
+ return;
+
+ auto& layout_node = *element->layout_node();
+
+ Gfx::FloatRect float_rect { layout_node.box_type_agnostic_position(), { (float)viewport_rect().width(), (float)viewport_rect().height() } };
+ if (is<Layout::Box>(layout_node)) {
+ auto& layout_box = downcast<Layout::Box>(layout_node);
+ auto padding_box = layout_box.box_model().padding_box();
+ float_rect.move_by(-padding_box.left, -padding_box.top);
+ }
+
+ if (m_page)
+ m_page->client().page_did_request_scroll_into_view(enclosing_int_rect(float_rect));
+}
+
+Gfx::IntRect Frame::to_main_frame_rect(const Gfx::IntRect& a_rect)
+{
+ auto rect = a_rect;
+ rect.set_location(to_main_frame_position(a_rect.location()));
+ return rect;
+}
+
+Gfx::IntPoint Frame::to_main_frame_position(const Gfx::IntPoint& a_position)
+{
+ auto position = a_position;
+ for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
+ if (ancestor->is_main_frame())
+ break;
+ if (!ancestor->host_element())
+ return {};
+ if (!ancestor->host_element()->layout_node())
+ return {};
+ position.move_by(ancestor->host_element()->layout_node()->box_type_agnostic_position().to_type<int>());
+ }
+ return position;
+}
+
+void Frame::set_cursor_position(const DOM::Position& position)
+{
+ if (m_cursor_position == position)
+ return;
+
+ if (m_cursor_position.node() && m_cursor_position.node()->layout_node())
+ m_cursor_position.node()->layout_node()->set_needs_display();
+
+ m_cursor_position = position;
+
+ if (m_cursor_position.node() && m_cursor_position.node()->layout_node())
+ m_cursor_position.node()->layout_node()->set_needs_display();
+
+ dbgln("Cursor position: {}", m_cursor_position);
+}
+
+String Frame::selected_text() const
+{
+ StringBuilder builder;
+ if (!m_document)
+ return {};
+ auto* layout_root = m_document->layout_node();
+ if (!layout_root)
+ return {};
+ if (!layout_root->selection().is_valid())
+ return {};
+
+ auto selection = layout_root->selection().normalized();
+
+ if (selection.start().layout_node == selection.end().layout_node) {
+ if (!is<Layout::TextNode>(*selection.start().layout_node))
+ return "";
+ return downcast<Layout::TextNode>(*selection.start().layout_node).text_for_rendering().substring(selection.start().index_in_node, selection.end().index_in_node - selection.start().index_in_node);
+ }
+
+ // Start node
+ auto layout_node = selection.start().layout_node;
+ if (is<Layout::TextNode>(*layout_node)) {
+ auto& text = downcast<Layout::TextNode>(*layout_node).text_for_rendering();
+ builder.append(text.substring(selection.start().index_in_node, text.length() - selection.start().index_in_node));
+ }
+
+ // Middle nodes
+ layout_node = layout_node->next_in_pre_order();
+ while (layout_node && layout_node != selection.end().layout_node) {
+ if (is<Layout::TextNode>(*layout_node))
+ builder.append(downcast<Layout::TextNode>(*layout_node).text_for_rendering());
+ else if (is<Layout::BreakNode>(*layout_node) || is<Layout::BlockBox>(*layout_node))
+ builder.append('\n');
+
+ layout_node = layout_node->next_in_pre_order();
+ }
+
+ // End node
+ ASSERT(layout_node == selection.end().layout_node);
+ if (is<Layout::TextNode>(*layout_node)) {
+ auto& text = downcast<Layout::TextNode>(*layout_node).text_for_rendering();
+ builder.append(text.substring(0, selection.end().index_in_node));
+ }
+
+ return builder.to_string();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Page/Frame.h b/Userland/Libraries/LibWeb/Page/Frame.h
new file mode 100644
index 0000000000..d6ba5eea8d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Page/Frame.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/Noncopyable.h>
+#include <AK/RefPtr.h>
+#include <AK/WeakPtr.h>
+#include <LibCore/Timer.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Rect.h>
+#include <LibGfx/Size.h>
+#include <LibWeb/DOM/Position.h>
+#include <LibWeb/Loader/FrameLoader.h>
+#include <LibWeb/Page/EventHandler.h>
+#include <LibWeb/TreeNode.h>
+
+namespace Web {
+
+class Frame : public TreeNode<Frame> {
+public:
+ static NonnullRefPtr<Frame> create_subframe(DOM::Element& host_element, Frame& main_frame) { return adopt(*new Frame(host_element, main_frame)); }
+ static NonnullRefPtr<Frame> create(Page& page) { return adopt(*new Frame(page)); }
+ ~Frame();
+
+ bool is_main_frame() const { return this == &m_main_frame; }
+ bool is_focused_frame() const;
+
+ const DOM::Document* document() const { return m_document; }
+ DOM::Document* document() { return m_document; }
+
+ void set_document(DOM::Document*);
+
+ Page* page() { return m_page; }
+ const Page* page() const { return m_page; }
+
+ const Gfx::IntSize& size() const { return m_size; }
+ void set_size(const Gfx::IntSize&);
+
+ void set_needs_display(const Gfx::IntRect&);
+
+ void set_viewport_scroll_offset(const Gfx::IntPoint&);
+ Gfx::IntRect viewport_rect() const { return { m_viewport_scroll_offset, m_size }; }
+
+ void did_scroll(Badge<InProcessWebView>);
+
+ FrameLoader& loader() { return m_loader; }
+ const FrameLoader& loader() const { return m_loader; }
+
+ EventHandler& event_handler() { return m_event_handler; }
+ const EventHandler& event_handler() const { return m_event_handler; }
+
+ void scroll_to(const Gfx::IntPoint&);
+ void scroll_to_anchor(const String&);
+
+ Frame& main_frame() { return m_main_frame; }
+ const Frame& main_frame() const { return m_main_frame; }
+
+ DOM::Element* host_element() { return m_host_element; }
+ const DOM::Element* host_element() const { return m_host_element; }
+
+ Gfx::IntPoint to_main_frame_position(const Gfx::IntPoint&);
+ Gfx::IntRect to_main_frame_rect(const Gfx::IntRect&);
+
+ DOM::Position& cursor_position() { return m_cursor_position; }
+ const DOM::Position& cursor_position() const { return m_cursor_position; }
+ void set_cursor_position(const DOM::Position&);
+
+ bool cursor_blink_state() const { return m_cursor_blink_state; }
+
+ String selected_text() const;
+
+ void did_edit(Badge<EditEventHandler>);
+
+private:
+ explicit Frame(DOM::Element& host_element, Frame& main_frame);
+ explicit Frame(Page&);
+
+ void setup();
+
+ WeakPtr<Page> m_page;
+ Frame& m_main_frame;
+
+ FrameLoader m_loader;
+ EventHandler m_event_handler;
+
+ WeakPtr<DOM::Element> m_host_element;
+ RefPtr<DOM::Document> m_document;
+ Gfx::IntSize m_size;
+ Gfx::IntPoint m_viewport_scroll_offset;
+
+ DOM::Position m_cursor_position;
+ RefPtr<Core::Timer> m_cursor_blink_timer;
+ bool m_cursor_blink_state { false };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Page/Page.cpp b/Userland/Libraries/LibWeb/Page/Page.cpp
new file mode 100644
index 0000000000..dfc9ada662
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Page/Page.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/InProcessWebView.h>
+#include <LibWeb/Page/Frame.h>
+#include <LibWeb/Page/Page.h>
+
+namespace Web {
+
+Page::Page(PageClient& client)
+ : m_client(client)
+{
+ m_main_frame = Frame::create(*this);
+}
+
+Page::~Page()
+{
+}
+
+Frame& Page::focused_frame()
+{
+ if (m_focused_frame)
+ return *m_focused_frame;
+ return main_frame();
+}
+
+void Page::set_focused_frame(Badge<EventHandler>, Frame& frame)
+{
+ m_focused_frame = frame.make_weak_ptr();
+}
+
+void Page::load(const URL& url)
+{
+ main_frame().loader().load(url, FrameLoader::Type::Navigation);
+}
+
+void Page::load(const LoadRequest& request)
+{
+ main_frame().loader().load(request, FrameLoader::Type::Navigation);
+}
+
+void Page::load_html(const StringView& html, const URL& url)
+{
+ main_frame().loader().load_html(html, url);
+}
+
+Gfx::Palette Page::palette() const
+{
+ return m_client.palette();
+}
+
+bool Page::handle_mouseup(const Gfx::IntPoint& position, unsigned button, unsigned modifiers)
+{
+ return main_frame().event_handler().handle_mouseup(position, button, modifiers);
+}
+
+bool Page::handle_mousedown(const Gfx::IntPoint& position, unsigned button, unsigned modifiers)
+{
+ return main_frame().event_handler().handle_mousedown(position, button, modifiers);
+}
+
+bool Page::handle_mousemove(const Gfx::IntPoint& position, unsigned buttons, unsigned modifiers)
+{
+ return main_frame().event_handler().handle_mousemove(position, buttons, modifiers);
+}
+
+bool Page::handle_keydown(KeyCode key, unsigned modifiers, u32 code_point)
+{
+ return focused_frame().event_handler().handle_keydown(key, modifiers, code_point);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Page/Page.h b/Userland/Libraries/LibWeb/Page/Page.h
new file mode 100644
index 0000000000..5d7325d283
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Page/Page.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Noncopyable.h>
+#include <AK/OwnPtr.h>
+#include <AK/RefPtr.h>
+#include <AK/URL.h>
+#include <Kernel/API/KeyCode.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/Palette.h>
+#include <LibWeb/Forward.h>
+
+namespace Web {
+
+class PageClient;
+
+class Page : public Weakable<Page> {
+ AK_MAKE_NONCOPYABLE(Page);
+ AK_MAKE_NONMOVABLE(Page);
+
+public:
+ explicit Page(PageClient&);
+ ~Page();
+
+ PageClient& client() { return m_client; }
+ const PageClient& client() const { return m_client; }
+
+ Web::Frame& main_frame() { return *m_main_frame; }
+ const Web::Frame& main_frame() const { return *m_main_frame; }
+
+ Web::Frame& focused_frame();
+ const Web::Frame& focused_frame() const { return const_cast<Page*>(this)->focused_frame(); }
+
+ void set_focused_frame(Badge<EventHandler>, Frame&);
+
+ void load(const URL&);
+ void load(const LoadRequest&);
+
+ void load_html(const StringView&, const URL&);
+
+ bool handle_mouseup(const Gfx::IntPoint&, unsigned button, unsigned modifiers);
+ bool handle_mousedown(const Gfx::IntPoint&, unsigned button, unsigned modifiers);
+ bool handle_mousemove(const Gfx::IntPoint&, unsigned buttons, unsigned modifiers);
+
+ bool handle_keydown(KeyCode, unsigned modifiers, u32 code_point);
+
+ Gfx::Palette palette() const;
+
+private:
+ PageClient& m_client;
+
+ RefPtr<Frame> m_main_frame;
+ WeakPtr<Frame> m_focused_frame;
+};
+
+class PageClient {
+public:
+ virtual Gfx::Palette palette() const = 0;
+ virtual void page_did_set_document_in_main_frame(DOM::Document*) { }
+ virtual void page_did_change_title(const String&) { }
+ virtual void page_did_start_loading(const URL&) { }
+ virtual void page_did_finish_loading(const URL&) { }
+ virtual void page_did_change_selection() { }
+ virtual void page_did_request_cursor_change(Gfx::StandardCursor) { }
+ virtual void page_did_request_context_menu(const Gfx::IntPoint&) { }
+ virtual void page_did_request_link_context_menu(const Gfx::IntPoint&, const URL&, [[maybe_unused]] const String& target, [[maybe_unused]] unsigned modifiers) { }
+ virtual void page_did_request_image_context_menu(const Gfx::IntPoint&, const URL&, [[maybe_unused]] const String& target, [[maybe_unused]] unsigned modifiers, const Gfx::Bitmap*) { }
+ virtual void page_did_click_link(const URL&, [[maybe_unused]] const String& target, [[maybe_unused]] unsigned modifiers) { }
+ virtual void page_did_middle_click_link(const URL&, [[maybe_unused]] const String& target, [[maybe_unused]] unsigned modifiers) { }
+ virtual void page_did_enter_tooltip_area(const Gfx::IntPoint&, const String&) { }
+ virtual void page_did_leave_tooltip_area() { }
+ virtual void page_did_hover_link(const URL&) { }
+ virtual void page_did_unhover_link() { }
+ virtual void page_did_invalidate(const Gfx::IntRect&) { }
+ virtual void page_did_change_favicon(const Gfx::Bitmap&) { }
+ virtual void page_did_layout() { }
+ virtual void page_did_request_scroll_into_view(const Gfx::IntRect&) { }
+ virtual void page_did_request_alert(const String&) { }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Painting/BorderPainting.cpp b/Userland/Libraries/LibWeb/Painting/BorderPainting.cpp
new file mode 100644
index 0000000000..3c5cd8831f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Painting/BorderPainting.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/Painter.h>
+#include <LibWeb/Painting/BorderPainting.h>
+#include <LibWeb/Painting/PaintContext.h>
+
+namespace Web::Painting {
+
+void paint_border(PaintContext& context, BorderEdge edge, const Gfx::FloatRect& rect, const CSS::ComputedValues& style)
+{
+ const auto& border_data = [&] {
+ switch (edge) {
+ case BorderEdge::Top:
+ return style.border_top();
+ case BorderEdge::Right:
+ return style.border_right();
+ case BorderEdge::Bottom:
+ return style.border_bottom();
+ default: // BorderEdge::Left:
+ return style.border_left();
+ }
+ }();
+
+ float width = border_data.width;
+ if (width <= 0)
+ return;
+
+ auto color = border_data.color;
+ auto border_style = border_data.line_style;
+ int int_width = max((int)width, 1);
+
+ struct Points {
+ Gfx::FloatPoint p1;
+ Gfx::FloatPoint p2;
+ };
+
+ auto points_for_edge = [](BorderEdge edge, const Gfx::FloatRect& rect) -> Points {
+ switch (edge) {
+ case BorderEdge::Top:
+ return { rect.top_left(), rect.top_right() };
+ case BorderEdge::Right:
+ return { rect.top_right(), rect.bottom_right() };
+ case BorderEdge::Bottom:
+ return { rect.bottom_left(), rect.bottom_right() };
+ default: // Edge::Left
+ return { rect.top_left(), rect.bottom_left() };
+ }
+ };
+
+ auto [p1, p2] = points_for_edge(edge, rect);
+
+ if (border_style == CSS::LineStyle::Inset) {
+ auto top_left_color = Color::from_rgb(0x5a5a5a);
+ auto bottom_right_color = Color::from_rgb(0x888888);
+ color = (edge == BorderEdge::Left || edge == BorderEdge::Top) ? top_left_color : bottom_right_color;
+ } else if (border_style == CSS::LineStyle::Outset) {
+ auto top_left_color = Color::from_rgb(0x888888);
+ auto bottom_right_color = Color::from_rgb(0x5a5a5a);
+ color = (edge == BorderEdge::Left || edge == BorderEdge::Top) ? top_left_color : bottom_right_color;
+ }
+
+ auto gfx_line_style = Gfx::Painter::LineStyle::Solid;
+ if (border_style == CSS::LineStyle::Dotted)
+ gfx_line_style = Gfx::Painter::LineStyle::Dotted;
+ if (border_style == CSS::LineStyle::Dashed)
+ gfx_line_style = Gfx::Painter::LineStyle::Dashed;
+
+ if (gfx_line_style != Gfx::Painter::LineStyle::Solid) {
+ switch (edge) {
+ case BorderEdge::Top:
+ p1.move_by(int_width / 2, int_width / 2);
+ p2.move_by(-int_width / 2, int_width / 2);
+ break;
+ case BorderEdge::Right:
+ p1.move_by(-int_width / 2, int_width / 2);
+ p2.move_by(-int_width / 2, -int_width / 2);
+ break;
+ case BorderEdge::Bottom:
+ p1.move_by(int_width / 2, -int_width / 2);
+ p2.move_by(-int_width / 2, -int_width / 2);
+ break;
+ case BorderEdge::Left:
+ p1.move_by(int_width / 2, int_width / 2);
+ p2.move_by(int_width / 2, -int_width / 2);
+ break;
+ }
+ context.painter().draw_line({ (int)p1.x(), (int)p1.y() }, { (int)p2.x(), (int)p2.y() }, color, int_width, gfx_line_style);
+ return;
+ }
+
+ auto draw_line = [&](auto& p1, auto& p2) {
+ context.painter().draw_line({ (int)p1.x(), (int)p1.y() }, { (int)p2.x(), (int)p2.y() }, color, 1, gfx_line_style);
+ };
+
+ float p1_step = 0;
+ float p2_step = 0;
+
+ switch (edge) {
+ case BorderEdge::Top:
+ p1_step = style.border_left().width / (float)int_width;
+ p2_step = style.border_right().width / (float)int_width;
+ for (int i = 0; i < int_width; ++i) {
+ draw_line(p1, p2);
+ p1.move_by(p1_step, 1);
+ p2.move_by(-p2_step, 1);
+ }
+ break;
+ case BorderEdge::Right:
+ p1_step = style.border_top().width / (float)int_width;
+ p2_step = style.border_bottom().width / (float)int_width;
+ for (int i = int_width - 1; i >= 0; --i) {
+ draw_line(p1, p2);
+ p1.move_by(-1, p1_step);
+ p2.move_by(-1, -p2_step);
+ }
+ break;
+ case BorderEdge::Bottom:
+ p1_step = style.border_left().width / (float)int_width;
+ p2_step = style.border_right().width / (float)int_width;
+ for (int i = int_width - 1; i >= 0; --i) {
+ draw_line(p1, p2);
+ p1.move_by(p1_step, -1);
+ p2.move_by(-p2_step, -1);
+ }
+ break;
+ case BorderEdge::Left:
+ p1_step = style.border_top().width / (float)int_width;
+ p2_step = style.border_bottom().width / (float)int_width;
+ for (int i = 0; i < int_width; ++i) {
+ draw_line(p1, p2);
+ p1.move_by(1, p1_step);
+ p2.move_by(1, -p2_step);
+ }
+ break;
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Painting/BorderPainting.h b/Userland/Libraries/LibWeb/Painting/BorderPainting.h
new file mode 100644
index 0000000000..47633eb980
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Painting/BorderPainting.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Forward.h>
+#include <LibWeb/CSS/ComputedValues.h>
+
+namespace Web::Painting {
+
+enum class BorderEdge {
+ Top,
+ Right,
+ Bottom,
+ Left,
+};
+void paint_border(PaintContext&, BorderEdge, const Gfx::FloatRect&, const CSS::ComputedValues&);
+
+}
diff --git a/Userland/Libraries/LibWeb/Painting/PaintContext.h b/Userland/Libraries/LibWeb/Painting/PaintContext.h
new file mode 100644
index 0000000000..f6ba50eb87
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Painting/PaintContext.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Forward.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/Rect.h>
+#include <LibWeb/SVG/SVGContext.h>
+
+namespace Web {
+
+class PaintContext {
+public:
+ explicit PaintContext(Gfx::Painter& painter, const Palette& palette, const Gfx::IntPoint& scroll_offset)
+ : m_painter(painter)
+ , m_palette(palette)
+ , m_scroll_offset(scroll_offset)
+ {
+ }
+
+ Gfx::Painter& painter() const { return m_painter; }
+ const Palette& palette() const { return m_palette; }
+
+ bool has_svg_context() const { return m_svg_context.has_value(); }
+ SVGContext& svg_context() { return m_svg_context.value(); }
+ void set_svg_context(SVGContext context) { m_svg_context = context; }
+
+ bool should_show_line_box_borders() const { return m_should_show_line_box_borders; }
+ void set_should_show_line_box_borders(bool value) { m_should_show_line_box_borders = value; }
+
+ Gfx::IntRect viewport_rect() const { return m_viewport_rect; }
+ void set_viewport_rect(const Gfx::IntRect& rect) { m_viewport_rect = rect; }
+
+ const Gfx::IntPoint& scroll_offset() const { return m_scroll_offset; }
+
+ bool has_focus() const { return m_focus; }
+ void set_has_focus(bool focus) { m_focus = focus; }
+
+private:
+ Gfx::Painter& m_painter;
+ Palette m_palette;
+ Optional<SVGContext> m_svg_context;
+ Gfx::IntRect m_viewport_rect;
+ Gfx::IntPoint m_scroll_offset;
+ bool m_should_show_line_box_borders { false };
+ bool m_focus { false };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp
new file mode 100644
index 0000000000..7524f4fff9
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/QuickSort.h>
+#include <AK/StringBuilder.h>
+#include <LibWeb/DOM/Node.h>
+#include <LibWeb/Layout/Box.h>
+#include <LibWeb/Layout/InitialContainingBlockBox.h>
+#include <LibWeb/Painting/StackingContext.h>
+
+namespace Web::Layout {
+
+StackingContext::StackingContext(Box& box, StackingContext* parent)
+ : m_box(box)
+ , m_parent(parent)
+{
+ ASSERT(m_parent != this);
+ if (m_parent) {
+ m_parent->m_children.append(this);
+
+ // FIXME: Don't sort on every append..
+ quick_sort(m_parent->m_children, [](auto& a, auto& b) {
+ return a->m_box.computed_values().z_index().value_or(0) < b->m_box.computed_values().z_index().value_or(0);
+ });
+ }
+}
+
+void StackingContext::paint(PaintContext& context, PaintPhase phase)
+{
+ if (!is<InitialContainingBlockBox>(m_box)) {
+ m_box.paint(context, phase);
+ } else {
+ // NOTE: InitialContainingBlockBox::paint() merely calls StackingContext::paint()
+ // so we call its base class instead.
+ downcast<InitialContainingBlockBox>(m_box).BlockBox::paint(context, phase);
+ }
+ for (auto* child : m_children) {
+ child->paint(context, phase);
+ }
+}
+
+HitTestResult StackingContext::hit_test(const Gfx::IntPoint& position, HitTestType type) const
+{
+ HitTestResult result;
+ if (!is<InitialContainingBlockBox>(m_box)) {
+ result = m_box.hit_test(position, type);
+ } else {
+ // NOTE: InitialContainingBlockBox::hit_test() merely calls StackingContext::hit_test()
+ // so we call its base class instead.
+ result = downcast<InitialContainingBlockBox>(m_box).BlockBox::hit_test(position, type);
+ }
+
+ for (auto* child : m_children) {
+ auto result_here = child->hit_test(position, type);
+ if (result_here.layout_node)
+ result = result_here;
+ }
+ return result;
+}
+
+void StackingContext::dump(int indent) const
+{
+ StringBuilder builder;
+ for (int i = 0; i < indent; ++i)
+ builder.append(' ');
+ builder.appendff("SC for {}({}) {} [children: {}]", m_box.class_name(), m_box.dom_node() ? m_box.dom_node()->node_name().characters() : "(anonymous)", m_box.absolute_rect().to_string().characters(), m_children.size());
+ dbgln("{}", builder.string_view());
+ for (auto& child : m_children)
+ child->dump(indent + 1);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.h b/Userland/Libraries/LibWeb/Painting/StackingContext.h
new file mode 100644
index 0000000000..b0b212ed2d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Painting/StackingContext.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Vector.h>
+#include <LibWeb/Layout/Node.h>
+
+namespace Web::Layout {
+
+class StackingContext {
+public:
+ StackingContext(Box&, StackingContext* parent);
+
+ StackingContext* parent() { return m_parent; }
+ const StackingContext* parent() const { return m_parent; }
+
+ void paint(PaintContext&, PaintPhase);
+ HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const;
+
+ void dump(int indent = 0) const;
+
+private:
+ Box& m_box;
+ StackingContext* const m_parent { nullptr };
+ Vector<StackingContext*> m_children;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/QualifiedName.h b/Userland/Libraries/LibWeb/QualifiedName.h
new file mode 100644
index 0000000000..08103b507d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/QualifiedName.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+
+namespace Web {
+
+class QualifiedName {
+public:
+ QualifiedName(const FlyString& local_name, const FlyString& prefix, const FlyString& namespace_)
+ : m_local_name(local_name)
+ , m_prefix(prefix)
+ , m_namespace(namespace_)
+ {
+ }
+
+ const FlyString& local_name() const { return m_local_name; }
+ const FlyString& prefix() const { return m_prefix; }
+ const FlyString& namespace_() const { return m_namespace; }
+
+private:
+ FlyString m_local_name;
+ FlyString m_prefix;
+ FlyString m_namespace;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/SVG/SVGContext.h b/Userland/Libraries/LibWeb/SVG/SVGContext.h
new file mode 100644
index 0000000000..ec7fe72eff
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGContext.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace Web {
+
+class SVGContext {
+public:
+ SVGContext()
+ {
+ m_states.append(State());
+ }
+
+ const Gfx::Color& fill_color() const { return state().fill_color; }
+ const Gfx::Color& stroke_color() const { return state().stroke_color; }
+ float stroke_width() const { return state().stroke_width; }
+
+ void set_fill_color(Gfx::Color color) { state().fill_color = color; }
+ void set_stroke_color(Gfx::Color color) { state().stroke_color = color; }
+ void set_stroke_width(float width) { state().stroke_width = width; }
+
+ void save() { m_states.append(m_states.last()); }
+ void restore() { m_states.take_last(); }
+
+private:
+ struct State {
+ Gfx::Color fill_color { Gfx::Color::Black };
+ Gfx::Color stroke_color { Gfx::Color::Transparent };
+ float stroke_width { 1.0 };
+ };
+
+ const State& state() const { return m_states.last(); }
+ State& state() { return m_states.last(); }
+
+ Vector<State> m_states;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/SVG/SVGElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGElement.cpp
new file mode 100644
index 0000000000..828e9b0e99
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGElement.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/SVG/SVGElement.h>
+
+namespace Web::SVG {
+
+SVGElement::SVGElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : Element(document, qualified_name)
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/SVG/SVGElement.h b/Userland/Libraries/LibWeb/SVG/SVGElement.h
new file mode 100644
index 0000000000..99041728f8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/DOM/Element.h>
+
+namespace Web::SVG {
+
+class SVGElement : public DOM::Element {
+public:
+ using WrapperType = Bindings::SVGElementWrapper;
+
+protected:
+ SVGElement(DOM::Document&, const QualifiedName& qualified_name);
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/SVG/SVGElement.idl b/Userland/Libraries/LibWeb/SVG/SVGElement.idl
new file mode 100644
index 0000000000..1eb7f7f18c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGElement.idl
@@ -0,0 +1,3 @@
+interface SVGElement : Element {
+
+};
diff --git a/Userland/Libraries/LibWeb/SVG/SVGGeometryElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGGeometryElement.cpp
new file mode 100644
index 0000000000..c3e8f386fd
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGGeometryElement.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/SVG/SVGGeometryElement.h>
+
+namespace Web::SVG {
+
+SVGGeometryElement::SVGGeometryElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : SVGGraphicsElement(document, qualified_name)
+{
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/SVG/SVGGeometryElement.h b/Userland/Libraries/LibWeb/SVG/SVGGeometryElement.h
new file mode 100644
index 0000000000..91c9e92288
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGGeometryElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/SVG/SVGGraphicsElement.h>
+
+namespace Web::SVG {
+
+class SVGGeometryElement : public SVGGraphicsElement {
+public:
+ using WrapperType = Bindings::SVGGeometryElementWrapper;
+
+protected:
+ SVGGeometryElement(DOM::Document& document, const QualifiedName& qualified_name);
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/SVG/SVGGeometryElement.idl b/Userland/Libraries/LibWeb/SVG/SVGGeometryElement.idl
new file mode 100644
index 0000000000..bd64356c71
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGGeometryElement.idl
@@ -0,0 +1,3 @@
+interface SVGGeometryElement : SVGGraphicsElement {
+
+};
diff --git a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp
new file mode 100644
index 0000000000..2fca526027
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/SVG/SVGGraphicsElement.h>
+
+namespace Web::SVG {
+
+SVGGraphicsElement::SVGGraphicsElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : SVGElement(document, qualified_name)
+{
+}
+
+void SVGGraphicsElement::parse_attribute(const FlyString& name, const String& value)
+{
+ SVGElement::parse_attribute(name, value);
+
+ if (name == "fill") {
+ m_fill_color = Gfx::Color::from_string(value).value_or(Color::Transparent);
+ } else if (name == "stroke") {
+ m_stroke_color = Gfx::Color::from_string(value).value_or(Color::Transparent);
+ } else if (name == "stroke-width") {
+ auto result = value.to_int();
+ if (result.has_value())
+ m_stroke_width = result.value();
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h
new file mode 100644
index 0000000000..ec8735a70b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Path.h>
+#include <LibWeb/DOM/Node.h>
+#include <LibWeb/SVG/SVGElement.h>
+#include <LibWeb/SVG/TagNames.h>
+
+namespace Web::SVG {
+
+class SVGGraphicsElement : public SVGElement {
+public:
+ using WrapperType = Bindings::SVGGraphicsElementWrapper;
+
+ SVGGraphicsElement(DOM::Document&, const QualifiedName& qualified_name);
+
+ virtual void parse_attribute(const FlyString& name, const String& value) override;
+
+ const Optional<Gfx::Color>& fill_color() const { return m_fill_color; }
+ const Optional<Gfx::Color>& stroke_color() const { return m_stroke_color; }
+ const Optional<float>& stroke_width() const { return m_stroke_width; }
+
+protected:
+ Optional<Gfx::Color> m_fill_color;
+ Optional<Gfx::Color> m_stroke_color;
+ Optional<float> m_stroke_width;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.idl b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.idl
new file mode 100644
index 0000000000..9636176dd0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.idl
@@ -0,0 +1,3 @@
+interface SVGGraphicsElement : SVGElement {
+
+};
diff --git a/Userland/Libraries/LibWeb/SVG/SVGPathElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGPathElement.cpp
new file mode 100644
index 0000000000..5a3c01fc4c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGPathElement.cpp
@@ -0,0 +1,659 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibGfx/Painter.h>
+#include <LibGfx/Path.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/Layout/SVGPathBox.h>
+#include <LibWeb/SVG/SVGPathElement.h>
+#include <ctype.h>
+
+//#define PATH_DEBUG
+
+namespace Web::SVG {
+
+#ifdef PATH_DEBUG
+static void print_instruction(const PathInstruction& instruction)
+{
+ auto& data = instruction.data;
+
+ switch (instruction.type) {
+ case PathInstructionType::Move:
+ dbg() << "Move (absolute=" << instruction.absolute << ")";
+ for (size_t i = 0; i < data.size(); i += 2)
+ dbg() << " x=" << data[i] << ", y=" << data[i + 1];
+ break;
+ case PathInstructionType::ClosePath:
+ dbg() << "ClosePath (absolute=" << instruction.absolute << ")";
+ break;
+ case PathInstructionType::Line:
+ dbg() << "Line (absolute=" << instruction.absolute << ")";
+ for (size_t i = 0; i < data.size(); i += 2)
+ dbg() << " x=" << data[i] << ", y=" << data[i + 1];
+ break;
+ case PathInstructionType::HorizontalLine:
+ dbg() << "HorizontalLine (absolute=" << instruction.absolute << ")";
+ for (size_t i = 0; i < data.size(); ++i)
+ dbg() << " x=" << data[i];
+ break;
+ case PathInstructionType::VerticalLine:
+ dbg() << "VerticalLine (absolute=" << instruction.absolute << ")";
+ for (size_t i = 0; i < data.size(); ++i)
+ dbg() << " y=" << data[i];
+ break;
+ case PathInstructionType::Curve:
+ dbg() << "Curve (absolute=" << instruction.absolute << ")";
+ for (size_t i = 0; i < data.size(); i += 6)
+ dbg() << " (x1=" << data[i] << ", y1=" << data[i + 1] << "), (x2=" << data[i + 2] << ", y2=" << data[i + 3] << "), (x=" << data[i + 4] << ", y=" << data[i + 5] << ")";
+ break;
+ case PathInstructionType::SmoothCurve:
+ dbg() << "SmoothCurve (absolute=" << instruction.absolute << ")";
+ for (size_t i = 0; i < data.size(); i += 4)
+ dbg() << " (x2=" << data[i] << ", y2=" << data[i + 1] << "), (x=" << data[i + 2] << ", y=" << data[i + 3] << ")";
+ break;
+ case PathInstructionType::QuadraticBezierCurve:
+ dbg() << "QuadraticBezierCurve (absolute=" << instruction.absolute << ")";
+ for (size_t i = 0; i < data.size(); i += 4)
+ dbg() << " (x1=" << data[i] << ", y1=" << data[i + 1] << "), (x=" << data[i + 2] << ", y=" << data[i + 3] << ")";
+ break;
+ case PathInstructionType::SmoothQuadraticBezierCurve:
+ dbg() << "SmoothQuadraticBezierCurve (absolute=" << instruction.absolute << ")";
+ for (size_t i = 0; i < data.size(); i += 2)
+ dbg() << " x=" << data[i] << ", y=" << data[i + 1];
+ break;
+ case PathInstructionType::EllipticalArc:
+ dbg() << "EllipticalArc (absolute=" << instruction.absolute << ")";
+ for (size_t i = 0; i < data.size(); i += 7)
+ dbg() << " (rx=" << data[i] << ", ry=" << data[i + 1] << ") x-axis-rotation=" << data[i + 2] << ", large-arc-flag=" << data[i + 3] << ", sweep-flag=" << data[i + 4] << ", (x=" << data[i + 5] << ", y=" << data[i + 6] << ")";
+ break;
+ case PathInstructionType::Invalid:
+ dbgln("Invalid");
+ break;
+ }
+}
+#endif
+
+PathDataParser::PathDataParser(const String& source)
+ : m_source(source)
+{
+}
+
+Vector<PathInstruction> PathDataParser::parse()
+{
+ parse_whitespace();
+ while (!done())
+ parse_drawto();
+ if (!m_instructions.is_empty() && m_instructions[0].type != PathInstructionType::Move)
+ ASSERT_NOT_REACHED();
+ return m_instructions;
+}
+
+void PathDataParser::parse_drawto()
+{
+ if (match('M') || match('m')) {
+ parse_moveto();
+ } else if (match('Z') || match('z')) {
+ parse_closepath();
+ } else if (match('L') || match('l')) {
+ parse_lineto();
+ } else if (match('H') || match('h')) {
+ parse_horizontal_lineto();
+ } else if (match('V') || match('v')) {
+ parse_vertical_lineto();
+ } else if (match('C') || match('c')) {
+ parse_curveto();
+ } else if (match('S') || match('s')) {
+ parse_smooth_curveto();
+ } else if (match('Q') || match('q')) {
+ parse_quadratic_bezier_curveto();
+ } else if (match('T') || match('t')) {
+ parse_smooth_quadratic_bezier_curveto();
+ } else if (match('A') || match('a')) {
+ parse_elliptical_arc();
+ } else {
+ dbg() << "PathDataParser::parse_drawto failed to match: '" << ch() << "'";
+ TODO();
+ }
+}
+
+void PathDataParser::parse_moveto()
+{
+ bool absolute = consume() == 'M';
+ parse_whitespace();
+ for (auto& pair : parse_coordinate_pair_sequence())
+ m_instructions.append({ PathInstructionType::Move, absolute, pair });
+}
+
+void PathDataParser::parse_closepath()
+{
+ bool absolute = consume() == 'Z';
+ parse_whitespace();
+ m_instructions.append({ PathInstructionType::ClosePath, absolute, {} });
+}
+
+void PathDataParser::parse_lineto()
+{
+ bool absolute = consume() == 'L';
+ parse_whitespace();
+ for (auto& pair : parse_coordinate_pair_sequence())
+ m_instructions.append({ PathInstructionType::Line, absolute, pair });
+}
+
+void PathDataParser::parse_horizontal_lineto()
+{
+ bool absolute = consume() == 'H';
+ parse_whitespace();
+ m_instructions.append({ PathInstructionType::HorizontalLine, absolute, parse_coordinate_sequence() });
+}
+
+void PathDataParser::parse_vertical_lineto()
+{
+ bool absolute = consume() == 'V';
+ parse_whitespace();
+ m_instructions.append({ PathInstructionType::VerticalLine, absolute, parse_coordinate_sequence() });
+}
+
+void PathDataParser::parse_curveto()
+{
+ bool absolute = consume() == 'C';
+ parse_whitespace();
+
+ while (true) {
+ m_instructions.append({ PathInstructionType::Curve, absolute, parse_coordinate_pair_triplet() });
+ if (match_comma_whitespace())
+ parse_comma_whitespace();
+ if (!match_coordinate())
+ break;
+ }
+}
+
+void PathDataParser::parse_smooth_curveto()
+{
+ bool absolute = consume() == 'S';
+ parse_whitespace();
+
+ while (true) {
+ m_instructions.append({ PathInstructionType::SmoothCurve, absolute, parse_coordinate_pair_double() });
+ if (match_comma_whitespace())
+ parse_comma_whitespace();
+ if (!match_coordinate())
+ break;
+ }
+}
+
+void PathDataParser::parse_quadratic_bezier_curveto()
+{
+ bool absolute = consume() == 'Q';
+ parse_whitespace();
+
+ while (true) {
+ m_instructions.append({ PathInstructionType::QuadraticBezierCurve, absolute, parse_coordinate_pair_double() });
+ if (match_comma_whitespace())
+ parse_comma_whitespace();
+ if (!match_coordinate())
+ break;
+ }
+}
+
+void PathDataParser::parse_smooth_quadratic_bezier_curveto()
+{
+ bool absolute = consume() == 'T';
+ parse_whitespace();
+
+ while (true) {
+ m_instructions.append({ PathInstructionType::SmoothQuadraticBezierCurve, absolute, parse_coordinate_pair() });
+ if (match_comma_whitespace())
+ parse_comma_whitespace();
+ if (!match_coordinate())
+ break;
+ }
+}
+
+void PathDataParser::parse_elliptical_arc()
+{
+ bool absolute = consume() == 'A';
+ parse_whitespace();
+
+ while (true) {
+ m_instructions.append({ PathInstructionType::EllipticalArc, absolute, parse_elliptical_arg_argument() });
+ if (match_comma_whitespace())
+ parse_comma_whitespace();
+ if (!match_coordinate())
+ break;
+ }
+}
+
+float PathDataParser::parse_coordinate()
+{
+ return parse_sign() * parse_number();
+}
+
+Vector<float> PathDataParser::parse_coordinate_pair()
+{
+ Vector<float> coordinates;
+ coordinates.append(parse_coordinate());
+ if (match_comma_whitespace())
+ parse_comma_whitespace();
+ coordinates.append(parse_coordinate());
+ return coordinates;
+}
+
+Vector<float> PathDataParser::parse_coordinate_sequence()
+{
+ Vector<float> sequence;
+ while (true) {
+ sequence.append(parse_coordinate());
+ if (match_comma_whitespace())
+ parse_comma_whitespace();
+ if (!match_comma_whitespace() && !match_coordinate())
+ break;
+ }
+ return sequence;
+}
+
+Vector<Vector<float>> PathDataParser::parse_coordinate_pair_sequence()
+{
+ Vector<Vector<float>> sequence;
+ while (true) {
+ sequence.append(parse_coordinate_pair());
+ if (match_comma_whitespace())
+ parse_comma_whitespace();
+ if (!match_comma_whitespace() && !match_coordinate())
+ break;
+ }
+ return sequence;
+}
+
+Vector<float> PathDataParser::parse_coordinate_pair_double()
+{
+ Vector<float> coordinates;
+ coordinates.append(parse_coordinate_pair());
+ if (match_comma_whitespace())
+ parse_comma_whitespace();
+ coordinates.append(parse_coordinate_pair());
+ return coordinates;
+}
+
+Vector<float> PathDataParser::parse_coordinate_pair_triplet()
+{
+ Vector<float> coordinates;
+ coordinates.append(parse_coordinate_pair());
+ if (match_comma_whitespace())
+ parse_comma_whitespace();
+ coordinates.append(parse_coordinate_pair());
+ if (match_comma_whitespace())
+ parse_comma_whitespace();
+ coordinates.append(parse_coordinate_pair());
+ return coordinates;
+}
+
+Vector<float> PathDataParser::parse_elliptical_arg_argument()
+{
+ Vector<float> numbers;
+ numbers.append(parse_number());
+ if (match_comma_whitespace())
+ parse_comma_whitespace();
+ numbers.append(parse_number());
+ if (match_comma_whitespace())
+ parse_comma_whitespace();
+ numbers.append(parse_number());
+ parse_comma_whitespace();
+ numbers.append(parse_flag());
+ if (match_comma_whitespace())
+ parse_comma_whitespace();
+ numbers.append(parse_flag());
+ if (match_comma_whitespace())
+ parse_comma_whitespace();
+ numbers.append(parse_coordinate_pair());
+
+ return numbers;
+}
+
+void PathDataParser::parse_whitespace(bool must_match_once)
+{
+ bool matched = false;
+ while (!done() && match_whitespace()) {
+ consume();
+ matched = true;
+ }
+
+ ASSERT(!must_match_once || matched);
+}
+
+void PathDataParser::parse_comma_whitespace()
+{
+ if (match(',')) {
+ consume();
+ parse_whitespace();
+ } else {
+ parse_whitespace(1);
+ if (match(','))
+ consume();
+ parse_whitespace();
+ }
+}
+
+float PathDataParser::parse_fractional_constant()
+{
+ StringBuilder builder;
+ bool floating_point = false;
+
+ while (!done() && isdigit(ch()))
+ builder.append(consume());
+
+ if (match('.')) {
+ floating_point = true;
+ builder.append('.');
+ consume();
+ while (!done() && isdigit(ch()))
+ builder.append(consume());
+ } else {
+ ASSERT(builder.length() > 0);
+ }
+
+ if (floating_point)
+ return strtof(builder.to_string().characters(), nullptr);
+ return builder.to_string().to_int().value();
+}
+
+float PathDataParser::parse_number()
+{
+ auto number = parse_fractional_constant();
+ if (match('e') || match('E'))
+ TODO();
+ return number;
+}
+
+float PathDataParser::parse_flag()
+{
+ if (!match('0') && !match('1'))
+ ASSERT_NOT_REACHED();
+ return consume() - '0';
+}
+
+int PathDataParser::parse_sign()
+{
+ if (match('-')) {
+ consume();
+ return -1;
+ }
+ if (match('+'))
+ consume();
+ return 1;
+}
+
+bool PathDataParser::match_whitespace() const
+{
+ if (done())
+ return false;
+ char c = ch();
+ return c == 0x9 || c == 0x20 || c == 0xa || c == 0xc || c == 0xd;
+}
+
+bool PathDataParser::match_comma_whitespace() const
+{
+ return match_whitespace() || match(',');
+}
+
+bool PathDataParser::match_coordinate() const
+{
+ return !done() && (isdigit(ch()) || ch() == '-' || ch() == '+' || ch() == '.');
+}
+
+SVGPathElement::SVGPathElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : SVGGeometryElement(document, qualified_name)
+{
+}
+
+RefPtr<Layout::Node> SVGPathElement::create_layout_node()
+{
+ auto style = document().style_resolver().resolve_style(*this);
+ if (style->display() == CSS::Display::None)
+ return nullptr;
+ return adopt(*new Layout::SVGPathBox(document(), *this, move(style)));
+}
+
+void SVGPathElement::parse_attribute(const FlyString& name, const String& value)
+{
+ SVGGeometryElement::parse_attribute(name, value);
+
+ if (name == "d")
+ m_instructions = PathDataParser(value).parse();
+}
+
+Gfx::Path& SVGPathElement::get_path()
+{
+ if (m_path.has_value())
+ return m_path.value();
+
+ Gfx::Path path;
+
+ for (auto& instruction : m_instructions) {
+ auto& absolute = instruction.absolute;
+ auto& data = instruction.data;
+
+#ifdef PATH_DEBUG
+ print_instruction(instruction);
+#endif
+
+ bool clear_last_control_point = true;
+
+ switch (instruction.type) {
+ case PathInstructionType::Move: {
+ Gfx::FloatPoint point = { data[0], data[1] };
+ if (absolute) {
+ path.move_to(point);
+ } else {
+ ASSERT(!path.segments().is_empty());
+ path.move_to(point + path.segments().last().point());
+ }
+ break;
+ }
+ case PathInstructionType::ClosePath:
+ path.close();
+ break;
+ case PathInstructionType::Line: {
+ Gfx::FloatPoint point = { data[0], data[1] };
+ if (absolute) {
+ path.line_to(point);
+ } else {
+ ASSERT(!path.segments().is_empty());
+ path.line_to(point + path.segments().last().point());
+ }
+ break;
+ }
+ case PathInstructionType::HorizontalLine: {
+ ASSERT(!path.segments().is_empty());
+ auto last_point = path.segments().last().point();
+ if (absolute) {
+ path.line_to(Gfx::FloatPoint { data[0], last_point.y() });
+ } else {
+ path.line_to(Gfx::FloatPoint { data[0] + last_point.x(), last_point.y() });
+ }
+ break;
+ }
+ case PathInstructionType::VerticalLine: {
+ ASSERT(!path.segments().is_empty());
+ auto last_point = path.segments().last().point();
+ if (absolute) {
+ path.line_to(Gfx::FloatPoint { last_point.x(), data[0] });
+ } else {
+ path.line_to(Gfx::FloatPoint { last_point.x(), data[0] + last_point.y() });
+ }
+ break;
+ }
+ case PathInstructionType::EllipticalArc: {
+ double rx = data[0];
+ double ry = data[1];
+ double x_axis_rotation = data[2] * M_DEG2RAD;
+ double large_arc_flag = data[3];
+ double sweep_flag = data[4];
+
+ double x_axis_rotation_c = cos(x_axis_rotation);
+ double x_axis_rotation_s = sin(x_axis_rotation);
+
+ auto& last_point = path.segments().last().point();
+
+ Gfx::FloatPoint next_point;
+
+ if (absolute) {
+ next_point = { data[5], data[6] };
+ } else {
+ next_point = { data[5] + last_point.x(), data[6] + last_point.y() };
+ }
+
+ // Step 1 of out-of-range radii correction
+ if (rx == 0.0 || ry == 0.0) {
+ path.line_to(next_point);
+ break;
+ }
+
+ // Step 2 of out-of-range radii correction
+ if (rx < 0)
+ rx *= -1.0;
+ if (ry < 0)
+ ry *= -1.0;
+
+ // Find (cx, cy), theta_1, theta_delta
+ // Step 1: Compute (x1', y1')
+ auto x_avg = (last_point.x() - next_point.x()) / 2.0f;
+ auto y_avg = (last_point.y() - next_point.y()) / 2.0f;
+ auto x1p = x_axis_rotation_c * x_avg + x_axis_rotation_s * y_avg;
+ auto y1p = -x_axis_rotation_s * x_avg + x_axis_rotation_c * y_avg;
+
+ // Step 2: Compute (cx', cy')
+ double x1p_sq = pow(x1p, 2.0);
+ double y1p_sq = pow(y1p, 2.0);
+ double rx_sq = pow(rx, 2.0);
+ double ry_sq = pow(ry, 2.0);
+
+ // Step 3 of out-of-range radii correction
+ double lambda = x1p_sq / rx_sq + y1p_sq / ry_sq;
+ double multiplier;
+
+ if (lambda > 1.0) {
+ auto lambda_sqrt = sqrt(lambda);
+ rx *= lambda_sqrt;
+ ry *= lambda_sqrt;
+ multiplier = 0.0;
+ } else {
+ double numerator = rx_sq * ry_sq - rx_sq * y1p_sq - ry_sq * x1p_sq;
+ double denominator = rx_sq * y1p_sq + ry_sq * x1p_sq;
+ multiplier = sqrt(numerator / denominator);
+ }
+
+ if (large_arc_flag == sweep_flag)
+ multiplier *= -1.0;
+
+ double cxp = multiplier * rx * y1p / ry;
+ double cyp = multiplier * -ry * x1p / rx;
+
+ // Step 3: Compute (cx, cy) from (cx', cy')
+ x_avg = (last_point.x() + next_point.x()) / 2.0f;
+ y_avg = (last_point.y() + next_point.y()) / 2.0f;
+ double cx = x_axis_rotation_c * cxp - x_axis_rotation_s * cyp + x_avg;
+ double cy = x_axis_rotation_s * cxp + x_axis_rotation_c * cyp + y_avg;
+
+ double theta_1 = atan2((y1p - cyp) / ry, (x1p - cxp) / rx);
+ double theta_2 = atan2((-y1p - cyp) / ry, (-x1p - cxp) / rx);
+
+ auto theta_delta = theta_2 - theta_1;
+
+ if (sweep_flag == 0 && theta_delta > 0.0f) {
+ theta_delta -= M_TAU;
+ } else if (sweep_flag != 0 && theta_delta < 0) {
+ theta_delta += M_TAU;
+ }
+
+ path.elliptical_arc_to(next_point, { cx, cy }, { rx, ry }, x_axis_rotation, theta_1, theta_delta);
+
+ break;
+ }
+ case PathInstructionType::QuadraticBezierCurve: {
+ clear_last_control_point = false;
+
+ Gfx::FloatPoint through = { data[0], data[1] };
+ Gfx::FloatPoint point = { data[2], data[3] };
+
+ if (absolute) {
+ path.quadratic_bezier_curve_to(through, point);
+ m_previous_control_point = through;
+ } else {
+ ASSERT(!path.segments().is_empty());
+ auto last_point = path.segments().last().point();
+ auto control_point = through + last_point;
+ path.quadratic_bezier_curve_to(control_point, point + last_point);
+ m_previous_control_point = control_point;
+ }
+ break;
+ }
+ case PathInstructionType::SmoothQuadraticBezierCurve: {
+ clear_last_control_point = false;
+
+ ASSERT(!path.segments().is_empty());
+ auto last_point = path.segments().last().point();
+
+ if (m_previous_control_point.is_null()) {
+ m_previous_control_point = last_point;
+ }
+
+ auto dx_end_control = last_point.dx_relative_to(m_previous_control_point);
+ auto dy_end_control = last_point.dy_relative_to(m_previous_control_point);
+ auto control_point = Gfx::FloatPoint { last_point.x() + dx_end_control, last_point.y() + dy_end_control };
+
+ Gfx::FloatPoint end_point = { data[0], data[1] };
+
+ if (absolute) {
+ path.quadratic_bezier_curve_to(control_point, end_point);
+ } else {
+ path.quadratic_bezier_curve_to(control_point, end_point + last_point);
+ }
+
+ m_previous_control_point = control_point;
+ break;
+ }
+
+ case PathInstructionType::Curve:
+ case PathInstructionType::SmoothCurve:
+ // Instead of crashing the browser every time we come across an SVG
+ // with these path instructions, let's just skip them
+ continue;
+ case PathInstructionType::Invalid:
+ ASSERT_NOT_REACHED();
+ }
+
+ if (clear_last_control_point) {
+ m_previous_control_point = Gfx::FloatPoint {};
+ }
+ }
+
+ m_path = path;
+ return m_path.value();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/SVG/SVGPathElement.h b/Userland/Libraries/LibWeb/SVG/SVGPathElement.h
new file mode 100644
index 0000000000..738df73a13
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGPathElement.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibWeb/HTML/HTMLElement.h>
+#include <LibWeb/SVG/SVGGeometryElement.h>
+
+namespace Web::SVG {
+
+enum class PathInstructionType {
+ Move,
+ ClosePath,
+ Line,
+ HorizontalLine,
+ VerticalLine,
+ Curve,
+ SmoothCurve,
+ QuadraticBezierCurve,
+ SmoothQuadraticBezierCurve,
+ EllipticalArc,
+ Invalid,
+};
+
+struct PathInstruction {
+ PathInstructionType type;
+ bool absolute;
+ Vector<float> data;
+};
+
+class PathDataParser final {
+public:
+ PathDataParser(const String& source);
+ ~PathDataParser() = default;
+
+ Vector<PathInstruction> parse();
+
+private:
+ void parse_drawto();
+
+ void parse_moveto();
+ void parse_closepath();
+ void parse_lineto();
+ void parse_horizontal_lineto();
+ void parse_vertical_lineto();
+ void parse_curveto();
+ void parse_smooth_curveto();
+ void parse_quadratic_bezier_curveto();
+ void parse_smooth_quadratic_bezier_curveto();
+ void parse_elliptical_arc();
+
+ float parse_coordinate();
+ Vector<float> parse_coordinate_pair();
+ Vector<float> parse_coordinate_sequence();
+ Vector<Vector<float>> parse_coordinate_pair_sequence();
+ Vector<float> parse_coordinate_pair_double();
+ Vector<float> parse_coordinate_pair_triplet();
+ Vector<float> parse_elliptical_arg_argument();
+ void parse_whitespace(bool must_match_once = false);
+ void parse_comma_whitespace();
+ float parse_fractional_constant();
+ float parse_number();
+ float parse_flag();
+ // -1 if negative, +1 otherwise
+ int parse_sign();
+
+ bool match_whitespace() const;
+ bool match_comma_whitespace() const;
+ bool match_coordinate() const;
+ bool match(char c) const { return !done() && ch() == c; }
+
+ bool done() const { return m_cursor >= m_source.length(); }
+ char ch() const { return m_source[m_cursor]; }
+ char consume() { return m_source[m_cursor++]; }
+
+ String m_source;
+ size_t m_cursor { 0 };
+ Vector<PathInstruction> m_instructions;
+};
+
+class SVGPathElement final : public SVGGeometryElement {
+public:
+ using WrapperType = Bindings::SVGPathElementWrapper;
+
+ SVGPathElement(DOM::Document&, const QualifiedName& qualified_name);
+ virtual ~SVGPathElement() override = default;
+
+ virtual RefPtr<Layout::Node> create_layout_node() override;
+
+ virtual void parse_attribute(const FlyString& name, const String& value) override;
+
+ Gfx::Path& get_path();
+
+private:
+ Vector<PathInstruction> m_instructions;
+ Gfx::FloatPoint m_previous_control_point = {};
+ Optional<Gfx::Path> m_path;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/SVG/SVGPathElement.idl b/Userland/Libraries/LibWeb/SVG/SVGPathElement.idl
new file mode 100644
index 0000000000..d0c195bf25
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGPathElement.idl
@@ -0,0 +1,3 @@
+interface SVGPathElement : SVGGeometryElement {
+
+};
diff --git a/Userland/Libraries/LibWeb/SVG/SVGSVGElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGSVGElement.cpp
new file mode 100644
index 0000000000..ad0eb2e637
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGSVGElement.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/Painter.h>
+#include <LibWeb/CSS/StyleResolver.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/Layout/SVGSVGBox.h>
+#include <LibWeb/SVG/SVGPathElement.h>
+#include <LibWeb/SVG/SVGSVGElement.h>
+#include <ctype.h>
+
+namespace Web::SVG {
+
+SVGSVGElement::SVGSVGElement(DOM::Document& document, const QualifiedName& qualified_name)
+ : SVGGraphicsElement(document, qualified_name)
+{
+}
+
+RefPtr<Layout::Node> SVGSVGElement::create_layout_node()
+{
+ auto style = document().style_resolver().resolve_style(*this);
+ if (style->display() == CSS::Display::None)
+ return nullptr;
+ return adopt(*new Layout::SVGSVGBox(document(), *this, move(style)));
+}
+
+unsigned SVGSVGElement::width() const
+{
+ return attribute(HTML::AttributeNames::width).to_uint().value_or(300);
+}
+
+unsigned SVGSVGElement::height() const
+{
+ return attribute(HTML::AttributeNames::height).to_uint().value_or(150);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/SVG/SVGSVGElement.h b/Userland/Libraries/LibWeb/SVG/SVGSVGElement.h
new file mode 100644
index 0000000000..c84a483d07
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGSVGElement.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibWeb/SVG/SVGGraphicsElement.h>
+
+namespace Web::SVG {
+
+class SVGSVGElement final : public SVGGraphicsElement {
+public:
+ using WrapperType = Bindings::SVGSVGElementWrapper;
+
+ SVGSVGElement(DOM::Document&, const QualifiedName& qualified_name);
+
+ virtual RefPtr<Layout::Node> create_layout_node() override;
+
+ unsigned width() const;
+ unsigned height() const;
+
+private:
+ RefPtr<Gfx::Bitmap> m_bitmap;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/SVG/SVGSVGElement.idl b/Userland/Libraries/LibWeb/SVG/SVGSVGElement.idl
new file mode 100644
index 0000000000..7182cc2389
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/SVGSVGElement.idl
@@ -0,0 +1,3 @@
+interface SVGSVGElement : SVGGraphicsElement {
+
+};
diff --git a/Userland/Libraries/LibWeb/SVG/TagNames.cpp b/Userland/Libraries/LibWeb/SVG/TagNames.cpp
new file mode 100644
index 0000000000..654e914181
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/TagNames.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/SVG/TagNames.h>
+
+namespace Web::SVG::TagNames {
+
+#define __ENUMERATE_SVG_TAG(name) FlyString name;
+ENUMERATE_SVG_TAGS
+#undef __ENUMERATE_SVG_TAG
+
+[[gnu::constructor]] static void initialize()
+{
+ static bool s_initialized = false;
+ if (s_initialized)
+ return;
+
+#define __ENUMERATE_SVG_TAG(name) name = #name;
+ ENUMERATE_SVG_TAGS
+#undef __ENUMERATE_SVG_TAG
+
+ s_initialized = true;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/SVG/TagNames.h b/Userland/Libraries/LibWeb/SVG/TagNames.h
new file mode 100644
index 0000000000..d24d5f95de
--- /dev/null
+++ b/Userland/Libraries/LibWeb/SVG/TagNames.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+
+namespace Web::SVG::TagNames {
+
+#define ENUMERATE_SVG_GRAPHICS_TAGS \
+ __ENUMERATE_SVG_TAG(path) \
+ __ENUMERATE_SVG_TAG(svg)
+
+#define ENUMERATE_SVG_TAGS \
+ ENUMERATE_SVG_GRAPHICS_TAGS \
+ __ENUMERATE_SVG_TAG(desc) \
+ __ENUMERATE_SVG_TAG(foreignObject) \
+ __ENUMERATE_SVG_TAG(script) \
+ __ENUMERATE_SVG_TAG(title)
+
+#define __ENUMERATE_SVG_TAG(name) extern FlyString name;
+ENUMERATE_SVG_TAGS
+#undef __ENUMERATE_SVG_TAG
+
+}
diff --git a/Userland/Libraries/LibWeb/Scripts/GenerateStyleSheetSource.sh b/Userland/Libraries/LibWeb/Scripts/GenerateStyleSheetSource.sh
new file mode 100755
index 0000000000..aef61fcfae
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Scripts/GenerateStyleSheetSource.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+echo "namespace Web::CSS {"
+echo "extern const char $1[];"
+echo "const char $1[] = \"\\"
+grep -v '^ *#' < "$2" | while IFS= read -r line; do
+ echo "$line""\\"
+done
+echo "\";"
+echo "}"
diff --git a/Userland/Libraries/LibWeb/StylePropertiesModel.cpp b/Userland/Libraries/LibWeb/StylePropertiesModel.cpp
new file mode 100644
index 0000000000..123337db8e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/StylePropertiesModel.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/QuickSort.h>
+#include <LibWeb/CSS/PropertyID.h>
+#include <LibWeb/CSS/StyleProperties.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/StylePropertiesModel.h>
+
+namespace Web {
+
+StylePropertiesModel::StylePropertiesModel(const CSS::StyleProperties& properties)
+ : m_properties(properties)
+{
+ properties.for_each_property([&](auto property_id, auto& property_value) {
+ Value value;
+ value.name = CSS::string_from_property_id(property_id);
+ value.value = property_value.to_string();
+ m_values.append(value);
+ });
+
+ quick_sort(m_values, [](auto& a, auto& b) { return a.name < b.name; });
+}
+
+int StylePropertiesModel::row_count(const GUI::ModelIndex&) const
+{
+ return m_values.size();
+}
+
+String StylePropertiesModel::column_name(int column_index) const
+{
+ switch (column_index) {
+ case Column::PropertyName:
+ return "Name";
+ case Column::PropertyValue:
+ return "Value";
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+GUI::Variant StylePropertiesModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
+{
+ auto& value = m_values[index.row()];
+ if (role == GUI::ModelRole::Display) {
+ if (index.column() == Column::PropertyName)
+ return value.name;
+ if (index.column() == Column::PropertyValue)
+ return value.value;
+ }
+ return {};
+}
+
+void StylePropertiesModel::update()
+{
+ did_update();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/StylePropertiesModel.h b/Userland/Libraries/LibWeb/StylePropertiesModel.h
new file mode 100644
index 0000000000..2c260b62f1
--- /dev/null
+++ b/Userland/Libraries/LibWeb/StylePropertiesModel.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtrVector.h>
+#include <LibGUI/Model.h>
+
+namespace Web {
+
+class StyleProperties;
+
+class StylePropertiesModel final : public GUI::Model {
+public:
+ enum Column {
+ PropertyName,
+ PropertyValue,
+ __Count
+ };
+
+ static NonnullRefPtr<StylePropertiesModel> create(const CSS::StyleProperties& properties) { return adopt(*new StylePropertiesModel(properties)); }
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return Column::__Count; }
+ virtual String column_name(int) const override;
+ virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
+ virtual void update() override;
+
+private:
+ explicit StylePropertiesModel(const CSS::StyleProperties& properties);
+ const CSS::StyleProperties& properties() const { return *m_properties; }
+
+ NonnullRefPtr<CSS::StyleProperties> m_properties;
+
+ struct Value {
+ String name;
+ String value;
+ };
+ Vector<Value> m_values;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Tests/DOM/Comment.js b/Userland/Libraries/LibWeb/Tests/DOM/Comment.js
new file mode 100644
index 0000000000..0dab522e9c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/DOM/Comment.js
@@ -0,0 +1,15 @@
+loadPage("file:///home/anon/web-tests/Pages/Comment.html");
+
+afterInitialPageLoad(() => {
+ test("Basic functionality", () => {
+ const comment = document.body.firstChild.nextSibling;
+ expect(comment).not.toBeNull();
+
+ // FIXME: Add this in once Comment's constructor is implemented.
+ //expect(comment).toBeInstanceOf(Comment);
+
+ expect(comment.nodeName).toBe("#comment");
+ expect(comment.data).toBe("This is a comment");
+ expect(comment).toHaveLength(17);
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/DOM/Element.js b/Userland/Libraries/LibWeb/Tests/DOM/Element.js
new file mode 100644
index 0000000000..16606829aa
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/DOM/Element.js
@@ -0,0 +1,39 @@
+loadPage("file:///res/html/misc/innertext_textcontent.html");
+
+afterInitialPageLoad(() => {
+ test("Element.innerText", () => {
+ var p = document.getElementsByTagName("p")[0];
+ expect(p.innerText).toBe("This is a very small test page :^)");
+
+ // FIXME: Call this on p once that's supported.
+ var b = document.getElementsByTagName("b")[0];
+ b.innerText = "foo";
+ expect(b.innerText).toBe("foo");
+ expect(p.innerText).toBe("This is a foo test page :^)");
+
+ p.innerText = "bar";
+ expect(p.innerText).toBe("bar");
+
+ var p = document.getElementById("source");
+ // FIXME: The leading and trailing two spaces each are wrong.
+ // FIXME: The text should be affected by the text-transform:uppercase.
+ expect(p.innerText).toBe(` Take a look at
+how this text
+is interpreted below. `);
+ });
+
+ test("Element.namespaceURI basics", () => {
+ const htmlNamespace = "http://www.w3.org/1999/xhtml";
+ const p = document.getElementsByTagName("p")[0];
+ expect(p.namespaceURI).toBe(htmlNamespace);
+
+ // createElement always sets the namespace to the HTML namespace in HTML documents.
+ const svgElement = document.createElement("svg");
+ expect(svgElement.namespaceURI).toBe(htmlNamespace);
+
+ const svgNamespace = "http://www.w3.org/2000/svg";
+ p.innerHTML = "<svg></svg>";
+ const domSVGElement = p.getElementsByTagName("svg")[0];
+ expect(domSVGElement.namespaceURI).toBe(svgNamespace);
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/DOM/Node.js b/Userland/Libraries/LibWeb/Tests/DOM/Node.js
new file mode 100644
index 0000000000..5427d143bd
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/DOM/Node.js
@@ -0,0 +1,28 @@
+loadPage("file:///res/html/misc/innertext_textcontent.html");
+
+afterInitialPageLoad(() => {
+ test("Node.textContent", () => {
+ var p = document.getElementsByTagName("p")[0];
+ expect(p.textContent).toBe("This is a very small test page :^)");
+ expect(p.firstChild.textContent).toBe("This is a ");
+ expect(p.firstChild.firstChild).toBe(null);
+
+ p.firstChild.textContent = "foo";
+ expect(p.firstChild.textContent).toBe("foo");
+ expect(p.firstChild.firstChild).toBe(null);
+ expect(p.textContent).toBe("foovery small test page :^)");
+
+ p.textContent = "bar";
+ expect(p.textContent).toBe("bar");
+ expect(p.firstChild.textContent).toBe("bar");
+ expect(p.firstChild.firstChild).toBe(null);
+
+ var p = document.getElementById("source");
+ expect(p.textContent).toBe(`
+ #source { color: red; } #text { text-transform: uppercase; }
+ Take a look athow this textis interpreted
+ below.
+ HIDDEN TEXT
+ `);
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/DOM/Text.js b/Userland/Libraries/LibWeb/Tests/DOM/Text.js
new file mode 100644
index 0000000000..1557a59d3f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/DOM/Text.js
@@ -0,0 +1,15 @@
+loadPage("file:///res/html/misc/blank.html");
+
+afterInitialPageLoad(() => {
+ test("Basic functionality", () => {
+ const title = document.getElementsByTagName("title")[0];
+ expect(title).toBeDefined();
+
+ // FIXME: Add this in once Text's constructor is implemented.
+ //expect(title.firstChild).toBeInstanceOf(Text);
+
+ expect(title.firstChild.nodeName).toBe("#text");
+ expect(title.firstChild.data).toBe("Blank");
+ expect(title.firstChild.length).toBe(5);
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/DOM/document.createComment.js b/Userland/Libraries/LibWeb/Tests/DOM/document.createComment.js
new file mode 100644
index 0000000000..a60d6bfcd5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/DOM/document.createComment.js
@@ -0,0 +1,13 @@
+loadPage("file:///res/html/misc/blank.html");
+
+afterInitialPageLoad(() => {
+ test("Basic functionality", () => {
+ const comment = document.createComment("Create Comment Test");
+
+ // FIXME: Add this in once Comment's constructor is implemented.
+ //expect(comment).toBeInstanceOf(Comment);
+ expect(comment.nodeName).toBe("#comment");
+ expect(comment.data).toBe("Create Comment Test");
+ expect(comment.length).toBe(19);
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/DOM/document.createDocumentFragment.js b/Userland/Libraries/LibWeb/Tests/DOM/document.createDocumentFragment.js
new file mode 100644
index 0000000000..bebc137313
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/DOM/document.createDocumentFragment.js
@@ -0,0 +1,11 @@
+loadPage("file:///res/html/misc/blank.html");
+
+afterInitialPageLoad(() => {
+ test("Basic functionality", () => {
+ const fragment = document.createDocumentFragment();
+
+ // FIXME: Add this in once DocumentFragment's constructor is implemented.
+ //expect(fragment).toBeInstanceOf(DocumentFragment);
+ expect(fragment.nodeName).toBe("#document-fragment");
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/DOM/document.createTextNode.js b/Userland/Libraries/LibWeb/Tests/DOM/document.createTextNode.js
new file mode 100644
index 0000000000..bef06a81c7
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/DOM/document.createTextNode.js
@@ -0,0 +1,13 @@
+loadPage("file:///res/html/misc/blank.html");
+
+afterInitialPageLoad(() => {
+ test("Basic functionality", () => {
+ const text = document.createTextNode("Create Text Test");
+
+ // FIXME: Add this in once Text's constructor is implemented.
+ //expect(text).toBeInstanceOf(Text);
+ expect(text.nodeName).toBe("#text");
+ expect(text.data).toBe("Create Text Test");
+ expect(text.length).toBe(16);
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/DOM/document.doctype.js b/Userland/Libraries/LibWeb/Tests/DOM/document.doctype.js
new file mode 100644
index 0000000000..5da52014d1
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/DOM/document.doctype.js
@@ -0,0 +1,18 @@
+loadPage("file:///res/html/misc/blank.html");
+
+afterInitialPageLoad(() => {
+ test("Basic functionality", () => {
+ expect(document.compatMode).toBe("CSS1Compat");
+ expect(document.doctype).not.toBeNull();
+ expect(document.doctype.name).toBe("html");
+ expect(document.doctype.publicId).toBe("");
+ expect(document.doctype.systemId).toBe("");
+ });
+
+ libweb_tester.changePage("file:///res/html/misc/blank-no-doctype.html");
+
+ test("Quirks mode", () => {
+ expect(document.compatMode).toBe("BackCompat");
+ expect(document.doctype).toBeNull();
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/DOM/document.documentElement.js b/Userland/Libraries/LibWeb/Tests/DOM/document.documentElement.js
new file mode 100644
index 0000000000..86d885c709
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/DOM/document.documentElement.js
@@ -0,0 +1,16 @@
+loadPage("file:///res/html/misc/blank.html");
+
+afterInitialPageLoad(() => {
+ test("Basic functionality", () => {
+ expect(document.documentElement).not.toBeNull();
+ // FIXME: Add this in once HTMLHtmlElement's constructor is implemented.
+ //expect(document.documentElement).toBeInstanceOf(HTMLHtmlElement);
+ expect(document.documentElement.nodeName).toBe("html");
+ });
+
+ // FIXME: Add this in once removeChild is implemented.
+ test.skip("Nullable", () => {
+ document.removeChild(document.documentElement);
+ expect(document.documentElement).toBeNull();
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/DOM/mixins/NonElementParentNode.js b/Userland/Libraries/LibWeb/Tests/DOM/mixins/NonElementParentNode.js
new file mode 100644
index 0000000000..5b269025f4
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/DOM/mixins/NonElementParentNode.js
@@ -0,0 +1,19 @@
+loadPage("file:///home/anon/web-tests/Pages/ParentNode.html");
+
+afterInitialPageLoad(() => {
+ test("getElementById basics", () => {
+ const unique = document.getElementById("unique");
+ expect(unique).not.toBeNull();
+ expect(unique.nodeName).toBe("div");
+ expect(unique.id).toBe("unique");
+
+ const caseSensitive = document.getElementById("Unique");
+ expect(caseSensitive).toBeNull();
+
+ const firstDuplicate = document.getElementById("dupeId");
+ expect(firstDuplicate).not.toBeNull();
+ expect(firstDuplicate.nodeName).toBe("div");
+ expect(firstDuplicate.id).toBe("dupeId");
+ expect(firstDuplicate.innerHTML).toBe("First ID");
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/DOM/mixins/ParentNode.js b/Userland/Libraries/LibWeb/Tests/DOM/mixins/ParentNode.js
new file mode 100644
index 0000000000..3558c0352f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/DOM/mixins/ParentNode.js
@@ -0,0 +1,25 @@
+loadPage("file:///home/anon/web-tests/Pages/ParentNode.html");
+
+afterInitialPageLoad(() => {
+ test("querySelector basics", () => {
+ const firstDuplicateElement = document.querySelector(".duplicate");
+ expect(firstDuplicateElement).not.toBeNull();
+ expect(firstDuplicateElement.nodeName).toBe("div");
+ expect(firstDuplicateElement.innerHTML).toBe("First");
+
+ const noElement = document.querySelector(".nonexistent");
+ expect(noElement).toBeNull();
+ });
+
+ test("querySelectorAll basics", () => {
+ const allDuplicates = document.querySelectorAll(".duplicate");
+ expect(allDuplicates).toHaveLength(2);
+ expect(allDuplicates[0].nodeName).toBe("div");
+ expect(allDuplicates[0].innerHTML).toBe("First");
+ expect(allDuplicates[1].nodeName).toBe("div");
+ expect(allDuplicates[1].innerHTML).toBe("Second");
+
+ const noElements = document.querySelectorAll(".nonexistent");
+ expect(noElements).toHaveLength(0);
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/HTML/HTMLElement.js b/Userland/Libraries/LibWeb/Tests/HTML/HTMLElement.js
new file mode 100644
index 0000000000..2950bb45c5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/HTML/HTMLElement.js
@@ -0,0 +1,9 @@
+loadPage("file:///res/html/misc/welcome.html");
+
+afterInitialPageLoad(() => {
+ test("contentEditable attribute", () => {
+ expect(document.body.contentEditable).toBe("inherit");
+ expect(document.firstChild.nextSibling.nodeName).toBe("html");
+ expect(document.firstChild.nextSibling.contentEditable).toBe("true");
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/HTML/HTMLTemplateElement.js b/Userland/Libraries/LibWeb/Tests/HTML/HTMLTemplateElement.js
new file mode 100644
index 0000000000..dd6c66e1f2
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/HTML/HTMLTemplateElement.js
@@ -0,0 +1,27 @@
+loadPage("file:///home/anon/web-tests/Pages/Template.html");
+
+afterInitialPageLoad(() => {
+ test("Basic functionality", () => {
+ const template = document.getElementById("template");
+ expect(template).not.toBeNull();
+
+ // The contents of a template element are not children of the actual element.
+ // The document fragment is not a child of the element either.
+ expect(template.firstChild).toBeNull();
+
+ // FIXME: Add this in once DocumentFragment's constructor is implemented.
+ //expect(template.content).toBeInstanceOf(DocumentFragment);
+ expect(template.content.nodeName).toBe("#document-fragment");
+
+ const templateDiv = template.content.getElementById("templatediv");
+ expect(templateDiv.nodeName).toBe("div");
+ expect(templateDiv.textContent).toBe("Hello template!");
+ });
+
+ test("Templates are inert (e.g. scripts won't run)", () => {
+ // The page has a template element with a script element in it.
+ // Templates are inert, for example, they won't run scripts.
+ // That script will set "templateScriptRan" to true if it ran.
+ expect(window.templateScriptRan).toBeUndefined();
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/HTML/document.body.js b/Userland/Libraries/LibWeb/Tests/HTML/document.body.js
new file mode 100644
index 0000000000..9fc1907583
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/HTML/document.body.js
@@ -0,0 +1,55 @@
+loadPage("file:///res/html/misc/blank.html");
+
+afterInitialPageLoad(() => {
+ test("Basic functionality", () => {
+ expect(document.body).not.toBeNull();
+ // FIXME: Add this in once HTMLBodyElement's constructor is implemented.
+ //expect(document.body).toBeInstanceOf(HTMLBodyElement);
+ expect(document.body.nodeName).toBe("body");
+ });
+
+ // FIXME: Add this in once set_body is fully implemented.
+ test.skip("Setting body to a new body element", () => {
+ // Add something to body to see if it's gone afterwards
+ const p = document.createElement("p");
+ document.body.appendChild(p);
+
+ expect(document.body.firstChild).toBe(p);
+
+ const newBody = document.createElement("body");
+ document.body = newBody;
+
+ expect(document.body).not.toBeNull();
+ expect(document.body.nodeName).toBe("body");
+
+ // FIXME: Add this in once HTMLBodyElement's constructor is implemented.
+ //expect(document.body).toBeInstanceOf(HTMLBodyElement);
+
+ expect(document.body.firstChild).toBeNull();
+ });
+
+ // FIXME: Add this in once set_body is fully implemented.
+ test.skip("Setting body to a new frameset element", () => {
+ const newFrameSet = document.createElement("frameset");
+ document.body = newFrameSet;
+
+ expect(document.body).not.toBeNull();
+ expect(document.body.nodeName).toBe("frameset");
+
+ // FIXME: Add this in once HTMLFrameSetElement's constructor is implemented.
+ //expect(document.body).toBeInstanceOf(HTMLFrameSetElement);
+ });
+
+ // FIXME: Add this in once set_body is fully implemented.
+ test.skip("Setting body to an element that isn't body/frameset", () => {
+ expect(() => {
+ document.body = document.createElement("div");
+ }).toThrow(DOMException);
+ });
+
+ // FIXME: Add this in once removeChild is implemented.
+ test.skip("Nullable", () => {
+ document.documentElement.removeChild(document.body);
+ expect(document.body).toBeNull();
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/HTML/document.head.js b/Userland/Libraries/LibWeb/Tests/HTML/document.head.js
new file mode 100644
index 0000000000..5d997fe3dd
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/HTML/document.head.js
@@ -0,0 +1,16 @@
+loadPage("file:///res/html/misc/blank.html");
+
+afterInitialPageLoad(() => {
+ test("Basic functionality", () => {
+ expect(document.head).not.toBeNull();
+ // FIXME: Add this in once HTMLHeadElement's constructor is implemented.
+ //expect(document.head).toBeInstanceOf(HTMLHeadElement);
+ expect(document.head.nodeName).toBe("head");
+ });
+
+ // FIXME: Add this in once removeChild is implemented.
+ test.skip("Nullable", () => {
+ document.documentElement.removeChild(document.head);
+ expect(document.head).toBeNull();
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/HTML/document.readyState.js b/Userland/Libraries/LibWeb/Tests/HTML/document.readyState.js
new file mode 100644
index 0000000000..12b6c1d8df
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/HTML/document.readyState.js
@@ -0,0 +1,31 @@
+loadPage("file:///res/html/misc/blank.html");
+
+beforeInitialPageLoad(() => {
+ window.events = [];
+
+ document.addEventListener("readystatechange", () => {
+ window.events.push(document.readyState);
+ });
+
+ document.addEventListener("DOMContentLoaded", () => {
+ test("Ready state should be 'interactive' when 'DOMContentLoaded' fires", () => {
+ expect(document.readyState).toBe("interactive");
+ });
+ });
+
+ test("Ready state should be 'loading' initially", () => {
+ expect(document.readyState).toBe("loading");
+ });
+});
+
+afterInitialPageLoad(() => {
+ test("'interactive' should come before 'complete' and both should have happened", () => {
+ expect(window.events).toHaveLength(2);
+ expect(window.events[0]).toBe("interactive");
+ expect(window.events[1]).toBe("complete");
+ });
+
+ test("Ready state should be 'complete' after loading", () => {
+ expect(document.readyState).toBe("complete");
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/Pages/Comment.html b/Userland/Libraries/LibWeb/Tests/Pages/Comment.html
new file mode 100644
index 0000000000..84ae91695f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/Pages/Comment.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<!--This is a comment-->
+</body>
+</html>
diff --git a/Userland/Libraries/LibWeb/Tests/Pages/ParentNode.html b/Userland/Libraries/LibWeb/Tests/Pages/ParentNode.html
new file mode 100644
index 0000000000..536b5ab35d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/Pages/ParentNode.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+ <div class="duplicate">First</div>
+ <div class="duplicate">Second</div>
+ <div id="unique"></div>
+ <div id="dupeId">First ID</div>
+ <div id="dupeId">Second ID</div>
+</body>
+</html>
diff --git a/Userland/Libraries/LibWeb/Tests/Pages/Template.html b/Userland/Libraries/LibWeb/Tests/Pages/Template.html
new file mode 100644
index 0000000000..e4478f408f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/Pages/Template.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<template id="template">
+ <div id="templatediv">Hello template!</div>
+ <script>
+ // I shouldn't be run.
+ window.templateScriptRan = true;
+ </script>
+</template>
+</body>
+</html>
diff --git a/Userland/Libraries/LibWeb/Tests/Window/Base64.js b/Userland/Libraries/LibWeb/Tests/Window/Base64.js
new file mode 100644
index 0000000000..c272d70b74
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/Window/Base64.js
@@ -0,0 +1,19 @@
+loadPage("file:///res/html/misc/blank.html");
+
+afterInitialPageLoad(() => {
+ test("atob", () => {
+ expect(atob("YQ==")).toBe("a");
+ expect(atob("YWE=")).toBe("aa");
+ expect(atob("YWFh")).toBe("aaa");
+ expect(atob("YWFhYQ==")).toBe("aaaa");
+ expect(atob("/w==")).toBe("\xff");
+ });
+
+ test("btoa", () => {
+ expect(btoa("a")).toBe("YQ==");
+ expect(btoa("aa")).toBe("YWE=");
+ expect(btoa("aaa")).toBe("YWFh");
+ expect(btoa("aaaa")).toBe("YWFhYQ==");
+ expect(btoa("\xff")).toBe("/w==");
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/Window/window.window_frames_self.js b/Userland/Libraries/LibWeb/Tests/Window/window.window_frames_self.js
new file mode 100644
index 0000000000..1d7799843e
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/Window/window.window_frames_self.js
@@ -0,0 +1,8 @@
+loadPage("file:///res/html/misc/blank.html");
+
+afterInitialPageLoad(() => {
+ test("window.{window,frames,self} all return the Window object", () => {
+ expect(window.window).toBe(window.frames);
+ expect(window.window).toBe(window.self);
+ });
+});
diff --git a/Userland/Libraries/LibWeb/Tests/libweb_tester.d.ts b/Userland/Libraries/LibWeb/Tests/libweb_tester.d.ts
new file mode 100644
index 0000000000..23570e70f8
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/libweb_tester.d.ts
@@ -0,0 +1,16 @@
+// NOTE: This file is only for syntax highlighting, documentation, etc.
+
+interface LibwebTester {
+ /**
+ * Changes the page to the specified URL. Everything afterwards will refer to the new page.
+ * @param url Page to load.
+ */
+ changePage(url: string): void;
+}
+
+interface Window {
+ /**
+ * Special test object used to ease test development for LibWeb.
+ */
+ readonly libweb_tester: LibwebTester;
+}
diff --git a/Userland/Libraries/LibWeb/Tests/test-common.js b/Userland/Libraries/LibWeb/Tests/test-common.js
new file mode 100644
index 0000000000..7fc4f914bf
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Tests/test-common.js
@@ -0,0 +1,31 @@
+// NOTE: The tester loads in LibJS's test-common to prevent duplication.
+
+// NOTE: "window.libweb_tester" is set to a special tester object.
+// See libweb_tester.d.ts for definitions.
+
+let __PageToLoad__;
+
+// This tells the tester which page to load.
+// This will only be checked when we look at which page the test wants to use.
+// Subsequent calls to loadPage in before/after initial load will be ignored.
+let loadPage;
+
+let __BeforeInitialPageLoad__ = () => {};
+
+// This function will be called just before loading the initial page.
+// This is useful for injecting event listeners.
+// Defaults to an empty function.
+let beforeInitialPageLoad;
+
+let __AfterInitialPageLoad__ = () => {};
+
+// This function will be called just after loading the initial page.
+// This is where the main bulk of the tests should be.
+// Defaults to an empty function.
+let afterInitialPageLoad;
+
+(() => {
+ loadPage = page => (__PageToLoad__ = page);
+ beforeInitialPageLoad = callback => (__BeforeInitialPageLoad__ = callback);
+ afterInitialPageLoad = callback => (__AfterInitialPageLoad__ = callback);
+})();
diff --git a/Userland/Libraries/LibWeb/TreeNode.h b/Userland/Libraries/LibWeb/TreeNode.h
new file mode 100644
index 0000000000..1673a52dc5
--- /dev/null
+++ b/Userland/Libraries/LibWeb/TreeNode.h
@@ -0,0 +1,443 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Assertions.h>
+#include <AK/NonnullRefPtr.h>
+#include <AK/TypeCasts.h>
+#include <AK/Weakable.h>
+#include <LibWeb/Forward.h>
+
+namespace Web {
+
+template<typename T>
+class TreeNode : public Weakable<T> {
+public:
+ void ref()
+ {
+ ASSERT(!m_in_removed_last_ref);
+ ASSERT(m_ref_count);
+ ++m_ref_count;
+ }
+
+ void unref()
+ {
+ ASSERT(!m_in_removed_last_ref);
+ ASSERT(m_ref_count);
+ if (!--m_ref_count) {
+ if constexpr (IsBaseOf<DOM::Node, T>::value) {
+ m_in_removed_last_ref = true;
+ static_cast<T*>(this)->removed_last_ref();
+ } else {
+ delete static_cast<T*>(this);
+ }
+ return;
+ }
+ }
+ int ref_count() const { return m_ref_count; }
+
+ T* parent() { return m_parent; }
+ const T* parent() const { return m_parent; }
+
+ bool has_children() const { return m_first_child; }
+ T* next_sibling() { return m_next_sibling; }
+ T* previous_sibling() { return m_previous_sibling; }
+ T* first_child() { return m_first_child; }
+ T* last_child() { return m_last_child; }
+ const T* next_sibling() const { return m_next_sibling; }
+ const T* previous_sibling() const { return m_previous_sibling; }
+ const T* first_child() const { return m_first_child; }
+ const T* last_child() const { return m_last_child; }
+
+ int child_count() const
+ {
+ int count = 0;
+ for (auto* child = first_child(); child; child = child->next_sibling())
+ ++count;
+ return count;
+ }
+
+ T* child_at_index(int index)
+ {
+ int count = 0;
+ for (auto* child = first_child(); child; child = child->next_sibling()) {
+ if (count == index)
+ return child;
+ ++count;
+ }
+ return nullptr;
+ }
+
+ const T* child_at_index(int index) const
+ {
+ return const_cast<TreeNode*>(this)->child_at_index(index);
+ }
+
+ bool is_ancestor_of(const TreeNode&) const;
+
+ void prepend_child(NonnullRefPtr<T> node);
+ void append_child(NonnullRefPtr<T> node, bool notify = true);
+ void insert_before(NonnullRefPtr<T> node, RefPtr<T> child, bool notify = true);
+ NonnullRefPtr<T> remove_child(NonnullRefPtr<T> node);
+
+ void remove_all_children();
+
+ bool is_child_allowed(const T&) const { return true; }
+
+ T* next_in_pre_order()
+ {
+ if (first_child())
+ return first_child();
+ T* node;
+ if (!(node = next_sibling())) {
+ node = parent();
+ while (node && !node->next_sibling())
+ node = node->parent();
+ if (node)
+ node = node->next_sibling();
+ }
+ return node;
+ }
+
+ const T* next_in_pre_order() const
+ {
+ return const_cast<TreeNode*>(this)->next_in_pre_order();
+ }
+
+ bool is_before(const T& other) const
+ {
+ if (this == &other)
+ return false;
+ for (auto* node = this; node; node = node->next_in_pre_order()) {
+ if (node == &other)
+ return true;
+ }
+ return false;
+ }
+
+ template<typename Callback>
+ IterationDecision for_each_in_subtree(Callback callback) const
+ {
+ if (callback(static_cast<const T&>(*this)) == IterationDecision::Break)
+ return IterationDecision::Break;
+ for (auto* child = first_child(); child; child = child->next_sibling()) {
+ if (child->for_each_in_subtree(callback) == IterationDecision::Break)
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ }
+
+ template<typename Callback>
+ IterationDecision for_each_in_subtree(Callback callback)
+ {
+ if (callback(static_cast<T&>(*this)) == IterationDecision::Break)
+ return IterationDecision::Break;
+ for (auto* child = first_child(); child; child = child->next_sibling()) {
+ if (child->for_each_in_subtree(callback) == IterationDecision::Break)
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ }
+
+ template<typename U, typename Callback>
+ IterationDecision for_each_in_subtree_of_type(Callback callback)
+ {
+ if (is<U>(static_cast<const T&>(*this))) {
+ if (callback(static_cast<U&>(*this)) == IterationDecision::Break)
+ return IterationDecision::Break;
+ }
+ for (auto* child = first_child(); child; child = child->next_sibling()) {
+ if (child->template for_each_in_subtree_of_type<U>(callback) == IterationDecision::Break)
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ }
+
+ template<typename U, typename Callback>
+ IterationDecision for_each_in_subtree_of_type(Callback callback) const
+ {
+ if (is<U>(static_cast<const T&>(*this))) {
+ if (callback(static_cast<const U&>(*this)) == IterationDecision::Break)
+ return IterationDecision::Break;
+ }
+ for (auto* child = first_child(); child; child = child->next_sibling()) {
+ if (child->template for_each_in_subtree_of_type<U>(callback) == IterationDecision::Break)
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ }
+
+ template<typename Callback>
+ void for_each_child(Callback callback) const
+ {
+ return const_cast<TreeNode*>(this)->template for_each_child(move(callback));
+ }
+
+ template<typename Callback>
+ void for_each_child(Callback callback)
+ {
+ for (auto* node = first_child(); node; node = node->next_sibling())
+ callback(*node);
+ }
+
+ template<typename U, typename Callback>
+ void for_each_child_of_type(Callback callback)
+ {
+ for (auto* node = first_child(); node; node = node->next_sibling()) {
+ if (is<U>(node))
+ callback(downcast<U>(*node));
+ }
+ }
+
+ template<typename U, typename Callback>
+ void for_each_child_of_type(Callback callback) const
+ {
+ return const_cast<TreeNode*>(this)->template for_each_child_of_type<U>(move(callback));
+ }
+
+ template<typename U>
+ const U* next_sibling_of_type() const
+ {
+ return const_cast<TreeNode*>(this)->template next_sibling_of_type<U>();
+ }
+
+ template<typename U>
+ inline U* next_sibling_of_type()
+ {
+ for (auto* sibling = next_sibling(); sibling; sibling = sibling->next_sibling()) {
+ if (is<U>(*sibling))
+ return &downcast<U>(*sibling);
+ }
+ return nullptr;
+ }
+
+ template<typename U>
+ const U* previous_sibling_of_type() const
+ {
+ return const_cast<TreeNode*>(this)->template previous_sibling_of_type<U>();
+ }
+
+ template<typename U>
+ U* previous_sibling_of_type()
+ {
+ for (auto* sibling = previous_sibling(); sibling; sibling = sibling->previous_sibling()) {
+ if (is<U>(*sibling))
+ return &downcast<U>(*sibling);
+ }
+ return nullptr;
+ }
+
+ template<typename U>
+ const U* first_child_of_type() const
+ {
+ return const_cast<TreeNode*>(this)->template first_child_of_type<U>();
+ }
+
+ template<typename U>
+ const U* last_child_of_type() const
+ {
+ return const_cast<TreeNode*>(this)->template last_child_of_type<U>();
+ }
+
+ template<typename U>
+ U* first_child_of_type()
+ {
+ for (auto* child = first_child(); child; child = child->next_sibling()) {
+ if (is<U>(*child))
+ return &downcast<U>(*child);
+ }
+ return nullptr;
+ }
+
+ template<typename U>
+ U* last_child_of_type()
+ {
+ for (auto* child = last_child(); child; child = child->previous_sibling()) {
+ if (is<U>(*child))
+ return &downcast<U>(*child);
+ }
+ return nullptr;
+ }
+
+ template<typename U>
+ const U* first_ancestor_of_type() const
+ {
+ return const_cast<TreeNode*>(this)->template first_ancestor_of_type<U>();
+ }
+
+ template<typename U>
+ U* first_ancestor_of_type()
+ {
+ for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
+ if (is<U>(*ancestor))
+ return &downcast<U>(*ancestor);
+ }
+ return nullptr;
+ }
+
+protected:
+ TreeNode() { }
+
+ bool m_deletion_has_begun { false };
+ bool m_in_removed_last_ref { false };
+
+private:
+ int m_ref_count { 1 };
+ T* m_parent { nullptr };
+ T* m_first_child { nullptr };
+ T* m_last_child { nullptr };
+ T* m_next_sibling { nullptr };
+ T* m_previous_sibling { nullptr };
+};
+
+template<typename T>
+inline void TreeNode<T>::remove_all_children()
+{
+ while (RefPtr<T> child = first_child())
+ remove_child(child.release_nonnull());
+}
+
+template<typename T>
+inline NonnullRefPtr<T> TreeNode<T>::remove_child(NonnullRefPtr<T> node)
+{
+ ASSERT(node->m_parent == this);
+
+ if (m_first_child == node)
+ m_first_child = node->m_next_sibling;
+
+ if (m_last_child == node)
+ m_last_child = node->m_previous_sibling;
+
+ if (node->m_next_sibling)
+ node->m_next_sibling->m_previous_sibling = node->m_previous_sibling;
+
+ if (node->m_previous_sibling)
+ node->m_previous_sibling->m_next_sibling = node->m_next_sibling;
+
+ node->m_next_sibling = nullptr;
+ node->m_previous_sibling = nullptr;
+ node->m_parent = nullptr;
+
+ node->removed_from(static_cast<T&>(*this));
+
+ node->unref();
+
+ static_cast<T*>(this)->children_changed();
+
+ return node;
+}
+
+template<typename T>
+inline void TreeNode<T>::append_child(NonnullRefPtr<T> node, bool notify)
+{
+ ASSERT(!node->m_parent);
+
+ if (!static_cast<T*>(this)->is_child_allowed(*node))
+ return;
+
+ if (m_last_child)
+ m_last_child->m_next_sibling = node.ptr();
+ node->m_previous_sibling = m_last_child;
+ node->m_parent = static_cast<T*>(this);
+ m_last_child = node.ptr();
+ if (!m_first_child)
+ m_first_child = m_last_child;
+ if (notify)
+ node->inserted_into(static_cast<T&>(*this));
+ [[maybe_unused]] auto& rc = node.leak_ref();
+
+ if (notify)
+ static_cast<T*>(this)->children_changed();
+}
+
+template<typename T>
+inline void TreeNode<T>::insert_before(NonnullRefPtr<T> node, RefPtr<T> child, bool notify)
+{
+ if (!child)
+ return append_child(move(node), notify);
+
+ ASSERT(!node->m_parent);
+ ASSERT(child->parent() == this);
+
+ if (!static_cast<T*>(this)->is_child_allowed(*node))
+ return;
+
+ node->m_previous_sibling = child->m_previous_sibling;
+ node->m_next_sibling = child;
+
+ if (child->m_previous_sibling)
+ child->m_previous_sibling->m_next_sibling = node;
+
+ if (m_first_child == child)
+ m_first_child = node;
+
+ child->m_previous_sibling = node;
+
+ if (m_first_child == child)
+ m_first_child = node;
+
+ node->m_parent = static_cast<T*>(this);
+ if (notify)
+ node->inserted_into(static_cast<T&>(*this));
+ [[maybe_unused]] auto& rc = node.leak_ref();
+
+ if (notify)
+ static_cast<T*>(this)->children_changed();
+}
+
+template<typename T>
+inline void TreeNode<T>::prepend_child(NonnullRefPtr<T> node)
+{
+ ASSERT(!node->m_parent);
+
+ if (!static_cast<T*>(this)->is_child_allowed(*node))
+ return;
+
+ if (m_first_child)
+ m_first_child->m_previous_sibling = node.ptr();
+ node->m_next_sibling = m_first_child;
+ node->m_parent = static_cast<T*>(this);
+ m_first_child = node.ptr();
+ if (!m_last_child)
+ m_last_child = m_first_child;
+ node->inserted_into(static_cast<T&>(*this));
+ [[maybe_unused]] auto& rc = node.leak_ref();
+
+ static_cast<T*>(this)->children_changed();
+}
+
+template<typename T>
+inline bool TreeNode<T>::is_ancestor_of(const TreeNode<T>& other) const
+{
+ for (auto* ancestor = other.parent(); ancestor; ancestor = ancestor->parent()) {
+ if (ancestor == this)
+ return true;
+ }
+ return false;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/UIEvents/EventNames.cpp b/Userland/Libraries/LibWeb/UIEvents/EventNames.cpp
new file mode 100644
index 0000000000..4832ab493b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/UIEvents/EventNames.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/UIEvents/EventNames.h>
+
+namespace Web::UIEvents::EventNames {
+
+#define __ENUMERATE_UI_EVENT(name) FlyString name;
+ENUMERATE_UI_EVENTS
+#undef __ENUMERATE_UI_EVENT
+
+[[gnu::constructor]] static void initialize()
+{
+ static bool s_initialized = false;
+ if (s_initialized)
+ return;
+
+#define __ENUMERATE_UI_EVENT(name) \
+ name = #name;
+ ENUMERATE_UI_EVENTS
+#undef __ENUMERATE_UI_EVENT
+
+ s_initialized = true;
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/UIEvents/EventNames.h b/Userland/Libraries/LibWeb/UIEvents/EventNames.h
new file mode 100644
index 0000000000..3667a32f9b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/UIEvents/EventNames.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+
+namespace Web::UIEvents::EventNames {
+
+// FIXME: This is not all of the events
+
+#define ENUMERATE_UI_EVENTS \
+ __ENUMERATE_UI_EVENT(click) \
+ __ENUMERATE_UI_EVENT(mousedown) \
+ __ENUMERATE_UI_EVENT(mouseenter) \
+ __ENUMERATE_UI_EVENT(mouseleave) \
+ __ENUMERATE_UI_EVENT(mousemove) \
+ __ENUMERATE_UI_EVENT(mouseout) \
+ __ENUMERATE_UI_EVENT(mouseover) \
+ __ENUMERATE_UI_EVENT(mouseup)
+
+#define __ENUMERATE_UI_EVENT(name) extern FlyString name;
+ENUMERATE_UI_EVENTS
+#undef __ENUMERATE_UI_EVENT
+
+}
diff --git a/Userland/Libraries/LibWeb/UIEvents/MouseEvent.cpp b/Userland/Libraries/LibWeb/UIEvents/MouseEvent.cpp
new file mode 100644
index 0000000000..e102a4e138
--- /dev/null
+++ b/Userland/Libraries/LibWeb/UIEvents/MouseEvent.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibWeb/HTML/EventNames.h>
+#include <LibWeb/UIEvents/EventNames.h>
+#include <LibWeb/UIEvents/MouseEvent.h>
+
+namespace Web::UIEvents {
+
+MouseEvent::MouseEvent(const FlyString& event_name, i32 offset_x, i32 offset_y)
+ : UIEvent(event_name)
+ , m_offset_x(offset_x)
+ , m_offset_y(offset_y)
+{
+ set_event_characteristics();
+}
+
+MouseEvent::~MouseEvent()
+{
+}
+
+void MouseEvent::set_event_characteristics()
+{
+ if (type().is_one_of(EventNames::mousedown, EventNames::mousemove, EventNames::mouseout, EventNames::mouseover, EventNames::mouseup, HTML::EventNames::click)) {
+ set_bubbles(true);
+ set_cancelable(true);
+ set_composed(true);
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/UIEvents/MouseEvent.h b/Userland/Libraries/LibWeb/UIEvents/MouseEvent.h
new file mode 100644
index 0000000000..13fc8d3322
--- /dev/null
+++ b/Userland/Libraries/LibWeb/UIEvents/MouseEvent.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/TypeCasts.h>
+#include <LibWeb/UIEvents/UIEvent.h>
+
+namespace Web::UIEvents {
+
+class MouseEvent final : public UIEvents::UIEvent {
+public:
+ using WrapperType = Bindings::MouseEventWrapper;
+
+ static NonnullRefPtr<MouseEvent> create(const FlyString& event_name, i32 offset_x, i32 offset_y)
+ {
+ return adopt(*new MouseEvent(event_name, offset_x, offset_y));
+ }
+
+ virtual ~MouseEvent() override;
+
+ i32 offset_x() const { return m_offset_x; }
+ i32 offset_y() const { return m_offset_y; }
+
+protected:
+ MouseEvent(const FlyString& event_name, i32 offset_x, i32 offset_y);
+
+private:
+ void set_event_characteristics();
+
+ i32 m_offset_x { 0 };
+ i32 m_offset_y { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/UIEvents/MouseEvent.idl b/Userland/Libraries/LibWeb/UIEvents/MouseEvent.idl
new file mode 100644
index 0000000000..2751f2353a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/UIEvents/MouseEvent.idl
@@ -0,0 +1,6 @@
+interface MouseEvent : Event {
+
+ readonly attribute double offsetX;
+ readonly attribute double offsetY;
+
+};
diff --git a/Userland/Libraries/LibWeb/UIEvents/UIEvent.h b/Userland/Libraries/LibWeb/UIEvents/UIEvent.h
new file mode 100644
index 0000000000..cbb8ee7de9
--- /dev/null
+++ b/Userland/Libraries/LibWeb/UIEvents/UIEvent.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibWeb/DOM/Event.h>
+
+namespace Web::UIEvents {
+
+class UIEvent : public DOM::Event {
+public:
+ using WrapperType = Bindings::UIEventWrapper;
+
+ virtual ~UIEvent() override { }
+
+protected:
+ explicit UIEvent(const FlyString& event_name)
+ : Event(event_name)
+ {
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/UIEvents/UIEvent.idl b/Userland/Libraries/LibWeb/UIEvents/UIEvent.idl
new file mode 100644
index 0000000000..f885f4669a
--- /dev/null
+++ b/Userland/Libraries/LibWeb/UIEvents/UIEvent.idl
@@ -0,0 +1,3 @@
+interface UIEvent : Event {
+
+};
diff --git a/Userland/Libraries/LibWeb/URLEncoder.cpp b/Userland/Libraries/LibWeb/URLEncoder.cpp
new file mode 100644
index 0000000000..a6be0174ea
--- /dev/null
+++ b/Userland/Libraries/LibWeb/URLEncoder.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <AK/URLParser.h>
+#include <LibWeb/URLEncoder.h>
+
+namespace Web {
+
+String urlencode(const Vector<URLQueryParam>& pairs)
+{
+ StringBuilder builder;
+ for (size_t i = 0; i < pairs.size(); ++i) {
+ builder.append(urlencode(pairs[i].name));
+ builder.append('=');
+ builder.append(urlencode(pairs[i].value));
+ if (i != pairs.size() - 1)
+ builder.append('&');
+ }
+ return builder.to_string();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/URLEncoder.h b/Userland/Libraries/LibWeb/URLEncoder.h
new file mode 100644
index 0000000000..ad8d3d14aa
--- /dev/null
+++ b/Userland/Libraries/LibWeb/URLEncoder.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/Vector.h>
+
+namespace Web {
+
+struct URLQueryParam {
+ String name;
+ String value;
+};
+
+String urlencode(const Vector<URLQueryParam>&);
+
+}
diff --git a/Userland/Libraries/LibWeb/WebContentClient.cpp b/Userland/Libraries/LibWeb/WebContentClient.cpp
new file mode 100644
index 0000000000..870b691d3b
--- /dev/null
+++ b/Userland/Libraries/LibWeb/WebContentClient.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "WebContentClient.h"
+#include "OutOfProcessWebView.h"
+#include <AK/SharedBuffer.h>
+
+namespace Web {
+
+WebContentClient::WebContentClient(OutOfProcessWebView& view)
+ : IPC::ServerConnection<WebContentClientEndpoint, WebContentServerEndpoint>(*this, "/tmp/portal/webcontent")
+ , m_view(view)
+{
+ handshake();
+}
+
+void WebContentClient::handshake()
+{
+ auto response = send_sync<Messages::WebContentServer::Greet>(getpid());
+ set_my_client_id(response->client_id());
+ set_server_pid(response->server_pid());
+}
+
+void WebContentClient::handle(const Messages::WebContentClient::DidPaint& message)
+{
+#ifdef DEBUG_SPAM
+ dbg() << "handle: WebContentClient::DidPaint! content_rect=" << message.content_rect() << ", shbuf_id=" << message.shbuf_id();
+#endif
+ m_view.notify_server_did_paint({}, message.shbuf_id());
+}
+
+void WebContentClient::handle([[maybe_unused]] const Messages::WebContentClient::DidFinishLoading& message)
+{
+ m_view.notify_server_did_finish_loading({}, message.url());
+}
+
+void WebContentClient::handle(const Messages::WebContentClient::DidInvalidateContentRect& message)
+{
+#ifdef DEBUG_SPAM
+ dbg() << "handle: WebContentClient::DidInvalidateContentRect! content_rect=" << message.content_rect();
+#endif
+
+ // FIXME: Figure out a way to coalesce these messages to reduce unnecessary painting
+ m_view.notify_server_did_invalidate_content_rect({}, message.content_rect());
+}
+
+void WebContentClient::handle(const Messages::WebContentClient::DidChangeSelection&)
+{
+#ifdef DEBUG_SPAM
+ dbgln("handle: WebContentClient::DidChangeSelection!");
+#endif
+ m_view.notify_server_did_change_selection({});
+}
+
+void WebContentClient::handle(const Messages::WebContentClient::DidLayout& message)
+{
+#ifdef DEBUG_SPAM
+ dbg() << "handle: WebContentClient::DidLayout! content_size=" << message.content_size();
+#endif
+ m_view.notify_server_did_layout({}, message.content_size());
+}
+
+void WebContentClient::handle(const Messages::WebContentClient::DidChangeTitle& message)
+{
+#ifdef DEBUG_SPAM
+ dbg() << "handle: WebContentClient::DidChangeTitle! title=" << message.title();
+#endif
+ m_view.notify_server_did_change_title({}, message.title());
+}
+
+void WebContentClient::handle(const Messages::WebContentClient::DidRequestScrollIntoView& message)
+{
+#ifdef DEBUG_SPAM
+ dbg() << "handle: WebContentClient::DidRequestScrollIntoView! rect=" << message.rect();
+#endif
+ m_view.notify_server_did_request_scroll_into_view({}, message.rect());
+}
+
+void WebContentClient::handle(const Messages::WebContentClient::DidHoverLink& message)
+{
+#ifdef DEBUG_SPAM
+ dbg() << "handle: WebContentClient::DidHoverLink! url=" << message.url();
+#endif
+ m_view.notify_server_did_hover_link({}, message.url());
+}
+
+void WebContentClient::handle(const Messages::WebContentClient::DidUnhoverLink&)
+{
+#ifdef DEBUG_SPAM
+ dbgln("handle: WebContentClient::DidUnhoverLink!");
+#endif
+ m_view.notify_server_did_unhover_link({});
+}
+
+void WebContentClient::handle(const Messages::WebContentClient::DidClickLink& message)
+{
+ m_view.notify_server_did_click_link({}, message.url(), message.target(), message.modifiers());
+}
+
+void WebContentClient::handle(const Messages::WebContentClient::DidMiddleClickLink& message)
+{
+ m_view.notify_server_did_middle_click_link({}, message.url(), message.target(), message.modifiers());
+}
+
+void WebContentClient::handle(const Messages::WebContentClient::DidStartLoading& message)
+{
+ m_view.notify_server_did_start_loading({}, message.url());
+}
+
+void WebContentClient::handle(const Messages::WebContentClient::DidRequestContextMenu& message)
+{
+ m_view.notify_server_did_request_context_menu({}, message.content_position());
+}
+
+void WebContentClient::handle(const Messages::WebContentClient::DidRequestLinkContextMenu& message)
+{
+ m_view.notify_server_did_request_link_context_menu({}, message.content_position(), message.url(), message.target(), message.modifiers());
+}
+
+OwnPtr<Messages::WebContentClient::DidRequestAlertResponse> WebContentClient::handle(const Messages::WebContentClient::DidRequestAlert& message)
+{
+ m_view.notify_server_did_request_alert({}, message.message());
+ return make<Messages::WebContentClient::DidRequestAlertResponse>();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/WebContentClient.h b/Userland/Libraries/LibWeb/WebContentClient.h
new file mode 100644
index 0000000000..2078882578
--- /dev/null
+++ b/Userland/Libraries/LibWeb/WebContentClient.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <LibIPC/ServerConnection.h>
+#include <WebContent/WebContentClientEndpoint.h>
+#include <WebContent/WebContentServerEndpoint.h>
+
+namespace Web {
+
+class OutOfProcessWebView;
+
+class WebContentClient
+ : public IPC::ServerConnection<WebContentClientEndpoint, WebContentServerEndpoint>
+ , public WebContentClientEndpoint {
+ C_OBJECT(WebContentClient);
+
+public:
+ virtual void handshake() override;
+
+private:
+ WebContentClient(OutOfProcessWebView&);
+
+ virtual void handle(const Messages::WebContentClient::DidPaint&) override;
+ virtual void handle(const Messages::WebContentClient::DidFinishLoading&) override;
+ virtual void handle(const Messages::WebContentClient::DidInvalidateContentRect&) override;
+ virtual void handle(const Messages::WebContentClient::DidChangeSelection&) override;
+ virtual void handle(const Messages::WebContentClient::DidLayout&) override;
+ virtual void handle(const Messages::WebContentClient::DidChangeTitle&) override;
+ virtual void handle(const Messages::WebContentClient::DidRequestScrollIntoView&) override;
+ virtual void handle(const Messages::WebContentClient::DidHoverLink&) override;
+ virtual void handle(const Messages::WebContentClient::DidUnhoverLink&) override;
+ virtual void handle(const Messages::WebContentClient::DidClickLink&) override;
+ virtual void handle(const Messages::WebContentClient::DidMiddleClickLink&) override;
+ virtual void handle(const Messages::WebContentClient::DidStartLoading&) override;
+ virtual void handle(const Messages::WebContentClient::DidRequestContextMenu&) override;
+ virtual void handle(const Messages::WebContentClient::DidRequestLinkContextMenu&) override;
+ virtual OwnPtr<Messages::WebContentClient::DidRequestAlertResponse> handle(const Messages::WebContentClient::DidRequestAlert&) override;
+
+ OutOfProcessWebView& m_view;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/WebViewHooks.h b/Userland/Libraries/LibWeb/WebViewHooks.h
new file mode 100644
index 0000000000..198526e019
--- /dev/null
+++ b/Userland/Libraries/LibWeb/WebViewHooks.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <LibGfx/Forward.h>
+#include <LibWeb/Forward.h>
+
+namespace Web {
+
+class WebViewHooks {
+public:
+ Function<void(const Gfx::IntPoint& screen_position)> on_context_menu_request;
+ Function<void(const URL&, const String& target, unsigned modifiers)> on_link_click;
+ Function<void(const URL&, const Gfx::IntPoint& screen_position)> on_link_context_menu_request;
+ Function<void(const URL&, const Gfx::IntPoint& screen_position, const Gfx::ShareableBitmap&)> on_image_context_menu_request;
+ Function<void(const URL&, const String& target, unsigned modifiers)> on_link_middle_click;
+ Function<void(const URL&)> on_link_hover;
+ Function<void(const String&)> on_title_change;
+ Function<void(const URL&)> on_load_start;
+ Function<void(const URL&)> on_load_finish;
+ Function<void(const Gfx::Bitmap&)> on_favicon_change;
+ Function<void(const URL&)> on_url_drop;
+ Function<void(DOM::Document*)> on_set_document;
+};
+
+}
diff --git a/Userland/Libraries/LibX86/CMakeLists.txt b/Userland/Libraries/LibX86/CMakeLists.txt
new file mode 100644
index 0000000000..2f5dad9214
--- /dev/null
+++ b/Userland/Libraries/LibX86/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(SOURCES
+ Instruction.cpp
+)
+
+serenity_lib(LibX86 x86)
+target_link_libraries(LibX86 LibC)
diff --git a/Userland/Libraries/LibX86/Disassembler.h b/Userland/Libraries/LibX86/Disassembler.h
new file mode 100644
index 0000000000..a958c27229
--- /dev/null
+++ b/Userland/Libraries/LibX86/Disassembler.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Optional.h>
+#include <LibX86/Instruction.h>
+
+namespace X86 {
+
+class Disassembler {
+public:
+ explicit Disassembler(InstructionStream& stream)
+ : m_stream(stream)
+ {
+ }
+
+ Optional<Instruction> next()
+ {
+ if (!m_stream.can_read())
+ return {};
+ return Instruction::from_stream(m_stream, true, true);
+ }
+
+private:
+ InstructionStream& m_stream;
+};
+
+}
diff --git a/Userland/Libraries/LibX86/ELFSymbolProvider.h b/Userland/Libraries/LibX86/ELFSymbolProvider.h
new file mode 100644
index 0000000000..f208cec0a0
--- /dev/null
+++ b/Userland/Libraries/LibX86/ELFSymbolProvider.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibELF/Image.h>
+
+namespace X86 {
+
+class ELFSymbolProvider final : public SymbolProvider {
+public:
+ ELFSymbolProvider(const ELF::Image& elf)
+ : m_elf(elf)
+ {
+ }
+
+ virtual String symbolicate(FlatPtr address, u32* offset = nullptr) const override
+ {
+ return m_elf.symbolicate(address, offset);
+ }
+
+private:
+ const ELF::Image& m_elf;
+};
+}
diff --git a/Userland/Libraries/LibX86/Instruction.cpp b/Userland/Libraries/LibX86/Instruction.cpp
new file mode 100644
index 0000000000..c8713e723a
--- /dev/null
+++ b/Userland/Libraries/LibX86/Instruction.cpp
@@ -0,0 +1,1868 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringBuilder.h>
+#include <LibX86/Instruction.h>
+#include <LibX86/Interpreter.h>
+#include <stdio.h>
+
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC optimize("O3")
+#endif
+
+namespace X86 {
+
+InstructionDescriptor s_table16[256];
+InstructionDescriptor s_table32[256];
+InstructionDescriptor s_0f_table16[256];
+InstructionDescriptor s_0f_table32[256];
+
+static bool opcode_has_register_index(u8 op)
+{
+ if (op >= 0x40 && op <= 0x5F)
+ return true;
+ if (op >= 0x90 && op <= 0x97)
+ return true;
+ if (op >= 0xB0 && op <= 0xBF)
+ return true;
+ return false;
+}
+
+static void build(InstructionDescriptor* table, u8 op, const char* mnemonic, InstructionFormat format, InstructionHandler handler, IsLockPrefixAllowed lock_prefix_allowed)
+{
+ InstructionDescriptor& d = table[op];
+
+ d.handler = handler;
+ d.mnemonic = mnemonic;
+ d.format = format;
+ d.lock_prefix_allowed = lock_prefix_allowed;
+
+ if ((format > __BeginFormatsWithRMByte && format < __EndFormatsWithRMByte) || format == MultibyteWithSlash)
+ d.has_rm = true;
+ else
+ d.opcode_has_register_index = opcode_has_register_index(op);
+
+ switch (format) {
+ case OP_RM8_imm8:
+ case OP_RM16_imm8:
+ case OP_RM32_imm8:
+ case OP_reg16_RM16_imm8:
+ case OP_reg32_RM32_imm8:
+ case OP_AL_imm8:
+ case OP_imm8:
+ case OP_reg8_imm8:
+ case OP_AX_imm8:
+ case OP_EAX_imm8:
+ case OP_short_imm8:
+ case OP_imm8_AL:
+ case OP_imm8_AX:
+ case OP_imm8_EAX:
+ case OP_RM16_reg16_imm8:
+ case OP_RM32_reg32_imm8:
+ d.imm1_bytes = 1;
+ break;
+ case OP_reg16_RM16_imm16:
+ case OP_AX_imm16:
+ case OP_imm16:
+ case OP_relimm16:
+ case OP_reg16_imm16:
+ case OP_RM16_imm16:
+ d.imm1_bytes = 2;
+ break;
+ case OP_RM32_imm32:
+ case OP_reg32_RM32_imm32:
+ case OP_reg32_imm32:
+ case OP_EAX_imm32:
+ case OP_imm32:
+ case OP_relimm32:
+ d.imm1_bytes = 4;
+ break;
+ case OP_imm16_imm8:
+ d.imm1_bytes = 2;
+ d.imm2_bytes = 1;
+ break;
+ case OP_imm16_imm16:
+ d.imm1_bytes = 2;
+ d.imm2_bytes = 2;
+ break;
+ case OP_imm16_imm32:
+ d.imm1_bytes = 2;
+ d.imm2_bytes = 4;
+ break;
+ case OP_moff8_AL:
+ case OP_moff16_AX:
+ case OP_moff32_EAX:
+ case OP_AL_moff8:
+ case OP_AX_moff16:
+ case OP_EAX_moff32:
+ case OP_NEAR_imm:
+ d.imm1_bytes = CurrentAddressSize;
+ break;
+ //default:
+ case InvalidFormat:
+ case MultibyteWithSlash:
+ case InstructionPrefix:
+ case __BeginFormatsWithRMByte:
+ case OP_RM16_reg16:
+ case OP_reg8_RM8:
+ case OP_reg16_RM16:
+ case OP_RM16_seg:
+ case OP_RM32_seg:
+ case OP_RM8:
+ case OP_RM16:
+ case OP_RM32:
+ case OP_FPU:
+ case OP_FPU_reg:
+ case OP_FPU_mem:
+ case OP_FPU_AX16:
+ case OP_FPU_RM16:
+ case OP_FPU_RM32:
+ case OP_FPU_RM64:
+ case OP_FPU_M80:
+ case OP_RM8_reg8:
+ case OP_RM32_reg32:
+ case OP_reg32_RM32:
+ case OP_reg16_mem16:
+ case OP_reg32_mem32:
+ case OP_seg_RM16:
+ case OP_seg_RM32:
+ case OP_RM8_1:
+ case OP_RM16_1:
+ case OP_RM32_1:
+ case OP_FAR_mem16:
+ case OP_FAR_mem32:
+ case OP_RM8_CL:
+ case OP_RM16_CL:
+ case OP_RM32_CL:
+ case OP_reg32_CR:
+ case OP_CR_reg32:
+ case OP_reg16_RM8:
+ case OP_reg32_RM8:
+ case OP_mm1_mm2m64:
+ case OP_mm1m64_mm2:
+ case __EndFormatsWithRMByte:
+ case OP_CS:
+ case OP_DS:
+ case OP_ES:
+ case OP_SS:
+ case OP_FS:
+ case OP_GS:
+ case OP:
+ case OP_reg16:
+ case OP_AX_reg16:
+ case OP_EAX_reg32:
+ case OP_3:
+ case OP_AL_DX:
+ case OP_AX_DX:
+ case OP_EAX_DX:
+ case OP_DX_AL:
+ case OP_DX_AX:
+ case OP_DX_EAX:
+ case OP_reg8_CL:
+ case OP_reg32:
+ case OP_reg32_RM16:
+ case OP_reg32_DR:
+ case OP_DR_reg32:
+ case OP_RM16_reg16_CL:
+ case OP_RM32_reg32_CL:
+ break;
+ }
+}
+
+static void build_slash(InstructionDescriptor* table, u8 op, u8 slash, const char* mnemonic, InstructionFormat format, InstructionHandler handler, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed)
+{
+ InstructionDescriptor& d = table[op];
+ ASSERT(d.handler == nullptr);
+ d.format = MultibyteWithSlash;
+ d.has_rm = true;
+ if (!d.slashes)
+ d.slashes = new InstructionDescriptor[8];
+
+ build(d.slashes, slash, mnemonic, format, handler, lock_prefix_allowed);
+}
+
+static void build_slash_rm(InstructionDescriptor* table, u8 op, u8 slash, u8 rm, const char* mnemonic, InstructionFormat format, InstructionHandler handler)
+{
+ ASSERT((rm & 0xc0) == 0xc0);
+ ASSERT(((rm >> 3) & 7) == slash);
+
+ InstructionDescriptor& d0 = table[op];
+ ASSERT(d0.format == MultibyteWithSlash);
+ InstructionDescriptor& d = d0.slashes[slash];
+
+ if (!d.slashes) {
+ // Slash/RM instructions are not always dense, so make them all default to the slash instruction.
+ d.slashes = new InstructionDescriptor[8];
+ for (int i = 0; i < 8; ++i) {
+ d.slashes[i] = d;
+ d.slashes[i].slashes = nullptr;
+ }
+ }
+
+ build(d.slashes, rm & 7, mnemonic, format, handler, LockPrefixNotAllowed);
+}
+
+static void build_0f(u8 op, const char* mnemonic, InstructionFormat format, InstructionHandler impl, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed)
+{
+ build(s_0f_table16, op, mnemonic, format, impl, lock_prefix_allowed);
+ build(s_0f_table32, op, mnemonic, format, impl, lock_prefix_allowed);
+}
+
+static void build(u8 op, const char* mnemonic, InstructionFormat format, InstructionHandler impl, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed)
+{
+ build(s_table16, op, mnemonic, format, impl, lock_prefix_allowed);
+ build(s_table32, op, mnemonic, format, impl, lock_prefix_allowed);
+}
+
+static void build(u8 op, const char* mnemonic, InstructionFormat format16, InstructionHandler impl16, InstructionFormat format32, InstructionHandler impl32, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed)
+{
+ build(s_table16, op, mnemonic, format16, impl16, lock_prefix_allowed);
+ build(s_table32, op, mnemonic, format32, impl32, lock_prefix_allowed);
+}
+
+static void build_0f(u8 op, const char* mnemonic, InstructionFormat format16, InstructionHandler impl16, InstructionFormat format32, InstructionHandler impl32, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed)
+{
+ build(s_0f_table16, op, mnemonic, format16, impl16, lock_prefix_allowed);
+ build(s_0f_table32, op, mnemonic, format32, impl32, lock_prefix_allowed);
+}
+
+static void build(u8 op, const char* mnemonic16, InstructionFormat format16, InstructionHandler impl16, const char* mnemonic32, InstructionFormat format32, InstructionHandler impl32, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed)
+{
+ build(s_table16, op, mnemonic16, format16, impl16, lock_prefix_allowed);
+ build(s_table32, op, mnemonic32, format32, impl32, lock_prefix_allowed);
+}
+
+static void build_0f(u8 op, const char* mnemonic16, InstructionFormat format16, InstructionHandler impl16, const char* mnemonic32, InstructionFormat format32, InstructionHandler impl32, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed)
+{
+ build(s_0f_table16, op, mnemonic16, format16, impl16, lock_prefix_allowed);
+ build(s_0f_table32, op, mnemonic32, format32, impl32, lock_prefix_allowed);
+}
+
+static void build_slash(u8 op, u8 slash, const char* mnemonic, InstructionFormat format, InstructionHandler impl, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed)
+{
+ build_slash(s_table16, op, slash, mnemonic, format, impl, lock_prefix_allowed);
+ build_slash(s_table32, op, slash, mnemonic, format, impl, lock_prefix_allowed);
+}
+
+static void build_slash(u8 op, u8 slash, const char* mnemonic, InstructionFormat format16, InstructionHandler impl16, InstructionFormat format32, InstructionHandler impl32, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed)
+{
+ build_slash(s_table16, op, slash, mnemonic, format16, impl16, lock_prefix_allowed);
+ build_slash(s_table32, op, slash, mnemonic, format32, impl32, lock_prefix_allowed);
+}
+
+static void build_0f_slash(u8 op, u8 slash, const char* mnemonic, InstructionFormat format16, InstructionHandler impl16, InstructionFormat format32, InstructionHandler impl32, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed)
+{
+ build_slash(s_0f_table16, op, slash, mnemonic, format16, impl16, lock_prefix_allowed);
+ build_slash(s_0f_table32, op, slash, mnemonic, format32, impl32, lock_prefix_allowed);
+}
+
+static void build_0f_slash(u8 op, u8 slash, const char* mnemonic, InstructionFormat format, InstructionHandler impl, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed)
+{
+ build_slash(s_0f_table16, op, slash, mnemonic, format, impl, lock_prefix_allowed);
+ build_slash(s_0f_table32, op, slash, mnemonic, format, impl, lock_prefix_allowed);
+}
+
+static void build_slash_rm(u8 op, u8 slash, u8 rm, const char* mnemonic, InstructionFormat format, InstructionHandler impl)
+{
+ build_slash_rm(s_table16, op, slash, rm, mnemonic, format, impl);
+ build_slash_rm(s_table32, op, slash, rm, mnemonic, format, impl);
+}
+
+static void build_slash_reg(u8 op, u8 slash, const char* mnemonic, InstructionFormat format, InstructionHandler impl)
+{
+ for (int i = 0; i < 8; ++i)
+ build_slash_rm(op, slash, 0xc0 | (slash << 3) | i, mnemonic, format, impl);
+}
+
+[[gnu::constructor]] static void build_opcode_tables()
+{
+ build(0x00, "ADD", OP_RM8_reg8, &Interpreter::ADD_RM8_reg8, LockPrefixAllowed);
+ build(0x01, "ADD", OP_RM16_reg16, &Interpreter::ADD_RM16_reg16, OP_RM32_reg32, &Interpreter::ADD_RM32_reg32, LockPrefixAllowed);
+ build(0x02, "ADD", OP_reg8_RM8, &Interpreter::ADD_reg8_RM8, LockPrefixAllowed);
+ build(0x03, "ADD", OP_reg16_RM16, &Interpreter::ADD_reg16_RM16, OP_reg32_RM32, &Interpreter::ADD_reg32_RM32, LockPrefixAllowed);
+ build(0x04, "ADD", OP_AL_imm8, &Interpreter::ADD_AL_imm8);
+ build(0x05, "ADD", OP_AX_imm16, &Interpreter::ADD_AX_imm16, OP_EAX_imm32, &Interpreter::ADD_EAX_imm32);
+ build(0x06, "PUSH", OP_ES, &Interpreter::PUSH_ES);
+ build(0x07, "POP", OP_ES, &Interpreter::POP_ES);
+ build(0x08, "OR", OP_RM8_reg8, &Interpreter::OR_RM8_reg8, LockPrefixAllowed);
+ build(0x09, "OR", OP_RM16_reg16, &Interpreter::OR_RM16_reg16, OP_RM32_reg32, &Interpreter::OR_RM32_reg32, LockPrefixAllowed);
+ build(0x0A, "OR", OP_reg8_RM8, &Interpreter::OR_reg8_RM8, LockPrefixAllowed);
+ build(0x0B, "OR", OP_reg16_RM16, &Interpreter::OR_reg16_RM16, OP_reg32_RM32, &Interpreter::OR_reg32_RM32, LockPrefixAllowed);
+ build(0x0C, "OR", OP_AL_imm8, &Interpreter::OR_AL_imm8);
+ build(0x0D, "OR", OP_AX_imm16, &Interpreter::OR_AX_imm16, OP_EAX_imm32, &Interpreter::OR_EAX_imm32);
+ build(0x0E, "PUSH", OP_CS, &Interpreter::PUSH_CS);
+
+ build(0x10, "ADC", OP_RM8_reg8, &Interpreter::ADC_RM8_reg8, LockPrefixAllowed);
+ build(0x11, "ADC", OP_RM16_reg16, &Interpreter::ADC_RM16_reg16, OP_RM32_reg32, &Interpreter::ADC_RM32_reg32, LockPrefixAllowed);
+ build(0x12, "ADC", OP_reg8_RM8, &Interpreter::ADC_reg8_RM8, LockPrefixAllowed);
+ build(0x13, "ADC", OP_reg16_RM16, &Interpreter::ADC_reg16_RM16, OP_reg32_RM32, &Interpreter::ADC_reg32_RM32, LockPrefixAllowed);
+ build(0x14, "ADC", OP_AL_imm8, &Interpreter::ADC_AL_imm8);
+ build(0x15, "ADC", OP_AX_imm16, &Interpreter::ADC_AX_imm16, OP_EAX_imm32, &Interpreter::ADC_EAX_imm32);
+ build(0x16, "PUSH", OP_SS, &Interpreter::PUSH_SS);
+ build(0x17, "POP", OP_SS, &Interpreter::POP_SS);
+ build(0x18, "SBB", OP_RM8_reg8, &Interpreter::SBB_RM8_reg8, LockPrefixAllowed);
+ build(0x19, "SBB", OP_RM16_reg16, &Interpreter::SBB_RM16_reg16, OP_RM32_reg32, &Interpreter::SBB_RM32_reg32, LockPrefixAllowed);
+ build(0x1A, "SBB", OP_reg8_RM8, &Interpreter::SBB_reg8_RM8, LockPrefixAllowed);
+ build(0x1B, "SBB", OP_reg16_RM16, &Interpreter::SBB_reg16_RM16, OP_reg32_RM32, &Interpreter::SBB_reg32_RM32, LockPrefixAllowed);
+ build(0x1C, "SBB", OP_AL_imm8, &Interpreter::SBB_AL_imm8);
+ build(0x1D, "SBB", OP_AX_imm16, &Interpreter::SBB_AX_imm16, OP_EAX_imm32, &Interpreter::SBB_EAX_imm32);
+ build(0x1E, "PUSH", OP_DS, &Interpreter::PUSH_DS);
+ build(0x1F, "POP", OP_DS, &Interpreter::POP_DS);
+
+ build(0x20, "AND", OP_RM8_reg8, &Interpreter::AND_RM8_reg8, LockPrefixAllowed);
+ build(0x21, "AND", OP_RM16_reg16, &Interpreter::AND_RM16_reg16, OP_RM32_reg32, &Interpreter::AND_RM32_reg32, LockPrefixAllowed);
+ build(0x22, "AND", OP_reg8_RM8, &Interpreter::AND_reg8_RM8, LockPrefixAllowed);
+ build(0x23, "AND", OP_reg16_RM16, &Interpreter::AND_reg16_RM16, OP_reg32_RM32, &Interpreter::AND_reg32_RM32, LockPrefixAllowed);
+ build(0x24, "AND", OP_AL_imm8, &Interpreter::AND_AL_imm8);
+ build(0x25, "AND", OP_AX_imm16, &Interpreter::AND_AX_imm16, OP_EAX_imm32, &Interpreter::AND_EAX_imm32);
+ build(0x27, "DAA", OP, &Interpreter::DAA);
+ build(0x28, "SUB", OP_RM8_reg8, &Interpreter::SUB_RM8_reg8, LockPrefixAllowed);
+ build(0x29, "SUB", OP_RM16_reg16, &Interpreter::SUB_RM16_reg16, OP_RM32_reg32, &Interpreter::SUB_RM32_reg32, LockPrefixAllowed);
+ build(0x2A, "SUB", OP_reg8_RM8, &Interpreter::SUB_reg8_RM8, LockPrefixAllowed);
+ build(0x2B, "SUB", OP_reg16_RM16, &Interpreter::SUB_reg16_RM16, OP_reg32_RM32, &Interpreter::SUB_reg32_RM32, LockPrefixAllowed);
+ build(0x2C, "SUB", OP_AL_imm8, &Interpreter::SUB_AL_imm8);
+ build(0x2D, "SUB", OP_AX_imm16, &Interpreter::SUB_AX_imm16, OP_EAX_imm32, &Interpreter::SUB_EAX_imm32);
+ build(0x2F, "DAS", OP, &Interpreter::DAS);
+
+ build(0x30, "XOR", OP_RM8_reg8, &Interpreter::XOR_RM8_reg8, LockPrefixAllowed);
+ build(0x31, "XOR", OP_RM16_reg16, &Interpreter::XOR_RM16_reg16, OP_RM32_reg32, &Interpreter::XOR_RM32_reg32, LockPrefixAllowed);
+ build(0x32, "XOR", OP_reg8_RM8, &Interpreter::XOR_reg8_RM8, LockPrefixAllowed);
+ build(0x33, "XOR", OP_reg16_RM16, &Interpreter::XOR_reg16_RM16, OP_reg32_RM32, &Interpreter::XOR_reg32_RM32, LockPrefixAllowed);
+ build(0x34, "XOR", OP_AL_imm8, &Interpreter::XOR_AL_imm8);
+ build(0x35, "XOR", OP_AX_imm16, &Interpreter::XOR_AX_imm16, OP_EAX_imm32, &Interpreter::XOR_EAX_imm32);
+ build(0x37, "AAA", OP, &Interpreter::AAA);
+ build(0x38, "CMP", OP_RM8_reg8, &Interpreter::CMP_RM8_reg8, LockPrefixAllowed);
+ build(0x39, "CMP", OP_RM16_reg16, &Interpreter::CMP_RM16_reg16, OP_RM32_reg32, &Interpreter::CMP_RM32_reg32, LockPrefixAllowed);
+ build(0x3A, "CMP", OP_reg8_RM8, &Interpreter::CMP_reg8_RM8, LockPrefixAllowed);
+ build(0x3B, "CMP", OP_reg16_RM16, &Interpreter::CMP_reg16_RM16, OP_reg32_RM32, &Interpreter::CMP_reg32_RM32, LockPrefixAllowed);
+ build(0x3C, "CMP", OP_AL_imm8, &Interpreter::CMP_AL_imm8);
+ build(0x3D, "CMP", OP_AX_imm16, &Interpreter::CMP_AX_imm16, OP_EAX_imm32, &Interpreter::CMP_EAX_imm32);
+ build(0x3F, "AAS", OP, &Interpreter::AAS);
+
+ for (u8 i = 0; i <= 7; ++i)
+ build(0x40 + i, "INC", OP_reg16, &Interpreter::INC_reg16, OP_reg32, &Interpreter::INC_reg32);
+
+ for (u8 i = 0; i <= 7; ++i)
+ build(0x48 + i, "DEC", OP_reg16, &Interpreter::DEC_reg16, OP_reg32, &Interpreter::DEC_reg32);
+
+ for (u8 i = 0; i <= 7; ++i)
+ build(0x50 + i, "PUSH", OP_reg16, &Interpreter::PUSH_reg16, OP_reg32, &Interpreter::PUSH_reg32);
+
+ for (u8 i = 0; i <= 7; ++i)
+ build(0x58 + i, "POP", OP_reg16, &Interpreter::POP_reg16, OP_reg32, &Interpreter::POP_reg32);
+
+ build(0x60, "PUSHAW", OP, &Interpreter::PUSHA, "PUSHAD", OP, &Interpreter::PUSHAD);
+ build(0x61, "POPAW", OP, &Interpreter::POPA, "POPAD", OP, &Interpreter::POPAD);
+ build(0x62, "BOUND", OP_reg16_RM16, &Interpreter::BOUND, "BOUND", OP_reg32_RM32, &Interpreter::BOUND);
+ build(0x63, "ARPL", OP_RM16_reg16, &Interpreter::ARPL);
+
+ build(0x68, "PUSH", OP_imm16, &Interpreter::PUSH_imm16, OP_imm32, &Interpreter::PUSH_imm32);
+ build(0x69, "IMUL", OP_reg16_RM16_imm16, &Interpreter::IMUL_reg16_RM16_imm16, OP_reg32_RM32_imm32, &Interpreter::IMUL_reg32_RM32_imm32);
+ build(0x6A, "PUSH", OP_imm8, &Interpreter::PUSH_imm8);
+ build(0x6B, "IMUL", OP_reg16_RM16_imm8, &Interpreter::IMUL_reg16_RM16_imm8, OP_reg32_RM32_imm8, &Interpreter::IMUL_reg32_RM32_imm8);
+ build(0x6C, "INSB", OP, &Interpreter::INSB);
+ build(0x6D, "INSW", OP, &Interpreter::INSW, "INSD", OP, &Interpreter::INSD);
+ build(0x6E, "OUTSB", OP, &Interpreter::OUTSB);
+ build(0x6F, "OUTSW", OP, &Interpreter::OUTSW, "OUTSD", OP, &Interpreter::OUTSD);
+
+ build(0x70, "JO", OP_short_imm8, &Interpreter::Jcc_imm8);
+ build(0x71, "JNO", OP_short_imm8, &Interpreter::Jcc_imm8);
+ build(0x72, "JC", OP_short_imm8, &Interpreter::Jcc_imm8);
+ build(0x73, "JNC", OP_short_imm8, &Interpreter::Jcc_imm8);
+ build(0x74, "JZ", OP_short_imm8, &Interpreter::Jcc_imm8);
+ build(0x75, "JNZ", OP_short_imm8, &Interpreter::Jcc_imm8);
+ build(0x76, "JNA", OP_short_imm8, &Interpreter::Jcc_imm8);
+ build(0x77, "JA", OP_short_imm8, &Interpreter::Jcc_imm8);
+ build(0x78, "JS", OP_short_imm8, &Interpreter::Jcc_imm8);
+ build(0x79, "JNS", OP_short_imm8, &Interpreter::Jcc_imm8);
+ build(0x7A, "JP", OP_short_imm8, &Interpreter::Jcc_imm8);
+ build(0x7B, "JNP", OP_short_imm8, &Interpreter::Jcc_imm8);
+ build(0x7C, "JL", OP_short_imm8, &Interpreter::Jcc_imm8);
+ build(0x7D, "JNL", OP_short_imm8, &Interpreter::Jcc_imm8);
+ build(0x7E, "JNG", OP_short_imm8, &Interpreter::Jcc_imm8);
+ build(0x7F, "JG", OP_short_imm8, &Interpreter::Jcc_imm8);
+
+ build(0x84, "TEST", OP_RM8_reg8, &Interpreter::TEST_RM8_reg8);
+ build(0x85, "TEST", OP_RM16_reg16, &Interpreter::TEST_RM16_reg16, OP_RM32_reg32, &Interpreter::TEST_RM32_reg32);
+ build(0x86, "XCHG", OP_reg8_RM8, &Interpreter::XCHG_reg8_RM8, LockPrefixAllowed);
+ build(0x87, "XCHG", OP_reg16_RM16, &Interpreter::XCHG_reg16_RM16, OP_reg32_RM32, &Interpreter::XCHG_reg32_RM32, LockPrefixAllowed);
+ build(0x88, "MOV", OP_RM8_reg8, &Interpreter::MOV_RM8_reg8);
+ build(0x89, "MOV", OP_RM16_reg16, &Interpreter::MOV_RM16_reg16, OP_RM32_reg32, &Interpreter::MOV_RM32_reg32);
+ build(0x8A, "MOV", OP_reg8_RM8, &Interpreter::MOV_reg8_RM8);
+ build(0x8B, "MOV", OP_reg16_RM16, &Interpreter::MOV_reg16_RM16, OP_reg32_RM32, &Interpreter::MOV_reg32_RM32);
+ build(0x8C, "MOV", OP_RM16_seg, &Interpreter::MOV_RM16_seg);
+ build(0x8D, "LEA", OP_reg16_mem16, &Interpreter::LEA_reg16_mem16, OP_reg32_mem32, &Interpreter::LEA_reg32_mem32);
+ build(0x8E, "MOV", OP_seg_RM16, &Interpreter::MOV_seg_RM16, OP_seg_RM32, &Interpreter::MOV_seg_RM32);
+
+ build(0x90, "NOP", OP, &Interpreter::NOP);
+
+ for (u8 i = 0; i <= 6; ++i)
+ build(0x91 + i, "XCHG", OP_AX_reg16, &Interpreter::XCHG_AX_reg16, OP_EAX_reg32, &Interpreter::XCHG_EAX_reg32);
+
+ build(0x98, "CBW", OP, &Interpreter::CBW, "CWDE", OP, &Interpreter::CWDE);
+ build(0x99, "CWD", OP, &Interpreter::CWD, "CDQ", OP, &Interpreter::CDQ);
+ build(0x9A, "CALL", OP_imm16_imm16, &Interpreter::CALL_imm16_imm16, OP_imm16_imm32, &Interpreter::CALL_imm16_imm32);
+ build(0x9B, "WAIT", OP, &Interpreter::WAIT);
+ build(0x9C, "PUSHFW", OP, &Interpreter::PUSHF, "PUSHFD", OP, &Interpreter::PUSHFD);
+ build(0x9D, "POPFW", OP, &Interpreter::POPF, "POPFD", OP, &Interpreter::POPFD);
+ build(0x9E, "SAHF", OP, &Interpreter::SAHF);
+ build(0x9F, "LAHF", OP, &Interpreter::LAHF);
+
+ build(0xA0, "MOV", OP_AL_moff8, &Interpreter::MOV_AL_moff8);
+ build(0xA1, "MOV", OP_AX_moff16, &Interpreter::MOV_AX_moff16, OP_EAX_moff32, &Interpreter::MOV_EAX_moff32);
+ build(0xA2, "MOV", OP_moff8_AL, &Interpreter::MOV_moff8_AL);
+ build(0xA3, "MOV", OP_moff16_AX, &Interpreter::MOV_moff16_AX, OP_moff32_EAX, &Interpreter::MOV_moff32_EAX);
+ build(0xA4, "MOVSB", OP, &Interpreter::MOVSB);
+ build(0xA5, "MOVSW", OP, &Interpreter::MOVSW, "MOVSD", OP, &Interpreter::MOVSD);
+ build(0xA6, "CMPSB", OP, &Interpreter::CMPSB);
+ build(0xA7, "CMPSW", OP, &Interpreter::CMPSW, "CMPSD", OP, &Interpreter::CMPSD);
+ build(0xA8, "TEST", OP_AL_imm8, &Interpreter::TEST_AL_imm8);
+ build(0xA9, "TEST", OP_AX_imm16, &Interpreter::TEST_AX_imm16, OP_EAX_imm32, &Interpreter::TEST_EAX_imm32);
+ build(0xAA, "STOSB", OP, &Interpreter::STOSB);
+ build(0xAB, "STOSW", OP, &Interpreter::STOSW, "STOSD", OP, &Interpreter::STOSD);
+ build(0xAC, "LODSB", OP, &Interpreter::LODSB);
+ build(0xAD, "LODSW", OP, &Interpreter::LODSW, "LODSD", OP, &Interpreter::LODSD);
+ build(0xAE, "SCASB", OP, &Interpreter::SCASB);
+ build(0xAF, "SCASW", OP, &Interpreter::SCASW, "SCASD", OP, &Interpreter::SCASD);
+
+ for (u8 i = 0xb0; i <= 0xb7; ++i)
+ build(i, "MOV", OP_reg8_imm8, &Interpreter::MOV_reg8_imm8);
+
+ for (u8 i = 0xb8; i <= 0xbf; ++i)
+ build(i, "MOV", OP_reg16_imm16, &Interpreter::MOV_reg16_imm16, OP_reg32_imm32, &Interpreter::MOV_reg32_imm32);
+
+ build(0xC2, "RET", OP_imm16, &Interpreter::RET_imm16);
+ build(0xC3, "RET", OP, &Interpreter::RET);
+ build(0xC4, "LES", OP_reg16_mem16, &Interpreter::LES_reg16_mem16, OP_reg32_mem32, &Interpreter::LES_reg32_mem32);
+ build(0xC5, "LDS", OP_reg16_mem16, &Interpreter::LDS_reg16_mem16, OP_reg32_mem32, &Interpreter::LDS_reg32_mem32);
+ build(0xC6, "MOV", OP_RM8_imm8, &Interpreter::MOV_RM8_imm8);
+ build(0xC7, "MOV", OP_RM16_imm16, &Interpreter::MOV_RM16_imm16, OP_RM32_imm32, &Interpreter::MOV_RM32_imm32);
+ build(0xC8, "ENTER", OP_imm16_imm8, &Interpreter::ENTER16, OP_imm16_imm8, &Interpreter::ENTER32);
+ build(0xC9, "LEAVE", OP, &Interpreter::LEAVE16, OP, &Interpreter::LEAVE32);
+ build(0xCA, "RETF", OP_imm16, &Interpreter::RETF_imm16);
+ build(0xCB, "RETF", OP, &Interpreter::RETF);
+ build(0xCC, "INT3", OP_3, &Interpreter::INT3);
+ build(0xCD, "INT", OP_imm8, &Interpreter::INT_imm8);
+ build(0xCE, "INTO", OP, &Interpreter::INTO);
+ build(0xCF, "IRET", OP, &Interpreter::IRET);
+
+ build(0xD4, "AAM", OP_imm8, &Interpreter::AAM);
+ build(0xD5, "AAD", OP_imm8, &Interpreter::AAD);
+ build(0xD6, "SALC", OP, &Interpreter::SALC);
+ build(0xD7, "XLAT", OP, &Interpreter::XLAT);
+
+ // D8-DF == FPU
+ build_slash(0xD8, 0, "FADD", OP_FPU_RM32, &Interpreter::FADD_RM32);
+ build_slash(0xD8, 1, "FMUL", OP_FPU_RM32, &Interpreter::FMUL_RM32);
+ build_slash(0xD8, 2, "FCOM", OP_FPU_RM32, &Interpreter::FCOM_RM32);
+ // FIXME: D8/2 D1 (...but isn't this what D8/2 does naturally, with D1 just being normal R/M?)
+ build_slash(0xD8, 3, "FCOMP", OP_FPU_RM32, &Interpreter::FCOMP_RM32);
+ // FIXME: D8/3 D9 (...but isn't this what D8/3 does naturally, with D9 just being normal R/M?)
+ build_slash(0xD8, 4, "FSUB", OP_FPU_RM32, &Interpreter::FSUB_RM32);
+ build_slash(0xD8, 5, "FSUBR", OP_FPU_RM32, &Interpreter::FSUBR_RM32);
+ build_slash(0xD8, 6, "FDIV", OP_FPU_RM32, &Interpreter::FDIV_RM32);
+ build_slash(0xD8, 7, "FDIVR", OP_FPU_RM32, &Interpreter::FDIVR_RM32);
+
+ build_slash(0xD9, 0, "FLD", OP_FPU_RM32, &Interpreter::FLD_RM32);
+ build_slash(0xD9, 1, "FXCH", OP_FPU_reg, &Interpreter::FXCH);
+ // FIXME: D9/1 C9 (...but isn't this what D9/1 does naturally, with C9 just being normal R/M?)
+ build_slash(0xD9, 2, "FST", OP_FPU_RM32, &Interpreter::FST_RM32);
+ build_slash_rm(0xD9, 2, 0xD0, "FNOP", OP_FPU, &Interpreter::FNOP);
+ build_slash(0xD9, 3, "FSTP", OP_FPU_RM32, &Interpreter::FSTP_RM32);
+ build_slash(0xD9, 4, "FLDENV", OP_FPU_RM32, &Interpreter::FLDENV);
+ build_slash_rm(0xD9, 4, 0xE0, "FCHS", OP_FPU, &Interpreter::FCHS);
+ build_slash_rm(0xD9, 4, 0xE1, "FABS", OP_FPU, &Interpreter::FABS);
+ build_slash_rm(0xD9, 4, 0xE2, "FTST", OP_FPU, &Interpreter::FTST);
+ build_slash_rm(0xD9, 4, 0xE3, "FXAM", OP_FPU, &Interpreter::FXAM);
+ build_slash(0xD9, 5, "FLDCW", OP_FPU_RM16, &Interpreter::FLDCW);
+ build_slash_rm(0xD9, 5, 0xE8, "FLD1", OP_FPU, &Interpreter::FLD1);
+ build_slash_rm(0xD9, 5, 0xE9, "FLDL2T", OP_FPU, &Interpreter::FLDL2T);
+ build_slash_rm(0xD9, 5, 0xEA, "FLDL2E", OP_FPU, &Interpreter::FLDL2E);
+ build_slash_rm(0xD9, 5, 0xEB, "FLDPI", OP_FPU, &Interpreter::FLDPI);
+ build_slash_rm(0xD9, 5, 0xEC, "FLDLG2", OP_FPU, &Interpreter::FLDLG2);
+ build_slash_rm(0xD9, 5, 0xED, "FLDLN2", OP_FPU, &Interpreter::FLDLN2);
+ build_slash_rm(0xD9, 5, 0xEE, "FLDZ", OP_FPU, &Interpreter::FLDZ);
+ build_slash(0xD9, 6, "FNSTENV", OP_FPU_RM32, &Interpreter::FNSTENV);
+ // FIXME: Extraodinary prefix 0x9B + 0xD9/6: FSTENV
+ build_slash_rm(0xD9, 6, 0xF0, "F2XM1", OP_FPU, &Interpreter::F2XM1);
+ build_slash_rm(0xD9, 6, 0xF1, "FYL2X", OP_FPU, &Interpreter::FYL2X);
+ build_slash_rm(0xD9, 6, 0xF2, "FPTAN", OP_FPU, &Interpreter::FPTAN);
+ build_slash_rm(0xD9, 6, 0xF3, "FPATAN", OP_FPU, &Interpreter::FPATAN);
+ build_slash_rm(0xD9, 6, 0xF4, "FXTRACT", OP_FPU, &Interpreter::FXTRACT);
+ build_slash_rm(0xD9, 6, 0xF5, "FPREM1", OP_FPU, &Interpreter::FPREM1);
+ build_slash_rm(0xD9, 6, 0xF6, "FDECSTP", OP_FPU, &Interpreter::FDECSTP);
+ build_slash_rm(0xD9, 6, 0xF7, "FINCSTP", OP_FPU, &Interpreter::FINCSTP);
+ build_slash(0xD9, 7, "FNSTCW", OP_FPU_RM16, &Interpreter::FNSTCW);
+ // FIXME: Extraodinary prefix 0x9B + 0xD9/7: FSTCW
+ build_slash_rm(0xD9, 7, 0xF8, "FPREM", OP_FPU, &Interpreter::FPREM);
+ build_slash_rm(0xD9, 7, 0xF9, "FYL2XP1", OP_FPU, &Interpreter::FYL2XP1);
+ build_slash_rm(0xD9, 7, 0xFA, "FSQRT", OP_FPU, &Interpreter::FSQRT);
+ build_slash_rm(0xD9, 7, 0xFB, "FSINCOS", OP_FPU, &Interpreter::FSINCOS);
+ build_slash_rm(0xD9, 7, 0xFC, "FRNDINT", OP_FPU, &Interpreter::FRNDINT);
+ build_slash_rm(0xD9, 7, 0xFD, "FSCALE", OP_FPU, &Interpreter::FSCALE);
+ build_slash_rm(0xD9, 7, 0xFE, "FSIN", OP_FPU, &Interpreter::FSIN);
+ build_slash_rm(0xD9, 7, 0xFF, "FCOS", OP_FPU, &Interpreter::FCOS);
+
+ build_slash(0xDA, 0, "FIADD", OP_FPU_RM32, &Interpreter::FIADD_RM32);
+ build_slash_reg(0xDA, 0, "FCMOVB", OP_FPU_reg, &Interpreter::FCMOVB);
+ build_slash(0xDA, 1, "FIMUL", OP_FPU_RM32, &Interpreter::FIMUL_RM32);
+ build_slash_reg(0xDA, 1, "FCMOVE", OP_FPU_reg, &Interpreter::FCMOVE);
+ build_slash(0xDA, 2, "FICOM", OP_FPU_RM32, &Interpreter::FICOM_RM32);
+ build_slash_reg(0xDA, 2, "FCMOVBE", OP_FPU_reg, &Interpreter::FCMOVBE);
+ build_slash(0xDA, 3, "FICOMP", OP_FPU_RM32, &Interpreter::FICOMP_RM32);
+ build_slash_reg(0xDA, 3, "FCMOVU", OP_FPU_reg, &Interpreter::FCMOVU);
+ build_slash(0xDA, 4, "FISUB", OP_FPU_RM32, &Interpreter::FISUB_RM32);
+ build_slash(0xDA, 5, "FISUBR", OP_FPU_RM32, &Interpreter::FISUBR_RM32);
+ build_slash_rm(0xDA, 5, 0xE9, "FUCOMPP", OP_FPU, &Interpreter::FUCOMPP);
+ build_slash(0xDA, 6, "FIDIV", OP_FPU_RM32, &Interpreter::FIDIV_RM32);
+ build_slash(0xDA, 7, "FIDIVR", OP_FPU_RM32, &Interpreter::FIDIVR_RM32);
+
+ build_slash(0xDB, 0, "FILD", OP_FPU_RM32, &Interpreter::FILD_RM32);
+ build_slash_reg(0xDB, 0, "FCMOVNB", OP_FPU_reg, &Interpreter::FCMOVNB);
+ build_slash(0xDB, 1, "FISTTP", OP_FPU_RM32, &Interpreter::FISTTP_RM32);
+ build_slash_reg(0xDB, 1, "FCMOVNE", OP_FPU_reg, &Interpreter::FCMOVNE);
+ build_slash(0xDB, 2, "FIST", OP_FPU_RM32, &Interpreter::FIST_RM32);
+ build_slash_reg(0xDB, 2, "FCMOVNBE", OP_FPU_reg, &Interpreter::FCMOVNBE);
+ build_slash(0xDB, 3, "FISTP", OP_FPU_RM32, &Interpreter::FISTP_RM32);
+ build_slash_reg(0xDB, 3, "FCMOVNU", OP_FPU_reg, &Interpreter::FCMOVNU);
+ build_slash(0xDB, 4, "FUNASSIGNED", OP_FPU, &Interpreter::ESCAPE);
+ build_slash_rm(0xDB, 4, 0xE0, "FNENI", OP_FPU_reg, &Interpreter::FNENI);
+ build_slash_rm(0xDB, 4, 0xE1, "FNDISI", OP_FPU_reg, &Interpreter::FNDISI);
+ build_slash_rm(0xDB, 4, 0xE2, "FNCLEX", OP_FPU_reg, &Interpreter::FNCLEX);
+ // FIXME: Extraodinary prefix 0x9B + 0xDB/4: FCLEX
+ build_slash_rm(0xDB, 4, 0xE3, "FNINIT", OP_FPU_reg, &Interpreter::FNINIT);
+ // FIXME: Extraodinary prefix 0x9B + 0xDB/4: FINIT
+ build_slash_rm(0xDB, 4, 0xE4, "FNSETPM", OP_FPU_reg, &Interpreter::FNSETPM);
+ build_slash(0xDB, 5, "FLD", OP_FPU_M80, &Interpreter::FLD_RM80);
+ build_slash_reg(0xDB, 5, "FUCOMI", OP_FPU_reg, &Interpreter::FUCOMI);
+ build_slash(0xDB, 6, "FCOMI", OP_FPU_reg, &Interpreter::FCOMI);
+ build_slash(0xDB, 7, "FSTP", OP_FPU_M80, &Interpreter::FSTP_RM80);
+
+ build_slash(0xDC, 0, "FADD", OP_FPU_RM64, &Interpreter::FADD_RM64);
+ build_slash(0xDC, 1, "FMUL", OP_FPU_RM64, &Interpreter::FMUL_RM64);
+ build_slash(0xDC, 2, "FCOM", OP_FPU_RM64, &Interpreter::FCOM_RM64);
+ build_slash(0xDC, 3, "FCOMP", OP_FPU_RM64, &Interpreter::FCOMP_RM64);
+ build_slash(0xDC, 4, "FSUB", OP_FPU_RM64, &Interpreter::FSUB_RM64);
+ build_slash(0xDC, 5, "FSUBR", OP_FPU_RM64, &Interpreter::FSUBR_RM64);
+ build_slash(0xDC, 6, "FDIV", OP_FPU_RM64, &Interpreter::FDIV_RM64);
+ build_slash(0xDC, 7, "FDIVR", OP_FPU_RM64, &Interpreter::FDIVR_RM64);
+
+ build_slash(0xDD, 0, "FLD", OP_FPU_RM64, &Interpreter::FLD_RM64);
+ build_slash_reg(0xDD, 0, "FFREE", OP_FPU_reg, &Interpreter::FFREE);
+ build_slash(0xDD, 1, "FISTTP", OP_FPU_RM64, &Interpreter::FISTTP_RM64);
+ build_slash_reg(0xDD, 1, "FXCH4", OP_FPU_reg, &Interpreter::FXCH);
+ build_slash(0xDD, 2, "FST", OP_FPU_RM64, &Interpreter::FST_RM64);
+ build_slash(0xDD, 3, "FSTP", OP_FPU_RM64, &Interpreter::FSTP_RM64);
+ build_slash(0xDD, 4, "FRSTOR", OP_FPU_mem, &Interpreter::FRSTOR);
+ build_slash_reg(0xDD, 4, "FUCOM", OP_FPU_reg, &Interpreter::FUCOM);
+ // FIXME: DD/4 E1 (...but isn't this what DD/4 does naturally, with E1 just being normal R/M?)
+ build_slash(0xDD, 5, "FUCOMP", OP_FPU_reg, &Interpreter::FUCOMP);
+ // FIXME: DD/5 E9 (...but isn't this what DD/5 does naturally, with E9 just being normal R/M?)
+ build_slash(0xDD, 6, "FNSAVE", OP_FPU_mem, &Interpreter::FNSAVE);
+ // FIXME: Extraodinary prefix 0x9B + 0xDD/6: FSAVE
+ build_slash(0xDD, 7, "FNSTSW", OP_FPU_RM16, &Interpreter::FNSTSW);
+ // FIXME: Extraodinary prefix 0x9B + 0xDD/7: FSTSW
+
+ build_slash(0xDE, 0, "FIADD", OP_FPU_RM16, &Interpreter::FIADD_RM16);
+ build_slash_reg(0xDE, 0, "FADDP", OP_FPU_reg, &Interpreter::FADDP);
+ // FIXME: DE/0 C1 (...but isn't this what DE/0 does naturally, with C1 just being normal R/M?)
+ build_slash(0xDE, 1, "FIMUL", OP_FPU_RM16, &Interpreter::FIMUL_RM16);
+ build_slash_reg(0xDE, 1, "FMULP", OP_FPU_reg, &Interpreter::FMULP);
+ // FIXME: DE/1 C9 (...but isn't this what DE/1 does naturally, with C9 just being normal R/M?)
+ build_slash(0xDE, 2, "FICOM", OP_FPU_RM16, &Interpreter::FICOM_RM16);
+ build_slash_reg(0xDE, 2, "FCOMP5", OP_FPU_reg, &Interpreter::FCOMP_RM32);
+ build_slash(0xDE, 3, "FICOMP", OP_FPU_RM16, &Interpreter::FICOMP_RM16);
+ build_slash_reg(0xDE, 3, "FCOMPP", OP_FPU_reg, &Interpreter::FCOMPP);
+ build_slash(0xDE, 4, "FISUB", OP_FPU_RM16, &Interpreter::FISUB_RM16);
+ build_slash_reg(0xDE, 4, "FSUBRP", OP_FPU_reg, &Interpreter::FSUBRP);
+ // FIXME: DE/4 E1 (...but isn't this what DE/4 does naturally, with E1 just being normal R/M?)
+ build_slash(0xDE, 5, "FISUBR", OP_FPU_RM16, &Interpreter::FISUBR_RM16);
+ build_slash_reg(0xDE, 5, "FSUBP", OP_FPU_reg, &Interpreter::FSUBP);
+ // FIXME: DE/5 E9 (...but isn't this what DE/5 does naturally, with E9 just being normal R/M?)
+ build_slash(0xDE, 6, "FIDIV", OP_FPU_RM16, &Interpreter::FIDIV_RM16);
+ build_slash_reg(0xDE, 6, "FDIVRP", OP_FPU_reg, &Interpreter::FDIVRP);
+ // FIXME: DE/6 F1 (...but isn't this what DE/6 does naturally, with F1 just being normal R/M?)
+ build_slash(0xDE, 7, "FIDIVR", OP_FPU_RM16, &Interpreter::FIDIVR_RM16);
+ build_slash_reg(0xDE, 7, "FDIVP", OP_FPU_reg, &Interpreter::FDIVP);
+ // FIXME: DE/7 F9 (...but isn't this what DE/7 does naturally, with F9 just being normal R/M?)
+
+ build_slash(0xDF, 0, "FILD", OP_FPU_RM32, &Interpreter::FILD_RM16);
+ build_slash_reg(0xDF, 0, "FFREEP", OP_FPU_reg, &Interpreter::FFREEP);
+ build_slash(0xDF, 1, "FISTTP", OP_FPU_RM32, &Interpreter::FISTTP_RM16);
+ build_slash_reg(0xDF, 1, "FXCH7", OP_FPU_reg, &Interpreter::FXCH);
+ build_slash(0xDF, 2, "FIST", OP_FPU_RM32, &Interpreter::FIST_RM16);
+ build_slash_reg(0xDF, 2, "FSTP8", OP_FPU_reg, &Interpreter::FSTP_RM32);
+ build_slash(0xDF, 3, "FISTP", OP_FPU_RM32, &Interpreter::FISTP_RM16);
+ build_slash_reg(0xDF, 3, "FSTP9", OP_FPU_reg, &Interpreter::FSTP_RM32);
+ build_slash(0xDF, 4, "FBLD", OP_FPU_M80, &Interpreter::FBLD_M80);
+ build_slash_reg(0xDF, 4, "FNSTSW", OP_FPU_AX16, &Interpreter::FNSTSW_AX);
+ // FIXME: Extraodinary prefix 0x9B + 0xDF/e: FSTSW_AX
+ build_slash(0xDF, 5, "FILD", OP_FPU_RM64, &Interpreter::FILD_RM64);
+ build_slash_reg(0xDF, 5, "FUCOMIP", OP_FPU_reg, &Interpreter::FUCOMIP);
+ build_slash(0xDF, 6, "FBSTP", OP_FPU_M80, &Interpreter::FBSTP_M80);
+ build_slash_reg(0xDF, 6, "FCOMIP", OP_FPU_reg, &Interpreter::FCOMIP);
+ build_slash(0xDF, 7, "FISTP", OP_FPU_RM64, &Interpreter::FISTP_RM64);
+
+ build(0xE0, "LOOPNZ", OP_imm8, &Interpreter::LOOPNZ_imm8);
+ build(0xE1, "LOOPZ", OP_imm8, &Interpreter::LOOPZ_imm8);
+ build(0xE2, "LOOP", OP_imm8, &Interpreter::LOOP_imm8);
+ build(0xE3, "JCXZ", OP_imm8, &Interpreter::JCXZ_imm8);
+ build(0xE4, "IN", OP_AL_imm8, &Interpreter::IN_AL_imm8);
+ build(0xE5, "IN", OP_AX_imm8, &Interpreter::IN_AX_imm8, OP_EAX_imm8, &Interpreter::IN_EAX_imm8);
+ build(0xE6, "OUT", OP_imm8_AL, &Interpreter::OUT_imm8_AL);
+ build(0xE7, "OUT", OP_imm8_AX, &Interpreter::OUT_imm8_AX, OP_imm8_EAX, &Interpreter::OUT_imm8_EAX);
+ build(0xE8, "CALL", OP_relimm16, &Interpreter::CALL_imm16, OP_relimm32, &Interpreter::CALL_imm32);
+ build(0xE9, "JMP", OP_relimm16, &Interpreter::JMP_imm16, OP_relimm32, &Interpreter::JMP_imm32);
+ build(0xEA, "JMP", OP_imm16_imm16, &Interpreter::JMP_imm16_imm16, OP_imm16_imm32, &Interpreter::JMP_imm16_imm32);
+ build(0xEB, "JMP", OP_short_imm8, &Interpreter::JMP_short_imm8);
+ build(0xEC, "IN", OP_AL_DX, &Interpreter::IN_AL_DX);
+ build(0xED, "IN", OP_AX_DX, &Interpreter::IN_AX_DX, OP_EAX_DX, &Interpreter::IN_EAX_DX);
+ build(0xEE, "OUT", OP_DX_AL, &Interpreter::OUT_DX_AL);
+ build(0xEF, "OUT", OP_DX_AX, &Interpreter::OUT_DX_AX, OP_DX_EAX, &Interpreter::OUT_DX_EAX);
+
+ build(0xF4, "HLT", OP, &Interpreter::HLT);
+ build(0xF5, "CMC", OP, &Interpreter::CMC);
+
+ build(0xF8, "CLC", OP, &Interpreter::CLC);
+ build(0xF9, "STC", OP, &Interpreter::STC);
+ build(0xFA, "CLI", OP, &Interpreter::CLI);
+ build(0xFB, "STI", OP, &Interpreter::STI);
+ build(0xFC, "CLD", OP, &Interpreter::CLD);
+ build(0xFD, "STD", OP, &Interpreter::STD);
+
+ build_slash(0x80, 0, "ADD", OP_RM8_imm8, &Interpreter::ADD_RM8_imm8, LockPrefixAllowed);
+ build_slash(0x80, 1, "OR", OP_RM8_imm8, &Interpreter::OR_RM8_imm8, LockPrefixAllowed);
+ build_slash(0x80, 2, "ADC", OP_RM8_imm8, &Interpreter::ADC_RM8_imm8, LockPrefixAllowed);
+ build_slash(0x80, 3, "SBB", OP_RM8_imm8, &Interpreter::SBB_RM8_imm8, LockPrefixAllowed);
+ build_slash(0x80, 4, "AND", OP_RM8_imm8, &Interpreter::AND_RM8_imm8, LockPrefixAllowed);
+ build_slash(0x80, 5, "SUB", OP_RM8_imm8, &Interpreter::SUB_RM8_imm8, LockPrefixAllowed);
+ build_slash(0x80, 6, "XOR", OP_RM8_imm8, &Interpreter::XOR_RM8_imm8, LockPrefixAllowed);
+ build_slash(0x80, 7, "CMP", OP_RM8_imm8, &Interpreter::CMP_RM8_imm8);
+
+ build_slash(0x81, 0, "ADD", OP_RM16_imm16, &Interpreter::ADD_RM16_imm16, OP_RM32_imm32, &Interpreter::ADD_RM32_imm32, LockPrefixAllowed);
+ build_slash(0x81, 1, "OR", OP_RM16_imm16, &Interpreter::OR_RM16_imm16, OP_RM32_imm32, &Interpreter::OR_RM32_imm32, LockPrefixAllowed);
+ build_slash(0x81, 2, "ADC", OP_RM16_imm16, &Interpreter::ADC_RM16_imm16, OP_RM32_imm32, &Interpreter::ADC_RM32_imm32, LockPrefixAllowed);
+ build_slash(0x81, 3, "SBB", OP_RM16_imm16, &Interpreter::SBB_RM16_imm16, OP_RM32_imm32, &Interpreter::SBB_RM32_imm32, LockPrefixAllowed);
+ build_slash(0x81, 4, "AND", OP_RM16_imm16, &Interpreter::AND_RM16_imm16, OP_RM32_imm32, &Interpreter::AND_RM32_imm32, LockPrefixAllowed);
+ build_slash(0x81, 5, "SUB", OP_RM16_imm16, &Interpreter::SUB_RM16_imm16, OP_RM32_imm32, &Interpreter::SUB_RM32_imm32, LockPrefixAllowed);
+ build_slash(0x81, 6, "XOR", OP_RM16_imm16, &Interpreter::XOR_RM16_imm16, OP_RM32_imm32, &Interpreter::XOR_RM32_imm32, LockPrefixAllowed);
+ build_slash(0x81, 7, "CMP", OP_RM16_imm16, &Interpreter::CMP_RM16_imm16, OP_RM32_imm32, &Interpreter::CMP_RM32_imm32);
+
+ build_slash(0x83, 0, "ADD", OP_RM16_imm8, &Interpreter::ADD_RM16_imm8, OP_RM32_imm8, &Interpreter::ADD_RM32_imm8, LockPrefixAllowed);
+ build_slash(0x83, 1, "OR", OP_RM16_imm8, &Interpreter::OR_RM16_imm8, OP_RM32_imm8, &Interpreter::OR_RM32_imm8, LockPrefixAllowed);
+ build_slash(0x83, 2, "ADC", OP_RM16_imm8, &Interpreter::ADC_RM16_imm8, OP_RM32_imm8, &Interpreter::ADC_RM32_imm8, LockPrefixAllowed);
+ build_slash(0x83, 3, "SBB", OP_RM16_imm8, &Interpreter::SBB_RM16_imm8, OP_RM32_imm8, &Interpreter::SBB_RM32_imm8, LockPrefixAllowed);
+ build_slash(0x83, 4, "AND", OP_RM16_imm8, &Interpreter::AND_RM16_imm8, OP_RM32_imm8, &Interpreter::AND_RM32_imm8, LockPrefixAllowed);
+ build_slash(0x83, 5, "SUB", OP_RM16_imm8, &Interpreter::SUB_RM16_imm8, OP_RM32_imm8, &Interpreter::SUB_RM32_imm8, LockPrefixAllowed);
+ build_slash(0x83, 6, "XOR", OP_RM16_imm8, &Interpreter::XOR_RM16_imm8, OP_RM32_imm8, &Interpreter::XOR_RM32_imm8, LockPrefixAllowed);
+ build_slash(0x83, 7, "CMP", OP_RM16_imm8, &Interpreter::CMP_RM16_imm8, OP_RM32_imm8, &Interpreter::CMP_RM32_imm8);
+
+ build_slash(0x8F, 0, "POP", OP_RM16, &Interpreter::POP_RM16, OP_RM32, &Interpreter::POP_RM32);
+
+ build_slash(0xC0, 0, "ROL", OP_RM8_imm8, &Interpreter::ROL_RM8_imm8);
+ build_slash(0xC0, 1, "ROR", OP_RM8_imm8, &Interpreter::ROR_RM8_imm8);
+ build_slash(0xC0, 2, "RCL", OP_RM8_imm8, &Interpreter::RCL_RM8_imm8);
+ build_slash(0xC0, 3, "RCR", OP_RM8_imm8, &Interpreter::RCR_RM8_imm8);
+ build_slash(0xC0, 4, "SHL", OP_RM8_imm8, &Interpreter::SHL_RM8_imm8);
+ build_slash(0xC0, 5, "SHR", OP_RM8_imm8, &Interpreter::SHR_RM8_imm8);
+ build_slash(0xC0, 6, "SHL", OP_RM8_imm8, &Interpreter::SHL_RM8_imm8); // Undocumented
+ build_slash(0xC0, 7, "SAR", OP_RM8_imm8, &Interpreter::SAR_RM8_imm8);
+
+ build_slash(0xC1, 0, "ROL", OP_RM16_imm8, &Interpreter::ROL_RM16_imm8, OP_RM32_imm8, &Interpreter::ROL_RM32_imm8);
+ build_slash(0xC1, 1, "ROR", OP_RM16_imm8, &Interpreter::ROR_RM16_imm8, OP_RM32_imm8, &Interpreter::ROR_RM32_imm8);
+ build_slash(0xC1, 2, "RCL", OP_RM16_imm8, &Interpreter::RCL_RM16_imm8, OP_RM32_imm8, &Interpreter::RCL_RM32_imm8);
+ build_slash(0xC1, 3, "RCR", OP_RM16_imm8, &Interpreter::RCR_RM16_imm8, OP_RM32_imm8, &Interpreter::RCR_RM32_imm8);
+ build_slash(0xC1, 4, "SHL", OP_RM16_imm8, &Interpreter::SHL_RM16_imm8, OP_RM32_imm8, &Interpreter::SHL_RM32_imm8);
+ build_slash(0xC1, 5, "SHR", OP_RM16_imm8, &Interpreter::SHR_RM16_imm8, OP_RM32_imm8, &Interpreter::SHR_RM32_imm8);
+ build_slash(0xC1, 6, "SHL", OP_RM16_imm8, &Interpreter::SHL_RM16_imm8, OP_RM32_imm8, &Interpreter::SHL_RM32_imm8); // Undocumented
+ build_slash(0xC1, 7, "SAR", OP_RM16_imm8, &Interpreter::SAR_RM16_imm8, OP_RM32_imm8, &Interpreter::SAR_RM32_imm8);
+
+ build_slash(0xD0, 0, "ROL", OP_RM8_1, &Interpreter::ROL_RM8_1);
+ build_slash(0xD0, 1, "ROR", OP_RM8_1, &Interpreter::ROR_RM8_1);
+ build_slash(0xD0, 2, "RCL", OP_RM8_1, &Interpreter::RCL_RM8_1);
+ build_slash(0xD0, 3, "RCR", OP_RM8_1, &Interpreter::RCR_RM8_1);
+ build_slash(0xD0, 4, "SHL", OP_RM8_1, &Interpreter::SHL_RM8_1);
+ build_slash(0xD0, 5, "SHR", OP_RM8_1, &Interpreter::SHR_RM8_1);
+ build_slash(0xD0, 6, "SHL", OP_RM8_1, &Interpreter::SHL_RM8_1); // Undocumented
+ build_slash(0xD0, 7, "SAR", OP_RM8_1, &Interpreter::SAR_RM8_1);
+
+ build_slash(0xD1, 0, "ROL", OP_RM16_1, &Interpreter::ROL_RM16_1, OP_RM32_1, &Interpreter::ROL_RM32_1);
+ build_slash(0xD1, 1, "ROR", OP_RM16_1, &Interpreter::ROR_RM16_1, OP_RM32_1, &Interpreter::ROR_RM32_1);
+ build_slash(0xD1, 2, "RCL", OP_RM16_1, &Interpreter::RCL_RM16_1, OP_RM32_1, &Interpreter::RCL_RM32_1);
+ build_slash(0xD1, 3, "RCR", OP_RM16_1, &Interpreter::RCR_RM16_1, OP_RM32_1, &Interpreter::RCR_RM32_1);
+ build_slash(0xD1, 4, "SHL", OP_RM16_1, &Interpreter::SHL_RM16_1, OP_RM32_1, &Interpreter::SHL_RM32_1);
+ build_slash(0xD1, 5, "SHR", OP_RM16_1, &Interpreter::SHR_RM16_1, OP_RM32_1, &Interpreter::SHR_RM32_1);
+ build_slash(0xD1, 6, "SHL", OP_RM16_1, &Interpreter::SHL_RM16_1, OP_RM32_1, &Interpreter::SHL_RM32_1); // Undocumented
+ build_slash(0xD1, 7, "SAR", OP_RM16_1, &Interpreter::SAR_RM16_1, OP_RM32_1, &Interpreter::SAR_RM32_1);
+
+ build_slash(0xD2, 0, "ROL", OP_RM8_CL, &Interpreter::ROL_RM8_CL);
+ build_slash(0xD2, 1, "ROR", OP_RM8_CL, &Interpreter::ROR_RM8_CL);
+ build_slash(0xD2, 2, "RCL", OP_RM8_CL, &Interpreter::RCL_RM8_CL);
+ build_slash(0xD2, 3, "RCR", OP_RM8_CL, &Interpreter::RCR_RM8_CL);
+ build_slash(0xD2, 4, "SHL", OP_RM8_CL, &Interpreter::SHL_RM8_CL);
+ build_slash(0xD2, 5, "SHR", OP_RM8_CL, &Interpreter::SHR_RM8_CL);
+ build_slash(0xD2, 6, "SHL", OP_RM8_CL, &Interpreter::SHL_RM8_CL); // Undocumented
+ build_slash(0xD2, 7, "SAR", OP_RM8_CL, &Interpreter::SAR_RM8_CL);
+
+ build_slash(0xD3, 0, "ROL", OP_RM16_CL, &Interpreter::ROL_RM16_CL, OP_RM32_CL, &Interpreter::ROL_RM32_CL);
+ build_slash(0xD3, 1, "ROR", OP_RM16_CL, &Interpreter::ROR_RM16_CL, OP_RM32_CL, &Interpreter::ROR_RM32_CL);
+ build_slash(0xD3, 2, "RCL", OP_RM16_CL, &Interpreter::RCL_RM16_CL, OP_RM32_CL, &Interpreter::RCL_RM32_CL);
+ build_slash(0xD3, 3, "RCR", OP_RM16_CL, &Interpreter::RCR_RM16_CL, OP_RM32_CL, &Interpreter::RCR_RM32_CL);
+ build_slash(0xD3, 4, "SHL", OP_RM16_CL, &Interpreter::SHL_RM16_CL, OP_RM32_CL, &Interpreter::SHL_RM32_CL);
+ build_slash(0xD3, 5, "SHR", OP_RM16_CL, &Interpreter::SHR_RM16_CL, OP_RM32_CL, &Interpreter::SHR_RM32_CL);
+ build_slash(0xD3, 6, "SHL", OP_RM16_CL, &Interpreter::SHL_RM16_CL, OP_RM32_CL, &Interpreter::SHL_RM32_CL); // Undocumented
+ build_slash(0xD3, 7, "SAR", OP_RM16_CL, &Interpreter::SAR_RM16_CL, OP_RM32_CL, &Interpreter::SAR_RM32_CL);
+
+ build_slash(0xF6, 0, "TEST", OP_RM8_imm8, &Interpreter::TEST_RM8_imm8);
+ build_slash(0xF6, 1, "TEST", OP_RM8_imm8, &Interpreter::TEST_RM8_imm8); // Undocumented
+ build_slash(0xF6, 2, "NOT", OP_RM8, &Interpreter::NOT_RM8, LockPrefixAllowed);
+ build_slash(0xF6, 3, "NEG", OP_RM8, &Interpreter::NEG_RM8, LockPrefixAllowed);
+ build_slash(0xF6, 4, "MUL", OP_RM8, &Interpreter::MUL_RM8);
+ build_slash(0xF6, 5, "IMUL", OP_RM8, &Interpreter::IMUL_RM8);
+ build_slash(0xF6, 6, "DIV", OP_RM8, &Interpreter::DIV_RM8);
+ build_slash(0xF6, 7, "IDIV", OP_RM8, &Interpreter::IDIV_RM8);
+
+ build_slash(0xF7, 0, "TEST", OP_RM16_imm16, &Interpreter::TEST_RM16_imm16, OP_RM32_imm32, &Interpreter::TEST_RM32_imm32);
+ build_slash(0xF7, 1, "TEST", OP_RM16_imm16, &Interpreter::TEST_RM16_imm16, OP_RM32_imm32, &Interpreter::TEST_RM32_imm32); // Undocumented
+ build_slash(0xF7, 2, "NOT", OP_RM16, &Interpreter::NOT_RM16, OP_RM32, &Interpreter::NOT_RM32, LockPrefixAllowed);
+ build_slash(0xF7, 3, "NEG", OP_RM16, &Interpreter::NEG_RM16, OP_RM32, &Interpreter::NEG_RM32, LockPrefixAllowed);
+ build_slash(0xF7, 4, "MUL", OP_RM16, &Interpreter::MUL_RM16, OP_RM32, &Interpreter::MUL_RM32);
+ build_slash(0xF7, 5, "IMUL", OP_RM16, &Interpreter::IMUL_RM16, OP_RM32, &Interpreter::IMUL_RM32);
+ build_slash(0xF7, 6, "DIV", OP_RM16, &Interpreter::DIV_RM16, OP_RM32, &Interpreter::DIV_RM32);
+ build_slash(0xF7, 7, "IDIV", OP_RM16, &Interpreter::IDIV_RM16, OP_RM32, &Interpreter::IDIV_RM32);
+
+ build_slash(0xFE, 0, "INC", OP_RM8, &Interpreter::INC_RM8, LockPrefixAllowed);
+ build_slash(0xFE, 1, "DEC", OP_RM8, &Interpreter::DEC_RM8, LockPrefixAllowed);
+
+ build_slash(0xFF, 0, "INC", OP_RM16, &Interpreter::INC_RM16, OP_RM32, &Interpreter::INC_RM32, LockPrefixAllowed);
+ build_slash(0xFF, 1, "DEC", OP_RM16, &Interpreter::DEC_RM16, OP_RM32, &Interpreter::DEC_RM32, LockPrefixAllowed);
+ build_slash(0xFF, 2, "CALL", OP_RM16, &Interpreter::CALL_RM16, OP_RM32, &Interpreter::CALL_RM32);
+ build_slash(0xFF, 3, "CALL", OP_FAR_mem16, &Interpreter::CALL_FAR_mem16, OP_FAR_mem32, &Interpreter::CALL_FAR_mem32);
+ build_slash(0xFF, 4, "JMP", OP_RM16, &Interpreter::JMP_RM16, OP_RM32, &Interpreter::JMP_RM32);
+ build_slash(0xFF, 5, "JMP", OP_FAR_mem16, &Interpreter::JMP_FAR_mem16, OP_FAR_mem32, &Interpreter::JMP_FAR_mem32);
+ build_slash(0xFF, 6, "PUSH", OP_RM16, &Interpreter::PUSH_RM16, OP_RM32, &Interpreter::PUSH_RM32);
+
+ // Instructions starting with 0x0F are multi-byte opcodes.
+ build_0f_slash(0x00, 0, "SLDT", OP_RM16, &Interpreter::SLDT_RM16);
+ build_0f_slash(0x00, 1, "STR", OP_RM16, &Interpreter::STR_RM16);
+ build_0f_slash(0x00, 2, "LLDT", OP_RM16, &Interpreter::LLDT_RM16);
+ build_0f_slash(0x00, 3, "LTR", OP_RM16, &Interpreter::LTR_RM16);
+ build_0f_slash(0x00, 4, "VERR", OP_RM16, &Interpreter::VERR_RM16);
+ build_0f_slash(0x00, 5, "VERW", OP_RM16, &Interpreter::VERW_RM16);
+
+ build_0f_slash(0x01, 0, "SGDT", OP_RM16, &Interpreter::SGDT);
+ build_0f_slash(0x01, 1, "SIDT", OP_RM16, &Interpreter::SIDT);
+ build_0f_slash(0x01, 2, "LGDT", OP_RM16, &Interpreter::LGDT);
+ build_0f_slash(0x01, 3, "LIDT", OP_RM16, &Interpreter::LIDT);
+ build_0f_slash(0x01, 4, "SMSW", OP_RM16, &Interpreter::SMSW_RM16);
+ build_0f_slash(0x01, 6, "LMSW", OP_RM16, &Interpreter::LMSW_RM16);
+ build_0f_slash(0x01, 7, "INVLPG", OP_RM32, &Interpreter::INVLPG);
+
+ build_0f_slash(0xBA, 4, "BT", OP_RM16_imm8, &Interpreter::BT_RM16_imm8, OP_RM32_imm8, &Interpreter::BT_RM32_imm8, LockPrefixAllowed);
+ build_0f_slash(0xBA, 5, "BTS", OP_RM16_imm8, &Interpreter::BTS_RM16_imm8, OP_RM32_imm8, &Interpreter::BTS_RM32_imm8, LockPrefixAllowed);
+ build_0f_slash(0xBA, 6, "BTR", OP_RM16_imm8, &Interpreter::BTR_RM16_imm8, OP_RM32_imm8, &Interpreter::BTR_RM32_imm8, LockPrefixAllowed);
+ build_0f_slash(0xBA, 7, "BTC", OP_RM16_imm8, &Interpreter::BTC_RM16_imm8, OP_RM32_imm8, &Interpreter::BTC_RM32_imm8, LockPrefixAllowed);
+
+ build_0f(0x02, "LAR", OP_reg16_RM16, &Interpreter::LAR_reg16_RM16, OP_reg32_RM32, &Interpreter::LAR_reg32_RM32);
+ build_0f(0x03, "LSL", OP_reg16_RM16, &Interpreter::LSL_reg16_RM16, OP_reg32_RM32, &Interpreter::LSL_reg32_RM32);
+ build_0f(0x06, "CLTS", OP, &Interpreter::CLTS);
+ build_0f(0x09, "WBINVD", OP, &Interpreter::WBINVD);
+ build_0f(0x0B, "UD2", OP, &Interpreter::UD2);
+
+ build_0f(0x20, "MOV", OP_reg32_CR, &Interpreter::MOV_reg32_CR);
+ build_0f(0x21, "MOV", OP_reg32_DR, &Interpreter::MOV_reg32_DR);
+ build_0f(0x22, "MOV", OP_CR_reg32, &Interpreter::MOV_CR_reg32);
+ build_0f(0x23, "MOV", OP_DR_reg32, &Interpreter::MOV_DR_reg32);
+
+ build_0f(0x31, "RDTSC", OP, &Interpreter::RDTSC);
+
+ build_0f(0x40, "CMOVO", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+ build_0f(0x41, "CMOVNO", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+ build_0f(0x42, "CMOVC", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+ build_0f(0x43, "CMOVNC", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+ build_0f(0x44, "CMOVZ", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+ build_0f(0x45, "CMOVNZ", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+ build_0f(0x46, "CMOVNA", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+ build_0f(0x47, "CMOVA", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+ build_0f(0x48, "CMOVS", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+ build_0f(0x49, "CMOVNS", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+ build_0f(0x4A, "CMOVP", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+ build_0f(0x4B, "CMOVNP", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+ build_0f(0x4C, "CMOVL", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+ build_0f(0x4D, "CMOVNL", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+ build_0f(0x4E, "CMOVNG", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+ build_0f(0x4F, "CMOVG", OP_reg16_RM16, &Interpreter::CMOVcc_reg16_RM16, OP_reg32_RM32, &Interpreter::CMOVcc_reg32_RM32);
+
+ build_0f(0x6F, "MOVQ", OP_mm1_mm2m64, &Interpreter::MOVQ_mm1_mm2m64);
+ build_0f(0x77, "EMMS", OP, &Interpreter::EMMS);
+ build_0f(0x7F, "MOVQ", OP_mm1m64_mm2, &Interpreter::MOVQ_mm1_m64_mm2);
+
+ build_0f(0x80, "JO", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+ build_0f(0x81, "JNO", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+ build_0f(0x82, "JC", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+ build_0f(0x83, "JNC", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+ build_0f(0x84, "JZ", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+ build_0f(0x85, "JNZ", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+ build_0f(0x86, "JNA", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+ build_0f(0x87, "JA", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+ build_0f(0x88, "JS", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+ build_0f(0x89, "JNS", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+ build_0f(0x8A, "JP", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+ build_0f(0x8B, "JNP", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+ build_0f(0x8C, "JL", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+ build_0f(0x8D, "JNL", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+ build_0f(0x8E, "JNG", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+ build_0f(0x8F, "JG", OP_NEAR_imm, &Interpreter::Jcc_NEAR_imm);
+
+ build_0f(0x90, "SETO", OP_RM8, &Interpreter::SETcc_RM8);
+ build_0f(0x91, "SETNO", OP_RM8, &Interpreter::SETcc_RM8);
+ build_0f(0x92, "SETC", OP_RM8, &Interpreter::SETcc_RM8);
+ build_0f(0x93, "SETNC", OP_RM8, &Interpreter::SETcc_RM8);
+ build_0f(0x94, "SETZ", OP_RM8, &Interpreter::SETcc_RM8);
+ build_0f(0x95, "SETNZ", OP_RM8, &Interpreter::SETcc_RM8);
+ build_0f(0x96, "SETNA", OP_RM8, &Interpreter::SETcc_RM8);
+ build_0f(0x97, "SETA", OP_RM8, &Interpreter::SETcc_RM8);
+ build_0f(0x98, "SETS", OP_RM8, &Interpreter::SETcc_RM8);
+ build_0f(0x99, "SETNS", OP_RM8, &Interpreter::SETcc_RM8);
+ build_0f(0x9A, "SETP", OP_RM8, &Interpreter::SETcc_RM8);
+ build_0f(0x9B, "SETNP", OP_RM8, &Interpreter::SETcc_RM8);
+ build_0f(0x9C, "SETL", OP_RM8, &Interpreter::SETcc_RM8);
+ build_0f(0x9D, "SETNL", OP_RM8, &Interpreter::SETcc_RM8);
+ build_0f(0x9E, "SETNG", OP_RM8, &Interpreter::SETcc_RM8);
+ build_0f(0x9F, "SETG", OP_RM8, &Interpreter::SETcc_RM8);
+
+ build_0f(0xA0, "PUSH", OP_FS, &Interpreter::PUSH_FS);
+ build_0f(0xA1, "POP", OP_FS, &Interpreter::POP_FS);
+ build_0f(0xA2, "CPUID", OP, &Interpreter::CPUID);
+ build_0f(0xA3, "BT", OP_RM16_reg16, &Interpreter::BT_RM16_reg16, OP_RM32_reg32, &Interpreter::BT_RM32_reg32);
+ build_0f(0xA4, "SHLD", OP_RM16_reg16_imm8, &Interpreter::SHLD_RM16_reg16_imm8, OP_RM32_reg32_imm8, &Interpreter::SHLD_RM32_reg32_imm8);
+ build_0f(0xA5, "SHLD", OP_RM16_reg16_CL, &Interpreter::SHLD_RM16_reg16_CL, OP_RM32_reg32_CL, &Interpreter::SHLD_RM32_reg32_CL);
+ build_0f(0xA8, "PUSH", OP_GS, &Interpreter::PUSH_GS);
+ build_0f(0xA9, "POP", OP_GS, &Interpreter::POP_GS);
+ build_0f(0xAB, "BTS", OP_RM16_reg16, &Interpreter::BTS_RM16_reg16, OP_RM32_reg32, &Interpreter::BTS_RM32_reg32);
+ build_0f(0xAC, "SHRD", OP_RM16_reg16_imm8, &Interpreter::SHRD_RM16_reg16_imm8, OP_RM32_reg32_imm8, &Interpreter::SHRD_RM32_reg32_imm8);
+ build_0f(0xAD, "SHRD", OP_RM16_reg16_CL, &Interpreter::SHRD_RM16_reg16_CL, OP_RM32_reg32_CL, &Interpreter::SHRD_RM32_reg32_CL);
+ build_0f(0xAF, "IMUL", OP_reg16_RM16, &Interpreter::IMUL_reg16_RM16, OP_reg32_RM32, &Interpreter::IMUL_reg32_RM32);
+ build_0f(0xB0, "CMPXCHG", OP_RM8_reg8, &Interpreter::CMPXCHG_RM8_reg8, LockPrefixAllowed);
+ build_0f(0xB1, "CMPXCHG", OP_RM16_reg16, &Interpreter::CMPXCHG_RM16_reg16, OP_RM32_reg32, &Interpreter::CMPXCHG_RM32_reg32, LockPrefixAllowed);
+ build_0f(0xB2, "LSS", OP_reg16_mem16, &Interpreter::LSS_reg16_mem16, OP_reg32_mem32, &Interpreter::LSS_reg32_mem32);
+ build_0f(0xB3, "BTR", OP_RM16_reg16, &Interpreter::BTR_RM16_reg16, OP_RM32_reg32, &Interpreter::BTR_RM32_reg32);
+ build_0f(0xB4, "LFS", OP_reg16_mem16, &Interpreter::LFS_reg16_mem16, OP_reg32_mem32, &Interpreter::LFS_reg32_mem32);
+ build_0f(0xB5, "LGS", OP_reg16_mem16, &Interpreter::LGS_reg16_mem16, OP_reg32_mem32, &Interpreter::LGS_reg32_mem32);
+ build_0f(0xB6, "MOVZX", OP_reg16_RM8, &Interpreter::MOVZX_reg16_RM8, OP_reg32_RM8, &Interpreter::MOVZX_reg32_RM8);
+ build_0f(0xB7, "0xB7", OP, nullptr, "MOVZX", OP_reg32_RM16, &Interpreter::MOVZX_reg32_RM16);
+ build_0f(0xB9, "UD1", OP, &Interpreter::UD1);
+ build_0f(0xBB, "BTC", OP_RM16_reg16, &Interpreter::BTC_RM16_reg16, OP_RM32_reg32, &Interpreter::BTC_RM32_reg32);
+ build_0f(0xBC, "BSF", OP_reg16_RM16, &Interpreter::BSF_reg16_RM16, OP_reg32_RM32, &Interpreter::BSF_reg32_RM32);
+ build_0f(0xBD, "BSR", OP_reg16_RM16, &Interpreter::BSR_reg16_RM16, OP_reg32_RM32, &Interpreter::BSR_reg32_RM32);
+ build_0f(0xBE, "MOVSX", OP_reg16_RM8, &Interpreter::MOVSX_reg16_RM8, OP_reg32_RM8, &Interpreter::MOVSX_reg32_RM8);
+ build_0f(0xBF, "0xBF", OP, nullptr, "MOVSX", OP_reg32_RM16, &Interpreter::MOVSX_reg32_RM16);
+ build_0f(0xC0, "XADD", OP_RM8_reg8, &Interpreter::XADD_RM8_reg8, LockPrefixAllowed);
+ build_0f(0xC1, "XADD", OP_RM16_reg16, &Interpreter::XADD_RM16_reg16, OP_RM32_reg32, &Interpreter::XADD_RM32_reg32, LockPrefixAllowed);
+
+ for (u8 i = 0xc8; i <= 0xcf; ++i)
+ build_0f(i, "BSWAP", OP_reg32, &Interpreter::BSWAP_reg32);
+
+ build_0f(0xFC, "PADDB", OP_mm1_mm2m64, &Interpreter::PADDB_mm1_mm2m64);
+ build_0f(0xFD, "PADDW", OP_mm1_mm2m64, &Interpreter::PADDW_mm1_mm2m64);
+ build_0f(0xFE, "PADDD", OP_mm1_mm2m64, &Interpreter::PADDD_mm1_mm2m64);
+ build_0f(0xFF, "UD0", OP, &Interpreter::UD0);
+}
+
+static const char* register_name(RegisterIndex8);
+static const char* register_name(RegisterIndex16);
+static const char* register_name(RegisterIndex32);
+static const char* register_name(FpuRegisterIndex);
+static const char* register_name(SegmentRegister);
+static const char* register_name(MMXRegisterIndex);
+
+const char* Instruction::reg8_name() const
+{
+ return register_name(static_cast<RegisterIndex8>(register_index()));
+}
+
+const char* Instruction::reg16_name() const
+{
+ return register_name(static_cast<RegisterIndex16>(register_index()));
+}
+
+const char* Instruction::reg32_name() const
+{
+ return register_name(static_cast<RegisterIndex32>(register_index()));
+}
+
+String MemoryOrRegisterReference::to_string_o8(const Instruction& insn) const
+{
+ if (is_register())
+ return register_name(reg8());
+ return String::format("[%s]", to_string(insn).characters());
+}
+
+String MemoryOrRegisterReference::to_string_o16(const Instruction& insn) const
+{
+ if (is_register())
+ return register_name(reg16());
+ return String::format("[%s]", to_string(insn).characters());
+}
+
+String MemoryOrRegisterReference::to_string_o32(const Instruction& insn) const
+{
+ if (is_register())
+ return register_name(reg32());
+ return String::format("[%s]", to_string(insn).characters());
+}
+
+String MemoryOrRegisterReference::to_string_fpu_reg() const
+{
+ ASSERT(is_register());
+ return register_name(reg_fpu());
+}
+
+String MemoryOrRegisterReference::to_string_fpu_mem(const Instruction& insn) const
+{
+ ASSERT(!is_register());
+ return String::format("[%s]", to_string(insn).characters());
+}
+
+String MemoryOrRegisterReference::to_string_fpu_ax16() const
+{
+ ASSERT(is_register());
+ return register_name(reg16());
+}
+
+String MemoryOrRegisterReference::to_string_fpu16(const Instruction& insn) const
+{
+ if (is_register())
+ return register_name(reg_fpu());
+ return String::format("word ptr [%s]", to_string(insn).characters());
+}
+
+String MemoryOrRegisterReference::to_string_fpu32(const Instruction& insn) const
+{
+ if (is_register())
+ return register_name(reg_fpu());
+ return String::format("dword ptr [%s]", to_string(insn).characters());
+}
+
+String MemoryOrRegisterReference::to_string_fpu64(const Instruction& insn) const
+{
+ if (is_register())
+ return register_name(reg_fpu());
+ return String::format("qword ptr [%s]", to_string(insn).characters());
+}
+
+String MemoryOrRegisterReference::to_string_fpu80(const Instruction& insn) const
+{
+ ASSERT(!is_register());
+ return String::format("tbyte ptr [%s]", to_string(insn).characters());
+}
+String MemoryOrRegisterReference::to_string_mm(const Instruction& insn) const
+{
+ if (is_register())
+ return register_name(static_cast<MMXRegisterIndex>(m_register_index));
+ return String::format("[%s]", to_string(insn).characters());
+}
+
+String MemoryOrRegisterReference::to_string(const Instruction& insn) const
+{
+ if (insn.a32())
+ return to_string_a32();
+ return to_string_a16();
+}
+
+String MemoryOrRegisterReference::to_string_a16() const
+{
+ String base;
+ bool hasDisplacement = false;
+
+ switch (m_rm & 7) {
+ case 0:
+ base = "bx+si";
+ break;
+ case 1:
+ base = "bx+di";
+ break;
+ case 2:
+ base = "bp+si";
+ break;
+ case 3:
+ base = "bp+di";
+ break;
+ case 4:
+ base = "si";
+ break;
+ case 5:
+ base = "di";
+ break;
+ case 7:
+ base = "bx";
+ break;
+ case 6:
+ if ((m_rm & 0xc0) == 0)
+ base = String::format("%#04x", m_displacement16);
+ else
+ base = "bp";
+ break;
+ }
+
+ switch (m_rm & 0xc0) {
+ case 0x40:
+ case 0x80:
+ hasDisplacement = true;
+ }
+
+ if (!hasDisplacement)
+ return base;
+
+ String disp;
+ if ((i16)m_displacement16 < 0)
+ disp = String::format("-%#x", -(i16)m_displacement16);
+ else
+ String::format("+%#x", m_displacement16);
+ return String::format("%s%s", base.characters(), disp.characters());
+}
+
+static String sib_to_string(u8 rm, u8 sib)
+{
+ String scale;
+ String index;
+ String base;
+ switch (sib & 0xC0) {
+ case 0x00:;
+ break;
+ case 0x40:
+ scale = "*2";
+ break;
+ case 0x80:
+ scale = "*4";
+ break;
+ case 0xC0:
+ scale = "*8";
+ break;
+ }
+ switch ((sib >> 3) & 0x07) {
+ case 0:
+ index = "eax";
+ break;
+ case 1:
+ index = "ecx";
+ break;
+ case 2:
+ index = "edx";
+ break;
+ case 3:
+ index = "ebx";
+ break;
+ case 4:
+ break;
+ case 5:
+ index = "ebp";
+ break;
+ case 6:
+ index = "esi";
+ break;
+ case 7:
+ index = "edi";
+ break;
+ }
+ switch (sib & 0x07) {
+ case 0:
+ base = "eax";
+ break;
+ case 1:
+ base = "ecx";
+ break;
+ case 2:
+ base = "edx";
+ break;
+ case 3:
+ base = "ebx";
+ break;
+ case 4:
+ base = "esp";
+ break;
+ case 6:
+ base = "esi";
+ break;
+ case 7:
+ base = "edi";
+ break;
+ default: // 5
+ switch ((rm >> 6) & 3) {
+ case 1:
+ case 2:
+ base = "ebp";
+ break;
+ }
+ break;
+ }
+ StringBuilder builder;
+ if (base.is_empty()) {
+ builder.append(index);
+ builder.append(scale);
+ } else {
+ builder.append(base);
+ if (!base.is_empty() && !index.is_empty())
+ builder.append('+');
+ builder.append(index);
+ builder.append(scale);
+ }
+ return builder.to_string();
+}
+
+String MemoryOrRegisterReference::to_string_a32() const
+{
+ if (is_register())
+ return register_name(static_cast<RegisterIndex32>(m_register_index));
+
+ bool has_displacement = false;
+ switch (m_rm & 0xc0) {
+ case 0x40:
+ case 0x80:
+ has_displacement = true;
+ }
+ if (m_has_sib && (m_sib & 7) == 5)
+ has_displacement = true;
+
+ String base;
+ switch (m_rm & 7) {
+ case 0:
+ base = "eax";
+ break;
+ case 1:
+ base = "ecx";
+ break;
+ case 2:
+ base = "edx";
+ break;
+ case 3:
+ base = "ebx";
+ break;
+ case 6:
+ base = "esi";
+ break;
+ case 7:
+ base = "edi";
+ break;
+ case 5:
+ if ((m_rm & 0xc0) == 0)
+ base = String::format("%#08x", m_displacement32);
+ else
+ base = "ebp";
+ break;
+ case 4:
+ base = sib_to_string(m_rm, m_sib);
+ break;
+ }
+
+ if (!has_displacement)
+ return base;
+
+ String disp;
+ if ((i32)m_displacement32 < 0)
+ disp = String::format("-%#x", -(i32)m_displacement32);
+ else
+ disp = String::format("+%#x", m_displacement32);
+ return String::format("%s%s", base.characters(), disp.characters());
+}
+
+static String relative_address(u32 origin, bool x32, i8 imm)
+{
+ if (x32)
+ return String::format("%#08x", origin + imm);
+ u16 w = origin & 0xffff;
+ return String::format("%#04x", w + imm);
+}
+
+static String relative_address(u32 origin, bool x32, i32 imm)
+{
+ if (x32)
+ return String::format("%#08x", origin + imm);
+ u16 w = origin & 0xffff;
+ i16 si = imm;
+ return String::format("%#04x", w + si);
+}
+
+String Instruction::to_string(u32 origin, const SymbolProvider* symbol_provider, bool x32) const
+{
+ StringBuilder builder;
+ if (has_segment_prefix())
+ builder.appendf("%s: ", register_name(segment_prefix().value()));
+ if (has_address_size_override_prefix())
+ builder.append(m_a32 ? "a32 " : "a16 ");
+ if (has_operand_size_override_prefix())
+ builder.append(m_o32 ? "o32 " : "o16 ");
+ if (has_lock_prefix())
+ builder.append("lock ");
+ if (has_rep_prefix())
+ builder.append(m_rep_prefix == Prefix::REPNZ ? "repnz " : "repz ");
+ to_string_internal(builder, origin, symbol_provider, x32);
+ return builder.to_string();
+}
+
+void Instruction::to_string_internal(StringBuilder& builder, u32 origin, const SymbolProvider* symbol_provider, bool x32) const
+{
+ if (!m_descriptor) {
+ builder.appendf("db %#02x", m_op);
+ return;
+ }
+
+ String mnemonic = String(m_descriptor->mnemonic).to_lowercase();
+
+ auto append_mnemonic = [&] { builder.append(mnemonic); };
+ auto append_mnemonic_space = [&] {
+ builder.append(mnemonic);
+ builder.append(' ');
+ };
+
+ auto formatted_address = [&](FlatPtr origin, bool x32, auto offset) {
+ builder.append(relative_address(origin, x32, offset));
+ if (symbol_provider) {
+ u32 symbol_offset = 0;
+ auto symbol = symbol_provider->symbolicate(origin + offset, &symbol_offset);
+ builder.append(" <");
+ builder.append(symbol);
+ if (symbol_offset)
+ builder.appendf("+%u", symbol_offset);
+ builder.append('>');
+ }
+ };
+
+ auto append_rm8 = [&] { builder.append(m_modrm.to_string_o8(*this)); };
+ auto append_rm16 = [&] { builder.append(m_modrm.to_string_o16(*this)); };
+ auto append_rm32 = [&] { builder.append(m_modrm.to_string_o32(*this)); };
+ auto append_fpu_reg = [&] { builder.append(m_modrm.to_string_fpu_reg()); };
+ auto append_fpu_mem = [&] { builder.append(m_modrm.to_string_fpu_mem(*this)); };
+ auto append_fpu_ax16 = [&] { builder.append(m_modrm.to_string_fpu_ax16()); };
+ auto append_fpu_rm16 = [&] { builder.append(m_modrm.to_string_fpu16(*this)); };
+ auto append_fpu_rm32 = [&] { builder.append(m_modrm.to_string_fpu32(*this)); };
+ auto append_fpu_rm64 = [&] { builder.append(m_modrm.to_string_fpu64(*this)); };
+ auto append_fpu_rm80 = [&] { builder.append(m_modrm.to_string_fpu80(*this)); };
+ auto append_imm8 = [&] { builder.appendf("%#02x", imm8()); };
+ auto append_imm8_2 = [&] { builder.appendf("%#02x", imm8_2()); };
+ auto append_imm16 = [&] { builder.appendf("%#04x", imm16()); };
+ auto append_imm16_1 = [&] { builder.appendf("%#04x", imm16_1()); };
+ auto append_imm16_2 = [&] { builder.appendf("%#04x", imm16_2()); };
+ auto append_imm32 = [&] { builder.appendf("%#08x", imm32()); };
+ auto append_imm32_2 = [&] { builder.appendf("%#08x", imm32_2()); };
+ auto append_reg8 = [&] { builder.append(reg8_name()); };
+ auto append_reg16 = [&] { builder.append(reg16_name()); };
+ auto append_reg32 = [&] { builder.append(reg32_name()); };
+ auto append_seg = [&] { builder.append(register_name(segment_register())); };
+ auto append_creg = [&] { builder.appendf("cr%u", register_index()); };
+ auto append_dreg = [&] { builder.appendf("dr%u", register_index()); };
+ auto append_relative_addr = [&] { formatted_address(origin + (m_a32 ? 6 : 4), x32, i32(m_a32 ? imm32() : imm16())); };
+ auto append_relative_imm8 = [&] { formatted_address(origin + 2, x32, i8(imm8())); };
+ auto append_relative_imm16 = [&] { formatted_address(origin + 3, x32, i16(imm16())); };
+ auto append_relative_imm32 = [&] { formatted_address(origin + 5, x32, i32(imm32())); };
+
+ auto append_mm = [&] { builder.appendf("mm%u", register_index()); };
+ auto append_mmrm64 = [&] { builder.append(m_modrm.to_string_mm(*this)); };
+
+ auto append = [&](auto& content) { builder.append(content); };
+ auto append_moff = [&] {
+ builder.append('[');
+ if (m_a32) {
+ append_imm32();
+ } else {
+ append_imm16();
+ }
+ builder.append(']');
+ };
+
+ switch (m_descriptor->format) {
+ case OP_RM8_imm8:
+ append_mnemonic_space();
+ append_rm8();
+ append(", ");
+ append_imm8();
+ break;
+ case OP_RM16_imm8:
+ append_mnemonic_space();
+ append_rm16();
+ append(", ");
+ append_imm8();
+ break;
+ case OP_RM32_imm8:
+ append_mnemonic_space();
+ append_rm32();
+ append(", ");
+ append_imm8();
+ break;
+ case OP_reg16_RM16_imm8:
+ append_mnemonic_space();
+ append_reg16();
+ append(", ");
+ append_rm16();
+ append(", ");
+ append_imm8();
+ break;
+ case OP_reg32_RM32_imm8:
+ append_mnemonic_space();
+ append_reg32();
+ append(", ");
+ append_rm32();
+ append(", ");
+ append_imm8();
+ break;
+ case OP_AL_imm8:
+ append_mnemonic_space();
+ append("al, ");
+ append_imm8();
+ break;
+ case OP_imm8:
+ append_mnemonic_space();
+ append_imm8();
+ break;
+ case OP_reg8_imm8:
+ append_mnemonic_space();
+ append_reg8();
+ append(", ");
+ append_imm8();
+ break;
+ case OP_AX_imm8:
+ append_mnemonic_space();
+ append("ax, ");
+ append_imm8();
+ break;
+ case OP_EAX_imm8:
+ append_mnemonic_space();
+ append("eax, ");
+ append_imm8();
+ break;
+ case OP_imm8_AL:
+ append_mnemonic_space();
+ append_imm8();
+ append(", al");
+ break;
+ case OP_imm8_AX:
+ append_mnemonic_space();
+ append_imm8();
+ append(", ax");
+ break;
+ case OP_imm8_EAX:
+ append_mnemonic_space();
+ append_imm8();
+ append(", eax");
+ break;
+ case OP_AX_imm16:
+ append_mnemonic_space();
+ append("ax, ");
+ append_imm16();
+ break;
+ case OP_imm16:
+ append_mnemonic_space();
+ append_imm16();
+ break;
+ case OP_reg16_imm16:
+ append_mnemonic_space();
+ append_reg16();
+ append(", ");
+ append_imm16();
+ break;
+ case OP_reg16_RM16_imm16:
+ append_mnemonic_space();
+ append_reg16();
+ append(", ");
+ append_rm16();
+ append(", ");
+ append_imm16();
+ break;
+ case OP_reg32_RM32_imm32:
+ append_mnemonic_space();
+ append_reg32();
+ append(", ");
+ append_rm32();
+ append(", ");
+ append_imm32();
+ break;
+ case OP_imm32:
+ append_mnemonic_space();
+ append_imm32();
+ break;
+ case OP_EAX_imm32:
+ append_mnemonic_space();
+ append("eax, ");
+ append_imm32();
+ break;
+ case OP_CS:
+ append_mnemonic_space();
+ append("cs");
+ break;
+ case OP_DS:
+ append_mnemonic_space();
+ append("ds");
+ break;
+ case OP_ES:
+ append_mnemonic_space();
+ append("es");
+ break;
+ case OP_SS:
+ append_mnemonic_space();
+ append("ss");
+ break;
+ case OP_FS:
+ append_mnemonic_space();
+ append("fs");
+ break;
+ case OP_GS:
+ append_mnemonic_space();
+ append("gs");
+ break;
+ case OP:
+ append_mnemonic_space();
+ break;
+ case OP_reg32:
+ append_mnemonic_space();
+ append_reg32();
+ break;
+ case OP_imm16_imm8:
+ append_mnemonic_space();
+ append_imm16_1();
+ append(", ");
+ append_imm8_2();
+ break;
+ case OP_moff8_AL:
+ append_mnemonic_space();
+ append_moff();
+ append(", al");
+ break;
+ case OP_moff16_AX:
+ append_mnemonic_space();
+ append_moff();
+ append(", ax");
+ break;
+ case OP_moff32_EAX:
+ append_mnemonic_space();
+ append_moff();
+ append(", eax");
+ break;
+ case OP_AL_moff8:
+ append_mnemonic_space();
+ append("al, ");
+ append_moff();
+ break;
+ case OP_AX_moff16:
+ append_mnemonic_space();
+ append("ax, ");
+ append_moff();
+ break;
+ case OP_EAX_moff32:
+ append_mnemonic_space();
+ append("eax, ");
+ append_moff();
+ break;
+ case OP_imm16_imm16:
+ append_mnemonic_space();
+ append_imm16_1();
+ append(":");
+ append_imm16_2();
+ break;
+ case OP_imm16_imm32:
+ append_mnemonic_space();
+ append_imm16_1();
+ append(":");
+ append_imm32_2();
+ break;
+ case OP_reg32_imm32:
+ append_mnemonic_space();
+ append_reg32();
+ append(", ");
+ append_imm32();
+ break;
+ case OP_RM8_1:
+ append_mnemonic_space();
+ append_rm8();
+ append(", 0x01");
+ break;
+ case OP_RM16_1:
+ append_mnemonic_space();
+ append_rm16();
+ append(", 0x01");
+ break;
+ case OP_RM32_1:
+ append_mnemonic_space();
+ append_rm32();
+ append(", 0x01");
+ break;
+ case OP_RM8_CL:
+ append_mnemonic_space();
+ append_rm8();
+ append(", cl");
+ break;
+ case OP_RM16_CL:
+ append_mnemonic_space();
+ append_rm16();
+ append(", cl");
+ break;
+ case OP_RM32_CL:
+ append_mnemonic_space();
+ append_rm32();
+ append(", cl");
+ break;
+ case OP_reg16:
+ append_mnemonic_space();
+ append_reg16();
+ break;
+ case OP_AX_reg16:
+ append_mnemonic_space();
+ append("ax, ");
+ append_reg16();
+ break;
+ case OP_EAX_reg32:
+ append_mnemonic_space();
+ append("eax, ");
+ append_reg32();
+ break;
+ case OP_3:
+ append_mnemonic_space();
+ append("0x03");
+ break;
+ case OP_AL_DX:
+ append_mnemonic_space();
+ append("al, dx");
+ break;
+ case OP_AX_DX:
+ append_mnemonic_space();
+ append("ax, dx");
+ break;
+ case OP_EAX_DX:
+ append_mnemonic_space();
+ append("eax, dx");
+ break;
+ case OP_DX_AL:
+ append_mnemonic_space();
+ append("dx, al");
+ break;
+ case OP_DX_AX:
+ append_mnemonic_space();
+ append("dx, ax");
+ break;
+ case OP_DX_EAX:
+ append_mnemonic_space();
+ append("dx, eax");
+ break;
+ case OP_reg8_CL:
+ append_mnemonic_space();
+ append_reg8();
+ append(", cl");
+ break;
+ case OP_RM8:
+ append_mnemonic_space();
+ append_rm8();
+ break;
+ case OP_RM16:
+ append_mnemonic_space();
+ append_rm16();
+ break;
+ case OP_RM32:
+ append_mnemonic_space();
+ append_rm32();
+ break;
+ case OP_FPU:
+ append_mnemonic_space();
+ break;
+ case OP_FPU_reg:
+ append_mnemonic_space();
+ append_fpu_reg();
+ break;
+ case OP_FPU_mem:
+ append_mnemonic_space();
+ append_fpu_mem();
+ break;
+ case OP_FPU_AX16:
+ append_mnemonic_space();
+ append_fpu_ax16();
+ break;
+ case OP_FPU_RM16:
+ append_mnemonic_space();
+ append_fpu_rm16();
+ break;
+ case OP_FPU_RM32:
+ append_mnemonic_space();
+ append_fpu_rm32();
+ break;
+ case OP_FPU_RM64:
+ append_mnemonic_space();
+ append_fpu_rm64();
+ break;
+ case OP_FPU_M80:
+ append_mnemonic_space();
+ append_fpu_rm80();
+ break;
+ case OP_RM8_reg8:
+ append_mnemonic_space();
+ append_rm8();
+ append(", ");
+ append_reg8();
+ break;
+ case OP_RM16_reg16:
+ append_mnemonic_space();
+ append_rm16();
+ append(", ");
+ append_reg16();
+ break;
+ case OP_RM32_reg32:
+ append_mnemonic_space();
+ append_rm32();
+ append(", ");
+ append_reg32();
+ break;
+ case OP_reg8_RM8:
+ append_mnemonic_space();
+ append_reg8();
+ append(", ");
+ append_rm8();
+ break;
+ case OP_reg16_RM16:
+ append_mnemonic_space();
+ append_reg16();
+ append(", ");
+ append_rm16();
+ break;
+ case OP_reg32_RM32:
+ append_mnemonic_space();
+ append_reg32();
+ append(", ");
+ append_rm32();
+ break;
+ case OP_reg32_RM16:
+ append_mnemonic_space();
+ append_reg32();
+ append(", ");
+ append_rm16();
+ break;
+ case OP_reg16_RM8:
+ append_mnemonic_space();
+ append_reg16();
+ append(", ");
+ append_rm8();
+ break;
+ case OP_reg32_RM8:
+ append_mnemonic_space();
+ append_reg32();
+ append(", ");
+ append_rm8();
+ break;
+ case OP_RM16_imm16:
+ append_mnemonic_space();
+ append_rm16();
+ append(", ");
+ append_imm16();
+ break;
+ case OP_RM32_imm32:
+ append_mnemonic_space();
+ append_rm32();
+ append(", ");
+ append_imm32();
+ break;
+ case OP_RM16_seg:
+ append_mnemonic_space();
+ append_rm16();
+ append(", ");
+ append_seg();
+ break;
+ case OP_RM32_seg:
+ append_mnemonic_space();
+ append_rm32();
+ append(", ");
+ append_seg();
+ break;
+ case OP_seg_RM16:
+ append_mnemonic_space();
+ append_seg();
+ append(", ");
+ append_rm16();
+ break;
+ case OP_seg_RM32:
+ append_mnemonic_space();
+ append_seg();
+ append(", ");
+ append_rm32();
+ break;
+ case OP_reg16_mem16:
+ append_mnemonic_space();
+ append_reg16();
+ append(", ");
+ append_rm16();
+ break;
+ case OP_reg32_mem32:
+ append_mnemonic_space();
+ append_reg32();
+ append(", ");
+ append_rm32();
+ break;
+ case OP_FAR_mem16:
+ append_mnemonic_space();
+ append("far ");
+ append_rm16();
+ break;
+ case OP_FAR_mem32:
+ append_mnemonic_space();
+ append("far ");
+ append_rm32();
+ break;
+ case OP_reg32_CR:
+ append_mnemonic_space();
+ builder.append(register_name(static_cast<RegisterIndex32>(rm() & 7)));
+ append(", ");
+ append_creg();
+ break;
+ case OP_CR_reg32:
+ append_mnemonic_space();
+ append_creg();
+ append(", ");
+ builder.append(register_name(static_cast<RegisterIndex32>(rm() & 7)));
+ break;
+ case OP_reg32_DR:
+ append_mnemonic_space();
+ builder.append(register_name(static_cast<RegisterIndex32>(rm() & 7)));
+ append(", ");
+ append_dreg();
+ break;
+ case OP_DR_reg32:
+ append_mnemonic_space();
+ append_dreg();
+ append(", ");
+ builder.append(register_name(static_cast<RegisterIndex32>(rm() & 7)));
+ break;
+ case OP_short_imm8:
+ append_mnemonic_space();
+ append("short ");
+ append_relative_imm8();
+ break;
+ case OP_relimm16:
+ append_mnemonic_space();
+ append_relative_imm16();
+ break;
+ case OP_relimm32:
+ append_mnemonic_space();
+ append_relative_imm32();
+ break;
+ case OP_NEAR_imm:
+ append_mnemonic_space();
+ append("near ");
+ append_relative_addr();
+ break;
+ case OP_RM16_reg16_imm8:
+ append_mnemonic_space();
+ append_rm16();
+ append(", ");
+ append_reg16();
+ append(", ");
+ append_imm8();
+ break;
+ case OP_RM32_reg32_imm8:
+ append_mnemonic_space();
+ append_rm32();
+ append(", ");
+ append_reg32();
+ append(", ");
+ append_imm8();
+ break;
+ case OP_RM16_reg16_CL:
+ append_mnemonic_space();
+ append_rm16();
+ append(", ");
+ append_reg16();
+ append(", cl");
+ break;
+ case OP_RM32_reg32_CL:
+ append_mnemonic_space();
+ append_rm32();
+ append(", ");
+ append_reg32();
+ append(", cl");
+ break;
+ case OP_mm1_mm2m64:
+ append_mnemonic_space();
+ append_mm();
+ append(", ");
+ append_mmrm64();
+ break;
+ case OP_mm1m64_mm2:
+ append_mnemonic_space();
+ append_mm();
+ append(", ");
+ append_mmrm64();
+ break;
+ case InstructionPrefix:
+ append_mnemonic();
+ break;
+ case InvalidFormat:
+ case MultibyteWithSlash:
+ case __BeginFormatsWithRMByte:
+ case __EndFormatsWithRMByte:
+ builder.append(String::format("(!%s)", mnemonic.characters()));
+ break;
+ }
+}
+
+String Instruction::mnemonic() const
+{
+ if (!m_descriptor) {
+ ASSERT_NOT_REACHED();
+ }
+ return m_descriptor->mnemonic;
+}
+
+const char* register_name(SegmentRegister index)
+{
+ static constexpr const char* names[] = { "es", "cs", "ss", "ds", "fs", "gs", "segr6", "segr7" };
+ return names[(int)index & 7];
+}
+
+const char* register_name(RegisterIndex8 register_index)
+{
+ static constexpr const char* names[] = { "al", "cl", "dl", "bl", "ah", "ch", "dh", "bh" };
+ return names[register_index & 7];
+}
+
+const char* register_name(RegisterIndex16 register_index)
+{
+ static constexpr const char* names[] = { "ax", "cx", "dx", "bx", "sp", "bp", "si", "di" };
+ return names[register_index & 7];
+}
+
+const char* register_name(RegisterIndex32 register_index)
+{
+ static constexpr const char* names[] = { "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi" };
+ return names[register_index & 7];
+}
+
+const char* register_name(FpuRegisterIndex register_index)
+{
+ static constexpr const char* names[] = { "st0", "st1", "st2", "st3", "st4", "st5", "st6", "st7" };
+ return names[register_index & 7];
+}
+
+const char* register_name(MMXRegisterIndex register_index)
+{
+ static constexpr const char* names[] = { "mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7" };
+ return names[register_index & 7];
+}
+
+}
diff --git a/Userland/Libraries/LibX86/Instruction.h b/Userland/Libraries/LibX86/Instruction.h
new file mode 100644
index 0000000000..98428b7334
--- /dev/null
+++ b/Userland/Libraries/LibX86/Instruction.h
@@ -0,0 +1,999 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Optional.h>
+#include <AK/StdLibExtras.h>
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <stdio.h>
+
+namespace X86 {
+
+class Instruction;
+class Interpreter;
+typedef void (Interpreter::*InstructionHandler)(const Instruction&);
+
+class SymbolProvider {
+public:
+ virtual String symbolicate(FlatPtr, u32* offset = nullptr) const = 0;
+};
+
+template<typename T>
+struct TypeTrivia {
+ static const size_t bits = sizeof(T) * 8;
+ static const T sign_bit = 1 << (bits - 1);
+ static const T mask = typename MakeUnsigned<T>::Type(-1);
+};
+
+template<typename T, typename U>
+constexpr T sign_extended_to(U value)
+{
+ if (!(value & TypeTrivia<U>::sign_bit))
+ return value;
+ return (TypeTrivia<T>::mask & ~TypeTrivia<U>::mask) | value;
+}
+
+enum IsLockPrefixAllowed {
+ LockPrefixNotAllowed = 0,
+ LockPrefixAllowed
+};
+
+enum InstructionFormat {
+ InvalidFormat,
+ MultibyteWithSlash,
+ InstructionPrefix,
+
+ __BeginFormatsWithRMByte,
+ OP_RM16_reg16,
+ OP_reg8_RM8,
+ OP_reg16_RM16,
+ OP_RM16_seg,
+ OP_RM32_seg,
+ OP_RM8_imm8,
+ OP_RM16_imm16,
+ OP_RM16_imm8,
+ OP_RM32_imm8,
+ OP_RM8,
+ OP_RM16,
+ OP_RM32,
+ OP_FPU,
+ OP_FPU_reg,
+ OP_FPU_mem,
+ OP_FPU_AX16,
+ OP_FPU_RM16,
+ OP_FPU_RM32,
+ OP_FPU_RM64,
+ OP_FPU_M80,
+ OP_RM8_reg8,
+ OP_RM32_reg32,
+ OP_reg32_RM32,
+ OP_RM32_imm32,
+ OP_reg16_RM16_imm8,
+ OP_reg32_RM32_imm8,
+ OP_reg16_RM16_imm16,
+ OP_reg32_RM32_imm32,
+ OP_reg16_mem16,
+ OP_reg32_mem32,
+ OP_seg_RM16,
+ OP_seg_RM32,
+ OP_RM8_1,
+ OP_RM16_1,
+ OP_RM32_1,
+ OP_FAR_mem16,
+ OP_FAR_mem32,
+ OP_RM8_CL,
+ OP_RM16_CL,
+ OP_RM32_CL,
+ OP_reg32_CR,
+ OP_CR_reg32,
+ OP_reg32_DR,
+ OP_DR_reg32,
+ OP_reg16_RM8,
+ OP_reg32_RM8,
+ OP_reg32_RM16,
+ OP_RM16_reg16_imm8,
+ OP_RM32_reg32_imm8,
+ OP_RM16_reg16_CL,
+ OP_RM32_reg32_CL,
+ OP_mm1_mm2m64,
+ OP_mm1m64_mm2,
+ __EndFormatsWithRMByte,
+
+ OP_reg32_imm32,
+ OP_AL_imm8,
+ OP_AX_imm16,
+ OP_EAX_imm32,
+ OP_CS,
+ OP_DS,
+ OP_ES,
+ OP_SS,
+ OP_FS,
+ OP_GS,
+ OP,
+ OP_reg16,
+ OP_imm16,
+ OP_relimm16,
+ OP_relimm32,
+ OP_imm8,
+ OP_imm16_imm16,
+ OP_imm16_imm32,
+ OP_AX_reg16,
+ OP_EAX_reg32,
+ OP_AL_moff8,
+ OP_AX_moff16,
+ OP_EAX_moff32,
+ OP_moff8_AL,
+ OP_moff16_AX,
+ OP_moff32_EAX,
+ OP_reg8_imm8,
+ OP_reg16_imm16,
+ OP_3,
+ OP_AX_imm8,
+ OP_EAX_imm8,
+ OP_short_imm8,
+ OP_AL_DX,
+ OP_AX_DX,
+ OP_EAX_DX,
+ OP_DX_AL,
+ OP_DX_AX,
+ OP_DX_EAX,
+ OP_imm8_AL,
+ OP_imm8_AX,
+ OP_imm8_EAX,
+ OP_reg8_CL,
+
+ OP_reg32,
+ OP_imm32,
+ OP_imm16_imm8,
+
+ OP_NEAR_imm,
+};
+
+static const unsigned CurrentAddressSize = 0xB33FBABE;
+
+struct InstructionDescriptor {
+ InstructionHandler handler { nullptr };
+ bool opcode_has_register_index { false };
+ const char* mnemonic { nullptr };
+ InstructionFormat format { InvalidFormat };
+ bool has_rm { false };
+ unsigned imm1_bytes { 0 };
+ unsigned imm2_bytes { 0 };
+
+ // Addressed by the 3 REG bits in the MOD-REG-R/M byte.
+ // Some slash instructions have further subgroups when MOD is 11,
+ // in that case the InstructionDescriptors in slashes have themselves
+ // a non-null slashes member that's indexed by the three R/M bits.
+ InstructionDescriptor* slashes { nullptr };
+
+ unsigned imm1_bytes_for_address_size(bool a32)
+ {
+ if (imm1_bytes == CurrentAddressSize)
+ return a32 ? 4 : 2;
+ return imm1_bytes;
+ }
+
+ unsigned imm2_bytes_for_address_size(bool a32)
+ {
+ if (imm2_bytes == CurrentAddressSize)
+ return a32 ? 4 : 2;
+ return imm2_bytes;
+ }
+
+ IsLockPrefixAllowed lock_prefix_allowed { LockPrefixNotAllowed };
+};
+
+extern InstructionDescriptor s_table16[256];
+extern InstructionDescriptor s_table32[256];
+extern InstructionDescriptor s_0f_table16[256];
+extern InstructionDescriptor s_0f_table32[256];
+
+struct Prefix {
+ enum Op {
+ OperandSizeOverride = 0x66,
+ AddressSizeOverride = 0x67,
+ REP = 0xf3,
+ REPZ = 0xf3,
+ REPNZ = 0xf2,
+ LOCK = 0xf0,
+ };
+};
+
+enum class SegmentRegister {
+ ES = 0,
+ CS,
+ SS,
+ DS,
+ FS,
+ GS,
+ SegR6,
+ SegR7,
+};
+
+enum RegisterIndex8 {
+ RegisterAL = 0,
+ RegisterCL,
+ RegisterDL,
+ RegisterBL,
+ RegisterAH,
+ RegisterCH,
+ RegisterDH,
+ RegisterBH
+};
+
+enum RegisterIndex16 {
+ RegisterAX = 0,
+ RegisterCX,
+ RegisterDX,
+ RegisterBX,
+ RegisterSP,
+ RegisterBP,
+ RegisterSI,
+ RegisterDI
+};
+
+enum RegisterIndex32 {
+ RegisterEAX = 0,
+ RegisterECX,
+ RegisterEDX,
+ RegisterEBX,
+ RegisterESP,
+ RegisterEBP,
+ RegisterESI,
+ RegisterEDI
+};
+
+enum FpuRegisterIndex {
+ ST0 = 0,
+ ST1,
+ ST2,
+ ST3,
+ ST4,
+ ST5,
+ ST6,
+ ST7
+};
+
+enum MMXRegisterIndex {
+ RegisterMM0 = 0,
+ RegisterMM1,
+ RegisterMM2,
+ RegisterMM3,
+ RegisterMM4,
+ RegisterMM5,
+ RegisterMM6,
+ RegisterMM7
+};
+
+class LogicalAddress {
+public:
+ LogicalAddress() { }
+ LogicalAddress(u16 selector, u32 offset)
+ : m_selector(selector)
+ , m_offset(offset)
+ {
+ }
+
+ u16 selector() const { return m_selector; }
+ u32 offset() const { return m_offset; }
+ void set_selector(u16 selector) { m_selector = selector; }
+ void set_offset(u32 offset) { m_offset = offset; }
+
+private:
+ u16 m_selector { 0 };
+ u32 m_offset { 0 };
+};
+
+class InstructionStream {
+public:
+ virtual bool can_read() = 0;
+ virtual u8 read8() = 0;
+ virtual u16 read16() = 0;
+ virtual u32 read32() = 0;
+ virtual u64 read64() = 0;
+};
+
+class SimpleInstructionStream final : public InstructionStream {
+public:
+ SimpleInstructionStream(const u8* data, size_t size)
+ : m_data(data)
+ , m_size(size)
+ {
+ }
+
+ virtual bool can_read() override { return m_offset < m_size; }
+
+ virtual u8 read8() override
+ {
+ if (!can_read())
+ return 0;
+ return m_data[m_offset++];
+ }
+ virtual u16 read16() override
+ {
+ u8 lsb = read8();
+ u8 msb = read8();
+ return ((u16)msb << 8) | (u16)lsb;
+ }
+
+ virtual u32 read32() override
+ {
+ u16 lsw = read16();
+ u16 msw = read16();
+ return ((u32)msw << 16) | (u32)lsw;
+ }
+
+ virtual u64 read64() override
+ {
+ u32 lsw = read32();
+ u32 msw = read32();
+ return ((u64)msw << 32) | (u64)lsw;
+ }
+ size_t offset() const { return m_offset; }
+
+private:
+ const u8* m_data { nullptr };
+ size_t m_offset { 0 };
+ size_t m_size { 0 };
+};
+
+class MemoryOrRegisterReference {
+ friend class Instruction;
+
+public:
+ String to_string_o8(const Instruction&) const;
+ String to_string_o16(const Instruction&) const;
+ String to_string_o32(const Instruction&) const;
+ String to_string_fpu_reg() const;
+ String to_string_fpu_mem(const Instruction&) const;
+ String to_string_fpu_ax16() const;
+ String to_string_fpu16(const Instruction&) const;
+ String to_string_fpu32(const Instruction&) const;
+ String to_string_fpu64(const Instruction&) const;
+ String to_string_fpu80(const Instruction&) const;
+ String to_string_mm(const Instruction&) const;
+
+ bool is_register() const { return m_register_index != 0x7f; }
+
+ unsigned register_index() const { return m_register_index; }
+ RegisterIndex32 reg32() const { return static_cast<RegisterIndex32>(register_index()); }
+ RegisterIndex16 reg16() const { return static_cast<RegisterIndex16>(register_index()); }
+ RegisterIndex8 reg8() const { return static_cast<RegisterIndex8>(register_index()); }
+ FpuRegisterIndex reg_fpu() const { return static_cast<FpuRegisterIndex>(register_index()); }
+
+ template<typename CPU, typename T>
+ void write8(CPU&, const Instruction&, T);
+ template<typename CPU, typename T>
+ void write16(CPU&, const Instruction&, T);
+ template<typename CPU, typename T>
+ void write32(CPU&, const Instruction&, T);
+ template<typename CPU, typename T>
+ void write64(CPU&, const Instruction&, T);
+
+ template<typename CPU>
+ typename CPU::ValueWithShadowType8 read8(CPU&, const Instruction&);
+ template<typename CPU>
+ typename CPU::ValueWithShadowType16 read16(CPU&, const Instruction&);
+ template<typename CPU>
+ typename CPU::ValueWithShadowType32 read32(CPU&, const Instruction&);
+ template<typename CPU>
+ typename CPU::ValueWithShadowType64 read64(CPU&, const Instruction&);
+
+ template<typename CPU>
+ LogicalAddress resolve(const CPU&, const Instruction&);
+
+private:
+ MemoryOrRegisterReference() { }
+
+ String to_string(const Instruction&) const;
+ String to_string_a16() const;
+ String to_string_a32() const;
+
+ template<typename InstructionStreamType>
+ void decode(InstructionStreamType&, bool a32);
+ template<typename InstructionStreamType>
+ void decode16(InstructionStreamType&);
+ template<typename InstructionStreamType>
+ void decode32(InstructionStreamType&);
+ template<typename CPU>
+ LogicalAddress resolve16(const CPU&, Optional<SegmentRegister>);
+ template<typename CPU>
+ LogicalAddress resolve32(const CPU&, Optional<SegmentRegister>);
+
+ template<typename CPU>
+ u32 evaluate_sib(const CPU&, SegmentRegister& default_segment) const;
+
+ union {
+ u32 m_displacement32 { 0 };
+ u16 m_displacement16;
+ };
+
+ u8 m_rm { 0 };
+ u8 m_sib { 0 };
+ u8 m_displacement_bytes { 0 };
+ u8 m_register_index : 7 { 0x7f };
+ bool m_has_sib : 1 { false };
+};
+
+class Instruction {
+public:
+ template<typename InstructionStreamType>
+ static Instruction from_stream(InstructionStreamType&, bool o32, bool a32);
+ ~Instruction() { }
+
+ ALWAYS_INLINE MemoryOrRegisterReference& modrm() const { return m_modrm; }
+
+ ALWAYS_INLINE InstructionHandler handler() const { return m_descriptor->handler; }
+
+ bool has_segment_prefix() const { return m_segment_prefix != 0xff; }
+ ALWAYS_INLINE Optional<SegmentRegister> segment_prefix() const
+ {
+ if (has_segment_prefix())
+ return static_cast<SegmentRegister>(m_segment_prefix);
+ return {};
+ }
+
+ bool has_address_size_override_prefix() const { return m_has_address_size_override_prefix; }
+ bool has_operand_size_override_prefix() const { return m_has_operand_size_override_prefix; }
+ bool has_lock_prefix() const { return m_has_lock_prefix; }
+ bool has_rep_prefix() const { return m_rep_prefix; }
+ u8 rep_prefix() const { return m_rep_prefix; }
+
+ bool is_valid() const { return m_descriptor; }
+
+ unsigned length() const;
+
+ String mnemonic() const;
+
+ u8 op() const { return m_op; }
+ u8 rm() const { return m_modrm.m_rm; }
+ u8 slash() const { return (rm() >> 3) & 7; }
+
+ u8 imm8() const { return m_imm1; }
+ u16 imm16() const { return m_imm1; }
+ u32 imm32() const { return m_imm1; }
+ u8 imm8_1() const { return imm8(); }
+ u8 imm8_2() const { return m_imm2; }
+ u16 imm16_1() const { return imm16(); }
+ u16 imm16_2() const { return m_imm2; }
+ u32 imm32_1() const { return imm32(); }
+ u32 imm32_2() const { return m_imm2; }
+ u32 imm_address() const { return m_a32 ? imm32() : imm16(); }
+
+ LogicalAddress imm_address16_16() const { return LogicalAddress(imm16_1(), imm16_2()); }
+ LogicalAddress imm_address16_32() const { return LogicalAddress(imm16_1(), imm32_2()); }
+
+ bool has_sub_op() const
+ {
+ return m_op == 0x0f;
+ }
+
+ unsigned register_index() const { return m_register_index; }
+ RegisterIndex32 reg32() const { return static_cast<RegisterIndex32>(register_index()); }
+ RegisterIndex16 reg16() const { return static_cast<RegisterIndex16>(register_index()); }
+ RegisterIndex8 reg8() const { return static_cast<RegisterIndex8>(register_index()); }
+
+ SegmentRegister segment_register() const { return static_cast<SegmentRegister>(register_index()); }
+
+ u8 cc() const { return has_sub_op() ? m_sub_op & 0xf : m_op & 0xf; }
+
+ bool a32() const { return m_a32; }
+
+ String to_string(u32 origin, const SymbolProvider* = nullptr, bool x32 = true) const;
+
+private:
+ template<typename InstructionStreamType>
+ Instruction(InstructionStreamType&, bool o32, bool a32);
+
+ void to_string_internal(StringBuilder&, u32 origin, const SymbolProvider*, bool x32) const;
+
+ const char* reg8_name() const;
+ const char* reg16_name() const;
+ const char* reg32_name() const;
+
+ InstructionDescriptor* m_descriptor { nullptr };
+ mutable MemoryOrRegisterReference m_modrm;
+ u32 m_imm1 { 0 };
+ u32 m_imm2 { 0 };
+ u8 m_segment_prefix { 0xff };
+ u8 m_register_index { 0xff };
+ u8 m_op { 0 };
+ u8 m_sub_op { 0 };
+ u8 m_extra_bytes { 0 };
+ u8 m_rep_prefix { 0 };
+ bool m_a32 : 1 { false };
+ bool m_o32 : 1 { false };
+ bool m_has_lock_prefix : 1 { false };
+ bool m_has_operand_size_override_prefix : 1 { false };
+ bool m_has_address_size_override_prefix : 1 { false };
+};
+
+template<typename CPU>
+ALWAYS_INLINE LogicalAddress MemoryOrRegisterReference::resolve16(const CPU& cpu, Optional<SegmentRegister> segment_prefix)
+{
+ auto default_segment = SegmentRegister::DS;
+ u16 offset = 0;
+
+ switch (m_rm & 7) {
+ case 0:
+ offset = cpu.bx().value() + cpu.si().value() + m_displacement16;
+ break;
+ case 1:
+ offset = cpu.bx().value() + cpu.di().value() + m_displacement16;
+ break;
+ case 2:
+ default_segment = SegmentRegister::SS;
+ offset = cpu.bp().value() + cpu.si().value() + m_displacement16;
+ break;
+ case 3:
+ default_segment = SegmentRegister::SS;
+ offset = cpu.bp().value() + cpu.di().value() + m_displacement16;
+ break;
+ case 4:
+ offset = cpu.si().value() + m_displacement16;
+ break;
+ case 5:
+ offset = cpu.di().value() + m_displacement16;
+ break;
+ case 6:
+ if ((m_rm & 0xc0) == 0)
+ offset = m_displacement16;
+ else {
+ default_segment = SegmentRegister::SS;
+ offset = cpu.bp().value() + m_displacement16;
+ }
+ break;
+ default:
+ offset = cpu.bx().value() + m_displacement16;
+ break;
+ }
+
+ u16 segment = cpu.segment(segment_prefix.value_or(default_segment));
+ return { segment, offset };
+}
+
+template<typename CPU>
+ALWAYS_INLINE LogicalAddress MemoryOrRegisterReference::resolve32(const CPU& cpu, Optional<SegmentRegister> segment_prefix)
+{
+ auto default_segment = SegmentRegister::DS;
+ u32 offset = 0;
+
+ switch (m_rm & 0x07) {
+ case 0 ... 3:
+ case 6 ... 7:
+ offset = cpu.const_gpr32((RegisterIndex32)(m_rm & 0x07)).value() + m_displacement32;
+ break;
+ case 4:
+ offset = evaluate_sib(cpu, default_segment);
+ break;
+ default: // 5
+ if ((m_rm & 0xc0) == 0x00) {
+ offset = m_displacement32;
+ break;
+ } else {
+ default_segment = SegmentRegister::SS;
+ offset = cpu.ebp().value() + m_displacement32;
+ break;
+ }
+ break;
+ }
+ u16 segment = cpu.segment(segment_prefix.value_or(default_segment));
+ return { segment, offset };
+}
+
+template<typename CPU>
+ALWAYS_INLINE u32 MemoryOrRegisterReference::evaluate_sib(const CPU& cpu, SegmentRegister& default_segment) const
+{
+ u32 scale_shift = m_sib >> 6;
+ u32 index = 0;
+ switch ((m_sib >> 3) & 0x07) {
+ case 0 ... 3:
+ case 5 ... 7:
+ index = cpu.const_gpr32((RegisterIndex32)((m_sib >> 3) & 0x07)).value();
+ break;
+ case 4:
+ index = 0;
+ break;
+ }
+
+ u32 base = m_displacement32;
+ switch (m_sib & 0x07) {
+ case 0 ... 3:
+ case 6 ... 7:
+ base += cpu.const_gpr32((RegisterIndex32)(m_sib & 0x07)).value();
+ break;
+ case 4:
+ default_segment = SegmentRegister::SS;
+ base += cpu.esp().value();
+ break;
+ default: // 5
+ switch ((m_rm >> 6) & 3) {
+ case 0:
+ break;
+ case 1:
+ case 2:
+ default_segment = SegmentRegister::SS;
+ base += cpu.ebp().value();
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ break;
+ }
+
+ return (index << scale_shift) + base;
+}
+
+template<typename CPU, typename T>
+ALWAYS_INLINE void MemoryOrRegisterReference::write8(CPU& cpu, const Instruction& insn, T value)
+{
+ if (is_register()) {
+ cpu.gpr8(reg8()) = value;
+ return;
+ }
+
+ auto address = resolve(cpu, insn);
+ cpu.write_memory8(address, value);
+}
+
+template<typename CPU, typename T>
+ALWAYS_INLINE void MemoryOrRegisterReference::write16(CPU& cpu, const Instruction& insn, T value)
+{
+ if (is_register()) {
+ cpu.gpr16(reg16()) = value;
+ return;
+ }
+
+ auto address = resolve(cpu, insn);
+ cpu.write_memory16(address, value);
+}
+
+template<typename CPU, typename T>
+ALWAYS_INLINE void MemoryOrRegisterReference::write32(CPU& cpu, const Instruction& insn, T value)
+{
+ if (is_register()) {
+ cpu.gpr32(reg32()) = value;
+ return;
+ }
+
+ auto address = resolve(cpu, insn);
+ cpu.write_memory32(address, value);
+}
+
+template<typename CPU, typename T>
+ALWAYS_INLINE void MemoryOrRegisterReference::write64(CPU& cpu, const Instruction& insn, T value)
+{
+ ASSERT(!is_register());
+ auto address = resolve(cpu, insn);
+ cpu.write_memory64(address, value);
+}
+
+template<typename CPU>
+ALWAYS_INLINE typename CPU::ValueWithShadowType8 MemoryOrRegisterReference::read8(CPU& cpu, const Instruction& insn)
+{
+ if (is_register())
+ return cpu.const_gpr8(reg8());
+
+ auto address = resolve(cpu, insn);
+ return cpu.read_memory8(address);
+}
+
+template<typename CPU>
+ALWAYS_INLINE typename CPU::ValueWithShadowType16 MemoryOrRegisterReference::read16(CPU& cpu, const Instruction& insn)
+{
+ if (is_register())
+ return cpu.const_gpr16(reg16());
+
+ auto address = resolve(cpu, insn);
+ return cpu.read_memory16(address);
+}
+
+template<typename CPU>
+ALWAYS_INLINE typename CPU::ValueWithShadowType32 MemoryOrRegisterReference::read32(CPU& cpu, const Instruction& insn)
+{
+ if (is_register())
+ return cpu.const_gpr32(reg32());
+
+ auto address = resolve(cpu, insn);
+ return cpu.read_memory32(address);
+}
+
+template<typename CPU>
+ALWAYS_INLINE typename CPU::ValueWithShadowType64 MemoryOrRegisterReference::read64(CPU& cpu, const Instruction& insn)
+{
+ ASSERT(!is_register());
+ auto address = resolve(cpu, insn);
+ return cpu.read_memory64(address);
+}
+
+template<typename InstructionStreamType>
+ALWAYS_INLINE Instruction Instruction::from_stream(InstructionStreamType& stream, bool o32, bool a32)
+{
+ return Instruction(stream, o32, a32);
+}
+
+ALWAYS_INLINE unsigned Instruction::length() const
+{
+ unsigned len = 1;
+ if (has_sub_op())
+ ++len;
+ if (m_descriptor->has_rm) {
+ ++len;
+ if (m_modrm.m_has_sib)
+ ++len;
+ len += m_modrm.m_displacement_bytes;
+ }
+ len += m_extra_bytes;
+ return len;
+}
+
+ALWAYS_INLINE Optional<SegmentRegister> to_segment_prefix(u8 op)
+{
+ switch (op) {
+ case 0x26:
+ return SegmentRegister::ES;
+ case 0x2e:
+ return SegmentRegister::CS;
+ case 0x36:
+ return SegmentRegister::SS;
+ case 0x3e:
+ return SegmentRegister::DS;
+ case 0x64:
+ return SegmentRegister::FS;
+ case 0x65:
+ return SegmentRegister::GS;
+ default:
+ return {};
+ }
+}
+
+template<typename InstructionStreamType>
+ALWAYS_INLINE Instruction::Instruction(InstructionStreamType& stream, bool o32, bool a32)
+ : m_a32(a32)
+ , m_o32(o32)
+{
+ u8 prefix_bytes = 0;
+ for (;; ++prefix_bytes) {
+ u8 opbyte = stream.read8();
+ if (opbyte == Prefix::OperandSizeOverride) {
+ m_o32 = !o32;
+ m_has_operand_size_override_prefix = true;
+ continue;
+ }
+ if (opbyte == Prefix::AddressSizeOverride) {
+ m_a32 = !a32;
+ m_has_address_size_override_prefix = true;
+ continue;
+ }
+ if (opbyte == Prefix::REPZ || opbyte == Prefix::REPNZ) {
+ m_rep_prefix = opbyte;
+ continue;
+ }
+ if (opbyte == Prefix::LOCK) {
+ m_has_lock_prefix = true;
+ continue;
+ }
+ auto segment_prefix = to_segment_prefix(opbyte);
+ if (segment_prefix.has_value()) {
+ m_segment_prefix = (u8)segment_prefix.value();
+ continue;
+ }
+ m_op = opbyte;
+ break;
+ }
+
+ if (m_op == 0x0f) {
+ m_sub_op = stream.read8();
+ m_descriptor = m_o32 ? &s_0f_table32[m_sub_op] : &s_0f_table16[m_sub_op];
+ } else {
+ m_descriptor = m_o32 ? &s_table32[m_op] : &s_table16[m_op];
+ }
+
+ if (m_descriptor->has_rm) {
+ // Consume ModR/M (may include SIB and displacement.)
+ m_modrm.decode(stream, m_a32);
+ m_register_index = (m_modrm.m_rm >> 3) & 7;
+ } else {
+ if (has_sub_op())
+ m_register_index = m_sub_op & 7;
+ else
+ m_register_index = m_op & 7;
+ }
+
+ bool has_slash = m_descriptor->format == MultibyteWithSlash;
+ if (has_slash) {
+ m_descriptor = &m_descriptor->slashes[slash()];
+ if ((rm() & 0xc0) == 0xc0 && m_descriptor->slashes)
+ m_descriptor = &m_descriptor->slashes[rm() & 7];
+ }
+
+ if (!m_descriptor->mnemonic) {
+ if (has_sub_op()) {
+ if (has_slash)
+ fprintf(stderr, "Instruction %02X %02X /%u not understood\n", m_op, m_sub_op, slash());
+ else
+ fprintf(stderr, "Instruction %02X %02X not understood\n", m_op, m_sub_op);
+ } else {
+ if (has_slash)
+ fprintf(stderr, "Instruction %02X /%u not understood\n", m_op, slash());
+ else
+ fprintf(stderr, "Instruction %02X not understood\n", m_op);
+ }
+ m_descriptor = nullptr;
+ return;
+ }
+
+ auto imm1_bytes = m_descriptor->imm1_bytes_for_address_size(m_a32);
+ auto imm2_bytes = m_descriptor->imm2_bytes_for_address_size(m_a32);
+
+ // Consume immediates if present.
+ switch (imm2_bytes) {
+ case 1:
+ m_imm2 = stream.read8();
+ break;
+ case 2:
+ m_imm2 = stream.read16();
+ break;
+ case 4:
+ m_imm2 = stream.read32();
+ break;
+ }
+
+ switch (imm1_bytes) {
+ case 1:
+ m_imm1 = stream.read8();
+ break;
+ case 2:
+ m_imm1 = stream.read16();
+ break;
+ case 4:
+ m_imm1 = stream.read32();
+ break;
+ }
+
+ m_extra_bytes = prefix_bytes + imm1_bytes + imm2_bytes;
+
+#ifdef DISALLOW_INVALID_LOCK_PREFIX
+ if (m_has_lock_prefix && !m_descriptor->lock_prefix_allowed) {
+ fprintf(stderr, "Instruction not allowed with LOCK prefix, this will raise #UD\n");
+ m_descriptor = nullptr;
+ }
+#endif
+}
+
+template<typename InstructionStreamType>
+ALWAYS_INLINE void MemoryOrRegisterReference::decode(InstructionStreamType& stream, bool a32)
+{
+ m_rm = stream.read8();
+
+ if (a32) {
+ decode32(stream);
+ switch (m_displacement_bytes) {
+ case 0:
+ break;
+ case 1:
+ m_displacement32 = sign_extended_to<u32>(stream.read8());
+ break;
+ case 4:
+ m_displacement32 = stream.read32();
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ } else {
+ decode16(stream);
+ switch (m_displacement_bytes) {
+ case 0:
+ break;
+ case 1:
+ m_displacement16 = sign_extended_to<u16>(stream.read8());
+ break;
+ case 2:
+ m_displacement16 = stream.read16();
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ }
+}
+
+template<typename InstructionStreamType>
+ALWAYS_INLINE void MemoryOrRegisterReference::decode16(InstructionStreamType&)
+{
+ switch (m_rm & 0xc0) {
+ case 0:
+ if ((m_rm & 0x07) == 6)
+ m_displacement_bytes = 2;
+ else
+ ASSERT(m_displacement_bytes == 0);
+ break;
+ case 0x40:
+ m_displacement_bytes = 1;
+ break;
+ case 0x80:
+ m_displacement_bytes = 2;
+ break;
+ case 0xc0:
+ m_register_index = m_rm & 7;
+ break;
+ }
+}
+
+template<typename InstructionStreamType>
+ALWAYS_INLINE void MemoryOrRegisterReference::decode32(InstructionStreamType& stream)
+{
+ switch (m_rm & 0xc0) {
+ case 0:
+ if ((m_rm & 0x07) == 5)
+ m_displacement_bytes = 4;
+ break;
+ case 0x40:
+ m_displacement_bytes = 1;
+ break;
+ case 0x80:
+ m_displacement_bytes = 4;
+ break;
+ case 0xc0:
+ m_register_index = m_rm & 7;
+ return;
+ }
+
+ m_has_sib = (m_rm & 0x07) == 4;
+ if (m_has_sib) {
+ m_sib = stream.read8();
+ if ((m_sib & 0x07) == 5) {
+ switch ((m_rm >> 6) & 0x03) {
+ case 0:
+ m_displacement_bytes = 4;
+ break;
+ case 1:
+ m_displacement_bytes = 1;
+ break;
+ case 2:
+ m_displacement_bytes = 4;
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ }
+ }
+}
+
+template<typename CPU>
+ALWAYS_INLINE LogicalAddress MemoryOrRegisterReference::resolve(const CPU& cpu, const Instruction& insn)
+{
+ if (insn.a32())
+ return resolve32(cpu, insn.segment_prefix());
+ return resolve16(cpu, insn.segment_prefix());
+}
+
+}
diff --git a/Userland/Libraries/LibX86/Interpreter.h b/Userland/Libraries/LibX86/Interpreter.h
new file mode 100644
index 0000000000..efbf4164d8
--- /dev/null
+++ b/Userland/Libraries/LibX86/Interpreter.h
@@ -0,0 +1,629 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+
+namespace X86 {
+
+class Instruction;
+
+class Interpreter {
+public:
+ virtual void AAA(const Instruction&) = 0;
+ virtual void AAD(const Instruction&) = 0;
+ virtual void AAM(const Instruction&) = 0;
+ virtual void AAS(const Instruction&) = 0;
+ virtual void ADC_AL_imm8(const Instruction&) = 0;
+ virtual void ADC_AX_imm16(const Instruction&) = 0;
+ virtual void ADC_EAX_imm32(const Instruction&) = 0;
+ virtual void ADC_RM16_imm16(const Instruction&) = 0;
+ virtual void ADC_RM16_imm8(const Instruction&) = 0;
+ virtual void ADC_RM16_reg16(const Instruction&) = 0;
+ virtual void ADC_RM32_imm32(const Instruction&) = 0;
+ virtual void ADC_RM32_imm8(const Instruction&) = 0;
+ virtual void ADC_RM32_reg32(const Instruction&) = 0;
+ virtual void ADC_RM8_imm8(const Instruction&) = 0;
+ virtual void ADC_RM8_reg8(const Instruction&) = 0;
+ virtual void ADC_reg16_RM16(const Instruction&) = 0;
+ virtual void ADC_reg32_RM32(const Instruction&) = 0;
+ virtual void ADC_reg8_RM8(const Instruction&) = 0;
+ virtual void ADD_AL_imm8(const Instruction&) = 0;
+ virtual void ADD_AX_imm16(const Instruction&) = 0;
+ virtual void ADD_EAX_imm32(const Instruction&) = 0;
+ virtual void ADD_RM16_imm16(const Instruction&) = 0;
+ virtual void ADD_RM16_imm8(const Instruction&) = 0;
+ virtual void ADD_RM16_reg16(const Instruction&) = 0;
+ virtual void ADD_RM32_imm32(const Instruction&) = 0;
+ virtual void ADD_RM32_imm8(const Instruction&) = 0;
+ virtual void ADD_RM32_reg32(const Instruction&) = 0;
+ virtual void ADD_RM8_imm8(const Instruction&) = 0;
+ virtual void ADD_RM8_reg8(const Instruction&) = 0;
+ virtual void ADD_reg16_RM16(const Instruction&) = 0;
+ virtual void ADD_reg32_RM32(const Instruction&) = 0;
+ virtual void ADD_reg8_RM8(const Instruction&) = 0;
+ virtual void AND_AL_imm8(const Instruction&) = 0;
+ virtual void AND_AX_imm16(const Instruction&) = 0;
+ virtual void AND_EAX_imm32(const Instruction&) = 0;
+ virtual void AND_RM16_imm16(const Instruction&) = 0;
+ virtual void AND_RM16_imm8(const Instruction&) = 0;
+ virtual void AND_RM16_reg16(const Instruction&) = 0;
+ virtual void AND_RM32_imm32(const Instruction&) = 0;
+ virtual void AND_RM32_imm8(const Instruction&) = 0;
+ virtual void AND_RM32_reg32(const Instruction&) = 0;
+ virtual void AND_RM8_imm8(const Instruction&) = 0;
+ virtual void AND_RM8_reg8(const Instruction&) = 0;
+ virtual void AND_reg16_RM16(const Instruction&) = 0;
+ virtual void AND_reg32_RM32(const Instruction&) = 0;
+ virtual void AND_reg8_RM8(const Instruction&) = 0;
+ virtual void ARPL(const Instruction&) = 0;
+ virtual void BOUND(const Instruction&) = 0;
+ virtual void BSF_reg16_RM16(const Instruction&) = 0;
+ virtual void BSF_reg32_RM32(const Instruction&) = 0;
+ virtual void BSR_reg16_RM16(const Instruction&) = 0;
+ virtual void BSR_reg32_RM32(const Instruction&) = 0;
+ virtual void BSWAP_reg32(const Instruction&) = 0;
+ virtual void BTC_RM16_imm8(const Instruction&) = 0;
+ virtual void BTC_RM16_reg16(const Instruction&) = 0;
+ virtual void BTC_RM32_imm8(const Instruction&) = 0;
+ virtual void BTC_RM32_reg32(const Instruction&) = 0;
+ virtual void BTR_RM16_imm8(const Instruction&) = 0;
+ virtual void BTR_RM16_reg16(const Instruction&) = 0;
+ virtual void BTR_RM32_imm8(const Instruction&) = 0;
+ virtual void BTR_RM32_reg32(const Instruction&) = 0;
+ virtual void BTS_RM16_imm8(const Instruction&) = 0;
+ virtual void BTS_RM16_reg16(const Instruction&) = 0;
+ virtual void BTS_RM32_imm8(const Instruction&) = 0;
+ virtual void BTS_RM32_reg32(const Instruction&) = 0;
+ virtual void BT_RM16_imm8(const Instruction&) = 0;
+ virtual void BT_RM16_reg16(const Instruction&) = 0;
+ virtual void BT_RM32_imm8(const Instruction&) = 0;
+ virtual void BT_RM32_reg32(const Instruction&) = 0;
+ virtual void CALL_FAR_mem16(const Instruction&) = 0;
+ virtual void CALL_FAR_mem32(const Instruction&) = 0;
+ virtual void CALL_RM16(const Instruction&) = 0;
+ virtual void CALL_RM32(const Instruction&) = 0;
+ virtual void CALL_imm16(const Instruction&) = 0;
+ virtual void CALL_imm16_imm16(const Instruction&) = 0;
+ virtual void CALL_imm16_imm32(const Instruction&) = 0;
+ virtual void CALL_imm32(const Instruction&) = 0;
+ virtual void CBW(const Instruction&) = 0;
+ virtual void CDQ(const Instruction&) = 0;
+ virtual void CLC(const Instruction&) = 0;
+ virtual void CLD(const Instruction&) = 0;
+ virtual void CLI(const Instruction&) = 0;
+ virtual void CLTS(const Instruction&) = 0;
+ virtual void CMC(const Instruction&) = 0;
+ virtual void CMOVcc_reg16_RM16(const Instruction&) = 0;
+ virtual void CMOVcc_reg32_RM32(const Instruction&) = 0;
+ virtual void CMPSB(const Instruction&) = 0;
+ virtual void CMPSD(const Instruction&) = 0;
+ virtual void CMPSW(const Instruction&) = 0;
+ virtual void CMPXCHG_RM16_reg16(const Instruction&) = 0;
+ virtual void CMPXCHG_RM32_reg32(const Instruction&) = 0;
+ virtual void CMPXCHG_RM8_reg8(const Instruction&) = 0;
+ virtual void CMP_AL_imm8(const Instruction&) = 0;
+ virtual void CMP_AX_imm16(const Instruction&) = 0;
+ virtual void CMP_EAX_imm32(const Instruction&) = 0;
+ virtual void CMP_RM16_imm16(const Instruction&) = 0;
+ virtual void CMP_RM16_imm8(const Instruction&) = 0;
+ virtual void CMP_RM16_reg16(const Instruction&) = 0;
+ virtual void CMP_RM32_imm32(const Instruction&) = 0;
+ virtual void CMP_RM32_imm8(const Instruction&) = 0;
+ virtual void CMP_RM32_reg32(const Instruction&) = 0;
+ virtual void CMP_RM8_imm8(const Instruction&) = 0;
+ virtual void CMP_RM8_reg8(const Instruction&) = 0;
+ virtual void CMP_reg16_RM16(const Instruction&) = 0;
+ virtual void CMP_reg32_RM32(const Instruction&) = 0;
+ virtual void CMP_reg8_RM8(const Instruction&) = 0;
+ virtual void CPUID(const Instruction&) = 0;
+ virtual void CWD(const Instruction&) = 0;
+ virtual void CWDE(const Instruction&) = 0;
+ virtual void DAA(const Instruction&) = 0;
+ virtual void DAS(const Instruction&) = 0;
+ virtual void DEC_RM16(const Instruction&) = 0;
+ virtual void DEC_RM32(const Instruction&) = 0;
+ virtual void DEC_RM8(const Instruction&) = 0;
+ virtual void DEC_reg16(const Instruction&) = 0;
+ virtual void DEC_reg32(const Instruction&) = 0;
+ virtual void DIV_RM16(const Instruction&) = 0;
+ virtual void DIV_RM32(const Instruction&) = 0;
+ virtual void DIV_RM8(const Instruction&) = 0;
+ virtual void ENTER16(const Instruction&) = 0;
+ virtual void ENTER32(const Instruction&) = 0;
+ virtual void ESCAPE(const Instruction&) = 0;
+ virtual void FADD_RM32(const Instruction&) = 0;
+ virtual void FMUL_RM32(const Instruction&) = 0;
+ virtual void FCOM_RM32(const Instruction&) = 0;
+ virtual void FCOMP_RM32(const Instruction&) = 0;
+ virtual void FSUB_RM32(const Instruction&) = 0;
+ virtual void FSUBR_RM32(const Instruction&) = 0;
+ virtual void FDIV_RM32(const Instruction&) = 0;
+ virtual void FDIVR_RM32(const Instruction&) = 0;
+ virtual void FLD_RM32(const Instruction&) = 0;
+ virtual void FXCH(const Instruction&) = 0;
+ virtual void FST_RM32(const Instruction&) = 0;
+ virtual void FNOP(const Instruction&) = 0;
+ virtual void FSTP_RM32(const Instruction&) = 0;
+ virtual void FLDENV(const Instruction&) = 0;
+ virtual void FCHS(const Instruction&) = 0;
+ virtual void FABS(const Instruction&) = 0;
+ virtual void FTST(const Instruction&) = 0;
+ virtual void FXAM(const Instruction&) = 0;
+ virtual void FLDCW(const Instruction&) = 0;
+ virtual void FLD1(const Instruction&) = 0;
+ virtual void FLDL2T(const Instruction&) = 0;
+ virtual void FLDL2E(const Instruction&) = 0;
+ virtual void FLDPI(const Instruction&) = 0;
+ virtual void FLDLG2(const Instruction&) = 0;
+ virtual void FLDLN2(const Instruction&) = 0;
+ virtual void FLDZ(const Instruction&) = 0;
+ virtual void FNSTENV(const Instruction&) = 0;
+ virtual void F2XM1(const Instruction&) = 0;
+ virtual void FYL2X(const Instruction&) = 0;
+ virtual void FPTAN(const Instruction&) = 0;
+ virtual void FPATAN(const Instruction&) = 0;
+ virtual void FXTRACT(const Instruction&) = 0;
+ virtual void FPREM1(const Instruction&) = 0;
+ virtual void FDECSTP(const Instruction&) = 0;
+ virtual void FINCSTP(const Instruction&) = 0;
+ virtual void FNSTCW(const Instruction&) = 0;
+ virtual void FPREM(const Instruction&) = 0;
+ virtual void FYL2XP1(const Instruction&) = 0;
+ virtual void FSQRT(const Instruction&) = 0;
+ virtual void FSINCOS(const Instruction&) = 0;
+ virtual void FRNDINT(const Instruction&) = 0;
+ virtual void FSCALE(const Instruction&) = 0;
+ virtual void FSIN(const Instruction&) = 0;
+ virtual void FCOS(const Instruction&) = 0;
+ virtual void FIADD_RM32(const Instruction&) = 0;
+ virtual void FADDP(const Instruction&) = 0;
+ virtual void FIMUL_RM32(const Instruction&) = 0;
+ virtual void FCMOVE(const Instruction&) = 0;
+ virtual void FICOM_RM32(const Instruction&) = 0;
+ virtual void FCMOVBE(const Instruction&) = 0;
+ virtual void FICOMP_RM32(const Instruction&) = 0;
+ virtual void FCMOVU(const Instruction&) = 0;
+ virtual void FISUB_RM32(const Instruction&) = 0;
+ virtual void FISUBR_RM32(const Instruction&) = 0;
+ virtual void FUCOMPP(const Instruction&) = 0;
+ virtual void FIDIV_RM32(const Instruction&) = 0;
+ virtual void FIDIVR_RM32(const Instruction&) = 0;
+ virtual void FILD_RM32(const Instruction&) = 0;
+ virtual void FCMOVNB(const Instruction&) = 0;
+ virtual void FISTTP_RM32(const Instruction&) = 0;
+ virtual void FCMOVNE(const Instruction&) = 0;
+ virtual void FIST_RM32(const Instruction&) = 0;
+ virtual void FCMOVNBE(const Instruction&) = 0;
+ virtual void FISTP_RM32(const Instruction&) = 0;
+ virtual void FCMOVNU(const Instruction&) = 0;
+ virtual void FNENI(const Instruction&) = 0;
+ virtual void FNDISI(const Instruction&) = 0;
+ virtual void FNCLEX(const Instruction&) = 0;
+ virtual void FNINIT(const Instruction&) = 0;
+ virtual void FNSETPM(const Instruction&) = 0;
+ virtual void FLD_RM80(const Instruction&) = 0;
+ virtual void FUCOMI(const Instruction&) = 0;
+ virtual void FCOMI(const Instruction&) = 0;
+ virtual void FSTP_RM80(const Instruction&) = 0;
+ virtual void FADD_RM64(const Instruction&) = 0;
+ virtual void FMUL_RM64(const Instruction&) = 0;
+ virtual void FCOM_RM64(const Instruction&) = 0;
+ virtual void FCOMP_RM64(const Instruction&) = 0;
+ virtual void FSUB_RM64(const Instruction&) = 0;
+ virtual void FSUBR_RM64(const Instruction&) = 0;
+ virtual void FDIV_RM64(const Instruction&) = 0;
+ virtual void FDIVR_RM64(const Instruction&) = 0;
+ virtual void FLD_RM64(const Instruction&) = 0;
+ virtual void FFREE(const Instruction&) = 0;
+ virtual void FISTTP_RM64(const Instruction&) = 0;
+ virtual void FST_RM64(const Instruction&) = 0;
+ virtual void FSTP_RM64(const Instruction&) = 0;
+ virtual void FRSTOR(const Instruction&) = 0;
+ virtual void FUCOM(const Instruction&) = 0;
+ virtual void FUCOMP(const Instruction&) = 0;
+ virtual void FNSAVE(const Instruction&) = 0;
+ virtual void FNSTSW(const Instruction&) = 0;
+ virtual void FIADD_RM16(const Instruction&) = 0;
+ virtual void FCMOVB(const Instruction&) = 0;
+ virtual void FIMUL_RM16(const Instruction&) = 0;
+ virtual void FMULP(const Instruction&) = 0;
+ virtual void FICOM_RM16(const Instruction&) = 0;
+ virtual void FICOMP_RM16(const Instruction&) = 0;
+ virtual void FCOMPP(const Instruction&) = 0;
+ virtual void FISUB_RM16(const Instruction&) = 0;
+ virtual void FSUBRP(const Instruction&) = 0;
+ virtual void FISUBR_RM16(const Instruction&) = 0;
+ virtual void FSUBP(const Instruction&) = 0;
+ virtual void FIDIV_RM16(const Instruction&) = 0;
+ virtual void FDIVRP(const Instruction&) = 0;
+ virtual void FIDIVR_RM16(const Instruction&) = 0;
+ virtual void FDIVP(const Instruction&) = 0;
+ virtual void FILD_RM16(const Instruction&) = 0;
+ virtual void FFREEP(const Instruction&) = 0;
+ virtual void FISTTP_RM16(const Instruction&) = 0;
+ virtual void FIST_RM16(const Instruction&) = 0;
+ virtual void FISTP_RM16(const Instruction&) = 0;
+ virtual void FBLD_M80(const Instruction&) = 0;
+ virtual void FNSTSW_AX(const Instruction&) = 0;
+ virtual void FILD_RM64(const Instruction&) = 0;
+ virtual void FUCOMIP(const Instruction&) = 0;
+ virtual void FBSTP_M80(const Instruction&) = 0;
+ virtual void FCOMIP(const Instruction&) = 0;
+ virtual void FISTP_RM64(const Instruction&) = 0;
+ virtual void HLT(const Instruction&) = 0;
+ virtual void IDIV_RM16(const Instruction&) = 0;
+ virtual void IDIV_RM32(const Instruction&) = 0;
+ virtual void IDIV_RM8(const Instruction&) = 0;
+ virtual void IMUL_RM16(const Instruction&) = 0;
+ virtual void IMUL_RM32(const Instruction&) = 0;
+ virtual void IMUL_RM8(const Instruction&) = 0;
+ virtual void IMUL_reg16_RM16(const Instruction&) = 0;
+ virtual void IMUL_reg16_RM16_imm16(const Instruction&) = 0;
+ virtual void IMUL_reg16_RM16_imm8(const Instruction&) = 0;
+ virtual void IMUL_reg32_RM32(const Instruction&) = 0;
+ virtual void IMUL_reg32_RM32_imm32(const Instruction&) = 0;
+ virtual void IMUL_reg32_RM32_imm8(const Instruction&) = 0;
+ virtual void INC_RM16(const Instruction&) = 0;
+ virtual void INC_RM32(const Instruction&) = 0;
+ virtual void INC_RM8(const Instruction&) = 0;
+ virtual void INC_reg16(const Instruction&) = 0;
+ virtual void INC_reg32(const Instruction&) = 0;
+ virtual void INSB(const Instruction&) = 0;
+ virtual void INSD(const Instruction&) = 0;
+ virtual void INSW(const Instruction&) = 0;
+ virtual void INT3(const Instruction&) = 0;
+ virtual void INTO(const Instruction&) = 0;
+ virtual void INT_imm8(const Instruction&) = 0;
+ virtual void INVLPG(const Instruction&) = 0;
+ virtual void IN_AL_DX(const Instruction&) = 0;
+ virtual void IN_AL_imm8(const Instruction&) = 0;
+ virtual void IN_AX_DX(const Instruction&) = 0;
+ virtual void IN_AX_imm8(const Instruction&) = 0;
+ virtual void IN_EAX_DX(const Instruction&) = 0;
+ virtual void IN_EAX_imm8(const Instruction&) = 0;
+ virtual void IRET(const Instruction&) = 0;
+ virtual void JCXZ_imm8(const Instruction&) = 0;
+ virtual void JMP_FAR_mem16(const Instruction&) = 0;
+ virtual void JMP_FAR_mem32(const Instruction&) = 0;
+ virtual void JMP_RM16(const Instruction&) = 0;
+ virtual void JMP_RM32(const Instruction&) = 0;
+ virtual void JMP_imm16(const Instruction&) = 0;
+ virtual void JMP_imm16_imm16(const Instruction&) = 0;
+ virtual void JMP_imm16_imm32(const Instruction&) = 0;
+ virtual void JMP_imm32(const Instruction&) = 0;
+ virtual void JMP_short_imm8(const Instruction&) = 0;
+ virtual void Jcc_NEAR_imm(const Instruction&) = 0;
+ virtual void Jcc_imm8(const Instruction&) = 0;
+ virtual void LAHF(const Instruction&) = 0;
+ virtual void LAR_reg16_RM16(const Instruction&) = 0;
+ virtual void LAR_reg32_RM32(const Instruction&) = 0;
+ virtual void LDS_reg16_mem16(const Instruction&) = 0;
+ virtual void LDS_reg32_mem32(const Instruction&) = 0;
+ virtual void LEAVE16(const Instruction&) = 0;
+ virtual void LEAVE32(const Instruction&) = 0;
+ virtual void LEA_reg16_mem16(const Instruction&) = 0;
+ virtual void LEA_reg32_mem32(const Instruction&) = 0;
+ virtual void LES_reg16_mem16(const Instruction&) = 0;
+ virtual void LES_reg32_mem32(const Instruction&) = 0;
+ virtual void LFS_reg16_mem16(const Instruction&) = 0;
+ virtual void LFS_reg32_mem32(const Instruction&) = 0;
+ virtual void LGDT(const Instruction&) = 0;
+ virtual void LGS_reg16_mem16(const Instruction&) = 0;
+ virtual void LGS_reg32_mem32(const Instruction&) = 0;
+ virtual void LIDT(const Instruction&) = 0;
+ virtual void LLDT_RM16(const Instruction&) = 0;
+ virtual void LMSW_RM16(const Instruction&) = 0;
+ virtual void LODSB(const Instruction&) = 0;
+ virtual void LODSD(const Instruction&) = 0;
+ virtual void LODSW(const Instruction&) = 0;
+ virtual void LOOPNZ_imm8(const Instruction&) = 0;
+ virtual void LOOPZ_imm8(const Instruction&) = 0;
+ virtual void LOOP_imm8(const Instruction&) = 0;
+ virtual void LSL_reg16_RM16(const Instruction&) = 0;
+ virtual void LSL_reg32_RM32(const Instruction&) = 0;
+ virtual void LSS_reg16_mem16(const Instruction&) = 0;
+ virtual void LSS_reg32_mem32(const Instruction&) = 0;
+ virtual void LTR_RM16(const Instruction&) = 0;
+ virtual void MOVSB(const Instruction&) = 0;
+ virtual void MOVSD(const Instruction&) = 0;
+ virtual void MOVSW(const Instruction&) = 0;
+ virtual void MOVSX_reg16_RM8(const Instruction&) = 0;
+ virtual void MOVSX_reg32_RM16(const Instruction&) = 0;
+ virtual void MOVSX_reg32_RM8(const Instruction&) = 0;
+ virtual void MOVZX_reg16_RM8(const Instruction&) = 0;
+ virtual void MOVZX_reg32_RM16(const Instruction&) = 0;
+ virtual void MOVZX_reg32_RM8(const Instruction&) = 0;
+ virtual void MOV_AL_moff8(const Instruction&) = 0;
+ virtual void MOV_AX_moff16(const Instruction&) = 0;
+ virtual void MOV_CR_reg32(const Instruction&) = 0;
+ virtual void MOV_DR_reg32(const Instruction&) = 0;
+ virtual void MOV_EAX_moff32(const Instruction&) = 0;
+ virtual void MOV_RM16_imm16(const Instruction&) = 0;
+ virtual void MOV_RM16_reg16(const Instruction&) = 0;
+ virtual void MOV_RM16_seg(const Instruction&) = 0;
+ virtual void MOV_RM32_imm32(const Instruction&) = 0;
+ virtual void MOV_RM32_reg32(const Instruction&) = 0;
+ virtual void MOV_RM8_imm8(const Instruction&) = 0;
+ virtual void MOV_RM8_reg8(const Instruction&) = 0;
+ virtual void MOV_moff16_AX(const Instruction&) = 0;
+ virtual void MOV_moff32_EAX(const Instruction&) = 0;
+ virtual void MOV_moff8_AL(const Instruction&) = 0;
+ virtual void MOV_reg16_RM16(const Instruction&) = 0;
+ virtual void MOV_reg16_imm16(const Instruction&) = 0;
+ virtual void MOV_reg32_CR(const Instruction&) = 0;
+ virtual void MOV_reg32_DR(const Instruction&) = 0;
+ virtual void MOV_reg32_RM32(const Instruction&) = 0;
+ virtual void MOV_reg32_imm32(const Instruction&) = 0;
+ virtual void MOV_reg8_RM8(const Instruction&) = 0;
+ virtual void MOV_reg8_imm8(const Instruction&) = 0;
+ virtual void MOV_seg_RM16(const Instruction&) = 0;
+ virtual void MOV_seg_RM32(const Instruction&) = 0;
+ virtual void MUL_RM16(const Instruction&) = 0;
+ virtual void MUL_RM32(const Instruction&) = 0;
+ virtual void MUL_RM8(const Instruction&) = 0;
+ virtual void NEG_RM16(const Instruction&) = 0;
+ virtual void NEG_RM32(const Instruction&) = 0;
+ virtual void NEG_RM8(const Instruction&) = 0;
+ virtual void NOP(const Instruction&) = 0;
+ virtual void NOT_RM16(const Instruction&) = 0;
+ virtual void NOT_RM32(const Instruction&) = 0;
+ virtual void NOT_RM8(const Instruction&) = 0;
+ virtual void OR_AL_imm8(const Instruction&) = 0;
+ virtual void OR_AX_imm16(const Instruction&) = 0;
+ virtual void OR_EAX_imm32(const Instruction&) = 0;
+ virtual void OR_RM16_imm16(const Instruction&) = 0;
+ virtual void OR_RM16_imm8(const Instruction&) = 0;
+ virtual void OR_RM16_reg16(const Instruction&) = 0;
+ virtual void OR_RM32_imm32(const Instruction&) = 0;
+ virtual void OR_RM32_imm8(const Instruction&) = 0;
+ virtual void OR_RM32_reg32(const Instruction&) = 0;
+ virtual void OR_RM8_imm8(const Instruction&) = 0;
+ virtual void OR_RM8_reg8(const Instruction&) = 0;
+ virtual void OR_reg16_RM16(const Instruction&) = 0;
+ virtual void OR_reg32_RM32(const Instruction&) = 0;
+ virtual void OR_reg8_RM8(const Instruction&) = 0;
+ virtual void OUTSB(const Instruction&) = 0;
+ virtual void OUTSD(const Instruction&) = 0;
+ virtual void OUTSW(const Instruction&) = 0;
+ virtual void OUT_DX_AL(const Instruction&) = 0;
+ virtual void OUT_DX_AX(const Instruction&) = 0;
+ virtual void OUT_DX_EAX(const Instruction&) = 0;
+ virtual void OUT_imm8_AL(const Instruction&) = 0;
+ virtual void OUT_imm8_AX(const Instruction&) = 0;
+ virtual void OUT_imm8_EAX(const Instruction&) = 0;
+ virtual void PADDB_mm1_mm2m64(const Instruction&) = 0;
+ virtual void PADDW_mm1_mm2m64(const Instruction&) = 0;
+ virtual void PADDD_mm1_mm2m64(const Instruction&) = 0;
+ virtual void POPA(const Instruction&) = 0;
+ virtual void POPAD(const Instruction&) = 0;
+ virtual void POPF(const Instruction&) = 0;
+ virtual void POPFD(const Instruction&) = 0;
+ virtual void POP_DS(const Instruction&) = 0;
+ virtual void POP_ES(const Instruction&) = 0;
+ virtual void POP_FS(const Instruction&) = 0;
+ virtual void POP_GS(const Instruction&) = 0;
+ virtual void POP_RM16(const Instruction&) = 0;
+ virtual void POP_RM32(const Instruction&) = 0;
+ virtual void POP_SS(const Instruction&) = 0;
+ virtual void POP_reg16(const Instruction&) = 0;
+ virtual void POP_reg32(const Instruction&) = 0;
+ virtual void PUSHA(const Instruction&) = 0;
+ virtual void PUSHAD(const Instruction&) = 0;
+ virtual void PUSHF(const Instruction&) = 0;
+ virtual void PUSHFD(const Instruction&) = 0;
+ virtual void PUSH_CS(const Instruction&) = 0;
+ virtual void PUSH_DS(const Instruction&) = 0;
+ virtual void PUSH_ES(const Instruction&) = 0;
+ virtual void PUSH_FS(const Instruction&) = 0;
+ virtual void PUSH_GS(const Instruction&) = 0;
+ virtual void PUSH_RM16(const Instruction&) = 0;
+ virtual void PUSH_RM32(const Instruction&) = 0;
+ virtual void PUSH_SP_8086_80186(const Instruction&) = 0;
+ virtual void PUSH_SS(const Instruction&) = 0;
+ virtual void PUSH_imm16(const Instruction&) = 0;
+ virtual void PUSH_imm32(const Instruction&) = 0;
+ virtual void PUSH_imm8(const Instruction&) = 0;
+ virtual void PUSH_reg16(const Instruction&) = 0;
+ virtual void PUSH_reg32(const Instruction&) = 0;
+ virtual void RCL_RM16_1(const Instruction&) = 0;
+ virtual void RCL_RM16_CL(const Instruction&) = 0;
+ virtual void RCL_RM16_imm8(const Instruction&) = 0;
+ virtual void RCL_RM32_1(const Instruction&) = 0;
+ virtual void RCL_RM32_CL(const Instruction&) = 0;
+ virtual void RCL_RM32_imm8(const Instruction&) = 0;
+ virtual void RCL_RM8_1(const Instruction&) = 0;
+ virtual void RCL_RM8_CL(const Instruction&) = 0;
+ virtual void RCL_RM8_imm8(const Instruction&) = 0;
+ virtual void RCR_RM16_1(const Instruction&) = 0;
+ virtual void RCR_RM16_CL(const Instruction&) = 0;
+ virtual void RCR_RM16_imm8(const Instruction&) = 0;
+ virtual void RCR_RM32_1(const Instruction&) = 0;
+ virtual void RCR_RM32_CL(const Instruction&) = 0;
+ virtual void RCR_RM32_imm8(const Instruction&) = 0;
+ virtual void RCR_RM8_1(const Instruction&) = 0;
+ virtual void RCR_RM8_CL(const Instruction&) = 0;
+ virtual void RCR_RM8_imm8(const Instruction&) = 0;
+ virtual void RDTSC(const Instruction&) = 0;
+ virtual void RET(const Instruction&) = 0;
+ virtual void RETF(const Instruction&) = 0;
+ virtual void RETF_imm16(const Instruction&) = 0;
+ virtual void RET_imm16(const Instruction&) = 0;
+ virtual void ROL_RM16_1(const Instruction&) = 0;
+ virtual void ROL_RM16_CL(const Instruction&) = 0;
+ virtual void ROL_RM16_imm8(const Instruction&) = 0;
+ virtual void ROL_RM32_1(const Instruction&) = 0;
+ virtual void ROL_RM32_CL(const Instruction&) = 0;
+ virtual void ROL_RM32_imm8(const Instruction&) = 0;
+ virtual void ROL_RM8_1(const Instruction&) = 0;
+ virtual void ROL_RM8_CL(const Instruction&) = 0;
+ virtual void ROL_RM8_imm8(const Instruction&) = 0;
+ virtual void ROR_RM16_1(const Instruction&) = 0;
+ virtual void ROR_RM16_CL(const Instruction&) = 0;
+ virtual void ROR_RM16_imm8(const Instruction&) = 0;
+ virtual void ROR_RM32_1(const Instruction&) = 0;
+ virtual void ROR_RM32_CL(const Instruction&) = 0;
+ virtual void ROR_RM32_imm8(const Instruction&) = 0;
+ virtual void ROR_RM8_1(const Instruction&) = 0;
+ virtual void ROR_RM8_CL(const Instruction&) = 0;
+ virtual void ROR_RM8_imm8(const Instruction&) = 0;
+ virtual void SAHF(const Instruction&) = 0;
+ virtual void SALC(const Instruction&) = 0;
+ virtual void SAR_RM16_1(const Instruction&) = 0;
+ virtual void SAR_RM16_CL(const Instruction&) = 0;
+ virtual void SAR_RM16_imm8(const Instruction&) = 0;
+ virtual void SAR_RM32_1(const Instruction&) = 0;
+ virtual void SAR_RM32_CL(const Instruction&) = 0;
+ virtual void SAR_RM32_imm8(const Instruction&) = 0;
+ virtual void SAR_RM8_1(const Instruction&) = 0;
+ virtual void SAR_RM8_CL(const Instruction&) = 0;
+ virtual void SAR_RM8_imm8(const Instruction&) = 0;
+ virtual void SBB_AL_imm8(const Instruction&) = 0;
+ virtual void SBB_AX_imm16(const Instruction&) = 0;
+ virtual void SBB_EAX_imm32(const Instruction&) = 0;
+ virtual void SBB_RM16_imm16(const Instruction&) = 0;
+ virtual void SBB_RM16_imm8(const Instruction&) = 0;
+ virtual void SBB_RM16_reg16(const Instruction&) = 0;
+ virtual void SBB_RM32_imm32(const Instruction&) = 0;
+ virtual void SBB_RM32_imm8(const Instruction&) = 0;
+ virtual void SBB_RM32_reg32(const Instruction&) = 0;
+ virtual void SBB_RM8_imm8(const Instruction&) = 0;
+ virtual void SBB_RM8_reg8(const Instruction&) = 0;
+ virtual void SBB_reg16_RM16(const Instruction&) = 0;
+ virtual void SBB_reg32_RM32(const Instruction&) = 0;
+ virtual void SBB_reg8_RM8(const Instruction&) = 0;
+ virtual void SCASB(const Instruction&) = 0;
+ virtual void SCASD(const Instruction&) = 0;
+ virtual void SCASW(const Instruction&) = 0;
+ virtual void SETcc_RM8(const Instruction&) = 0;
+ virtual void SGDT(const Instruction&) = 0;
+ virtual void SHLD_RM16_reg16_CL(const Instruction&) = 0;
+ virtual void SHLD_RM16_reg16_imm8(const Instruction&) = 0;
+ virtual void SHLD_RM32_reg32_CL(const Instruction&) = 0;
+ virtual void SHLD_RM32_reg32_imm8(const Instruction&) = 0;
+ virtual void SHL_RM16_1(const Instruction&) = 0;
+ virtual void SHL_RM16_CL(const Instruction&) = 0;
+ virtual void SHL_RM16_imm8(const Instruction&) = 0;
+ virtual void SHL_RM32_1(const Instruction&) = 0;
+ virtual void SHL_RM32_CL(const Instruction&) = 0;
+ virtual void SHL_RM32_imm8(const Instruction&) = 0;
+ virtual void SHL_RM8_1(const Instruction&) = 0;
+ virtual void SHL_RM8_CL(const Instruction&) = 0;
+ virtual void SHL_RM8_imm8(const Instruction&) = 0;
+ virtual void SHRD_RM16_reg16_CL(const Instruction&) = 0;
+ virtual void SHRD_RM16_reg16_imm8(const Instruction&) = 0;
+ virtual void SHRD_RM32_reg32_CL(const Instruction&) = 0;
+ virtual void SHRD_RM32_reg32_imm8(const Instruction&) = 0;
+ virtual void SHR_RM16_1(const Instruction&) = 0;
+ virtual void SHR_RM16_CL(const Instruction&) = 0;
+ virtual void SHR_RM16_imm8(const Instruction&) = 0;
+ virtual void SHR_RM32_1(const Instruction&) = 0;
+ virtual void SHR_RM32_CL(const Instruction&) = 0;
+ virtual void SHR_RM32_imm8(const Instruction&) = 0;
+ virtual void SHR_RM8_1(const Instruction&) = 0;
+ virtual void SHR_RM8_CL(const Instruction&) = 0;
+ virtual void SHR_RM8_imm8(const Instruction&) = 0;
+ virtual void SIDT(const Instruction&) = 0;
+ virtual void SLDT_RM16(const Instruction&) = 0;
+ virtual void SMSW_RM16(const Instruction&) = 0;
+ virtual void STC(const Instruction&) = 0;
+ virtual void STD(const Instruction&) = 0;
+ virtual void STI(const Instruction&) = 0;
+ virtual void STOSB(const Instruction&) = 0;
+ virtual void STOSD(const Instruction&) = 0;
+ virtual void STOSW(const Instruction&) = 0;
+ virtual void STR_RM16(const Instruction&) = 0;
+ virtual void SUB_AL_imm8(const Instruction&) = 0;
+ virtual void SUB_AX_imm16(const Instruction&) = 0;
+ virtual void SUB_EAX_imm32(const Instruction&) = 0;
+ virtual void SUB_RM16_imm16(const Instruction&) = 0;
+ virtual void SUB_RM16_imm8(const Instruction&) = 0;
+ virtual void SUB_RM16_reg16(const Instruction&) = 0;
+ virtual void SUB_RM32_imm32(const Instruction&) = 0;
+ virtual void SUB_RM32_imm8(const Instruction&) = 0;
+ virtual void SUB_RM32_reg32(const Instruction&) = 0;
+ virtual void SUB_RM8_imm8(const Instruction&) = 0;
+ virtual void SUB_RM8_reg8(const Instruction&) = 0;
+ virtual void SUB_reg16_RM16(const Instruction&) = 0;
+ virtual void SUB_reg32_RM32(const Instruction&) = 0;
+ virtual void SUB_reg8_RM8(const Instruction&) = 0;
+ virtual void TEST_AL_imm8(const Instruction&) = 0;
+ virtual void TEST_AX_imm16(const Instruction&) = 0;
+ virtual void TEST_EAX_imm32(const Instruction&) = 0;
+ virtual void TEST_RM16_imm16(const Instruction&) = 0;
+ virtual void TEST_RM16_reg16(const Instruction&) = 0;
+ virtual void TEST_RM32_imm32(const Instruction&) = 0;
+ virtual void TEST_RM32_reg32(const Instruction&) = 0;
+ virtual void TEST_RM8_imm8(const Instruction&) = 0;
+ virtual void TEST_RM8_reg8(const Instruction&) = 0;
+ virtual void UD0(const Instruction&) = 0;
+ virtual void UD1(const Instruction&) = 0;
+ virtual void UD2(const Instruction&) = 0;
+ virtual void VERR_RM16(const Instruction&) = 0;
+ virtual void VERW_RM16(const Instruction&) = 0;
+ virtual void WAIT(const Instruction&) = 0;
+ virtual void WBINVD(const Instruction&) = 0;
+ virtual void XADD_RM16_reg16(const Instruction&) = 0;
+ virtual void XADD_RM32_reg32(const Instruction&) = 0;
+ virtual void XADD_RM8_reg8(const Instruction&) = 0;
+ virtual void XCHG_AX_reg16(const Instruction&) = 0;
+ virtual void XCHG_EAX_reg32(const Instruction&) = 0;
+ virtual void XCHG_reg16_RM16(const Instruction&) = 0;
+ virtual void XCHG_reg32_RM32(const Instruction&) = 0;
+ virtual void XCHG_reg8_RM8(const Instruction&) = 0;
+ virtual void XLAT(const Instruction&) = 0;
+ virtual void XOR_AL_imm8(const Instruction&) = 0;
+ virtual void XOR_AX_imm16(const Instruction&) = 0;
+ virtual void XOR_EAX_imm32(const Instruction&) = 0;
+ virtual void XOR_RM16_imm16(const Instruction&) = 0;
+ virtual void XOR_RM16_imm8(const Instruction&) = 0;
+ virtual void XOR_RM16_reg16(const Instruction&) = 0;
+ virtual void XOR_RM32_imm32(const Instruction&) = 0;
+ virtual void XOR_RM32_imm8(const Instruction&) = 0;
+ virtual void XOR_RM32_reg32(const Instruction&) = 0;
+ virtual void XOR_RM8_imm8(const Instruction&) = 0;
+ virtual void XOR_RM8_reg8(const Instruction&) = 0;
+ virtual void XOR_reg16_RM16(const Instruction&) = 0;
+ virtual void XOR_reg32_RM32(const Instruction&) = 0;
+ virtual void XOR_reg8_RM8(const Instruction&) = 0;
+ virtual void MOVQ_mm1_mm2m64(const Instruction&) = 0;
+ virtual void EMMS(const Instruction&) = 0;
+ virtual void MOVQ_mm1_m64_mm2(const Instruction&) = 0;
+ virtual void wrap_0xC0(const Instruction&) = 0;
+ virtual void wrap_0xC1_16(const Instruction&) = 0;
+ virtual void wrap_0xC1_32(const Instruction&) = 0;
+ virtual void wrap_0xD0(const Instruction&) = 0;
+ virtual void wrap_0xD1_16(const Instruction&) = 0;
+ virtual void wrap_0xD1_32(const Instruction&) = 0;
+ virtual void wrap_0xD2(const Instruction&) = 0;
+ virtual void wrap_0xD3_16(const Instruction&) = 0;
+ virtual void wrap_0xD3_32(const Instruction&) = 0;
+};
+
+typedef void (Interpreter::*InstructionHandler)(const Instruction&);
+
+}
diff --git a/Userland/Utilities/test-js.cpp b/Userland/Utilities/test-js.cpp
index 9245d06492..c429a75009 100644
--- a/Userland/Utilities/test-js.cpp
+++ b/Userland/Utilities/test-js.cpp
@@ -744,7 +744,7 @@ int main(int argc, char** argv)
warnln("No test root given, test-js requires the SERENITY_ROOT environment variable to be set");
return 1;
}
- test_root = String::formatted("{}/Libraries/LibJS/Tests", serenity_root);
+ test_root = String::formatted("{}/Userland/Libraries/LibJS/Tests", serenity_root);
#endif
}
if (!Core::File::is_directory(test_root)) {
diff --git a/Userland/Utilities/test-web.cpp b/Userland/Utilities/test-web.cpp
index e4172ddd03..b63029ab53 100644
--- a/Userland/Utilities/test-web.cpp
+++ b/Userland/Utilities/test-web.cpp
@@ -703,7 +703,7 @@ int main(int argc, char** argv)
printf("test-web requires the SERENITY_ROOT environment variable to be set");
return 1;
}
- TestRunner(String::format("%s/Libraries/LibWeb/Tests", serenity_root), String::format("%s/Libraries/LibJS/Tests", serenity_root), view, print_times).run();
+ TestRunner(String::format("%s/Userland/Libraries/LibWeb/Tests", serenity_root), String::format("%s/Userland/Libraries/LibJS/Tests", serenity_root), view, print_times).run();
#endif
return 0;
}